/*
 * 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.
 */
package org.apache.aries.subsystem.itests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.ops4j.pax.exam.CoreOptions.composite;
import static org.ops4j.pax.exam.CoreOptions.junitBundles;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.streamBundle;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
import static org.ops4j.pax.exam.CoreOptions.when;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.aries.itest.AbstractIntegrationTest;
import org.apache.aries.itest.RichBundleContext;
import org.apache.aries.subsystem.AriesSubsystem;
import org.apache.aries.subsystem.core.archive.AriesProvisionDependenciesDirective;
import org.apache.aries.subsystem.core.archive.ProvisionPolicyDirective;
import org.apache.aries.subsystem.core.archive.SubsystemTypeHeader;
import org.apache.aries.subsystem.core.archive.TypeAttribute;
import org.apache.aries.subsystem.core.internal.BasicSubsystem;
import org.apache.aries.subsystem.core.internal.BundleResource;
import org.apache.aries.subsystem.core.internal.SubsystemIdentifier;
import org.apache.aries.subsystem.itests.util.TestRepository;
import org.apache.aries.subsystem.itests.util.Utils;
import org.apache.aries.unittest.fixture.ArchiveFixture;
import org.apache.aries.unittest.fixture.ArchiveFixture.JarFixture;
import org.apache.aries.unittest.fixture.ArchiveFixture.ManifestFixture;
import org.apache.aries.unittest.fixture.ArchiveFixture.ZipFixture;
import org.apache.aries.util.filesystem.FileSystem;
import org.eclipse.equinox.region.Region;
import org.eclipse.equinox.region.RegionDigraph;
import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.ops4j.io.StreamUtils;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
import org.ops4j.pax.exam.spi.reactors.PerClass;
import org.ops4j.pax.tinybundles.core.TinyBundles;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.Version;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.framework.startlevel.BundleStartLevel;
import org.osgi.framework.startlevel.FrameworkStartLevel;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.FrameworkWiring;
import org.osgi.resource.Capability;
import org.osgi.resource.Resource;
import org.osgi.service.repository.Repository;
import org.osgi.service.subsystem.Subsystem;
import org.osgi.service.subsystem.Subsystem.State;
import org.osgi.service.subsystem.SubsystemConstants;

@RunWith(PaxExam.class)
@ExamReactorStrategy(PerClass.class)
public abstract class SubsystemTest extends AbstractIntegrationTest {
	private static final String SUBSYSTEM_CORE_NAME = "org.apache.aries.subsystem.core";
    protected static boolean createdApplications = false;
	boolean installModeler = true;
    boolean installConfigAdmin = false;

	public SubsystemTest() {
	}

	public SubsystemTest(boolean installModeller) {
		this.installModeler = installModeller;
	}

	public Option baseOptions() {
        return composite(
                junitBundles(),
                addPaxLoggingBundles(),
                setPaxExamLogLevel("DEBUG"),
                configurePaxUrlLocalMavenRepoIfNeeded(),
                setupRemoteDebugging(),
                failOnUnresolvableBundles()
         );
    }

	@Configuration
	public Option[] configuration() throws Exception {
	    new File("target").mkdirs();
	    init();
		return new Option[] {
				baseOptions(),
				systemProperty("org.osgi.framework.bsnversion").value("multiple"),
				systemProperty("org.osgi.framework.storage.clean").value("onFirstInit"),
				// Bundles
				mavenBundle("org.apache.aries",             "org.apache.aries.util").versionAsInProject(),
				when(installModeler).useOptions(modelerBundles()),
                when(installConfigAdmin).useOptions(
                        mavenBundle("org.apache.felix",     "org.apache.felix.configadmin").versionAsInProject()),
				mavenBundle("org.apache.aries.subsystem",   "org.apache.aries.subsystem.api").versionAsInProject(),
				mavenBundle("org.apache.aries.subsystem",   SUBSYSTEM_CORE_NAME).versionAsInProject(),
				streamBundle(createCoreFragment()).noStart(),
				mavenBundle("org.apache.aries.subsystem",   "org.apache.aries.subsystem.itest.interfaces").versionAsInProject(),
				mavenBundle("org.apache.aries.testsupport", "org.apache.aries.testsupport.unit").versionAsInProject(),
				//mavenBundle("org.apache.felix",             "org.apache.felix.resolver").versionAsInProject(),
				mavenBundle("org.eclipse.equinox",          "org.eclipse.equinox.coordinator").version("1.1.0.v20120522-1841"),
				mavenBundle("org.eclipse.equinox",          "org.eclipse.equinox.event").versionAsInProject(),
				mavenBundle("org.eclipse.equinox",          "org.eclipse.equinox.region").version("1.1.0.v20120522-1841"),
				mavenBundle("org.osgi",                     "osgi.enterprise").versionAsInProject(),
				mavenBundle("org.easymock",					"easymock").versionAsInProject(),
                mavenBundle("org.ops4j.pax.tinybundles",    "tinybundles").versionAsInProject(),
                mavenBundle("biz.aQute.bnd",                "bndlib").versionAsInProject(),
                mavenBundle("org.apache.aries.subsystem",	"org.apache.aries.subsystem.obr").versionAsInProject(),
                mavenBundle("org.apache.felix",				"org.apache.felix.bundlerepository").versionAsInProject(),
		};
	}

    protected void init() throws Exception {

    }
    
    protected long lastSubsystemId() throws Exception {
    	Method method = SubsystemIdentifier.class.getDeclaredMethod("getLastId");
    	method.setAccessible(true);
    	return (Long)method.invoke(null);
    }

