001/*
002 * (C) Copyright 2006-2014 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Nuxeo - initial API and implementation
018 *
019 */
020
021package org.nuxeo.osgi;
022
023import java.io.IOException;
024import java.io.InputStream;
025import java.net.URL;
026import java.util.ArrayList;
027import java.util.Dictionary;
028import java.util.Enumeration;
029import java.util.Map;
030import java.util.jar.Manifest;
031
032import org.nuxeo.common.utils.ExceptionUtils;
033import org.nuxeo.osgi.util.CompoundEnumeration;
034import org.nuxeo.runtime.api.Framework;
035import org.osgi.framework.Bundle;
036import org.osgi.framework.BundleActivator;
037import org.osgi.framework.BundleContext;
038import org.osgi.framework.BundleEvent;
039import org.osgi.framework.BundleException;
040import org.osgi.framework.Constants;
041import org.osgi.framework.ServiceReference;
042import org.osgi.framework.Version;
043import org.osgi.service.packageadmin.PackageAdmin;
044
045/**
046 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
047 */
048public class BundleImpl implements Bundle {
049
050    protected final long id;
051
052    protected final String symbolicName;
053
054    protected final Dictionary<String, String> headers;
055
056    protected final BundleContext context;
057
058    protected final OSGiAdapter osgi;
059
060    protected final BundleFile file;
061
062    protected final ClassLoader loader;
063
064    protected int state;
065
066    protected long lastModified;
067
068    protected BundleActivator activator;
069
070    protected double startupTime;
071
072    protected boolean allowHostOverride;
073
074    public BundleImpl(OSGiAdapter osgi, BundleFile file, ClassLoader loader) throws BundleException {
075        this(osgi, file, loader, false);
076    }
077
078    public BundleImpl(OSGiAdapter osgi, BundleFile file, ClassLoader loader, boolean isSystemBundle)
079            throws BundleException {
080        this.osgi = osgi;
081        this.loader = loader;
082        this.file = file;
083        Manifest mf = file.getManifest();
084        if (mf == null) {
085            headers = null;
086            symbolicName = null;
087            id = -1;
088            context = null;
089            return;
090        }
091        try {
092            headers = BundleManifestReader.getHeaders(mf);
093        } catch (BundleException e) {
094            throw new BundleException("Invalid OSGi Manifest in file " + file + " : " + e.getMessage(), e);
095        }
096        symbolicName = headers.get(Constants.BUNDLE_SYMBOLICNAME);
097        allowHostOverride = Boolean.parseBoolean(headers.get(BundleManifestReader.ALLOW_HOST_OVERRIDE));
098        id = isSystemBundle ? 0 : osgi.getBundleId(symbolicName);
099        context = createContext();
100        state = UNINSTALLED;
101    }
102
103    public BundleFile getBundleFile() {
104        return file;
105    }
106
107    protected final BundleContext createContext() {
108        return new OSGiBundleContext(this);
109    }
110
111    @Override
112    public BundleContext getBundleContext() {
113        // ensure BundleContext is not visible in RESOLVED state - to ensure
114        // OSGi compat. - in our component activate method.
115        // TODO NXP-6035: disable for now the check until a better compatibility
116        // mode is implemented.
117        // if (state == RESOLVED) {
118        // throw new IllegalStateException(
119        // "You cannot use a BundleContext when in RESOLVED state. Do not use this in your component activate method!");
120        // }
121        return context;
122    }
123
124    @Override
125    public void start(int options) throws BundleException {
126        // TODO Auto-generated method stub
127    }
128
129    @Override
130    public void stop(int options) throws BundleException {
131        // TODO
132    }
133
134    @Override
135    public String getLocation() {
136        return file.getLocation();
137    }
138
139    @Override
140    public URL getResource(String name) {
141        return loader.getResource(name);
142    }
143
144    @Override
145    public Enumeration<URL> getResources(String name) throws IOException {
146        return loader.getResources(name);
147    }
148
149    @Override
150    public Class<?> loadClass(String name) throws ClassNotFoundException {
151        try {
152            return loader.loadClass(name);
153        } catch (NoClassDefFoundError e) {
154            throw e;
155        }
156    }
157
158    @Override
159    public URL getEntry(String name) {
160        return file.getEntry(name);
161    }
162
163    public static PackageAdmin getPackageAdmin() {
164        BundleContext sysctx = Framework.getRuntime().getContext().getBundle().getBundleContext();
165        ServiceReference ref = sysctx.getServiceReference(PackageAdmin.class.getName());
166        return (PackageAdmin) sysctx.getService(ref);
167    }
168
169    protected static class CompoundEnumerationBuilder {
170
171        protected final ArrayList<Enumeration<URL>> collected = new ArrayList<>();
172
173        public CompoundEnumerationBuilder add(Enumeration<URL> e) {
174            collected.add(e);
175            return this;
176        }
177
178        public Enumeration<URL> build() {
179            return new CompoundEnumeration<>(collected.toArray(new Enumeration[collected.size()]));
180        }
181
182    }
183
184    @Override
185    public Enumeration<URL> findEntries(String path, String filePattern, boolean recurse) {
186        Enumeration<URL> hostEntries = file.findEntries(path, filePattern, recurse);
187        Bundle[] fragments = osgi.getRegistry().getFragments(symbolicName);
188        if (fragments.length == 0) {
189            return hostEntries;
190        }
191
192        CompoundEnumerationBuilder builder = new CompoundEnumerationBuilder();
193        if (!allowHostOverride) {
194            builder.add(hostEntries);
195        }
196
197        for (Bundle fragment : fragments) {
198            Enumeration<URL> fragmentEntries = fragment.findEntries(path, filePattern, recurse);
199            builder.add(fragmentEntries);
200        }
201
202        if (allowHostOverride) {
203            builder.add(hostEntries);
204        }
205
206        return builder.build();
207    }
208
209    @Override
210    public Enumeration<String> getEntryPaths(String path) {
211        return file.getEntryPaths(path);
212    }
213
214    @Override
215    public long getBundleId() {
216        return id;
217    }
218
219    @Override
220    public Dictionary<String, String> getHeaders() {
221        return headers;
222    }
223
224    @Override
225    public Dictionary<String, String> getHeaders(String locale) {
226        return headers; // TODO
227    }
228
229    @Override
230    public long getLastModified() {
231        return lastModified;
232    }
233
234    @Override
235    public ServiceReference[] getRegisteredServices() {
236        // RegistrationInfo ri =
237        // (RegistrationInfo)di.context.get("RegistrationInfo");
238        // TODO Auto-generated method stub
239        return null;
240    }
241
242    @Override
243    public ServiceReference[] getServicesInUse() {
244        // TODO Auto-generated method stub
245        return null;
246    }
247
248    @Override
249    public int getState() {
250        return state;
251    }
252
253    @Override
254    public String getSymbolicName() {
255        return symbolicName;
256    }
257
258    @Override
259    public boolean hasPermission(Object permission) {
260        return true; // TODO
261    }
262
263    protected String getActivatorClassName() {
264        return headers == null ? null : headers.get(Constants.BUNDLE_ACTIVATOR);
265    }
266
267    public BundleActivator getActivator() throws BundleException {
268        if (activator == null) {
269            activator = NullActivator.INSTANCE;
270            String className = getActivatorClassName();
271            if (className == null) {
272                return activator;
273            }
274            try {
275                activator = (BundleActivator) loadClass(className).newInstance();
276            } catch (ClassNotFoundException e) {
277                throw new BundleException("Activator not found: " + className, e);
278            } catch (InstantiationException e) {
279                throw new BundleException("Activator not instantiable: " + className, e);
280            } catch (IllegalAccessException e) {
281                throw new BundleException("Activator not accessible: " + className, e);
282            }
283        }
284        return activator;
285    }
286
287    @Override
288    public void start() throws BundleException {
289        try {
290            setStarting();
291            getActivator().start(context);
292            setStarted();
293        } catch (BundleException e) {
294            throw new BundleException("Failed to start bundle at: " + file + " with activator: "
295                    + getActivatorClassName(), e);
296        } catch (Exception e) { // stupid OSGi API throws Exception
297            RuntimeException re = ExceptionUtils.runtimeException(e);
298            throw new BundleException("Failed to start bundle at: " + file + " with activator: "
299                    + getActivatorClassName(), re);
300        }
301    }
302
303    @Override
304    public void stop() throws BundleException {
305        try {
306            setStopping();
307            getActivator().stop(context);
308            setStopped();
309        } catch (BundleException e) {
310            throw new BundleException("Failed to stop activator: " + getActivatorClassName(), e);
311        } catch (Exception e) { // stupid OSGi API throws Exception
312            RuntimeException re = ExceptionUtils.runtimeException(e);
313            throw new BundleException("Failed to stop activator: " + getActivatorClassName(), re);
314        }
315    }
316
317    public void shutdown() throws BundleException {
318        try {
319            state = STOPPING;
320            getActivator().stop(context);
321            lastModified = System.currentTimeMillis();
322            state = UNINSTALLED;
323        } catch (BundleException e) {
324            throw new BundleException("Failed to stop activator: " + getActivatorClassName(), e);
325        } catch (Exception e) { // stupid OSGi API throws Exception
326            RuntimeException re = ExceptionUtils.runtimeException(e);
327            throw new BundleException("Failed to stop activator: " + getActivatorClassName(), re);
328        }
329    }
330
331    @Override
332    public void uninstall() throws BundleException {
333        osgi.uninstall(this);
334        try {
335            file.close(osgi);
336        } catch (IOException e) {
337            throw new BundleException("Cannot close underlying file resources " + symbolicName, e);
338        }
339    }
340
341    @Override
342    public void update() throws BundleException {
343        lastModified = System.currentTimeMillis();
344        throw new UnsupportedOperationException("Bundle.update() operations was not yet implemented");
345    }
346
347    @Override
348    public void update(InputStream in) throws BundleException {
349        lastModified = System.currentTimeMillis();
350        throw new UnsupportedOperationException("Bundle.update() operations was not yet implemented");
351    }
352
353    void setInstalled() {
354        if (state == INSTALLED) {
355            return;
356        }
357        lastModified = System.currentTimeMillis();
358        state = INSTALLED;
359        BundleEvent event = new BundleEvent(BundleEvent.INSTALLED, this);
360        osgi.fireBundleEvent(event);
361    }
362
363    void setUninstalled() {
364        if (state == UNINSTALLED) {
365            return;
366        }
367        lastModified = System.currentTimeMillis();
368        state = UNINSTALLED;
369        BundleEvent event = new BundleEvent(BundleEvent.UNINSTALLED, this);
370        osgi.fireBundleEvent(event);
371    }
372
373    void setResolved() {
374        if (state == RESOLVED) {
375            return;
376        }
377        state = RESOLVED;
378        BundleEvent event = new BundleEvent(BundleEvent.RESOLVED, this);
379        osgi.fireBundleEvent(event);
380    }
381
382    void setUnResolved() {
383        state = INSTALLED;
384        BundleEvent event = new BundleEvent(BundleEvent.UNRESOLVED, this);
385        osgi.fireBundleEvent(event);
386    }
387
388    void setStarting() {
389        if (state != RESOLVED) {
390            return;
391        }
392        state = STARTING;
393        BundleEvent event = new BundleEvent(BundleEvent.STARTING, this);
394        osgi.fireBundleEvent(event);
395    }
396
397    void setStarted() {
398        if (state != STARTING) {
399            return;
400        }
401        state = ACTIVE;
402        BundleEvent event = new BundleEvent(BundleEvent.STARTED, this);
403        osgi.fireBundleEvent(event);
404    }
405
406    void setStopping() {
407        if (state != ACTIVE) {
408            return;
409        }
410        state = STOPPING;
411        BundleEvent event = new BundleEvent(BundleEvent.STOPPING, this);
412        osgi.fireBundleEvent(event);
413    }
414
415    void setStopped() {
416        if (state != STOPPING) {
417            return;
418        }
419        state = RESOLVED;
420        BundleEvent event = new BundleEvent(BundleEvent.STOPPED, this);
421        osgi.fireBundleEvent(event);
422    }
423
424    public double getStartupTime() {
425        return startupTime;
426    }
427
428    @Override
429    public int hashCode() {
430        return symbolicName.hashCode();
431    }
432
433    @Override
434    public boolean equals(Object obj) {
435        if (obj instanceof Bundle) {
436            return symbolicName.equals(((Bundle) obj).getSymbolicName());
437        }
438        return false;
439    }
440
441    @Override
442    public String toString() {
443        return symbolicName;
444    }
445
446    @Override
447    public Map getSignerCertificates(int signersType) {
448        throw new UnsupportedOperationException("not yet implemented");
449    }
450
451    @Override
452    public Version getVersion() {
453        return Version.parseVersion(headers.get(Constants.BUNDLE_VERSION));
454    }
455
456}