/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package org.apache.sling.paxexam.util; import static org.ops4j.pax.exam.CoreOptions.junitBundles; import static org.ops4j.pax.exam.CoreOptions.mavenBundle; import static org.ops4j.pax.exam.CoreOptions.systemProperty; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import org.apache.sling.maven.projectsupport.BundleListUtils; import org.apache.sling.maven.projectsupport.bundlelist.v1_0_0.Bundle; import org.apache.sling.maven.projectsupport.bundlelist.v1_0_0.BundleList; import org.apache.sling.maven.projectsupport.bundlelist.v1_0_0.StartLevel; import org.ops4j.pax.exam.CoreOptions; import org.ops4j.pax.exam.options.CompositeOption; import org.ops4j.pax.exam.options.DefaultCompositeOption; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Pax exam options and utilities to test Sling applications * The basic idea is to get a vanilla Sling launchpad instance * setup with a minimal amount of boilerplate code. * See SlingSetupTest for an example. */ public class SlingPaxOptions { private static final Logger log = LoggerFactory.getLogger(SlingPaxOptions.class); public static final int DEFAULT_SLING_START_LEVEL = 30; public static final String PROP_TELNET_PORT = "osgi.shell.telnet.port"; public static final String PROP_HTTP_PORT = "org.osgi.service.http.port"; public static final String DEFAULT_RUN_MODES = "jackrabbit"; private static String [] ignoredBundlePrefixes; private static int getAvailablePort() { int result = Integer.MIN_VALUE; try { final ServerSocket s = new ServerSocket(0); result = s.getLocalPort(); s.close(); } catch(IOException ignore) { } return result; } /** * When reading bundle lists, ignore bundles which have symbolic names * starting with one of the supplied prefixes * @param symbolicNamePrefix Symbolic name prefixes */ public static void setIgnoredBundles(String ... symbolicNamePrefix) { if(symbolicNamePrefix == null || symbolicNamePrefix.length == 0) { ignoredBundlePrefixes = new String[] {}; } else { ignoredBundlePrefixes = symbolicNamePrefix; } } /** * Get run modes to use for our tests, as set by the sling.run.modes property * @return Run modes */ public static Collection<String> getTestRunModes() { final String runModes = System.getProperty("sling.run.modes", DEFAULT_RUN_MODES); return Arrays.asList(runModes.split(",")); } /** * Set default launchpad options * @param launchpadVersion null means use the latest * @return Composite option */ public static CompositeOption defaultLaunchpadOptions(String launchpadVersion) { final String paxLogLevel = System.getProperty("pax.exam.log.level", "INFO"); final int slingStartLevel = DEFAULT_SLING_START_LEVEL; final String telnetPort = System.getProperty(PROP_TELNET_PORT, String.valueOf(getAvailablePort())); final String httpPort = System.getProperty(PROP_HTTP_PORT, String.valueOf(getAvailablePort())); log.info("{}={}", PROP_TELNET_PORT, telnetPort); log.info("{}={}", PROP_HTTP_PORT, httpPort); return new DefaultCompositeOption( junitBundles(), systemProperty( "org.ops4j.pax.logging.DefaultServiceLog.level" ).value(paxLogLevel), SlingPaxOptions.felixRemoteShellBundles(), SlingPaxOptions.slingBootstrapBundles(), SlingPaxOptions.slingLaunchpadBundles(launchpadVersion), CoreOptions.frameworkStartLevel(slingStartLevel), CoreOptions.frameworkProperty(PROP_TELNET_PORT).value(telnetPort), CoreOptions.frameworkProperty(PROP_HTTP_PORT).value(httpPort) ); } private static boolean ignore(Bundle b) { boolean result = false; if(ignoredBundlePrefixes != null) { final String sn = b.getArtifactId(); for(String prefix : ignoredBundlePrefixes) { if(sn.startsWith(prefix)) { result = true; break; } } } return result; } public static CompositeOption slingBundleList(String groupId, String artifactId, String version, String type, String classifier) { final DefaultCompositeOption result = new DefaultCompositeOption(); final String paxUrl = new StringBuilder() .append("mvn:") .append(groupId) .append("/") .append(artifactId) .append("/") .append(version == null ? "" : version) .append("/") .append(type == null ? "" : type) .append("/") .append(classifier == null ? "" : classifier) .toString(); // TODO BundleList should take an InputStream - for now copy to a tmp file for parsing log.info("Getting bundle list {}", paxUrl); File tmp = null; final Collection<String> testRunModes = getTestRunModes(); try { tmp = dumpMvnUrlToTmpFile(paxUrl); final BundleList list = BundleListUtils.readBundleList(tmp); int counter = 0; int ignored = 0; for(StartLevel s : list.getStartLevels()) { // Start level < 0 means bootstrap in our bundle lists final int startLevel = s.getStartLevel() < 0 ? 1 : s.getStartLevel(); for(Bundle b : s.getBundles()) { if(ignore(b)) { log.info("Bundle ignored due to setIgnoredBundles: {}", b); ignored++; continue; } counter++; // TODO need better fragment detection // (but pax exam should really detect that by itself?) final List<String> KNOWN_FRAGMENTS = new ArrayList<String>(); KNOWN_FRAGMENTS.add("org.apache.sling.extensions.webconsolebranding"); final boolean isFragment = b.getArtifactId().contains("fragment") || KNOWN_FRAGMENTS.contains(b.getArtifactId()); // Ignore bundles with run modes that do not match ours final String bundleRunModes = b.getRunModes(); if(bundleRunModes != null && bundleRunModes.length() > 0) { boolean active = false; for(String m : bundleRunModes.split(",")) { if(testRunModes.contains(m)) { active = true; break; } } if(!active) { log.info("Ignoring bundle {} as none of its run modes [{}] are active in this test run {}", new Object[] { b.getArtifactId(), bundleRunModes, testRunModes} ); continue; } } if(isFragment) { result.add(mavenBundle(b.getGroupId(), b.getArtifactId(), b.getVersion()).noStart()); } else if(startLevel == 0){ result.add(mavenBundle(b.getGroupId(), b.getArtifactId(), b.getVersion())); } else { result.add(mavenBundle(b.getGroupId(), b.getArtifactId(), b.getVersion()).startLevel(startLevel)); } log.info("Bundle added: {}/{}/{}", new Object [] { b.getGroupId(), b.getArtifactId(), b.getVersion()}); } } log.info("Got {} bundles ({} ignored) from {}", new Object[] { counter, ignored, paxUrl }); } catch(Exception e) { throw new RuntimeException("Error getting bundle list " + paxUrl, e); } finally { if(tmp != null) { tmp.delete(); } } return result; } public static CompositeOption slingBootstrapBundles() { return new DefaultCompositeOption( mavenBundle("org.apache.felix", "org.apache.felix.http.jetty", "2.2.0"), // TODO: why is this needed? mavenBundle("org.apache.sling", "org.apache.sling.launchpad.api", "1.1.0") ); } public static CompositeOption slingLaunchpadBundles(String version) { return slingBundleList("org.apache.sling", "org.apache.sling.launchpad", version, "xml", "bundlelist"); } /** * Felix remote shell bundles * @return Composite option */ public static CompositeOption felixRemoteShellBundles() { final String gogoVersion = "0.10.0"; return new DefaultCompositeOption( mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.gogo.runtime").version(gogoVersion), mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.gogo.shell").version(gogoVersion), mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.gogo.command").version(gogoVersion), mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.shell.remote").version("1.1.2") ); } private static File dumpMvnUrlToTmpFile(String mvnUrl) throws IOException { final URL url = new URL(mvnUrl); final InputStream is = new BufferedInputStream(url.openStream()); final File tmp = File.createTempFile(SlingPaxOptions.class.getName(), "xml"); log.debug("Copying bundle list contents to {}", tmp.getAbsolutePath()); tmp.deleteOnExit(); final OutputStream os = new BufferedOutputStream(new FileOutputStream(tmp)); try { final byte [] buffer = new byte[16384]; int len = 0; while( (len = is.read(buffer, 0, buffer.length)) > 0) { os.write(buffer, 0, len); } os.flush(); } finally { os.close(); is.close(); } return tmp; } }