    private Option modelerBundles() {
        return CoreOptions.composite(
                mavenBundle("org.apache.aries.subsystem", "org.apache.aries.subsystem.modeller").versionAsInProject(),
        		mavenBundle("org.apache.aries.blueprint",   "org.apache.aries.blueprint.core").versionAsInProject(),
        		mavenBundle("org.apache.aries.proxy",       "org.apache.aries.proxy").versionAsInProject());
    }

    /**
     * The itests need private packages from the core subsystems bundle.
     * So this fragment exports them.
     * @return stream containing the fragment
     */
    private InputStream createCoreFragment() {
	return TinyBundles.bundle()
	    .set("Bundle-SymbolicName", SUBSYSTEM_CORE_NAME + ".fragment")
	    .set("Export-Package", "org.apache.aries.subsystem.core.internal,org.apache.aries.subsystem.core.archive")
	    .set("Fragment-Host", SUBSYSTEM_CORE_NAME)
	    .build();
    }

	protected final SubsystemEventHandler subsystemEvents = new SubsystemEventHandler();

	@SuppressWarnings("rawtypes")
    protected Collection<ServiceRegistration> serviceRegistrations = new ArrayList<ServiceRegistration>();

	protected final List<Region> deletableRegions = Collections.synchronizedList(new ArrayList<Region>());
	protected final List<Subsystem> stoppableSubsystems = Collections.synchronizedList(new ArrayList<Subsystem>());
	protected final List<Bundle> uninstallableBundles = Collections.synchronizedList(new ArrayList<Bundle>());
    protected final List<Subsystem> uninstallableSubsystems = Collections.synchronizedList(new ArrayList<Subsystem>());
	
	@Before
	public void setUp() throws Exception {
		serviceRegistrations.clear();
		deletableRegions.clear();
		stoppableSubsystems.clear();
		uninstallableBundles.clear();
        uninstallableSubsystems.clear();
		if (!createdApplications) {
			createApplications();
			createdApplications = true;
		}
		bundleContext.getBundle(0).getBundleContext().addServiceListener(subsystemEvents, '(' + Constants.OBJECTCLASS + '=' + Subsystem.class.getName() + ')');
	}
	
	@SuppressWarnings("rawtypes")
    @After
	public void tearDown() throws Exception
	{
		for (Subsystem subsystem : stoppableSubsystems) {
			stopSubsystemSilently(subsystem);
		}
		for (Subsystem subsystem : uninstallableSubsystems) {
			uninstallSubsystemSilently(subsystem);
		}
		RegionDigraph digraph = context().getService(RegionDigraph.class);
		for (Region region : deletableRegions) {
			digraph.removeRegion(region);
		}
		for (Bundle bundle : uninstallableBundles) {
			uninstallSilently(bundle);
		}
		bundleContext.removeServiceListener(subsystemEvents);
		for (ServiceRegistration registration : serviceRegistrations)
			Utils.unregisterQuietly(registration);
	}

	protected void createApplications() throws Exception {
	}

	protected RichBundleContext context(Subsystem subsystem) {
		return new RichBundleContext(subsystem.getBundleContext());
	}

	protected void assertEmptySubsystem(Subsystem subsystem) {
    	assertSymbolicName("org.apache.aries.subsystem.itests.subsystem.empty", subsystem);
    	assertVersion("0", subsystem);
    	assertType(SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION, subsystem);
    }

	protected void assertBundleState(int state, String symbolicName, Subsystem subsystem) {
    	Bundle bundle = context(subsystem).getBundleByName(symbolicName);
    	assertNotNull("Bundle not found: " + symbolicName, bundle);
    	assertBundleState(bundle, state);
    }

	protected void assertBundleState(Bundle bundle, int state) {
		assertTrue("Wrong state: " + bundle + " [expected " + state + " but was " + bundle.getState() + "]", (bundle.getState() & state) != 0);
	}

	protected Subsystem assertChild(Subsystem parent, String symbolicName) {
		return assertChild(parent, symbolicName, null, null);
	}

	protected Subsystem assertChild(Subsystem parent, String symbolicName, Version version) {
		return assertChild(parent, symbolicName, version, null);
	}

	protected Subsystem assertChild(Subsystem parent, String symbolicName, Version version, String type) {
		Subsystem result = getChild(parent, symbolicName, version, type);
		assertNotNull("Child does not exist: " + symbolicName, result);
		return result;
	}

	protected void assertChild(Subsystem parent, Subsystem child) {
		Collection<Subsystem> children = new ArrayList<Subsystem>(1);
		children.add(child);
		assertChildren(parent, children);
	}

	protected void assertChildren(int size, Subsystem subsystem) {
		assertEquals("Wrong number of children", size, subsystem.getChildren().size());
	}

	protected void assertChildren(Subsystem parent, Collection<Subsystem> children) {
		assertTrue("Parent did not contain all children", parent.getChildren().containsAll(children));
	}

	protected void assertClassLoadable(String clazz, Bundle bundle) {
		try {
			bundle.loadClass(clazz);
		}
		catch (Exception e) {
			e.printStackTrace();
			fail("Class " + clazz + " from bundle " + bundle + " should be loadable");
		}
	}

	protected void assertConstituent(Subsystem subsystem, String symbolicName) {
		assertConstituent(subsystem, symbolicName, Version.emptyVersion);
	}

	protected void assertConstituent(Subsystem subsystem, String symbolicName, Version version) {
		assertConstituent(subsystem, symbolicName, version, IdentityNamespace.TYPE_BUNDLE);
	}

	protected void assertConstituent(Subsystem subsystem, String symbolicName, String type) {
		assertConstituent(subsystem, symbolicName, Version.emptyVersion, type);
	}

