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