1 package com.atlassian.plugin.osgi.factory;
2
3 import com.atlassian.plugin.IllegalPluginStateException;
4 import com.atlassian.plugin.Plugin;
5 import com.atlassian.plugin.PluginArtifact;
6 import com.atlassian.plugin.PluginDependencies;
7 import com.atlassian.plugin.PluginException;
8 import com.atlassian.plugin.PluginState;
9 import com.atlassian.plugin.impl.AbstractPlugin;
10 import com.atlassian.plugin.module.ContainerAccessor;
11 import com.atlassian.plugin.module.ContainerManagedPlugin;
12 import com.atlassian.plugin.osgi.container.OsgiContainerException;
13 import com.atlassian.plugin.osgi.container.OsgiContainerManager;
14 import com.atlassian.plugin.osgi.util.BundleClassLoaderAccessor;
15 import com.atlassian.plugin.osgi.util.OsgiPluginUtil;
16 import com.atlassian.plugin.util.resource.AlternativeDirectoryResourceLoader;
17 import org.osgi.framework.Bundle;
18 import org.osgi.framework.BundleEvent;
19 import org.osgi.framework.BundleException;
20 import org.osgi.framework.Constants;
21 import org.osgi.framework.ServiceReference;
22 import org.osgi.framework.SynchronousBundleListener;
23 import org.osgi.service.packageadmin.PackageAdmin;
24 import org.osgi.util.tracker.ServiceTracker;
25 import org.osgi.util.tracker.ServiceTrackerCustomizer;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 import javax.annotation.Nonnull;
30 import javax.annotation.Nullable;
31 import java.io.File;
32 import java.io.InputStream;
33 import java.net.URL;
34 import java.util.Date;
35 import java.util.jar.Manifest;
36
37 import static com.atlassian.plugin.osgi.util.OsgiHeaderUtil.extractOsgiPluginInformation;
38 import static com.atlassian.plugin.osgi.util.OsgiHeaderUtil.getAttributeWithoutValidation;
39 import static com.atlassian.plugin.osgi.util.OsgiHeaderUtil.getManifest;
40 import static com.google.common.base.Preconditions.checkNotNull;
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 public class OsgiBundlePlugin extends AbstractPlugin implements OsgiBackedPlugin, ContainerManagedPlugin, SynchronousBundleListener {
56 private static final Logger log = LoggerFactory.getLogger(OsgiBundlePlugin.class);
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 private final Date dateLoaded;
74
75
76
77
78 private OsgiContainerManager osgiContainerManager;
79
80
81
82
83 private volatile Bundle bundle;
84
85
86
87
88 private ClassLoader bundleClassLoader;
89
90
91
92
93
94 private
95 @Nullable
96 ServiceTracker<ContainerAccessor, ContainerAccessor> containerAccessorTracker;
97
98
99
100
101
102 private
103 @Nullable
104 ServiceTracker<PackageAdmin, PackageAdmin> pkgAdminService;
105
106 private OsgiBundlePlugin(final String pluginKey, final PluginArtifact pluginArtifact) {
107 super(checkNotNull(pluginArtifact));
108 this.dateLoaded = new Date();
109 setPluginsVersion(2);
110 setKey(pluginKey);
111 setSystemPlugin(false);
112 }
113
114 @Override
115 public Bundle getBundle() throws IllegalPluginStateException {
116 if (bundle == null) {
117 throw new IllegalPluginStateException("This operation must occur while the plugin '" + getKey() + "' is installed");
118 }
119
120 return bundle;
121 }
122
123
124
125
126
127
128
129
130 public OsgiBundlePlugin(final OsgiContainerManager osgiContainerManager,
131 final String pluginKey,
132 final PluginArtifact pluginArtifact) {
133 this(pluginKey, pluginArtifact);
134 this.osgiContainerManager = checkNotNull(osgiContainerManager);
135
136
137 final Manifest manifest = getManifest(pluginArtifact);
138 if (null != manifest) {
139 setName(getAttributeWithoutValidation(manifest, Constants.BUNDLE_NAME));
140
141 setPluginInformation(extractOsgiPluginInformation(manifest, false));
142 }
143
144 }
145
146 @Override
147 public Date getDateLoaded() {
148 return dateLoaded;
149 }
150
151 @Override
152 public Date getDateInstalled() {
153 long date = getPluginArtifact().toFile().lastModified();
154 if (date == 0) {
155 date = getDateLoaded().getTime();
156 }
157 return new Date(date);
158 }
159
160 @Override
161 public boolean isUninstallable() {
162 return true;
163 }
164
165 @Override
166 public boolean isDeleteable() {
167 return true;
168 }
169
170 @Override
171 public boolean isDynamicallyLoaded() {
172 return true;
173 }
174
175 @Override
176 public <T> Class<T> loadClass(final String clazz, final Class<?> callingClass) throws ClassNotFoundException {
177 return BundleClassLoaderAccessor.loadClass(getBundleOrFail(), clazz);
178 }
179
180 @Override
181 public URL getResource(final String name) {
182 return getBundleClassLoaderOrFail().getResource(name);
183 }
184
185 @Override
186 public InputStream getResourceAsStream(final String name) {
187 return getBundleClassLoaderOrFail().getResourceAsStream(name);
188 }
189
190 @Override
191 public void resolve() {
192
193
194 if (pkgAdminService == null) {
195
196 return;
197 }
198 PackageAdmin packageAdmin = pkgAdminService.getService();
199 packageAdmin.resolveBundles(new Bundle[]{bundle});
200 }
201
202
203
204
205 @Nonnull
206 @Override
207 public PluginDependencies getDependencies() {
208
209
210
211 if (this.getPluginState() == PluginState.UNINSTALLED) {
212 throw new IllegalPluginStateException("This operation requires the plugin '" + getKey() + "' to be installed");
213 }
214 return OsgiPluginUtil.getDependencies(bundle);
215 }
216
217 @Override
218 protected void installInternal() throws OsgiContainerException, IllegalPluginStateException {
219 super.installInternal();
220 if (null != osgiContainerManager) {
221
222
223
224 osgiContainerManager.addBundleListener(this);
225
226
227 final File file = pluginArtifact.toFile();
228 bundle = osgiContainerManager.installBundle(file, pluginArtifact.getReferenceMode());
229 bundleClassLoader = BundleClassLoaderAccessor.getClassLoader(bundle, new AlternativeDirectoryResourceLoader());
230 pkgAdminService = osgiContainerManager.getServiceTracker(PackageAdmin.class.getName());
231 } else if (null == bundle) {
232 throw new IllegalPluginStateException("Cannot reuse instance for bundle '" + getKey() + "'");
233 }
234
235 }
236
237 @Override
238 protected void uninstallInternal() {
239 try {
240 if (bundleIsUsable("uninstall")) {
241 if (bundle.getState() != Bundle.UNINSTALLED) {
242 if (null != osgiContainerManager && osgiContainerManager.isRunning()) {
243 pkgAdminService.close();
244 osgiContainerManager.removeBundleListener(this);
245 } else {
246 log.warn("OSGi container not running or undefined: Will not remove bundle listener and will not close package admin service");
247 }
248 bundle.uninstall();
249 } else {
250
251 log.warn("Bundle '{}' already UNINSTALLED, but still held", getKey());
252 }
253 bundle = null;
254 bundleClassLoader = null;
255 }
256 } catch (final BundleException e) {
257 throw new PluginException(e);
258 }
259 }
260
261 @Override
262 protected PluginState enableInternal() {
263 log.debug("Enabling OSGi bundled plugin '{}'", getKey());
264 try {
265 if (bundleIsUsable("enable")) {
266 if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) == null) {
267 log.debug("Plugin '{}' bundle is NOT a fragment, starting.", getKey());
268
269
270
271
272
273 setPluginState(PluginState.ENABLING);
274
275
276
277
278 if (bundle.getState() == Bundle.INSTALLED || bundle.getState() == Bundle.RESOLVED) {
279 log.debug("Start plugin '{}' bundle", getKey());
280 bundle.start();
281 } else {
282 log.debug("Skip plugin '{}' bundle start because of its state: {}", getKey(), bundle.getState());
283 }
284
285
286
287
288
289
290
291 containerAccessorTracker = new ServiceTracker<>(
292 bundle.getBundleContext(),
293 ContainerAccessor.class,
294 new ServiceTrackerCustomizer<ContainerAccessor, ContainerAccessor>() {
295
296 @Override
297 public ContainerAccessor addingService(ServiceReference<ContainerAccessor> reference) {
298 if (reference.getBundle() == bundle) {
299 return bundle.getBundleContext().getService(reference);
300 }
301 return null;
302 }
303
304 @Override
305 public void modifiedService(ServiceReference<ContainerAccessor> reference, ContainerAccessor service) {
306 }
307
308 @Override
309 public void removedService(ServiceReference<ContainerAccessor> reference, ContainerAccessor service) {
310 if (reference.getBundle() == bundle) {
311 bundle.getBundleContext().ungetService(reference);
312 }
313 }
314
315 }
316 );
317 containerAccessorTracker.open();
318 } else {
319 log.debug("Plugin '{}' bundle is a fragment, not doing anything.", getKey());
320 }
321 }
322 return PluginState.ENABLED;
323 } catch (final BundleException e) {
324 throw new PluginException(e);
325 }
326 }
327
328 @Override
329 protected void disableInternal() {
330 try {
331 if (bundleIsUsable("disable")) {
332 if (bundle.getState() == Bundle.ACTIVE) {
333
334 if (containerAccessorTracker != null) {
335 containerAccessorTracker.close();
336 }
337 bundle.stop();
338 } else {
339 log.warn("Cannot disable Bundle '{}', not ACTIVE", getKey());
340 }
341 }
342 } catch (final BundleException e) {
343 throw new PluginException(e);
344 }
345 }
346
347 @Override
348 public void bundleChanged(BundleEvent event) {
349
350 if (event.getBundle() != bundle) {
351 return;
352 }
353
354 switch (event.getType()) {
355
356
357
358
359
360 case BundleEvent.STARTED:
361 log.info("Plugin '{}' bundle started: {}", getKey(), getPluginState());
362 if (getPluginState() != PluginState.ENABLING) {
363 enable();
364 }
365 break;
366
367
368
369
370
371
372 case BundleEvent.STOPPED:
373 log.info("Plugin '{}' bundle stopped: {}", getKey(), getPluginState());
374 if (getPluginState() != PluginState.DISABLING) {
375 disable();
376 }
377 break;
378 }
379 }
380
381 @Override
382 public ContainerAccessor getContainerAccessor() {
383
384 ContainerAccessor result = OsgiPluginUtil.createNonExistingPluginContainer(getKey());
385 if (containerAccessorTracker != null) {
386 ContainerAccessor tmp = containerAccessorTracker.getService();
387 if (tmp != null) {
388 result = tmp;
389 }
390 }
391 return result;
392 }
393
394 public ClassLoader getClassLoader() {
395 return getBundleClassLoaderOrFail();
396 }
397
398 private String getInstallationStateExplanation() {
399 return (null != osgiContainerManager) ? "not yet installed" : "already uninstalled";
400 }
401
402 private boolean bundleIsUsable(final String task) {
403 if (null != bundle) {
404 return true;
405 } else {
406 final String why = getInstallationStateExplanation();
407 log.warn("Cannot {} {} bundle '{}'", task, why, getKey());
408 return false;
409 }
410 }
411
412 private <T> T getOrFail(final T what, final String name) throws PluginException {
413 if (null == what) {
414 throw new IllegalPluginStateException("Cannot use " + name + " of " + getInstallationStateExplanation() + " '"
415 + getKey() + "' from '" + pluginArtifact + "'");
416 } else {
417 return what;
418 }
419 }
420
421 private Bundle getBundleOrFail() throws PluginException {
422 return getOrFail(bundle, "bundle");
423 }
424
425 private ClassLoader getBundleClassLoaderOrFail() throws PluginException {
426 return getOrFail(bundleClassLoader, "bundleClassLoader");
427 }
428 }