	protected Resource assertConstituent(Subsystem subsystem, String symbolicName, Version version, String type) {
		Resource constituent = getConstituent(subsystem, symbolicName, version, type);
		assertNotNull("Constituent not found: " + symbolicName + ';' + version + ';' + type, constituent);
		return constituent;
	}

	protected void assertConstituents(int size, Subsystem subsystem) {
		assertEquals("Wrong number of constituents", size, subsystem.getConstituents().size());
	}

 	protected void assertEvent(Subsystem subsystem, Subsystem.State state) throws InterruptedException {
 		assertEvent(subsystem, state, 0);
 	}

 	protected void assertEvent(Subsystem subsystem, Subsystem.State state, long timeout) throws InterruptedException {
 		assertEvent(subsystem, state, subsystemEvents.poll(subsystem.getSubsystemId(), timeout));
 	}
 	protected void assertEvent(Subsystem subsystem, Subsystem.State state, SubsystemEventHandler.ServiceEventInfo event) {
 		if (State.INSTALLING.equals(state))
			assertEvent(subsystem, state, event, ServiceEvent.REGISTERED);
 		else
 			assertEvent(subsystem, state, event, ServiceEvent.MODIFIED);
 	}

	protected void assertEvent(Subsystem subsystem, Subsystem.State state, SubsystemEventHandler.ServiceEventInfo event, int type) {
		assertEvent(subsystem.getSubsystemId(), subsystem.getSymbolicName(), 
				subsystem.getVersion(), subsystem.getType(), state, event, type);
	}
	
	protected void assertEvent(
			long id, 
			String symbolicName, 
			Version version, 
			String type, 
			Subsystem.State state,
			SubsystemEventHandler.ServiceEventInfo event,
			int eventType) {
		// TODO Could accept a ServiceRegistration as an argument and verify it against the one in the event.
		assertNotNull("No event", event);
		assertEquals("Wrong ID", id, event.getId());
		assertEquals("Wrong symbolic name", symbolicName, event.getSymbolicName());
		assertEquals("Wrong version", version, event.getVersion());
		assertEquals("Wrong type", type, event.getType());
		assertEquals("Wrong state", state, event.getState());
		assertEquals("Wrong event type", eventType, event.getEventType());
	}

	protected String assertHeaderExists(Subsystem subsystem, String name) {
		String header = subsystem.getSubsystemHeaders(null).get(name);
		assertNotNull("Missing header: " + name, header);
		return header;
	}

	protected void assertId(Subsystem subsystem) {
		assertId(subsystem.getSubsystemId());
	}

	protected void assertId(Long id) {
		assertTrue("Subsystem ID was not a positive integer: " + id, id > 0);
	}

	protected void assertLastId(long id) throws Exception {
		Subsystem root = getRootSubsystem();
		Field lastId = SubsystemIdentifier.class.getDeclaredField("lastId");
		lastId.setAccessible(true);
		assertEquals("Wrong lastId", id, lastId.getLong(root));
	}

	protected void resetLastId() throws Exception {
		Field lastId = SubsystemIdentifier.class.getDeclaredField("lastId");
		lastId.setAccessible(true);
		lastId.setInt(SubsystemIdentifier.class, 0);
	}

	protected void assertLocation(String expected, String actual) {
		assertTrue("Wrong location: " + actual, actual.indexOf(expected) != -1);
	}

	protected void assertLocation(String expected, Subsystem subsystem) {
		assertLocation(expected, subsystem.getLocation());
	}

	protected void assertNotChild(Subsystem parent, Subsystem child) {
		assertFalse("Parent contained child", parent.getChildren().contains(child));
	}

	protected void assertNotConstituent(Subsystem subsystem, String symbolicName) {
		assertNotConstituent(subsystem, symbolicName, Version.emptyVersion, IdentityNamespace.TYPE_BUNDLE);
	}

	protected void assertNotConstituent(Subsystem subsystem, String symbolicName, Version version, String type) {
		Resource constituent = getConstituent(subsystem, symbolicName, version, type);
		assertNull("Constituent found: " + symbolicName + ';' + version + ';' + type, constituent);
	}

	protected void assertParent(Subsystem expected, Subsystem subsystem) {
		for (Subsystem parent : subsystem.getParents()) {
			if (parent.equals(expected))
				return;

		}
		fail("Parent did not exist: " + expected.getSymbolicName());
	}

	protected void assertProvisionPolicy(Subsystem subsystem, boolean acceptsDependencies) {
		String headerStr = subsystem.getSubsystemHeaders(null).get(SubsystemConstants.SUBSYSTEM_TYPE);
		assertNotNull("Missing subsystem type header", headerStr);
		SubsystemTypeHeader header = new SubsystemTypeHeader(headerStr);
		ProvisionPolicyDirective directive = header.getProvisionPolicyDirective();
		if (acceptsDependencies)
			assertTrue("Subsystem does not accept dependencies", directive.isAcceptDependencies());
		else
			assertTrue("Subsystem accepts dependencies", directive.isRejectDependencies());
	}

	protected void assertRefresh(Collection<Bundle> bundles) throws InterruptedException {
		FrameworkWiring wiring = getSystemBundleAsFrameworkWiring();
		final AtomicBoolean refreshed = new AtomicBoolean(false);
		wiring.refreshBundles(bundles, new FrameworkListener[]{ new FrameworkListener() {
			@Override
			public void frameworkEvent(FrameworkEvent event) {
				if (FrameworkEvent.PACKAGES_REFRESHED == event.getType()) {
					synchronized (refreshed) {
						refreshed.set(true);
						refreshed.notify();
					}
				}
			}
		}});
		synchronized (refreshed) {
			refreshed.wait(5000);
		}
		assertTrue("Bundles not refreshed", refreshed.get());
	}

