/* * 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.core.internal; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URISyntaxException; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.locks.ReentrantLock; import org.apache.aries.subsystem.AriesSubsystem; import org.apache.aries.subsystem.core.archive.AriesProvisionDependenciesDirective; import org.apache.aries.subsystem.core.archive.AriesSubsystemParentsHeader; import org.apache.aries.subsystem.core.archive.DeployedContentHeader; import org.apache.aries.subsystem.core.archive.DeploymentManifest; import org.apache.aries.subsystem.core.archive.Header; import org.apache.aries.subsystem.core.archive.ProvisionResourceHeader; import org.apache.aries.subsystem.core.archive.SubsystemContentHeader; import org.apache.aries.subsystem.core.archive.SubsystemManifest; import org.apache.aries.subsystem.core.archive.SubsystemTypeHeader; import org.apache.aries.util.filesystem.FileSystem; import org.apache.aries.util.filesystem.ICloseableDirectory; import org.apache.aries.util.filesystem.IDirectory; import org.apache.aries.util.io.IOUtils; import org.eclipse.equinox.region.Region; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.Version; import org.osgi.framework.namespace.HostNamespace; import org.osgi.framework.namespace.IdentityNamespace; import org.osgi.resource.Capability; import org.osgi.resource.Requirement; import org.osgi.resource.Resource; import org.osgi.service.coordinator.Coordination; import org.osgi.service.coordinator.Participant; import org.osgi.service.resolver.ResolutionException; import org.osgi.service.subsystem.Subsystem; import org.osgi.service.subsystem.SubsystemConstants; import org.osgi.service.subsystem.SubsystemException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class BasicSubsystem implements Resource, AriesSubsystem { private static final Logger logger = LoggerFactory.getLogger(BasicSubsystem.class); public static final String ROOT_SYMBOLIC_NAME = "org.osgi.service.subsystem.root"; public static final Version ROOT_VERSION = Version.parseVersion("1.0.0"); public static final String ROOT_LOCATION = "subsystem://?" + SubsystemConstants.SUBSYSTEM_SYMBOLICNAME + '=' + ROOT_SYMBOLIC_NAME + '&' + SubsystemConstants.SUBSYSTEM_VERSION + '=' + ROOT_VERSION; private volatile Bundle regionContextBundle; private DeploymentManifest deploymentManifest; private SubsystemResource resource; private SubsystemManifest subsystemManifest; private final IDirectory directory; public BasicSubsystem(SubsystemResource resource) throws URISyntaxException, IOException, BundleException, InvalidSyntaxException { this(resource, null); } public BasicSubsystem(SubsystemResource resource, InputStream deploymentManifest) throws URISyntaxException, IOException, BundleException, InvalidSyntaxException { this.resource = resource; final File file = new File(Activator.getInstance().getBundleContext().getDataFile(""), Long.toString(resource.getId())); file.mkdirs(); Coordination coordination = Activator.getInstance().getCoordinator().peek(); if (coordination != null) { coordination.addParticipant(new Participant() { @Override public void ended(Coordination c) throws Exception { // Nothing } @Override public void failed(Coordination c) throws Exception { IOUtils.deleteRecursive(file); } }); } directory = FileSystem.getFSRoot(file); setSubsystemManifest(resource.getSubsystemManifest()); SubsystemManifestValidator.validate(this, getSubsystemManifest()); setDeploymentManifest(new DeploymentManifest.Builder() .manifest(resource.getSubsystemManifest()) .manifest(deploymentManifest == null ? resource.getDeploymentManifest() : new DeploymentManifest(deploymentManifest)) .location(resource.getLocation()) .autostart(false) .id(resource.getId()) .lastId(SubsystemIdentifier.getLastId()) .region(resource.getRegion().getName()) .state(State.INSTALLING) .build()); setTranslations(); } public BasicSubsystem(File file) throws IOException, URISyntaxException, ResolutionException { this(FileSystem.getFSRoot(file)); } public BasicSubsystem(IDirectory directory) throws IOException, URISyntaxException, ResolutionException { this.directory = directory; State state = State .valueOf(getDeploymentManifestHeaderValue(DeploymentManifest.ARIESSUBSYSTEM_STATE)); if (EnumSet.of(State.STARTING, State.ACTIVE, State.STOPPING).contains( state)) { state = State.RESOLVED; } else if (State.RESOLVING.equals(state)) { state = State.INSTALLED; } setDeploymentManifest(new DeploymentManifest.Builder() .manifest(getDeploymentManifest()).state(state).build()); } /* BEGIN Resource interface methods. */ @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof BasicSubsystem)) return false; BasicSubsystem that = (BasicSubsystem)o; return getLocation().equals(that.getLocation()); } @Override public List<Capability> getCapabilities(String namespace) { // First, add the capabilities from the manifest. SubsystemManifest manifest = getSubsystemManifest(); List<Capability> result = manifest.toCapabilities(this); if (namespace != null) for (Iterator<Capability> i = result.iterator(); i.hasNext();) if (!i.next().getNamespace().equals(namespace)) i.remove(); // TODO Somehow, exposing the capabilities of content resources of a // feature is causing an infinite regression of feature2 installations // in FeatureTest.testSharedContent() under certain conditions. if (isScoped() || IdentityNamespace.IDENTITY_NAMESPACE.equals(namespace)) return result; SubsystemContentHeader header = manifest.getSubsystemContentHeader(); for (Resource constituent : getConstituents()) { if (header.contains(constituent)) { for (Capability capability : constituent.getCapabilities(namespace)) { if (namespace == null && (IdentityNamespace.IDENTITY_NAMESPACE.equals(capability.getNamespace()) || HostNamespace.HOST_NAMESPACE.equals(capability.getNamespace()))) { // Don't want to include the osgi.identity and/or osgi.wiring.host capabilities of // content. Need a second check here in case the namespace // is null. continue; } result.add(new BasicCapability(capability, this)); } } } return result; } @Override public List<Requirement> getRequirements(String namespace) { // First, add the requirements from the manifest. SubsystemManifest manifest = getSubsystemManifest(); List<Requirement> result = manifest.toRequirements(this); if (namespace != null) for (Iterator<Requirement> i = result.iterator(); i.hasNext();) if (!i.next().getNamespace().equals(namespace)) i.remove(); if (isScoped()) return result; SubsystemContentHeader header = manifest.getSubsystemContentHeader(); for (Resource constituent : getConstituents()) if (header.contains(constituent)) for (Requirement requirement : constituent.getRequirements(namespace)) result.add(new BasicRequirement(requirement, this)); return result; } @Override public int hashCode() { int result = 17; result = 31 * result + getLocation().hashCode(); return result; } /* END Resource interface methods. */ /* BEGIN Subsystem interface methods. */ @Override public BundleContext getBundleContext() { SecurityManager.checkContextPermission(this); return AccessController.doPrivileged(new GetBundleContextAction(this)); } @Override public Collection<Subsystem> getChildren() { return Activator.getInstance().getSubsystems().getChildren(this); } @Override public Map<String, String> getSubsystemHeaders(Locale locale) { SecurityManager.checkMetadataPermission(this); return AccessController.doPrivileged(new GetSubsystemHeadersAction(this, locale)); } @Override public String getLocation() { SecurityManager.checkMetadataPermission(this); return getDeploymentManifestHeaderValue(DeploymentManifest.ARIESSUBSYSTEM_LOCATION); } @Override public Collection<Subsystem> getParents() { AriesSubsystemParentsHeader header = getDeploymentManifest().getAriesSubsystemParentsHeader(); if (header == null) return Collections.emptyList(); Collection<Subsystem> result = new ArrayList<Subsystem>(header.getClauses().size()); for (AriesSubsystemParentsHeader.Clause clause : header.getClauses()) { BasicSubsystem subsystem = Activator.getInstance().getSubsystems().getSubsystemById(clause.getId()); if (subsystem == null) continue; result.add(subsystem); } return result; } @Override public Collection<Resource> getConstituents() { return Activator.getInstance().getSubsystems().getConstituents(this); } @Override public State getState() { return State.valueOf(getDeploymentManifestHeaderValue(DeploymentManifest.ARIESSUBSYSTEM_STATE)); } @Override public long getSubsystemId() { return Long.parseLong(getDeploymentManifestHeaderValue(DeploymentManifest.ARIESSUBSYSTEM_ID)); } @Override public String getSymbolicName() { return getSubsystemManifest().getSubsystemSymbolicNameHeader().getSymbolicName(); } @Override public String getType() { return getSubsystemManifest().getSubsystemTypeHeader().getType(); } @Override public Version getVersion() { return getSubsystemManifest().getSubsystemVersionHeader().getVersion(); } /** Get "aries-provision-dependencies" directive. * * @return requested directive or null if the directive is not specified in the header */ public AriesProvisionDependenciesDirective getAriesProvisionDependenciesDirective() { return getSubsystemManifest().getSubsystemTypeHeader().getAriesProvisionDependenciesDirective(); } @Override public AriesSubsystem install(String location) { return install(location, (InputStream)null); } @Override public AriesSubsystem install(String location, InputStream content) { return install(location, content, null); } @Override public void start() { SecurityManager.checkExecutePermission(this); // Changing the autostart setting must be privileged because of file IO. // It cannot be done within StartAction because we only want to change // it on an explicit start operation but StartAction is also used for // implicit operations. AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { setAutostart(true); return null; } }); AccessController.doPrivileged(new StartAction(this, this, this)); } @Override public void stop() { SecurityManager.checkExecutePermission(this); // Changing the autostart setting must be privileged because of file IO. // It cannot be done within StopAction because we only want to change it // on an explicit stop operation but StopAction is also used for // implicit operations. AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { setAutostart(false); return null; } }); AccessController.doPrivileged(new StopAction(this, this, !isRoot())); } @Override public void uninstall() { SecurityManager.checkLifecyclePermission(this); AccessController.doPrivileged(new UninstallAction(this, this, false)); } /* END Subsystem interface methods. */ void addedConstituent(Resource resource, boolean referenced) { try { if (logger.isDebugEnabled()) logger.debug("Adding constituent {} to deployment manifest...", resource); synchronized (this) { setDeploymentManifest(new DeploymentManifest.Builder() .manifest(getDeploymentManifest()).content(resource, referenced).build()); } if (logger.isDebugEnabled()) logger.debug("Added constituent {} to deployment manifest", resource); } catch (Exception e) { throw new SubsystemException(e); } } void addedParent(BasicSubsystem subsystem, boolean referenceCount) { try { if (logger.isDebugEnabled()) logger.debug("Adding parent {} to deployment manifest...", subsystem.getSymbolicName()); synchronized (this) { setDeploymentManifest(new DeploymentManifest.Builder() .manifest(getDeploymentManifest()).parent(subsystem, referenceCount).build()); } if (logger.isDebugEnabled()) logger.debug("Added parent {} to deployment manifest", subsystem.getSymbolicName()); } catch (Exception e) { throw new SubsystemException(e); } } synchronized DeploymentManifest getDeploymentManifest() { if (deploymentManifest == null) { try { deploymentManifest = new DeploymentManifest(directory.getFile("OSGI-INF/DEPLOYMENT.MF").open()); } catch (Throwable t) { throw new SubsystemException(t); } } return deploymentManifest; } File getDirectory() { try { return new File(directory.toURL().toURI()); } catch (Exception e) { throw new SubsystemException(e); } } Region getRegion() { Bundle bundle = regionContextBundle; // volatile variable if (bundle == null) { // At best, RegionDigraph.getRegion(String) is linear time. // Continue to call this when necessary, however, as a fail safe. return Activator.getInstance().getRegionDigraph().getRegion(getRegionName()); } // RegionDigraph.getRegion(Bundle) is constant time. return Activator.getInstance().getRegionDigraph().getRegion(bundle); } String getRegionName() { DeploymentManifest manifest = getDeploymentManifest(); Header<?> header = manifest.getHeaders().get(DeploymentManifest.ARIESSUBSYSTEM_REGION); if (header == null) return null; return header.getValue(); } synchronized SubsystemResource getResource() { if (resource == null) { try { resource = new SubsystemResource(null, directory); } catch (Exception e) { throw new SubsystemException(e); } Collection<DeployedContentHeader.Clause> missingResources = resource.getMissingResources(); if (!missingResources.isEmpty()) { if (isRoot()) // We don't care if the root subsystem has missing resources // because they are either (1) extraneous bundles outside of // the subsystems API or (2) provisioned dependencies of // other subsystems. Those that fall in the latter category // will be detected by the dependent subsystems. removedContent(missingResources); else // If a non-root subsystem has missing dependencies, let's // fail fast for now. throw new SubsystemException("Missing resources: " + missingResources); } } return resource; } synchronized SubsystemManifest getSubsystemManifest() { if (subsystemManifest == null) { try { subsystemManifest = new SubsystemManifest(directory.getFile("OSGI-INF/SUBSYSTEM.MF").open()); } catch (Throwable t) { throw new SubsystemException(t); } } return subsystemManifest; } boolean isApplication() { return getSubsystemManifest().getSubsystemTypeHeader().isApplication(); } boolean isAutostart() { DeploymentManifest manifest = getDeploymentManifest(); Header<?> header = manifest.getHeaders().get(DeploymentManifest.ARIESSUBSYSTEM_AUTOSTART); return Boolean.valueOf(header.getValue()); } boolean isComposite() { return getSubsystemManifest().getSubsystemTypeHeader().isComposite(); } boolean isFeature() { return getSubsystemManifest().getSubsystemTypeHeader().isFeature(); } boolean isReadyToStart() { if (isRoot()) return true; for (Subsystem parent : getParents()) if (EnumSet.of(State.STARTING, State.ACTIVE).contains(parent.getState()) && isAutostart()) return true; return false; } boolean isReferenced(Resource resource) { // Everything is referenced for the root subsystem during initialization. if (isRoot() && EnumSet.of(State.INSTALLING, State.INSTALLED).contains(getState())) return true; DeployedContentHeader header = getDeploymentManifest().getDeployedContentHeader(); if (header == null) return false; return header.isReferenced(resource); } boolean isRoot() { return ROOT_LOCATION.equals(getLocation()); } boolean isScoped() { return isApplication() || isComposite(); } void removedContent(Resource resource) { DeploymentManifest manifest = getDeploymentManifest(); DeployedContentHeader header = manifest.getDeployedContentHeader(); if (header == null) return; DeployedContentHeader.Clause clause = header.getClause(resource); if (clause == null) return; removedContent(Collections.singleton(clause)); } synchronized void removedContent(Collection<DeployedContentHeader.Clause> content) { DeploymentManifest manifest = getDeploymentManifest(); DeployedContentHeader header = manifest.getDeployedContentHeader(); if (header == null) return; Collection<DeployedContentHeader.Clause> clauses = new ArrayList<DeployedContentHeader.Clause>(header.getClauses()); for (Iterator<DeployedContentHeader.Clause> i = clauses.iterator(); i.hasNext();) if (content.contains(i.next())) { i.remove(); break; } DeploymentManifest.Builder builder = new DeploymentManifest.Builder(); for (Entry<String, Header<?>> entry : manifest.getHeaders().entrySet()) { if (DeployedContentHeader.NAME.equals(entry.getKey())) continue; builder.header(entry.getValue()); } if (!clauses.isEmpty()) builder.header(new DeployedContentHeader(clauses)); try { setDeploymentManifest(builder.build()); } catch (Exception e) { throw new SubsystemException(e); } } void setAutostart(boolean value) { try { synchronized (this) { setDeploymentManifest(new DeploymentManifest.Builder() .manifest(getDeploymentManifest()).autostart(value).build()); } } catch (Exception e) { throw new SubsystemException(e); } } synchronized void setDeploymentManifest(DeploymentManifest value) throws IOException { deploymentManifest = value; Coordination coordination = Activator.getInstance().getCoordinator().peek(); if (logger.isDebugEnabled()) logger.debug("Setting deployment manifest for subsystem {} using coordination {}", getSymbolicName(), coordination == null ? null : coordination.getName()); if (coordination == null) { saveDeploymentManifest(); } else { Map<Class<?>, Object> variables = coordination.getVariables(); synchronized (variables) { @SuppressWarnings("unchecked") Set<BasicSubsystem> dirtySubsystems = (Set<BasicSubsystem>) variables.get(SaveManifestParticipant.class); if (dirtySubsystems == null) { // currently no dirty subsystems found; // create a list to hold them and store it as a variable dirtySubsystems = new HashSet<BasicSubsystem>(); variables.put(SaveManifestParticipant.class, dirtySubsystems); // add the save manifest participant coordination.addParticipant(new SaveManifestParticipant()); } dirtySubsystems.add(this); } } } synchronized void saveDeploymentManifest() throws IOException { File file = new File(getDirectory(), "OSGI-INF"); if (!file.exists()) file.mkdirs(); BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(new File(file, "DEPLOYMENT.MF"))); try { if (logger.isDebugEnabled()) logger.debug("Writing deployment manifest for subsystem {} in state {}", getSymbolicName(), getState()); deploymentManifest.write(out); if (logger.isDebugEnabled()) logger.debug("Wrote deployment manifest for subsystem {} in state {}", getSymbolicName(), getState()); } finally { IOUtils.close(out); } } void setState(State value) { if (logger.isDebugEnabled()) logger.debug("Setting state of subsystem {} to {}", getSymbolicName(), value); State state = getState(); if (value.equals(state)) { if (logger.isDebugEnabled()) logger.debug("Requested state {} equals current state {}", value, state); return; } try { if (logger.isDebugEnabled()) logger.debug("Setting the deployment manifest..."); synchronized (this) { setDeploymentManifest(new DeploymentManifest.Builder() .manifest(getDeploymentManifest()).state(value).build()); } } catch (Exception e) { throw new SubsystemException(e); } Activator.getInstance().getSubsystemServiceRegistrar().update(this); synchronized (this) { if (logger.isDebugEnabled()) logger.debug("Notifying all waiting for state change of subsystem {}", getSymbolicName()); notifyAll(); } } void setRegionContextBundle(Bundle value) { regionContextBundle = value; // volatile variable } synchronized void setSubsystemManifest(SubsystemManifest value) throws URISyntaxException, IOException { File file = new File(getDirectory(), "OSGI-INF"); if (!file.exists()) file.mkdirs(); FileOutputStream fos = new FileOutputStream(new File(file, "SUBSYSTEM.MF")); try { value.write(fos); subsystemManifest = value; } finally { IOUtils.close(fos); } } private final ReentrantLock stateChangeLock = new ReentrantLock(); ReentrantLock stateChangeLock() { return stateChangeLock; } private String getDeploymentManifestHeaderValue(String name) { DeploymentManifest manifest = getDeploymentManifest(); if (manifest == null) return null; Header<?> header = manifest.getHeaders().get(name); if (header == null) return null; return header.getValue(); } @Override public synchronized void addRequirements(Collection<Requirement> requirements) { // The root subsystem has no requirements (there is no parent to import from). if (isRoot()) throw new UnsupportedOperationException("The root subsystem does not accept additional requirements"); // Unscoped subsystems import everything already. if (!isScoped()) return; RegionUpdater updater = new RegionUpdater(getRegion(), ((BasicSubsystem)getParents().iterator().next()).getRegion()); try { updater.addRequirements(requirements); } catch (Exception e) { throw new SubsystemException(e); } } @Override public AriesSubsystem install(String location, IDirectory content) { return install(location, content, null); } @Override public AriesSubsystem install(String location, IDirectory content, InputStream deploymentManifest) { try { return AccessController.doPrivileged(new InstallAction(location, content, this, AccessController.getContext(), deploymentManifest)); } finally { IOUtils.close(deploymentManifest); } } private static class SaveManifestParticipant implements Participant { protected SaveManifestParticipant() {} @Override public void ended(Coordination coordination) throws Exception { if (logger.isDebugEnabled()) logger.debug("Saving deployment manifests because coordination {} ended", coordination.getName()); Map<Class<?>, Object> variables = coordination.getVariables(); Set<BasicSubsystem> dirtySubsystems; synchronized (variables) { @SuppressWarnings("unchecked") Set<BasicSubsystem> temp = (Set<BasicSubsystem>) variables.remove(SaveManifestParticipant.class); dirtySubsystems = temp == null ? Collections. <BasicSubsystem>emptySet() : temp; } for (BasicSubsystem dirtySubsystem : dirtySubsystems) { if (logger.isDebugEnabled()) logger.debug("Saving deployment manifest of subsystem {} for coordination {}", dirtySubsystem.getSymbolicName(), coordination.getName()); dirtySubsystem.saveDeploymentManifest(); } } @Override public void failed(Coordination coordination) throws Exception { // Do no saving } } @Override public Map<String, String> getDeploymentHeaders() { SecurityManager.checkMetadataPermission(this); return AccessController.doPrivileged(new GetDeploymentHeadersAction(this)); } @Override public AriesSubsystem install(String location, final InputStream content, InputStream deploymentManifest) { AriesSubsystem result = null; IDirectory directory = null; try { directory = content == null ? null : AccessController.doPrivileged(new PrivilegedAction<IDirectory>() { @Override public IDirectory run() { return FileSystem.getFSRoot(content); } }); result = install(location, directory, deploymentManifest); return result; } finally { // This method must guarantee the content input stream was closed. // TODO Not sure closing the content is necessary. The content will // either be null of will have been closed while copying the data // to the temporary file. IOUtils.close(content); // If appropriate, delete the temporary file. Subsystems having // apache-aries-provision-dependencies:=resolve may need the file // at start time if it contains any dependencies. if (directory instanceof ICloseableDirectory) { if (result == null || Utils.isProvisionDependenciesInstall((BasicSubsystem)result) || !wasInstalledWithChildrenHavingProvisionDependenciesResolve()) { final IDirectory toClose = directory; AccessController.doPrivileged(new PrivilegedAction<Void>() { @Override public Void run() { try { ((ICloseableDirectory) toClose).close(); } catch (IOException ioex) { logger.info("Exception calling close for content {}. Exception {}", content, ioex); } return null; } }); } } } } private void setTranslations() throws IOException { String directoryName = getSubsystemManifest().getSubsystemLocalizationHeader().getDirectoryName(); File file = directoryName == null ? getDirectory() : new File(getDirectory(), directoryName); if (!file.exists()) file.mkdirs(); for (TranslationFile translation : getResource().getTranslations()) { translation.write(file); } } void computeDependenciesPostInstallation(Coordination coordination) throws IOException { resource.computeDependencies(null, coordination); ProvisionResourceHeader header = resource.computeProvisionResourceHeader(); setDeploymentManifest( new DeploymentManifest.Builder() .manifest(deploymentManifest) .header(header) .build()); } private boolean wasInstalledWithChildrenHavingProvisionDependenciesResolve() { return wasInstalledWithChildrenHavingProvisionDependenciesResolve(this); } private static boolean wasInstalledWithChildrenHavingProvisionDependenciesResolve(Subsystem child) { BasicSubsystem bs = (BasicSubsystem) child; SubsystemManifest manifest = bs.getSubsystemManifest(); SubsystemTypeHeader header = manifest.getSubsystemTypeHeader(); AriesProvisionDependenciesDirective directive = header.getAriesProvisionDependenciesDirective(); if (directive.isResolve()) { return true; } return wasInstalledWithChildrenHavingProvisionDependenciesResolve(child.getChildren()); } private static boolean wasInstalledWithChildrenHavingProvisionDependenciesResolve(Collection<Subsystem> children) { for (Subsystem child : children) { if (wasInstalledWithChildrenHavingProvisionDependenciesResolve(child)) { return true; } } return false; } @Override public String toString() { return new StringBuilder() .append(getClass().getName()) .append(": ") .append("children=") .append(getChildren().size()) .append(", constituents=") .append(getConstituents().size()) .append(", id=") .append(getSubsystemId()) .append(", location=") .append(getLocation()) .append(", parents=") .append(getParents().size()) .append(", state=") .append(getState()) .append(", symbolicName=") .append(getSymbolicName()) .append(", type=") .append(getType()) .append(", version=") .append(getVersion()) .toString(); } }