aboutsummaryrefslogtreecommitdiffstats
path: root/jdisc_core/src/main/java/com/yahoo/jdisc/core/FelixFramework.java
blob: 6ff83096917942a794a95688b346efefc1ca2428 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jdisc.core;

import com.google.inject.Inject;
import com.yahoo.jdisc.application.BundleInstallationException;
import com.yahoo.jdisc.application.OsgiFramework;
import com.yahoo.jdisc.application.OsgiHeader;
import org.apache.felix.framework.Felix;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.wiring.FrameworkWiring;

import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author Simon Thoresen Hult
 * @author gjoranv
 */
public class FelixFramework implements OsgiFramework {

    private static final Logger log = Logger.getLogger(FelixFramework.class.getName());
    private final OsgiLogService logService = new OsgiLogService();
    private final ConsoleLogManager logListener;
    private final Felix felix;

    private final BundleCollisionHook collisionHook;

    @Inject
    public FelixFramework(FelixParams params) {
        deleteDirContents(new File(params.getCachePath()));
        felix = new Felix(params.toConfig());
        logListener = params.isLoggerEnabled() ? new ConsoleLogManager() : null;
        collisionHook = new BundleCollisionHook();
    }

    @Override
    public void start() throws BundleException {
        log.finer("Starting Felix.");
        felix.start();

        BundleContext ctx = felix.getBundleContext();
        collisionHook.start(ctx);
        logService.start(ctx);
        if (logListener != null) {
            logListener.install(ctx);
        }
    }

    @Override
    public void stop() throws BundleException {
        log.fine("Stopping felix.");
        BundleContext ctx = felix.getBundleContext();
        if (ctx != null) {
            if (logListener != null) {
                logListener.uninstall();
            }
            logService.stop();
            collisionHook.stop();
        }
        felix.stop();
        try {
            felix.waitForStop(0);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    @Override
    public List<Bundle> installBundle(String bundleLocation) throws BundleException {
        List<Bundle> bundles = new LinkedList<>();
        try {
            installBundle(bundleLocation, new HashSet<>(), bundles);
        } catch (Exception e) {
            throw new BundleInstallationException(bundles, e);
        }
        return bundles;
    }

    @Override
    public void startBundles(List<Bundle> bundles, boolean privileged) throws BundleException {
        for (Bundle bundle : bundles) {
            if (!privileged && OsgiHeader.isSet(bundle, OsgiHeader.PRIVILEGED_ACTIVATOR)) {
                log.log(Level.INFO, "OSGi bundle '" + bundle.getSymbolicName() + "' " +
                        "states that it requires privileged " +
                        "initialization, but privileges are not available. YMMV.");
            }
            if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null) {
                continue; // fragments can not be started
            }
            bundle.start();
        }
        log.fine(startedBundlesMessage(bundles));
    }

    private String startedBundlesMessage(List<Bundle> bundles) {
        StringBuilder sb = new StringBuilder("Started bundles: {");
        for (Bundle b : bundles)
            sb.append("[" + b.getBundleId() + "]" + b.getSymbolicName() + ":" + b.getVersion() + ", ");
        sb.setLength(sb.length() - 2);
        sb.append("}");
        return sb.toString();
    }

    /**
     * NOTE: This method is no longer used by the Jdisc container framework, but kept for completeness.
     */
    @Override
    public void refreshPackages() {
        FrameworkWiring wiring = felix.adapt(FrameworkWiring.class);
        final CountDownLatch latch = new CountDownLatch(1);
        wiring.refreshBundles(null,
                              event -> {
                                  switch (event.getType()) {
                                      case FrameworkEvent.PACKAGES_REFRESHED:
                                          latch.countDown();
                                          break;
                                      case FrameworkEvent.ERROR:
                                          log.log(Level.SEVERE, "ERROR FrameworkEvent received.", event.getThrowable());
                                          break;
                                  }
                              });
        try {
            long TIMEOUT_SECONDS = 60L;
            if (!latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
                log.warning("No PACKAGES_REFRESHED FrameworkEvent received within " + TIMEOUT_SECONDS +
                                    " seconds of calling FrameworkWiring.refreshBundles()");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    @Override
    public BundleContext bundleContext() {
        return felix.getBundleContext();
    }

    @Override
    public List<Bundle> bundles() {
        return Arrays.asList(felix.getBundleContext().getBundles());
    }

    @Override
    public List<Bundle> getBundles(Bundle requestingBundle) {
        log.fine(() -> "All bundles: " + bundles());
        log.fine(() -> "Getting visible bundles for bundle " + requestingBundle);
        List<Bundle> visibleBundles = Arrays.asList(requestingBundle.getBundleContext().getBundles());
        log.fine(() -> "Visible bundles: " + visibleBundles);
        return visibleBundles;
    }

    public void allowDuplicateBundles(Collection<Bundle> bundles) {
        collisionHook.allowDuplicateBundles(bundles);
    }

    @Override
    public boolean isFelixFramework() {
        return true;
    }

    private void installBundle(String bundleLocation, Set<String> mask, List<Bundle> out) throws BundleException {
        bundleLocation = BundleLocationResolver.resolve(bundleLocation);
        if (mask.contains(bundleLocation)) {
            log.finer("OSGi bundle from '" + bundleLocation + "' already installed.");
            return;
        }
        log.finer("Installing OSGi bundle from '" + bundleLocation + "'.");
        mask.add(bundleLocation);

        Bundle bundle = felix.getBundleContext().installBundle(bundleLocation);
        String symbol = bundle.getSymbolicName();
        if (symbol == null) {
            bundle.uninstall();
            throw new BundleException("Missing Bundle-SymbolicName in manifest from '" + bundleLocation + " " +
                                      "(it might not be an OSGi bundle).");
        }
        out.add(bundle);
        for (String preInstall : OsgiHeader.asList(bundle, OsgiHeader.PREINSTALL_BUNDLE)) {
            log.finer("OSGi bundle '" + symbol + "' requires install from '" + preInstall + "'.");
            installBundle(preInstall, mask, out);
        }
    }

    private static void deleteDirContents(File parent) {
        File[] children = parent.listFiles();
        if (children != null) {
            for (File child : children) {
                deleteDirContents(child);
                boolean deleted = child.delete();
                if (! deleted)
                    throw new RuntimeException("Could not delete file '" + child.getAbsolutePath() +
                                               "'. Please check file permissions!");
            }
        }
    }

}