	protected void assertRefreshAndResolve(Collection<Bundle> bundles) throws InterruptedException {
		assertRefresh(bundles);
		assertResolve(bundles);
	}

	protected void assertRegionContextBundle(Subsystem s) {
		Bundle b = getRegionContextBundle(s);
		assertEquals("Not active", Bundle.ACTIVE, b.getState());
		assertEquals("Wrong location", s.getLocation() + '/' + s.getSubsystemId(), b.getLocation());
		assertEquals("Wrong symbolic name", "org.osgi.service.subsystem.region.context." + s.getSubsystemId(), b.getSymbolicName());
		assertEquals("Wrong version", Version.parseVersion("1.0.0"), b.getVersion());
		assertConstituent(s, "org.osgi.service.subsystem.region.context." + s.getSubsystemId(), Version.parseVersion("1.0.0"), IdentityNamespace.TYPE_BUNDLE);
	}

	protected void assertResolve(Collection<Bundle> bundles) {
		FrameworkWiring wiring = getSystemBundleAsFrameworkWiring();
		assertTrue("Bundles not resolved", wiring.resolveBundles(bundles));
	}

	protected void assertServiceEventsInstall(Subsystem subsystem) throws InterruptedException {
		assertEvent(subsystem, Subsystem.State.INSTALLING, subsystemEvents.poll(subsystem.getSubsystemId(), 5000));
		assertEvent(subsystem, Subsystem.State.INSTALLED, subsystemEvents.poll(subsystem.getSubsystemId(), 5000));
	}

	protected void assertServiceEventsResolve(Subsystem subsystem) throws InterruptedException {
		assertEvent(subsystem, Subsystem.State.RESOLVING, subsystemEvents.poll(subsystem.getSubsystemId(), 5000));
		assertServiceEventResolved(subsystem, ServiceEvent.MODIFIED);
	}

	protected void assertServiceEventsStart(Subsystem subsystem) throws InterruptedException {
		assertEvent(subsystem, Subsystem.State.STARTING, subsystemEvents.poll(subsystem.getSubsystemId(), 5000));
		assertEvent(subsystem, Subsystem.State.ACTIVE, subsystemEvents.poll(subsystem.getSubsystemId(), 5000));
	}

	protected void assertServiceEventsStop(Subsystem subsystem) throws InterruptedException {
		assertEvent(subsystem, Subsystem.State.STOPPING, subsystemEvents.poll(subsystem.getSubsystemId(), 5000));
		assertServiceEventResolved(subsystem, ServiceEvent.MODIFIED);
		// Don't forget about the unregistering event, which will have the same state as before.
		assertServiceEventResolved(subsystem, ServiceEvent.UNREGISTERING);
	}

	protected void assertServiceEventResolved(Subsystem subsystem, int type) throws InterruptedException {
		assertEvent(subsystem, Subsystem.State.RESOLVED, subsystemEvents.poll(subsystem.getSubsystemId(), 5000), type);
	}

	protected void assertStartLevel(Bundle bundle, int expected) {
		assertNotNull("Bundle is null", bundle);
		assertEquals("Wrong start level", expected, bundle.adapt(BundleStartLevel.class).getStartLevel());
	}

	protected void assertState(State expected, State actual) {
		assertState(EnumSet.of(expected), actual);
	}

	protected void assertState(EnumSet<State> expected, State actual) {
		assertTrue("Wrong state: expected=" + expected + ", actual=" + actual, expected.contains(actual));
	}

	protected void assertState(State expected, Subsystem subsystem) {
		assertState(expected, subsystem.getState());
	}

	protected void assertState(EnumSet<State> expected, Subsystem subsystem) {
		assertState(expected, subsystem.getState());
	}

	protected Subsystem assertSubsystemLifeCycle(File file) throws Exception {
		Subsystem rootSubsystem = context().getService(Subsystem.class);
        assertNotNull("Root subsystem was null", rootSubsystem);
        Subsystem subsystem = rootSubsystem.install(file.toURI().toURL().toExternalForm());
        assertNotNull("The subsystem was null", subsystem);
        assertState(EnumSet.of(State.INSTALLING, State.INSTALLED), subsystem.getState());
		assertEvent(subsystem, Subsystem.State.INSTALLING, subsystemEvents.poll(subsystem.getSubsystemId(), 5000));
		assertEvent(subsystem, Subsystem.State.INSTALLED, subsystemEvents.poll(subsystem.getSubsystemId(), 5000));
		assertChild(rootSubsystem, subsystem);
        subsystem.start();
        assertState(EnumSet.of(State.RESOLVING, State.RESOLVED, State.STARTING, State.ACTIVE), subsystem.getState());
		assertEvent(subsystem, Subsystem.State.RESOLVING, subsystemEvents.poll(subsystem.getSubsystemId(), 5000));
		assertEvent(subsystem, Subsystem.State.RESOLVED, subsystemEvents.poll(subsystem.getSubsystemId(), 5000));
		assertEvent(subsystem, Subsystem.State.STARTING, subsystemEvents.poll(subsystem.getSubsystemId(), 5000));
		assertEvent(subsystem, Subsystem.State.ACTIVE, subsystemEvents.poll(subsystem.getSubsystemId(), 5000));
		subsystem.stop();
		assertState(EnumSet.of(State.STOPPING, State.RESOLVED), subsystem.getState());
		assertEvent(subsystem, Subsystem.State.STOPPING, subsystemEvents.poll(subsystem.getSubsystemId(), 5000));
		assertEvent(subsystem, Subsystem.State.RESOLVED, subsystemEvents.poll(subsystem.getSubsystemId(), 5000));
		subsystem.uninstall();
		assertState(EnumSet.of(State.UNINSTALLING, State.UNINSTALLED), subsystem.getState());
		assertEvent(subsystem, Subsystem.State.UNINSTALLING, subsystemEvents.poll(subsystem.getSubsystemId(), 5000));
		assertEvent(subsystem, Subsystem.State.UNINSTALLED, subsystemEvents.poll(subsystem.getSubsystemId(), 5000));
		assertNotChild(rootSubsystem, subsystem);
		return subsystem;
	}

