001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.script.osgi;
018    
019    import java.io.BufferedReader;
020    import java.io.InputStreamReader;
021    import java.lang.reflect.Method;
022    import java.net.URL;
023    import java.util.ArrayList;
024    import java.util.Enumeration;
025    import java.util.List;
026    import java.util.Map;
027    import java.util.concurrent.ConcurrentHashMap;
028    
029    import javax.script.ScriptEngine;
030    import javax.script.ScriptEngineFactory;
031    
032    import org.apache.camel.impl.osgi.tracker.BundleTracker;
033    import org.apache.camel.impl.osgi.tracker.BundleTrackerCustomizer;
034    import org.osgi.framework.Bundle;
035    import org.osgi.framework.BundleActivator;
036    import org.osgi.framework.BundleContext;
037    import org.osgi.framework.BundleEvent;
038    import org.osgi.framework.InvalidSyntaxException;
039    import org.osgi.framework.ServiceReference;
040    import org.osgi.framework.ServiceRegistration;
041    import org.slf4j.Logger;
042    import org.slf4j.LoggerFactory;
043    
044    public class Activator implements BundleActivator, BundleTrackerCustomizer {
045        public static final String META_INF_SERVICES_DIR = "META-INF/services";
046        public static final String SCRIPT_ENGINE_SERVICE_FILE = "javax.script.ScriptEngineFactory";
047    
048        private static final transient Logger LOG = LoggerFactory.getLogger(Activator.class);
049        private static BundleContext context;
050        private BundleTracker tracker;
051        
052        private Map<Long, List<BundleScriptEngineResolver>> resolvers 
053            = new ConcurrentHashMap<Long, List<BundleScriptEngineResolver>>();
054    
055        public static BundleContext getBundleContext() {
056            return context;
057        }
058        
059        public void start(BundleContext context) throws Exception {
060            Activator.context = context;
061            LOG.info("Camel-Script activator starting");
062            tracker = new BundleTracker(context, Bundle.ACTIVE, this);
063            tracker.open();
064            LOG.info("Camel-Script activator started");
065        }
066    
067        public void stop(BundleContext context) throws Exception {
068            LOG.info("Camel-Script activator stopping");
069            tracker.close();
070            LOG.info("Camel-Script activator stopped");
071            Activator.context = null;
072        }
073    
074        public Object addingBundle(Bundle bundle, BundleEvent event) {
075            List<BundleScriptEngineResolver> r = new ArrayList<BundleScriptEngineResolver>();
076            registerScriptEngines(bundle, r);
077            for (BundleScriptEngineResolver service : r) {
078                service.register();
079            }
080            resolvers.put(bundle.getBundleId(), r);
081            return bundle;
082        }
083    
084        public void modifiedBundle(Bundle bundle, BundleEvent event, Object object) {
085        }
086    
087        public void removedBundle(Bundle bundle, BundleEvent event, Object object) {
088            LOG.debug("Bundle stopped: {}", bundle.getSymbolicName());
089            List<BundleScriptEngineResolver> r = resolvers.remove(bundle.getBundleId());
090            if (r != null) {
091                for (BundleScriptEngineResolver service : r) {
092                    service.unregister();
093                }
094            }
095        }
096    
097        public static ScriptEngine resolveScriptEngine(String scriptEngineName) throws InvalidSyntaxException {
098            ServiceReference[] refs = context.getServiceReferences(ScriptEngineResolver.class.getName(), null);
099            if (refs == null) {
100                LOG.info("No OSGi script engine resolvers available!");
101                return null;
102            }
103            
104            LOG.debug("Found " + refs.length + " OSGi ScriptEngineResolver services");
105            
106            for (ServiceReference ref : refs) {
107                ScriptEngineResolver resolver = (ScriptEngineResolver) context.getService(ref);
108                ScriptEngine engine = resolver.resolveScriptEngine(scriptEngineName);
109                context.ungetService(ref);
110                LOG.debug("OSGi resolver " + resolver + " produced " + scriptEngineName + " engine " + engine);
111                if (engine != null) {
112                    return engine;
113                }
114            }
115            return null;
116        }
117    
118    
119        protected void registerScriptEngines(Bundle bundle, List<BundleScriptEngineResolver> resolvers) {
120            URL configURL = null;
121            for (Enumeration e = bundle.findEntries(META_INF_SERVICES_DIR, SCRIPT_ENGINE_SERVICE_FILE, false); e != null && e.hasMoreElements();) {
122                configURL = (URL) e.nextElement();
123            }
124            if (configURL != null) {
125                LOG.info("Found ScriptEngineFactory in " + bundle.getSymbolicName());
126                resolvers.add(new BundleScriptEngineResolver(bundle, configURL));
127            }
128        } 
129        public static interface ScriptEngineResolver {
130            ScriptEngine resolveScriptEngine(String name);
131        }
132        protected static class BundleScriptEngineResolver implements ScriptEngineResolver {
133            protected final Bundle bundle;
134            private ServiceRegistration reg;
135            private final URL configFile;
136    
137            public BundleScriptEngineResolver(Bundle bundle, URL configFile) {
138                this.bundle = bundle;
139                this.configFile = configFile;
140            }
141            public void register() {
142                reg = bundle.getBundleContext().registerService(ScriptEngineResolver.class.getName(), 
143                                                                this, null);
144            }
145            public void unregister() {
146                reg.unregister();
147            }
148            public ScriptEngine resolveScriptEngine(String name) {
149                try {
150                    BufferedReader in = new BufferedReader(new InputStreamReader(configFile.openStream()));
151                    String className = in.readLine();
152                    in.close();
153                    Class cls = bundle.loadClass(className);
154                    if (!ScriptEngineFactory.class.isAssignableFrom(cls)) {
155                        throw new IllegalStateException("Invalid ScriptEngineFactory: " + cls.getName());
156                    }
157                    ScriptEngineFactory factory = (ScriptEngineFactory) cls.newInstance();
158                    List<String> names = factory.getNames();
159                    for (String test : names) {
160                        if (test.equals(name)) {
161                            ClassLoader old = Thread.currentThread().getContextClassLoader();
162                            ScriptEngine engine;
163                            try {
164                                // JRuby seems to require the correct TCCL to call getScriptEngine
165                                Thread.currentThread().setContextClassLoader(factory.getClass().getClassLoader());
166                                engine = factory.getScriptEngine();
167                            } finally {
168                                Thread.currentThread().setContextClassLoader(old);
169                            }
170                            LOG.trace("Resolved ScriptEngineFactory: {} for expected name: {}", engine, name);
171                            return engine;
172                        }
173                    }
174                    LOG.debug("ScriptEngineFactory: {} does not match expected name: {}", factory.getEngineName(), name);
175                    return null;
176                } catch (Exception e) {
177                    LOG.warn("Cannot create ScriptEngineFactory: " + e.getClass().getName(), e);
178                    return null;
179                }
180            }
181    
182            @Override
183            public String toString() {
184                return "OSGi script engine resolver for " + bundle.getSymbolicName();
185            }
186        }
187    
188    
189    }