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).getDeclaredConstructor().newInstance();
279            } catch (ClassNotFoundException e) {
280                throw new BundleException("Activator not found: " + className, e);
281            } catch (ReflectiveOperationException e) {
282                throw new BundleException("Activator not instantiable: " + className, e);
283            }
284        }
285        return activator;
286    }
287
288    @Override
289    public void start() throws BundleException {
290        try {
291            setStarting();
292            getActivator().start(context);
293            setStarted();
294        } catch (BundleException e) {
295            throw new BundleException("Failed to start bundle at: " + file + " with activator: "
296                    + getActivatorClassName(), e);
297        } catch (Exception e) { // stupid OSGi API throws Exception
298            RuntimeException re = ExceptionUtils.runtimeException(e);
299            throw new BundleException("Failed to start bundle at: " + file + " with activator: "
300                    + getActivatorClassName(), re);
301        }
302    }
303
304    @Override
305    public void stop() throws BundleException {
306        try {
307            setStopping();
308            getActivator().stop(context);
309            setStopped();
310        } catch (BundleException e) {
311            throw new BundleException("Failed to stop activator: " + getActivatorClassName(), e);
312        } catch (Exception e) { // stupid OSGi API throws Exception
313            RuntimeException re = ExceptionUtils.runtimeException(e);
314            throw new BundleException("Failed to stop activator: " + getActivatorClassName(), re);
315        }
316    }
317
318    public void shutdown() throws BundleException {
319        try {
320            state = STOPPING;
321            getActivator().stop(context);
322            lastModified = System.currentTimeMillis();
323            state = UNINSTALLED;
324        } catch (BundleException e) {
325            throw new BundleException("Failed to stop activator: " + getActivatorClassName(), e);
326        } catch (Exception e) { // stupid OSGi API throws Exception
327            RuntimeException re = ExceptionUtils.runtimeException(e);
328            throw new BundleException("Failed to stop activator: " + getActivatorClassName(), re);
329        }
330    }
331
332    @Override
333    public void uninstall() throws BundleException {
334        osgi.uninstall(this);
335        try {
336            file.close();
337        } catch (IOException e) {
338            throw new BundleException("Cannot close underlying file resources " + symbolicName, e);
339        }
340    }
341
342    @Override
343    public void update() throws BundleException {
344        lastModified = System.currentTimeMillis();
345        throw new UnsupportedOperationException("Bundle.update() operations was not yet implemented");
346    }
347
348    @Override
349    public void update(InputStream in) throws BundleException {
350        lastModified = System.currentTimeMillis();
351        throw new UnsupportedOperationException("Bundle.update() operations was not yet implemented");
352    }
353
354    void setInstalled() {
355        if (state == INSTALLED) {
356            return;
357        }
358        lastModified = System.currentTimeMillis();
359        state = INSTALLED;
360        BundleEvent event = new BundleEvent(BundleEvent.INSTALLED, this);
361        osgi.fireBundleEvent(event);
362    }
363
364    void setUninstalled() {
365        if (state == UNINSTALLED) {
366            return;
367        }
368        lastModified = System.currentTimeMillis();
369        state = UNINSTALLED;
370        BundleEvent event = new BundleEvent(BundleEvent.UNINSTALLED, this);
371        osgi.fireBundleEvent(event);
372    }
373
374    void setResolved() {
375        if (state == RESOLVED) {
376            return;
377        }
378        state = RESOLVED;
379        BundleEvent event = new BundleEvent(BundleEvent.RESOLVED, this);
380        osgi.fireBundleEvent(event);
381    }
382
383    void setUnResolved() {
384        state = INSTALLED;
385        BundleEvent event = new BundleEvent(BundleEvent.UNRESOLVED, this);
386        osgi.fireBundleEvent(event);
387    }
388
389    void setStarting() {
390        if (state != RESOLVED) {
391            return;
392        }
393        state = STARTING;
394        BundleEvent event = new BundleEvent(BundleEvent.STARTING, this);
395        osgi.fireBundleEvent(event);
396    }
397
398    void setStarted() {
399        if (state != STARTING) {
400            return;
401        }
402        state = ACTIVE;
403        BundleEvent event = new BundleEvent(BundleEvent.STARTED, this);
404        osgi.fireBundleEvent(event);
405    }
406
407    void setStopping() {
408        if (state != ACTIVE) {
409            return;
410        }
411        state = STOPPING;
412        BundleEvent event = new BundleEvent(BundleEvent.STOPPING, this);
413        osgi.fireBundleEvent(event);
414    }
415
416    void setStopped() {
417        if (state != STOPPING) {
418            return;
419        }
420        state = RESOLVED;
421        BundleEvent event = new BundleEvent(BundleEvent.STOPPED, this);
422        osgi.fireBundleEvent(event);
423    }
424
425    public double getStartupTime() {
426        return startupTime;
427    }
428
429    @Override
430    public int hashCode() {
431        return symbolicName.hashCode();
432    }
433
434    @Override
435    public boolean equals(Object obj) {
436        if (obj instanceof Bundle) {
437            return symbolicName.equals(((Bundle) obj).getSymbolicName());
438        }
439        return false;
440    }
441
442    @Override
443    public String toString() {
444        return symbolicName;
445    }
446
447    @SuppressWarnings("rawtypes")
448    @Override
449    public Map getSignerCertificates(int signersType) {
450        throw new UnsupportedOperationException("not yet implemented");
451    }
452
453    @Override
454    public Version getVersion() {
455        return Version.parseVersion(headers.get(Constants.BUNDLE_VERSION));
456    }
457
458}