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.test.blueprint;
018    
019    import java.io.File;
020    import java.io.FileInputStream;
021    import java.io.FileNotFoundException;
022    import java.io.FileOutputStream;
023    import java.io.InputStream;
024    import java.net.MalformedURLException;
025    import java.net.URL;
026    import java.util.ArrayList;
027    import java.util.Arrays;
028    import java.util.Collection;
029    import java.util.Collections;
030    import java.util.Dictionary;
031    import java.util.Enumeration;
032    import java.util.HashMap;
033    import java.util.Iterator;
034    import java.util.List;
035    import java.util.Locale;
036    import java.util.Map;
037    import java.util.jar.JarInputStream;
038    
039    import de.kalpatec.pojosr.framework.PojoServiceRegistryFactoryImpl;
040    import de.kalpatec.pojosr.framework.launch.BundleDescriptor;
041    import de.kalpatec.pojosr.framework.launch.ClasspathScanner;
042    import de.kalpatec.pojosr.framework.launch.PojoServiceRegistry;
043    import de.kalpatec.pojosr.framework.launch.PojoServiceRegistryFactory;
044    import org.apache.camel.impl.DefaultClassResolver;
045    import org.apache.camel.spi.ClassResolver;
046    import org.apache.camel.util.FileUtil;
047    import org.apache.camel.util.IOHelper;
048    import org.apache.camel.util.ObjectHelper;
049    import org.ops4j.pax.swissbox.tinybundles.core.TinyBundle;
050    import org.ops4j.pax.swissbox.tinybundles.core.TinyBundles;
051    import org.osgi.framework.Bundle;
052    import org.osgi.framework.BundleContext;
053    import org.osgi.framework.BundleException;
054    import org.osgi.framework.Constants;
055    import org.osgi.framework.Filter;
056    import org.osgi.framework.FrameworkUtil;
057    import org.osgi.framework.InvalidSyntaxException;
058    import org.osgi.framework.ServiceReference;
059    import org.osgi.util.tracker.ServiceTracker;
060    import org.slf4j.Logger;
061    import org.slf4j.LoggerFactory;
062    
063    import static org.apache.camel.test.junit4.TestSupport.createDirectory;
064    import static org.apache.camel.test.junit4.TestSupport.deleteDirectory;
065    
066    /**
067     * Helper for using Blueprint with Camel.
068     */
069    public final class CamelBlueprintHelper {
070    
071        public static final long DEFAULT_TIMEOUT = 30000;
072        public static final String BUNDLE_FILTER = "(Bundle-SymbolicName=*)";
073        public static final String BUNDLE_VERSION = "1.0.0";
074        private static final transient Logger LOG = LoggerFactory.getLogger(CamelBlueprintHelper.class);
075        private static final ClassResolver RESOLVER = new DefaultClassResolver();
076    
077        private CamelBlueprintHelper() {
078        }
079    
080        public static BundleContext createBundleContext(String name, String descriptors, boolean includeTestBundle) throws Exception {
081            return createBundleContext(name, descriptors, includeTestBundle, BUNDLE_FILTER, BUNDLE_VERSION);
082        }
083    
084        public static BundleContext createBundleContext(String name, String descriptors, boolean includeTestBundle,
085                                                        String bundleFilter, String testBundleVersion) throws Exception {
086            TinyBundle bundle = null;
087    
088            if (includeTestBundle) {
089                // add ourselves as a bundle
090                bundle = createTestBundle(name, testBundleVersion, descriptors);
091            }
092    
093            return createBundleContext(name, bundleFilter, bundle);
094        }
095    
096        public static BundleContext createBundleContext(String name, String bundleFilter, TinyBundle bundle) throws Exception {
097            // ensure pojosr stores bundles in an unique target directory
098            String uid = "" + System.currentTimeMillis();
099            String tempDir = "target/bundles/" + uid;
100            System.setProperty("org.osgi.framework.storage", tempDir);
101            createDirectory(tempDir);
102    
103            // use another directory for the jar of the bundle as it cannot be in the same directory
104            // as it has a file lock during running the tests which will cause the temp dir to not be
105            // fully deleted between tests
106            createDirectory("target/test-bundles");
107    
108            // get the bundles
109            List<BundleDescriptor> bundles = getBundleDescriptors(bundleFilter);
110    
111            if (bundle != null) {
112                String jarName = name.toLowerCase(Locale.ENGLISH) + "-" + uid + ".jar";
113                bundles.add(getBundleDescriptor("target/test-bundles/" + jarName, bundle));
114            }
115    
116            if (LOG.isDebugEnabled()) {
117                for (int i = 0; i < bundles.size(); i++) {
118                    BundleDescriptor desc = bundles.get(i);
119                    LOG.debug("Bundle #{} -> {}", i, desc);
120                }
121            }
122    
123            // setup pojosr to use our bundles
124            Map<String, List<BundleDescriptor>> config = new HashMap<String, List<BundleDescriptor>>();
125            config.put(PojoServiceRegistryFactory.BUNDLE_DESCRIPTORS, bundles);
126    
127            // create pojorsr osgi service registry
128            PojoServiceRegistry reg = new PojoServiceRegistryFactoryImpl().newPojoServiceRegistry(config);
129            return reg.getBundleContext();
130        }
131    
132        public static void disposeBundleContext(BundleContext bundleContext) throws BundleException {
133            try {
134                if (bundleContext != null) {
135                    List<Bundle> bundles = new ArrayList<Bundle>();
136                    bundles.addAll(Arrays.asList(bundleContext.getBundles()));
137                    Collections.reverse(bundles);
138                    for (Bundle bundle : bundles) {
139                        LOG.debug("Stopping bundle {}", bundle);
140                        bundle.stop();
141                    }
142                }
143            } catch (Exception e) {
144                LOG.warn("Error during disposing BundleContext. This exception will be ignored.", e);
145            } finally {
146                String tempDir = System.clearProperty("org.osgi.framework.storage");
147                if (tempDir != null) {
148                    LOG.info("Deleting work directory {}", tempDir);
149                    deleteDirectory(tempDir);
150                }
151            }
152        }
153    
154        public static <T> T getOsgiService(BundleContext bundleContext, Class<T> type, long timeout) {
155            return getOsgiService(bundleContext, type, null, timeout);
156        }
157    
158        public static <T> T getOsgiService(BundleContext bundleContext, Class<T> type) {
159            return getOsgiService(bundleContext, type, null, DEFAULT_TIMEOUT);
160        }
161    
162        public static <T> T getOsgiService(BundleContext bundleContext, Class<T> type, String filter) {
163            return getOsgiService(bundleContext, type, filter, DEFAULT_TIMEOUT);
164        }
165    
166        public static <T> T getOsgiService(BundleContext bundleContext, Class<T> type, String filter, long timeout) {
167            ServiceTracker tracker = null;
168            try {
169                String flt;
170                if (filter != null) {
171                    if (filter.startsWith("(")) {
172                        flt = "(&(" + Constants.OBJECTCLASS + "=" + type.getName() + ")" + filter + ")";
173                    } else {
174                        flt = "(&(" + Constants.OBJECTCLASS + "=" + type.getName() + ")(" + filter + "))";
175                    }
176                } else {
177                    flt = "(" + Constants.OBJECTCLASS + "=" + type.getName() + ")";
178                }
179                Filter osgiFilter = FrameworkUtil.createFilter(flt);
180                tracker = new ServiceTracker(bundleContext, osgiFilter, null);
181                tracker.open(true);
182                // Note that the tracker is not closed to keep the reference
183                // This is buggy, as the service reference may change i think
184                Object svc = tracker.waitForService(timeout);
185                if (svc == null) {
186                    Dictionary<?, ?> dic = bundleContext.getBundle().getHeaders();
187                    System.err.println("Test bundle headers: " + explode(dic));
188    
189                    for (ServiceReference<?> ref : asCollection(bundleContext.getAllServiceReferences(null, null))) {
190                        System.err.println("ServiceReference: " + ref + ", bundle: " + ref.getBundle() + ", symbolicName: " + ref.getBundle().getSymbolicName());
191                    }
192    
193                    for (ServiceReference<?> ref : asCollection(bundleContext.getAllServiceReferences(null, flt))) {
194                        System.err.println("Filtered ServiceReference: " + ref + ", bundle: " + ref.getBundle() + ", symbolicName: " + ref.getBundle().getSymbolicName());
195                    }
196    
197                    throw new RuntimeException("Gave up waiting for service " + flt);
198                }
199                return type.cast(svc);
200            } catch (InvalidSyntaxException e) {
201                throw new IllegalArgumentException("Invalid filter", e);
202            } catch (InterruptedException e) {
203                throw new RuntimeException(e);
204            }
205        }
206    
207        protected static TinyBundle createTestBundle(String name, String version, String descriptors) throws FileNotFoundException, MalformedURLException {
208            TinyBundle bundle = TinyBundles.newBundle();
209            for (URL url : getBlueprintDescriptors(descriptors)) {
210                LOG.info("Using Blueprint XML file: " + url.getFile());
211                bundle.add("OSGI-INF/blueprint/blueprint-" + url.getFile().replace("/", "-"), url);
212            }
213            bundle.set("Manifest-Version", "2")
214                    .set("Bundle-ManifestVersion", "2")
215                    .set("Bundle-SymbolicName", name)
216                    .set("Bundle-Version", version);
217            return bundle;
218        }
219    
220        /**
221         * Explode the dictionary into a <code>,</code> delimited list of <code>key=value</code> pairs.
222         */
223        private static String explode(Dictionary<?, ?> dictionary) {
224            Enumeration<?> keys = dictionary.keys();
225            StringBuffer result = new StringBuffer();
226            while (keys.hasMoreElements()) {
227                Object key = keys.nextElement();
228                result.append(String.format("%s=%s", key, dictionary.get(key)));
229                if (keys.hasMoreElements()) {
230                    result.append(", ");
231                }
232            }
233            return result.toString();
234        }
235    
236        /**
237         * Provides an iterable collection of references, even if the original array is <code>null</code>.
238         */
239        private static Collection<ServiceReference<?>> asCollection(ServiceReference<?>[] references) {
240            return references  == null ? new ArrayList<ServiceReference<?>>(0) : Arrays.asList(references);
241        }
242    
243        /**
244         * Gets list of bundle descriptors.
245         * @param bundleFilter Filter expression for OSGI bundles.
246         *
247         * @return List pointers to OSGi bundles.
248         * @throws Exception If looking up the bundles fails.
249         */
250        private static List<BundleDescriptor> getBundleDescriptors(final String bundleFilter) throws Exception {
251            return new ClasspathScanner().scanForBundles(bundleFilter);
252        }
253    
254        /**
255         * Gets the bundle descriptors as {@link URL} resources.
256         *
257         * @param descriptors the bundle descriptors, can be separated by comma
258         * @return the bundle descriptors.
259         * @throws FileNotFoundException is thrown if a bundle descriptor cannot be found
260         */
261        private static Collection<URL> getBlueprintDescriptors(String descriptors) throws FileNotFoundException, MalformedURLException {
262            List<URL> answer = new ArrayList<URL>();
263            String descriptor = descriptors;
264            if (descriptor != null) {
265                // there may be more resources separated by comma
266                Iterator<Object> it = ObjectHelper.createIterator(descriptor);
267                while (it.hasNext()) {
268                    String s = (String) it.next();
269                    LOG.trace("Resource descriptor: {}", s);
270    
271                    // remove leading / to be able to load resource from the classpath
272                    s = FileUtil.stripLeadingSeparator(s);
273    
274                    // if there is wildcards for *.xml then we need to find the urls from the package
275                    if (s.endsWith("*.xml")) {
276                        String packageName = s.substring(0, s.length() - 5);
277                        // remove trailing / to be able to load resource from the classpath
278                        Enumeration<URL> urls = ResourceHelper.loadResourcesAsURL(packageName);
279                        while (urls.hasMoreElements()) {
280                            URL url = urls.nextElement();
281                            File dir = new File(url.getFile());
282                            if (dir.isDirectory()) {
283                                File[] files = dir.listFiles();
284                                if (files != null) {
285                                    for (File file : files) {
286                                        if (file.isFile() && file.exists() && file.getName().endsWith(".xml")) {
287                                            String name = packageName + file.getName();
288                                            LOG.debug("Resolving resource: {}", name);
289                                            URL xmlUrl = ObjectHelper.loadResourceAsURL(name);
290                                            if (xmlUrl != null) {
291                                                answer.add(xmlUrl);
292                                            }
293                                        }
294                                    }
295                                }
296                            }
297                        }
298                    } else {
299                        LOG.debug("Resolving resource: {}", s);
300                        URL url = ResourceHelper.resolveMandatoryResourceAsUrl(RESOLVER, s);
301                        if (url == null) {
302                            throw new FileNotFoundException("Resource " + s + " not found");
303                        }
304                        answer.add(url);
305                    }
306                }
307            } else {
308                throw new IllegalArgumentException("No bundle descriptor configured. Override getBlueprintDescriptor() or getBlueprintDescriptors() method");
309            }
310    
311            if (answer.isEmpty()) {
312                throw new IllegalArgumentException("Cannot find any resources in classpath from descriptor " + descriptors);
313            }
314            return answer;
315        }
316    
317        private static BundleDescriptor getBundleDescriptor(String path, TinyBundle bundle) throws Exception {
318            File file = new File(path);
319            // tell the JVM its okay to delete this file on exit as its a temporary file
320            // the JVM may not successfully delete the file though
321            file.deleteOnExit();
322    
323            FileOutputStream fos = new FileOutputStream(file, false);
324            InputStream is = bundle.build();
325            try {
326                IOHelper.copyAndCloseInput(is, fos);
327            } finally {
328                IOHelper.close(is);
329                IOHelper.close(fos);
330            }
331    
332            BundleDescriptor answer = null;
333            FileInputStream fis = null;
334            JarInputStream jis = null;
335            try {
336                fis = new FileInputStream(file);
337                jis = new JarInputStream(fis);
338                Map<String, String> headers = new HashMap<String, String>();
339                for (Map.Entry<Object, Object> entry : jis.getManifest().getMainAttributes().entrySet()) {
340                    headers.put(entry.getKey().toString(), entry.getValue().toString());
341                }
342    
343                answer = new BundleDescriptor(
344                        bundle.getClass().getClassLoader(),
345                        new URL("jar:" + file.toURI().toString() + "!/"),
346                        headers);
347            } finally {
348                IOHelper.close(fis);
349                IOHelper.close(jis);
350            }
351    
352            return answer;
353        }
354    
355    }