	protected void assertSubsystemNotNull(Subsystem subsystem) {
		assertNotNull("Subsystem was null", subsystem);
	}

	protected void assertSymbolicName(String expected, Subsystem subsystem) {
		assertSymbolicName(expected, subsystem.getSymbolicName());
	}

	protected void assertSymbolicName(String expected, String actual) {
		assertEquals("Wrong symbolic name", expected, actual);
	}

	protected void assertType(String expected, Subsystem subsystem) {
		assertEquals("Wrong type", expected, subsystem.getType());
	}

	protected void assertVersion(String expected, Subsystem subsystem) {
		assertVersion(Version.parseVersion(expected), subsystem);
	}

	protected void assertVersion(Version expected, Subsystem subsystem) {
		assertVersion(expected, subsystem.getVersion());
	}

	protected void assertVersion(Version expected, Version actual) {
		assertEquals("Wrong version", expected, actual);
	}

	protected Header version(String version) {
		return new Header(Constants.BUNDLE_VERSION, version);
	}

	protected Header name(String name) {
		return new Header(Constants.BUNDLE_SYMBOLICNAME, name);
	}

	protected Header exportPackage(String exportPackage) {
		return new Header(Constants.EXPORT_PACKAGE, exportPackage);
	}

	protected Header importPackage(String importPackage) {
		return new Header(Constants.IMPORT_PACKAGE, importPackage);
	}

	protected Header requireBundle(String bundleName) {
		return new Header(Constants.REQUIRE_BUNDLE, bundleName);
	}

	protected Header requireCapability(String capability) {
		return new Header(Constants.REQUIRE_CAPABILITY, capability);
	}

	protected Header provideCapability(String capability) {
		return new Header(Constants.PROVIDE_CAPABILITY, capability);
	}
	protected static void createBundle(Header...  headers) throws IOException {
		createBundle(Collections.<String> emptyList(), headers);
	}

	protected static void createBundle(List<String> emptyFiles, Header...  headers) throws IOException {
		HashMap<String, String> headerMap = new HashMap<String, String>();
		for (Header header : headers) {
			headerMap.put(header.key, header.value);
		}
		createBundle(emptyFiles, headerMap);
	}
	
	protected static void createBundle(List<String> emptyFiles, Map<String, String> headers) throws IOException {
		createBundle(headers.get(Constants.BUNDLE_SYMBOLICNAME), emptyFiles, headers);
	}

	protected static void createBundle(String fileName, List<String> emptyFiles, Map<String, String> headers) throws IOException
	{
		JarFixture bundle = ArchiveFixture.newJar();
		ManifestFixture manifest = bundle.manifest();
		for (Entry<String, String> header : headers.entrySet()) {
			manifest.attribute(header.getKey(), header.getValue());
		}
		for (String path : emptyFiles) {
			bundle.file(path).end();
		}
		write(fileName, bundle);
	}

	protected static void createBlueprintBundle(String symbolicName, String blueprintXml)
			throws IOException {
		write(symbolicName,
				ArchiveFixture.newJar().manifest().symbolicName(symbolicName)
						.end().file("OSGI-INF/blueprint/blueprint.xml", blueprintXml));
	}

	protected Resource createBundleRepositoryContent(String file) throws Exception {
		return createBundleRepositoryContent(new File(file));
	}

	protected Resource createBundleRepositoryContent(File file) throws Exception {
		return new BundleResource(FileSystem.getFSRoot(file));
	}

	protected static void createManifest(String name, Map<String, String> headers) throws IOException {
		ManifestFixture manifest = ArchiveFixture.newJar().manifest();
		for (Entry<String, String> header : headers.entrySet()) {
			manifest.attribute(header.getKey(), header.getValue());
		}
		write(name, manifest);
	}

	protected static void createSubsystem(String name, String...contents) throws IOException {
		File manifest = new File(name + ".mf");
		ZipFixture fixture = ArchiveFixture.newZip();
		if (manifest.exists())
			// The following input stream is closed by ArchiveFixture.copy.
			fixture.binary("OSGI-INF/SUBSYSTEM.MF", new FileInputStream(name + ".mf"));
		if (contents != null) {
			for (String content : contents) {
				// The following input stream is closed by ArchiveFixture.copy.
				fixture.binary(content, new FileInputStream(content));
			}
		}
		write(name, fixture);
	}

	protected Subsystem findSubsystemService(long id) throws InvalidSyntaxException {
		String filter = "(" + SubsystemConstants.SUBSYSTEM_ID_PROPERTY + "=" + id + ")";
		return context().getService(Subsystem.class, filter, 5000);
	}

	protected Subsystem getChild(Subsystem parent, String symbolicName) {
		return getChild(parent, symbolicName, null, null);
	}

	protected Subsystem getChild(Subsystem parent, String symbolicName, Version version) {
		return getChild(parent, symbolicName, version, null);
	}

