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 }