| /* |
| * 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.crankstart.junit; |
| |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.fail; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.Reader; |
| import java.net.ServerSocket; |
| import java.util.*; |
| |
| import org.apache.http.client.methods.HttpGet; |
| import org.apache.http.client.methods.HttpUriRequest; |
| import org.apache.http.impl.client.DefaultHttpClient; |
| import org.apache.sling.crankstart.launcher.Launcher; |
| import org.apache.sling.crankstart.launcher.LauncherListener; |
| import org.apache.sling.crankstart.launcher.PropertiesVariableResolver; |
| import org.apache.sling.provisioning.model.ModelUtility.VariableResolver; |
| import org.junit.rules.ExternalResource; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** JUnit Rule that starts a Crankstart instance, using a set of provisioning |
| * models. See our integration tests for examples. |
| */ |
| public class CrankstartSetup extends ExternalResource { |
| |
| private static final Logger log = LoggerFactory.getLogger(CrankstartSetup.class); |
| private final int port = getAvailablePort(); |
| private final String storagePath = getOsgiStoragePath(); |
| private Thread crankstartThread; |
| private final String baseUrl = "http://localhost:" + port; |
| private final Properties replacementProps = new Properties(); |
| |
| private static List<CrankstartSetup> toCleanup = new ArrayList<CrankstartSetup>(); |
| private static Thread shutdownHook; |
| |
| private VariableResolver variablesResolver = new PropertiesVariableResolver(replacementProps, Launcher.VARIABLE_OVERRIDE_PREFIX); |
| |
| private String [] classpathModelPaths; |
| private boolean shutdown; |
| private int bundlesStarted; |
| private int bundlesFailed; |
| private int totalBundles; |
| |
| |
| @Override |
| public String toString() { |
| return getClass().getSimpleName() + ", port " + port + ", OSGi storage " + storagePath; |
| } |
| |
| public CrankstartSetup() { |
| synchronized (getClass()) { |
| if(shutdownHook == null) { |
| shutdownHook = new Thread(CrankstartSetup.class.getSimpleName() + " shutdown thread") { |
| @Override |
| public void run() { |
| log.info("Starting cleanup"); |
| cleanup(); |
| log.info("Cleanup done"); |
| } |
| }; |
| Runtime.getRuntime().addShutdownHook(shutdownHook); |
| } |
| } |
| } |
| |
| |
| public CrankstartSetup withModelResources(String ... classpathModelPaths) { |
| this.classpathModelPaths = classpathModelPaths; |
| return this; |
| } |
| |
| private static int getAvailablePort() { |
| int result = -1; |
| ServerSocket s = null; |
| try { |
| try { |
| s = new ServerSocket(0); |
| result = s.getLocalPort(); |
| } finally { |
| if(s != null) { |
| s.close(); |
| } |
| } |
| } catch(Exception e) { |
| throw new RuntimeException("getAvailablePort failed", e); |
| } |
| return result; |
| } |
| |
| private static void mergeModelResource(Launcher launcher, String path) throws Exception { |
| final InputStream is = CrankstartSetup.class.getResourceAsStream(path); |
| assertNotNull("Expecting test resource to be found:" + path, is); |
| final Reader input = new InputStreamReader(is); |
| try { |
| Launcher.mergeModel(launcher.getModel(), input, path); |
| launcher.computeEffectiveModel(); |
| } finally { |
| input.close(); |
| } |
| } |
| |
| public String getBaseUrl() { |
| return baseUrl; |
| } |
| |
| public int getBundlesFailed() { |
| return bundlesFailed; |
| } |
| |
| public int getBundlesStarted() { |
| return bundlesStarted; |
| } |
| |
| public int getTotalBundles() { |
| return totalBundles; |
| } |
| |
| public boolean isShutdownComplete() { |
| return shutdown; |
| } |
| |
| private static void cleanup() { |
| synchronized (toCleanup) { |
| if(toCleanup.isEmpty()) { |
| log.info("No Crankstart instances to cleanup"); |
| return; |
| } |
| log.info("Stopping {} running Crankstart instances...", toCleanup.size()); |
| for(CrankstartSetup s : toCleanup) { |
| s.stopCrankstartInstance(); |
| } |
| toCleanup.clear(); |
| } |
| } |
| |
| @Override |
| protected void before() throws Throwable { |
| if(crankstartThread != null) { |
| log.debug("Already running"); |
| return; |
| } |
| |
| cleanup(); |
| |
| log.info("Starting {}", this); |
| |
| // Add system properties which have the expected prefix |
| for(Object o : System.getProperties().keySet()) { |
| final String key = o.toString(); |
| if(key.startsWith(Launcher.VARIABLE_OVERRIDE_PREFIX)) { |
| replacementProps.setProperty(key, System.getProperty(key)); |
| } |
| } |
| |
| final HttpUriRequest get = new HttpGet(baseUrl); |
| replacementProps.setProperty("crankstart.model.http.port", String.valueOf(port)); |
| replacementProps.setProperty("crankstart.model.osgi.storage.path", storagePath); |
| |
| try { |
| new DefaultHttpClient().execute(get); |
| fail("Expecting connection to " + port + " to fail before starting HTTP service"); |
| } catch(IOException expected) { |
| } |
| shutdown = false; |
| bundlesStarted = 0; |
| bundlesFailed = 0; |
| totalBundles = 0; |
| final Launcher launcher = new Launcher().withVariableResolver(variablesResolver).withListener(new LauncherListener() { |
| @Override |
| public void onStartup(int started, int failed, int totalBundles) { |
| CrankstartSetup.this.bundlesStarted = started; |
| CrankstartSetup.this.bundlesFailed = failed; |
| CrankstartSetup.this.totalBundles = totalBundles; |
| } |
| |
| @Override |
| public void onShutdown() { |
| shutdown = true; |
| } |
| }); |
| for(String path : classpathModelPaths) { |
| mergeModelResource(launcher, path); |
| } |
| launcher.computeEffectiveModel(); |
| |
| crankstartThread = new Thread() { |
| public void run() { |
| try { |
| launcher.launch(); |
| } catch(InterruptedException e) { |
| log.info("Launcher thread was interrupted, exiting"); |
| } catch(Exception e) { |
| e.printStackTrace(); |
| fail("Launcher exception:" + e); |
| } |
| } |
| }; |
| crankstartThread.setDaemon(true); |
| crankstartThread.start(); |
| |
| Runtime.getRuntime().addShutdownHook(new Thread() { |
| @Override |
| public void run() { |
| stopCrankstartInstance(); |
| } |
| }); |
| } |
| |
| private void stopCrankstartInstance() { |
| log.info("Stopping {}", this); |
| if(crankstartThread == null) { |
| return; |
| } |
| crankstartThread.interrupt(); |
| try { |
| crankstartThread.join(); |
| } catch(InterruptedException ignore) { |
| } |
| crankstartThread = null; |
| } |
| |
| private static String getOsgiStoragePath() { |
| final File tmpRoot = new File(System.getProperty("java.io.tmpdir")); |
| final File tmpFolder = new File(tmpRoot, CrankstartSetup.class.getSimpleName() + "_" + UUID.randomUUID()); |
| if(!tmpFolder.mkdir()) { |
| fail("Failed to create " + tmpFolder.getAbsolutePath()); |
| } |
| tmpFolder.deleteOnExit(); |
| return tmpFolder.getAbsolutePath(); |
| } |
| } |