	protected Subsystem getChild(Subsystem parent, String symbolicName, Version version, String type) {
		for (Subsystem child : parent.getChildren()) {
			if (symbolicName.equals(child.getSymbolicName())) {
				if (version == null)
					version = Version.emptyVersion;
				if (version.equals(child.getVersion())) {
					if (type == null)
						type = SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION;
					if (type.equals(child.getType())) {
						return child;
					}
				}
			}
		}
		return null;
	}

	public static Object getIdentityAttribute(Resource resource, String name) {
	    List<Capability> capabilities = resource.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE);
        Capability capability = capabilities.get(0);
        return capability.getAttributes().get(name);
    }

	public static String getSymbolicNameAttribute(Resource resource) {
        return (String)getIdentityAttribute(resource, IdentityNamespace.IDENTITY_NAMESPACE);
    }

   public static Version getVersionAttribute(Resource resource) {
        Version result = (Version)getIdentityAttribute(resource, IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE);
        if (result == null)
            result = Version.emptyVersion;
        return result;
    }

   public static String getTypeAttribute(Resource resource) {
       String result = (String)getIdentityAttribute(resource, IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE);
       if (result == null)
           result = TypeAttribute.DEFAULT_VALUE;
       return result;
   }

   protected Resource getConstituent(Subsystem subsystem, String symbolicName, Version version, String type) {
		for (Resource resource : subsystem.getConstituents()) {
			if (symbolicName.equals(getSymbolicNameAttribute(resource))) {
				if (version == null)
					version = Version.emptyVersion;
				if (version.equals(getVersionAttribute(resource))) {
					if (type == null)
						type = IdentityNamespace.TYPE_BUNDLE;
					if (type.equals(getTypeAttribute(resource))) {
						return resource;
					}
				}
			}
		}
		return null;
	}

	protected AriesSubsystem getConstituentAsAriesSubsystem(Subsystem subsystem, String symbolicName, Version version, String type) {
		Resource resource = getConstituent(subsystem, symbolicName, version, type);
		return (AriesSubsystem)resource;
	}

	protected Bundle getConstituentAsBundle(Subsystem subsystem, String symbolicName, Version version, String type) {
		return getConstituentAsBundleRevision(subsystem, symbolicName, version, type).getBundle();
	}

	protected BundleRevision getConstituentAsBundleRevision(Subsystem subsystem, String symbolicName, Version version, String type) {
		Resource resource = getConstituent(subsystem, symbolicName, version, type);
		return (BundleRevision)resource;
	}

	protected Subsystem getConstituentAsSubsystem(Subsystem subsystem, String symbolicName, Version version, String type) {
		Resource resource = getConstituent(subsystem, symbolicName, version, type);
		return (Subsystem)resource;
	}

	protected Region getRegion(Subsystem subsystem) {
		RegionDigraph digraph = context().getService(RegionDigraph.class);
		String name = getRegionName(subsystem);
		Region region = digraph.getRegion(name);
		assertNotNull("Region not found: " + name, region);
		return region;
	}

	protected Bundle getRegionContextBundle(Subsystem subsystem) {
		BundleContext bc = subsystem.getBundleContext();
		assertNotNull("No region context bundle", bc);
		return bc.getBundle();
	}

	protected String getRegionName(Subsystem subsystem) {
		if (subsystem.getSubsystemId() == 0)
			return "org.eclipse.equinox.region.kernel";
		return subsystem.getSymbolicName() + ';' + subsystem.getVersion() + ';' + subsystem.getType() + ';' + subsystem.getSubsystemId();
	}

	protected AriesSubsystem getRootAriesSubsystem() {
		return context().getService(AriesSubsystem.class);
	}

	protected Subsystem getRootSubsystem() {
		return context().getService(Subsystem.class, "(subsystem.id=0)");
	}

	protected Subsystem getRootSubsystemInState(Subsystem.State state, long timeout) throws InterruptedException {
		Subsystem root = getRootSubsystem();
		long now = System.currentTimeMillis();
		long then = now + timeout;
		while (!root.getState().equals(state) && System.currentTimeMillis() < then)
			Thread.sleep(100);
		if (!root.getState().equals(state))
			fail("Root subsystem never achieved state: " + state);
		return root;
	}

	protected Bundle getSystemBundle() {
		return bundleContext.getBundle(Constants.SYSTEM_BUNDLE_LOCATION);
	}

	protected FrameworkStartLevel getSystemBundleAsFrameworkStartLevel() {
		return getSystemBundle().adapt(FrameworkStartLevel.class);
	}

	protected FrameworkWiring getSystemBundleAsFrameworkWiring() {
		return getSystemBundle().adapt(FrameworkWiring.class);
	}

	protected Bundle getSubsystemCoreBundle() {
		return context().getBundleByName(SUBSYSTEM_CORE_NAME);
	}

	protected Bundle installBundleFromFile(String fileName) throws FileNotFoundException, BundleException {
		return installBundleFromFile(new File(fileName), getRootSubsystem());
	}

	protected Bundle installBundleFromFile(String fileName, Subsystem subsystem) throws FileNotFoundException, BundleException {
		return installBundleFromFile(new File(fileName), subsystem);
	}

	private Bundle installBundleFromFile(File file, Subsystem subsystem) throws FileNotFoundException, BundleException {
		Bundle bundle = installBundleFromFile(file, subsystem.getBundleContext());
		assertBundleState(Bundle.INSTALLED|Bundle.RESOLVED, bundle.getSymbolicName(), subsystem);
		return bundle;
	}

	private Bundle installBundleFromFile(File file, BundleContext bundleContext) throws FileNotFoundException, BundleException {
		// The following input stream is closed by the bundle context.
		return bundleContext.installBundle(file.toURI().toString(), new FileInputStream(file));
	}

	protected Subsystem installSubsystemFromFile(Subsystem parent, String fileName) throws Exception {
		return installSubsystemFromFile(parent, new File(fileName));
	}

	protected Subsystem installSubsystemFromFile(String fileName, Boolean ... configChecks) throws Exception {
		return installSubsystemFromFile(new File(fileName), configChecks);
	}

	protected Subsystem installSubsystemFromFile(Subsystem parent, File file) throws Exception {
		return installSubsystem(parent, file.toURI().toURL().toExternalForm());
	}

	private Subsystem installSubsystemFromFile(File file, Boolean ... configChecks) throws Exception {
		return installSubsystem(getRootSubsystem(), file.toURI().toURL().toExternalForm(), configChecks);
	}
	
	protected Subsystem installSubsystemFromFile(Subsystem parent, File file, String location) throws Exception {
		return installSubsystem(parent, location, new URL(file.toURI().toURL().toExternalForm()).openStream());
	}

	protected Subsystem installSubsystem(String location) throws Exception {
		return installSubsystem(getRootSubsystem(), location);
	}

	protected Subsystem installSubsystem(String location, InputStream content) throws Exception {
		return installSubsystem(getRootSubsystem(), location, content);
	}

	protected Subsystem installSubsystem(Subsystem parent, String location, Boolean ... configChecks) throws Exception {
		// The following input stream is closed by Subsystem.install.
		return installSubsystem(parent, location, new URL(location).openStream(), configChecks);
	}

	protected Subsystem installSubsystem(Subsystem parent, String location, InputStream content, Boolean ... configChecks) throws Exception {
	    boolean ariesProvisionDepsAtInstall = true; //set default value
	    if (configChecks!=null && configChecks.length > 0) {
	        ariesProvisionDepsAtInstall = configChecks[0].booleanValue();
	    }
	    subsystemEvents.clear();
	    Subsystem subsystem = parent.install(location, content);
	    assertSubsystemNotNull(subsystem);
	    assertEvent(subsystem, State.INSTALLING, 5000);
	    if (ariesProvisionDepsAtInstall) {
	        assertEvent(subsystem, State.INSTALLED, 5000);
	    }
	    assertChild(parent, subsystem);
	    assertLocation(location, subsystem);
	    assertParent(parent, subsystem);
	    State finalState=State.INSTALLED;
	    if (!ariesProvisionDepsAtInstall) {
	        finalState=State.INSTALLING;
	    }
	    assertState(finalState, subsystem);
	    assertLocation(location, subsystem);
	    assertId(subsystem);
	    // TODO This does not take into account nested directories.
	    //		assertDirectory(subsystem);
	    return subsystem;
	}

	protected void registerRepositoryService(Repository repository) {
		serviceRegistrations.add(bundleContext.registerService(
				Repository.class, repository, null));
	}

	protected void registerRepositoryService(Resource...resources) {
		TestRepository.Builder builder = new TestRepository.Builder();
		for (Resource resource : resources) {
			builder.resource(resource);
		}
		registerRepositoryService(builder.build());
	}

	protected void registerRepositoryService(String...files) throws Exception {
		Resource[] resources = new Resource[files.length];
		int i = 0;
		for (String file : files) {
			resources[i++] = (Resource)createBundleRepositoryContent(file);
		}
		registerRepositoryService(resources);
	}
	
	protected void removeConnectionWithParent(Subsystem subsystem) throws BundleException {
		Region tail = getRegion(subsystem);
		RegionDigraph digraph = tail.getRegionDigraph();
		RegionDigraph copy = digraph.copy();
		Region tailCopy = copy.getRegion(tail.getName());
		Set<Long> ids = tail.getBundleIds();
		copy.removeRegion(tailCopy);
		tailCopy= copy.createRegion(tailCopy.getName());
		for (long id : ids) {
			tailCopy.addBundle(id);
		}
		digraph.replace(copy);
	}

	protected void restartSubsystemsImplBundle() throws BundleException {
		Bundle b = getSubsystemCoreBundle();
		b.stop();
		b.start();
	}

	protected void startBundle(Bundle bundle) throws BundleException {
		startBundle(bundle, getRootSubsystem());
	}

	protected void startBundle(Bundle bundle, Subsystem subsystem) throws BundleException {
		bundle.start();
		assertBundleState(Bundle.ACTIVE, bundle.getSymbolicName(), subsystem);
	}

	protected void startSubsystem(Subsystem subsystem, Boolean ... configChecks) throws Exception {
		startSubsystemFromInstalled(subsystem, configChecks);
	}

	protected void startSubsystemFromInstalled(Subsystem subsystem, Boolean ... configChecks) throws InterruptedException {
	    boolean ariesProvisionDependenciesAtInstall = true; //set default value
	    if (configChecks.length>0) {
	        ariesProvisionDependenciesAtInstall = configChecks[0].booleanValue();
	    }
	    if (ariesProvisionDependenciesAtInstall) {
		    assertState(State.INSTALLED, subsystem);
	    }
	    else {
	        assertState(State.INSTALLING, subsystem);
	    }
	        
		subsystemEvents.clear();
		subsystem.start();
		if (!ariesProvisionDependenciesAtInstall) {
		    assertEvent(subsystem, State.INSTALLED, 5000);
		}
		assertEvent(subsystem, State.RESOLVING, 5000);
		assertEvent(subsystem, State.RESOLVED, 5000);
		assertEvent(subsystem, State.STARTING, 5000);
		assertEvent(subsystem, State.ACTIVE, 5000);
		assertState(State.ACTIVE, subsystem);
	}

	protected void startSubsystemFromResolved(Subsystem subsystem) throws InterruptedException {
		assertState(State.RESOLVED, subsystem);
		subsystemEvents.clear();
		subsystem.start();
		assertEvent(subsystem, State.STARTING, 5000);
		assertEvent(subsystem, State.ACTIVE, 5000);
		assertState(State.ACTIVE, subsystem);
	}

	protected void stopAndUninstallSubsystemSilently(Subsystem subsystem) {
		stopSubsystemSilently(subsystem);
		uninstallSubsystemSilently(subsystem);
	}

	protected void stopSubsystem(Subsystem subsystem) throws Exception {
		assertState(State.ACTIVE, subsystem);
		subsystemEvents.clear();
		subsystem.stop();
		assertEvent(subsystem, State.STOPPING, 5000);
		assertEvent(subsystem, State.RESOLVED, 5000);
		assertState(State.RESOLVED, subsystem);
	}

	protected void stopSubsystemSilently(Subsystem subsystem) {
		try {
			stopSubsystem(subsystem);
		}
		catch (Throwable t) {
			t.printStackTrace();
		}
	}

	protected void uninstallSilently(Bundle bundle) {
		if (bundle == null)
			return;
		try {
			bundle.uninstall();
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}

	protected void uninstallSubsystem(Subsystem subsystem) throws Exception {
		uninstallSubsystem(subsystem, false);
	}
	
	protected void uninstallSubsystem(Subsystem subsystem, boolean quietly) throws Exception {
		BasicSubsystem basicSubsystem = (BasicSubsystem)subsystem;
		AriesProvisionDependenciesDirective directive = basicSubsystem.getAriesProvisionDependenciesDirective();
		Bundle b = null;
		Region region = null;
		RegionDigraph digraph = context().getService(RegionDigraph.class);
		if (!quietly) {
			if (directive.isResolve()) {
				assertState(EnumSet.of(State.INSTALLING, State.INSTALLED, State.RESOLVED), subsystem);
			}
			else {
				assertState(EnumSet.of(State.INSTALLED, State.RESOLVED), subsystem);
			}
			subsystemEvents.clear();
			if (subsystem.getType().equals(SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION)
					|| subsystem.getType().equals(SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE)) {
				b = getRegionContextBundle(subsystem);
				region = digraph.getRegion(b);
			}
		}
		State state = subsystem.getState();
		subsystem.uninstall();
		if (quietly) {
			return;
		}
		Collection<Subsystem> parents = subsystem.getParents();
		if (!EnumSet.of(State.INSTALL_FAILED, State.INSTALLED, State.INSTALLING).contains(state)) {
			assertEvent(subsystem, State.INSTALLED, 5000);
		}
		assertEvent(subsystem, State.UNINSTALLING, 5000);
		assertEvent(subsystem, State.UNINSTALLED, 5000);
		assertState(State.UNINSTALLED, subsystem);
		for (Subsystem parent : parents) {
			assertNotChild(parent, subsystem);
		}
		if (subsystem.getType().equals(SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION)
				|| subsystem.getType().equals(SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE)) {
			assertEquals("Region context bundle not uninstalled", Bundle.UNINSTALLED, b.getState());
			assertNull("Region not removed", digraph.getRegion(region.getName()));
		}
	}

	protected void uninstallSubsystemSilently(Subsystem subsystem) {
		if (subsystem == null)
			return;
		try {
			uninstallSubsystem(subsystem, true);
		}
		catch (Throwable t) {
			t.printStackTrace();
		}
	}

	protected void writeToFile(InputStream is, String name) {
        try {
            FileOutputStream dest = new FileOutputStream(name);
            StreamUtils.copyStream(is, dest, true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    protected static void write(String file, ArchiveFixture.AbstractFixture fixture) throws IOException
	{
		write(new File(file), fixture);
	}

	private static void write(File file, ArchiveFixture.AbstractFixture fixture) throws IOException {
		FileOutputStream fos = new FileOutputStream(file);
    	try {
    		fixture.writeOut(fos);
    	}
    	finally {
    		fos.close();
    	}
	}

	static void createApplication(String name, String ... contents) throws Exception
	{
		ClassLoader cl = SubsystemTest.class.getClassLoader();
        ZipFixture feature = ArchiveFixture
				.newZip()
				.binary("OSGI-INF/SUBSYSTEM.MF",
						// The following input stream is closed by ArchiveFixture.copy.
						cl.getResourceAsStream(name + "/OSGI-INF/SUBSYSTEM.MF"));
		for (String content : contents) {
			try {
				feature.binary(content,
						// The following input stream is closed by ArchiveFixture.copy.
						cl.getResourceAsStream(name + '/' + content));
			}
			catch (Exception e) {
				// The following input stream is closed by ArchiveFixture.copy.
				feature.binary(content, new FileInputStream(new File(content)));
			}
		}
		feature.end();
		FileOutputStream fos = new FileOutputStream(name + ".esa");
		try {
			feature.writeOut(fos);
		} finally {
			Utils.closeQuietly(fos);
		}
	}

	protected static String normalizeBundleLocation(Bundle bundle) {
		return normalizeBundleLocation(bundle.getLocation());
	}
	
	protected static String normalizeBundleLocation(String location) {
		if (location.startsWith("initial@"))
			return location.substring(8);
		return location;
	}

	protected InputStream getResource(String path) {
	    InputStream is = this.getClass().getClassLoader().getResourceAsStream(path);
	    if (is == null) {
	        throw new IllegalArgumentException("No resource found at path " + path);
	    }
	    return is;
	}

}
