/* * #%L * Wisdom-Framework * %% * Copyright (C) 2013 - 2014 Wisdom Framework * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package org.wisdom.test.internals; import aQute.bnd.osgi.*; import com.google.common.reflect.ClassPath; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.felix.ipojo.manipulator.Pojoization; import org.apache.felix.ipojo.manipulator.util.IsolatedClassLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.wisdom.maven.osgi.BundlePackager; import org.wisdom.maven.osgi.ProjectScanner; import org.wisdom.test.probe.Activator; import org.wisdom.test.shared.InVivoRunnerFactory; import java.io.*; import java.lang.reflect.Array; import java.util.*; /** * Class responsible for creating the probe bundle. * The probe does not include the application bundle, only the test classes and some additional resources. * Application class are accessed using a custom classloader. */ public class ProbeBundleMaker { /** * The name of the probe bundle. */ public static final String BUNDLE_NAME = "wisdom-probe-bundle"; /** * The packages to add in the probe bundle. * Helpers need to be integrated to the probe bundle to avoid class loading issue (#446) */ public static final String PACKAGES_TO_ADD = "org.wisdom.test.parents.*, " + "org.wisdom.test.probe, org.wisdom.test.assertions, " + "org.ow2.chameleon.testing.helpers," + "org.ow2.chameleon.testing.helpers.constants"; /** * The path of the probe bundle file. */ public static final String PROBE_FILE = "target/osgi/probe.jar"; /** * The test classes path. */ public static final String TEST_CLASSES = "target/test-classes"; private static final Logger LOGGER = LoggerFactory.getLogger(ProbeBundleMaker.class); static { // At initialization, delete the probe bundle if exist File probe = new File(PROBE_FILE); if (probe.isFile()) { FileUtils.deleteQuietly(probe); } } private ProbeBundleMaker() { //Unused } /** * Creates the test probe. The test probe is a bundle containing all the classes from `src/test/java` and * resources from `src/test/resources`. The bundle computes the imports automatically, but all are optionals. The * created bundle has a specific bundle activator exposing the {@link InVivoRunnerFactory} service. The created * bundle has the following symbolic name: "Wisdom-Test-Probe" * * @return an input stream on the bundle * @throws Exception if the probe creation fails */ public static InputStream probe() throws Exception { //NOSONAR we throw exception as BND throws Exception. File probe = new File(PROBE_FILE); if (probe.isFile()) { return new FileInputStream(probe); } Properties maven = BundlePackager.readMavenProperties(new File(".")); Properties instructions = new Properties(); getProbeInstructions(instructions, maven); Builder builder = getOSGiBuilder(instructions, computeClassPath()); builder.build(); reportErrors("BND ~> ", builder.getWarnings(), builder.getErrors()); File bnd = File.createTempFile("probe", ".jar"); builder.getJar().write(bnd); // No need for a privilege block here, we are in Maven - no security involved. IsolatedClassLoader classLoader = new IsolatedClassLoader(ProbeBundleMaker.class.getClassLoader(), //NOSONAR true); File tests = new File(TEST_CLASSES); classLoader.addURL(tests.toURI().toURL()); Pojoization pojoization = new Pojoization(); pojoization.pojoization(bnd, probe, new File("src/test/resources"), classLoader); reportErrors("iPOJO ~> ", pojoization.getWarnings(), pojoization.getErrors()); return new FileInputStream(probe); } private static void getProbeInstructions(Properties instructions, Properties maven) throws IOException { List<String> privates = new ArrayList<>(); List<String> exports = new ArrayList<>(); final File basedir = new File(maven.getProperty("project.baseDir")); ProjectScanner scanner = new ProjectScanner(basedir); // Do local resources String resources = BundlePackager.getLocalResources(basedir, true, scanner); if (!resources.isEmpty()) { instructions.put(Analyzer.INCLUDE_RESOURCE, resources); } Set<String> packages = scanner.getPackagesFromTestSources(); for (String s : packages) { if (s.endsWith("service") || s.endsWith("services")) { exports.add(s); } else { if (!s.isEmpty()) { privates.add(s + ";-split-package:=first"); } } } instructions.put(Constants.PRIVATE_PACKAGE, toClause(privates) + "," + PACKAGES_TO_ADD); if (!exports.isEmpty()) { instructions.put(Constants.EXPORT_PACKAGE, toClause(privates)); } instructions.put(Constants.IMPORT_PACKAGE, "org.osgi.framework;version=1.7, *;resolution:=optional"); instructions.put(Constants.BUNDLE_SYMBOLIC_NAME_ATTRIBUTE, BUNDLE_NAME); instructions.put(Constants.BUNDLE_ACTIVATOR, Activator.class.getName()); // For debugging purpose, dump the instructions to target/osgi/default-instructions.instructions FileOutputStream fos = null; try { File out = new File("target/osgi/probe-instructions.properties"); fos = new FileOutputStream(out); instructions.store(fos, "BND Instructions for test probe"); } catch (IOException e) { // NOSONAR // Ignore it. } finally { IOUtils.closeQuietly(fos); } } private static String toClause(List<String> packages) { StringBuilder builder = new StringBuilder(); for (String p : packages) { if (builder.length() != 0) { builder.append(", "); } builder.append(p); } return builder.toString(); } private static Jar[] computeClassPath() throws IOException { List<Jar> list = new ArrayList<>(); File tests = new File(TEST_CLASSES); if (tests.isDirectory()) { list.add(new Jar(".", tests)); } ClassPath classpath = ClassPath.from(ProbeBundleMaker.class.getClassLoader()); list.add(new JarFromClassloader(classpath)); Jar[] cp = new Jar[list.size()]; list.toArray(cp); return cp; } protected static Builder getOSGiBuilder(Properties properties, Jar[] classpath) throws Exception { //NOSONAR rethrows exceptions from BND Builder builder = new Builder(); // protect setBase...getBndLastModified which uses static DateFormat synchronized (ProbeBundleMaker.class) { builder.setBase(new File("")); } builder.setProperties(sanitize(properties)); if (classpath != null) { builder.setClasspath(classpath); } return builder; } protected static Properties sanitize(Properties properties) { // convert any non-String keys/values to Strings Properties sanitizedEntries = new Properties(); for (Iterator<?> itr = properties.entrySet().iterator(); itr.hasNext(); ) { Map.Entry entry = (Map.Entry) itr.next(); if (!(entry.getKey() instanceof String)) { String key = sanitize(entry.getKey()); if (!properties.containsKey(key)) { sanitizedEntries.setProperty(key, sanitize(entry.getValue())); } itr.remove(); } else if (!(entry.getValue() instanceof String)) { entry.setValue(sanitize(entry.getValue())); } } properties.putAll(sanitizedEntries); return properties; } protected static String sanitize(Object value) { if (value instanceof String) { return (String) value; } else if (value instanceof Iterable) { String delim = ""; StringBuilder buf = new StringBuilder(); for (Object i : (Iterable<?>) value) { buf.append(delim).append(i); delim = ", "; } return buf.toString(); } else if (value.getClass().isArray()) { String delim = ""; StringBuilder buf = new StringBuilder(); for (int i = 0, len = Array.getLength(value); i < len; i++) { buf.append(delim).append(Array.get(value, i)); delim = ", "; } return buf.toString(); } else { return String.valueOf(value); } } protected static boolean reportErrors(String prefix, List<String> warnings, List<String> errors) { for (String msg : warnings) { LOGGER.error(prefix + " : " + msg); } boolean hasErrors = false; String fileNotFound = "Input file does not exist: "; for (String msg : errors) { if (msg.startsWith(fileNotFound) && msg.endsWith("~")) { // treat as warning; this error happens when you have duplicate entries in Include-Resource String duplicate = Processor.removeDuplicateMarker(msg.substring(fileNotFound.length())); LOGGER.error(prefix + " Duplicate path '" + duplicate + "' in Include-Resource"); } else { LOGGER.error(prefix + " : " + msg); hasErrors = true; } } return hasErrors; } /** * Makes the given classpath looks like a Jar. */ private static class JarFromClassloader extends Jar { /** * Creates an instance of {@link org.wisdom.test.internals.ProbeBundleMaker.JarFromClassloader} * @param classpath the classpath */ public JarFromClassloader(ClassPath classpath) { super("classrealms"); ClassPathResource.build(this, classpath, null); } } }