/* * 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.tuscany.sca.impl; import java.io.Reader; import java.io.StringReader; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamException; import org.apache.tuscany.sca.Node; import org.apache.tuscany.sca.TuscanyRuntime; import org.apache.tuscany.sca.assembly.Composite; import org.apache.tuscany.sca.assembly.xml.Utils; import org.apache.tuscany.sca.common.java.io.IOHelper; import org.apache.tuscany.sca.common.xml.dom.DOMHelper; import org.apache.tuscany.sca.contribution.Contribution; import org.apache.tuscany.sca.contribution.ContributionMetadata; import org.apache.tuscany.sca.contribution.Import; import org.apache.tuscany.sca.contribution.java.JavaImport; import org.apache.tuscany.sca.contribution.namespace.NamespaceImport; import org.apache.tuscany.sca.contribution.processor.ContributionReadException; import org.apache.tuscany.sca.contribution.resolver.ClassReference; import org.apache.tuscany.sca.contribution.resolver.ExtensibleModelResolver; import org.apache.tuscany.sca.contribution.resolver.ModelResolver; import org.apache.tuscany.sca.core.ExtensionPointRegistry; import org.apache.tuscany.sca.core.UtilityExtensionPoint; import org.apache.tuscany.sca.deployment.Deployer; import org.apache.tuscany.sca.monitor.Monitor; import org.apache.tuscany.sca.monitor.ValidationException; import org.apache.tuscany.sca.runtime.ActivationException; import org.apache.tuscany.sca.runtime.ActiveNodes; import org.apache.tuscany.sca.runtime.CompositeActivator; import org.apache.tuscany.sca.runtime.ContributionDescription; import org.apache.tuscany.sca.runtime.ContributionListener; import org.apache.tuscany.sca.runtime.DomainRegistry; import org.apache.tuscany.sca.runtime.RuntimeProperties; import org.oasisopen.sca.NoSuchServiceException; import sun.misc.ClassLoaderUtil; public class NodeImpl implements Node { private static final Logger logger = Logger.getLogger(NodeImpl.class.getName()); private Deployer deployer; private CompositeActivator compositeActivator; private DomainRegistry domainRegistry; private ExtensionPointRegistry extensionPointRegistry; private UtilityExtensionPoint utilityExtensionPoint; private TuscanyRuntime tuscanyRuntime; private Map loadedContributions = new ConcurrentHashMap(); private Map startedComposites = new HashMap(); private Map stoppedComposites = new HashMap(); private boolean endpointsIncludeDomainName; private boolean quietLogging; private boolean releaseOnUnload; private ContributionListener contributionListener; public NodeImpl(Deployer deployer, CompositeActivator compositeActivator, DomainRegistry domainRegistry, ExtensionPointRegistry extensionPointRegistry, TuscanyRuntime tuscanyRuntime) { this.deployer = deployer; this.compositeActivator = compositeActivator; this.domainRegistry = domainRegistry; this.extensionPointRegistry = extensionPointRegistry; this.tuscanyRuntime = tuscanyRuntime; utilityExtensionPoint = extensionPointRegistry.getExtensionPoint(UtilityExtensionPoint.class); utilityExtensionPoint.getUtility(ActiveNodes.class).getActiveNodes().add(this); contributionListener = new ContributionListener() { public void contributionInstalled(String uri) { // Do nothing } public void contributionUpdated(String uri) { unloadContribution(uri); } public void contributionRemoved(String uri) { unloadContribution(uri); } private void unloadContribution(String curi) { Contribution c = loadedContributions.remove(curi); if (c != null) { ClassLoader cl = c.getClassLoader(); ContributionHelper.close(c, NodeImpl.this.extensionPointRegistry); if (releaseOnUnload) { if (cl instanceof URLClassLoader) { ClassLoaderUtil.releaseLoader((URLClassLoader)cl); } } } } }; this.domainRegistry.addContributionListener(contributionListener); endpointsIncludeDomainName = !TuscanyRuntime.DEFAUL_DOMAIN_NAME.equals(domainRegistry.getDomainName()); UtilityExtensionPoint utilities = extensionPointRegistry.getExtensionPoint(UtilityExtensionPoint.class); this.releaseOnUnload = Boolean.parseBoolean(utilities.getUtility(RuntimeProperties.class).getProperties().getProperty(RuntimeProperties.RELEASE_ON_UNLOAD, "true")); this.quietLogging = Boolean.parseBoolean(utilities.getUtility(RuntimeProperties.class).getProperties().getProperty(RuntimeProperties.QUIET_LOGGING)); if (logger.isLoggable(quietLogging? Level.FINE : Level.INFO)) logger.log(quietLogging? Level.FINE : Level.INFO, "domain: " + domainRegistry.getDomainName() + (!domainRegistry.getDomainName().equals(domainRegistry.getDomainURI()) ? "" : (" domainURI: " + domainRegistry.getDomainURI()))); } // TODO: install shouldn't throw ValidationException as it shouldn't do any validation, its // only here from the loadContribution in mergeContributionMetaData so change that approach public String installContribution(String contributionURL) throws ContributionReadException, ValidationException { return installContribution(null, contributionURL, null, null); } public String installContribution(String uri, String contributionURL) throws ContributionReadException, ValidationException { if (logger.isLoggable(Level.FINE)) logger.log(Level.FINE, "updateUsingComposites", contributionURL); return installContribution(uri, contributionURL, null, null); } public boolean updateContribution(String uri, String contributionURL, String metaDataURL, List dependentContributionURIs) throws ContributionReadException, ValidationException, ActivationException { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "updateContribution" + Arrays.asList(new Object[]{uri, contributionURL, metaDataURL, dependentContributionURIs})); } ContributionDescription ic = domainRegistry.getInstalledContribution(uri); if (ic == null) { installContribution(uri, contributionURL, metaDataURL, dependentContributionURIs); return true; } // do this if only updating if the contribution has been modified: // if url equal and a file and last modified not changed // if metadata url equal and a file and laqst modified not changed // if (dependent contributions uris not changed) // return false uninstallContribution(uri); installContribution(uri, contributionURL, metaDataURL, dependentContributionURIs); // merge in additional deployables if (ic.getAdditionalDeployables().size() > 0) { ContributionDescription newIC = getInstalledContribution(uri); newIC.getAdditionalDeployables().putAll(ic.getAdditionalDeployables()); domainRegistry.updateInstalledContribution(newIC); } // stop/start all started composites using the contribution for (DeployedComposite dc : new ArrayList(startedComposites.values())) { if (dc.getContributionURIs().contains(uri)) { String dcContributionURI = dc.getContributionURIs().get(0); String dcCompositeURI = dc.getURI(); stopComposite(dcContributionURI, dcCompositeURI); String key = dcContributionURI + "/" + dcCompositeURI; stoppedComposites.remove(key); startComposite(dcContributionURI, dcCompositeURI); } } // remove all stopped composites using the contribution for (DeployedComposite dc : new ArrayList(stoppedComposites.values())) { if (dc.getContributionURIs().contains(uri)) { stoppedComposites.remove(uri + "/" + dc.getURI()); } } if (logger.isLoggable(quietLogging? Level.FINE : Level.INFO)) logger.log(quietLogging? Level.FINE : Level.INFO, "updateContribution: " + uri); return true; } public String installContribution(String uri, String contributionURL, String metaDataURL, List dependentContributionURIs) throws ContributionReadException, ValidationException { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "installContribution" + Arrays.asList(new Object[]{uri, contributionURL, metaDataURL, dependentContributionURIs})); } ContributionDescription cd = new ContributionDescription(uri, IOHelper.getLocationAsURL(contributionURL).toString()); if (dependentContributionURIs != null) { cd.getDependentContributionURIs().addAll(dependentContributionURIs); } if (metaDataURL != null) { mergeContributionMetaData(metaDataURL, loadContribution(cd)); } peekIntoContribution(cd); domainRegistry.installContribution(cd); if (logger.isLoggable(quietLogging? Level.FINE : Level.INFO)) logger.log(quietLogging? Level.FINE : Level.INFO, "installContribution: " + cd.getURI()); return cd.getURI(); } public void installContribution(Contribution contribution, List dependentContributionURIs) { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "installContribution" + Arrays.asList(new Object[]{contribution, dependentContributionURIs})); } ContributionDescription cd = new ContributionDescription(contribution.getURI(), contribution.getLocation()); if (dependentContributionURIs != null) { cd.getDependentContributionURIs().addAll(dependentContributionURIs); } cd.configureMetaData(contribution); domainRegistry.installContribution(cd); loadedContributions.put(cd.getURI(), contribution); if (logger.isLoggable(quietLogging? Level.FINE : Level.INFO)) logger.log(quietLogging? Level.FINE : Level.INFO, "installContribution: " + cd.getURI()); } public void uninstallContribution(String contributionURI) { // note that the contribution listener that this class registers will free up the contribution's resources domainRegistry.uninstallContribution(contributionURI); // remove any stopped composite that used the contribution Iterator i = stoppedComposites.keySet().iterator(); while (i.hasNext()) { DeployedComposite dc = stoppedComposites.get(i.next()); if (dc.getContributionURIs().contains(contributionURI)) { i.remove(); } } if (loadedContributions.size() < 1) { DOMHelper.getInstance(extensionPointRegistry).stop(); java.beans.Introspector.flushCaches(); } if (logger.isLoggable(quietLogging? Level.FINE : Level.INFO)) logger.log(quietLogging? Level.FINE : Level.INFO, "uninstallContribution: " + contributionURI); } protected void mergeContributionMetaData(String metaDataURL, Contribution contribution) throws ValidationException { ContributionMetadata metaData; Monitor monitor = deployer.createMonitor(); try { metaData = deployer.loadXMLDocument(IOHelper.getLocationAsURL(metaDataURL), monitor); } catch (Exception e) { throw new ValidationException(e); } monitor.analyzeProblems(); contribution.mergeMetaData(metaData); } /** * Peek into the contribution to find its attributes. * ASM12032 and ASM12033 say no error checking should be done during install and that should happen later, but * we need to know about deployables and exports so peek into the contribution to try to get those, * and just ignore any errors they might happen while doing that. */ protected void peekIntoContribution(ContributionDescription cd) { Contribution contribution = null; try { contribution = loadContribution(cd); } catch (Exception e) { // ignore it } if (contribution != null) { cd.configureMetaData(contribution); } } public List getInstalledContributionURIs() { return new ArrayList(domainRegistry.getInstalledContributionURIs()); } public Contribution getContribution(String contributionURI) throws ContributionReadException, ValidationException { return loadContribution(getInstalledContribution(contributionURI)); } public List getDeployableCompositeURIs(String contributionURI) { ContributionDescription cd = domainRegistry.getInstalledContribution(contributionURI); List deployables = new ArrayList(cd.getDeployables()); deployables.addAll(cd.getAdditionalDeployables().keySet()); return deployables; } public String addDeploymentComposite(String contributionURI, Reader compositeXML) throws ContributionReadException, XMLStreamException, ValidationException { ContributionDescription cd = getInstalledContribution(contributionURI); // load it to check its valid composite XML Composite composite = deployer.loadXMLDocument(compositeXML); return addDeploymentComposite(cd, composite); } public String addDeploymentComposite(String contributionURI, Composite composite) { ContributionDescription cd = getInstalledContribution(contributionURI); return addDeploymentComposite(cd, composite); } protected String addDeploymentComposite(ContributionDescription cd, Composite composite) { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "addDeploymentComposite" + Arrays.asList(new Object[]{cd, composite})); } if (composite.getURI() == null || composite.getURI().length() < 1) { composite.setURI(composite.getName().getLocalPart() + ".composite"); } composite.setContributionURI(cd.getURI()); cd.getAdditionalDeployables().put(composite.getURI(), Utils.modelToXML(composite, false, extensionPointRegistry)); domainRegistry.updateInstalledContribution(cd); if (logger.isLoggable(quietLogging? Level.FINE : Level.INFO)) logger.log(quietLogging? Level.FINE : Level.INFO, "addDeploymentComposite: " + composite.getURI()); return composite.getURI(); } public void validateContribution(String contributionURI) throws ContributionReadException, ValidationException { ContributionDescription cd = getInstalledContribution(contributionURI); Contribution contribution = loadContribution(cd); Monitor monitor = deployer.createMonitor(); try { ArrayList cs = new ArrayList(); cs.add(contribution); cs.addAll(calculateDependentContributions(cd)); deployer.resolve(cs, null, monitor); } catch (Exception e) { loadedContributions.remove(cd.getURI()); throw new RuntimeException(e); } try { monitor.analyzeProblems(); } catch (ValidationException e) { loadedContributions.remove(cd.getURI()); throw e; } if (contribution.getClassLoader() == null && contribution.getModelResolver() instanceof ExtensibleModelResolver) { ModelResolver o = ((ExtensibleModelResolver)contribution.getModelResolver()).getModelResolverInstance(ClassReference.class); if (o instanceof ClassLoader) { contribution.setClassLoader((ClassLoader)o); } } } public Map> getStartedCompositeURIs() { return Collections.unmodifiableMap(domainRegistry.getRunningCompositeURIs()); } public void startComposite(String contributionURI, String compositeURI) throws ActivationException, ValidationException, ContributionReadException { String key = contributionURI+"/"+compositeURI; if (startedComposites.containsKey(key)) { throw new IllegalStateException("composite already started: " + compositeURI); } DeployedComposite dc = stoppedComposites.remove(key); try { if (dc != null) { dc.start(); startedComposites.put(key, dc); } else { ContributionDescription cd = getInstalledContribution(contributionURI); Contribution contribution = loadContribution(cd); Composite composite = contribution.getArtifactModel(compositeURI); List dependentContributions = calculateDependentContributions(cd); dc = new DeployedComposite(composite, contribution, dependentContributions, deployer, compositeActivator, domainRegistry, extensionPointRegistry, endpointsIncludeDomainName); dc.start(); startedComposites.put(key, dc); } }catch(ActivationException e){ if(dc != null){ try { // try to stop the composite. This should have already happened // in the activator if the composite failed to start but we're // being sure dc.stop(); } catch (Exception ex) { // do nothing as we are going to throw the // original exception } stoppedComposites.put(key, dc); } throw e; } if (logger.isLoggable(quietLogging? Level.FINE : Level.INFO)) logger.log(quietLogging? Level.FINE : Level.INFO, "startComposite: " + key); } @Override public void startComposite(String contributionURI, String compositeURI, String nodeName) throws ActivationException { String response = domainRegistry.remoteCommand(nodeName, new RemoteCommand(domainRegistry.getDomainName(), "start", contributionURI, compositeURI)); if (!"Started.".equals(response)) { throw new ActivationException(response); } if (logger.isLoggable(quietLogging? Level.FINE : Level.INFO)) logger.log(quietLogging? Level.FINE : Level.INFO, "startComposite: " + contributionURI + " " + compositeURI + " " + nodeName); } public void stopComposite(String contributionURI, String compositeURI) throws ActivationException { String key = contributionURI+"/"+compositeURI; DeployedComposite dc = startedComposites.remove(key); if (dc != null) { dc.stop(); stoppedComposites.put(key, dc); } else { String member = domainRegistry.getRunningNodeName(contributionURI, compositeURI); if (member == null) { throw new IllegalStateException("composite not started: " + compositeURI); } RemoteCommand command = new RemoteCommand(domainRegistry.getDomainName(), "stop", contributionURI, compositeURI); String response = domainRegistry.remoteCommand(member, command); if (!"Stopped.".equals(response)) { throw new ActivationException(response); } } if (logger.isLoggable(quietLogging? Level.FINE : Level.INFO)) logger.log(quietLogging? Level.FINE : Level.INFO, "stopComposite: " + key); } public void stopCompositeAndUninstallUnused(String contributionURI, String compositeURI) throws ActivationException { String key = contributionURI+"/"+compositeURI; DeployedComposite dc = startedComposites.remove(key); if (dc != null) { dc.stop(); } else { // check in the stopped list in case it stopped on failure during start dc = stoppedComposites.get(key); } if (dc != null) { loop: for (String curi : dc.getContributionURIs()) { for (DeployedComposite started : startedComposites.values()) { if (started.getContributionURIs().contains(curi)) { continue loop; } } uninstallContribution(curi); } } if (logger.isLoggable(quietLogging? Level.FINE : Level.INFO)) logger.log(quietLogging? Level.FINE : Level.INFO, "stopCompositeAndUninstallUnused: " + key); } public String getDomainURI() { return domainRegistry.getDomainURI(); } public String getDomainName() { return domainRegistry.getDomainName(); } public Composite getDomainComposite() { return domainRegistry.getDomainComposite(); } public T getService(Class interfaze, String serviceURI) throws NoSuchServiceException { return ServiceHelper.getService(interfaze, serviceURI, domainRegistry, extensionPointRegistry, deployer); } public ContributionDescription getInstalledContribution(String contributionURI) { ContributionDescription cd = domainRegistry.getInstalledContribution(contributionURI); if (cd == null) { throw new IllegalArgumentException("Contribution not installed: " + contributionURI); } return cd; } protected Contribution loadContribution(ContributionDescription cd) throws ContributionReadException, ValidationException { Contribution contribution = loadedContributions.get(cd.getURI()); if (contribution == null) { Monitor monitor = deployer.createMonitor(); contribution = deployer.loadContribution(IOHelper.createURI(cd.getURI()), IOHelper.getLocationAsURL(cd.getURL()), monitor); // TODO: should the monitor be checked? If it is then the peek in to get the metadata doesn't work if there's a problem // monitor.analyzeProblems(); if (cd.getAdditionalDeployables().size() > 0) { for (String uri : cd.getAdditionalDeployables().keySet()) { String compositeXML = cd.getAdditionalDeployables().get(uri); Composite composite; try { composite = deployer.loadXMLDocument(new StringReader(compositeXML)); } catch (XMLStreamException e) { throw new ContributionReadException(e); } composite.setURI(composite.getName().getLocalPart() + ".composite"); contribution.addComposite(composite); } } loadedContributions.put(cd.getURI(), contribution); } return contribution; } protected List calculateDependentContributions(ContributionDescription cd) throws ContributionReadException, ValidationException { Map dependentContributions = new HashMap(); if (cd.getDependentContributionURIs() != null && cd.getDependentContributionURIs().size() > 0) { // if the install specified dependent uris use just those contributions for (String uri : cd.getDependentContributionURIs()) { if (!!!dependentContributions.containsKey(uri)) { ContributionDescription dependee = domainRegistry.getInstalledContribution(uri); if (dependee != null) { dependentContributions.put(uri, loadContribution(dependee)); } } } } else { for (Import imprt : loadContribution(cd).getImports()) { for (ContributionDescription exportingIC : findExportingContributions(imprt)) { if (!!!dependentContributions.containsKey(exportingIC.getURI()) && !!!cd.getURI().equals(exportingIC.getURI())) { dependentContributions.put(exportingIC.getURI(), loadContribution(exportingIC)); } } } } // TODO: there is also the location attribute on the import which should be taken into account return new ArrayList(dependentContributions.values()); } private List findExportingContributions(Import imprt) { List ics = new ArrayList(); // TODO: Handle Imports in a more extensible way for (String curi : domainRegistry.getInstalledContributionURIs()) { ContributionDescription cd = domainRegistry.getInstalledContribution(curi); if (imprt instanceof JavaImport) { for (String s : cd.getJavaExports()) { if (s.startsWith(((JavaImport)imprt).getPackage())) { ics.add(cd); } } } else if (imprt instanceof NamespaceImport) { if (cd.getNamespaceExports().contains(((NamespaceImport)imprt).getNamespace())) { ics.add(cd); } } } return ics; } @Override public Object getQNameDefinition(String contributionURI, QName definition, QName symbolSpace) { // TODO Auto-generated method stub return null; } @Override public List startDeployables(String contributionURI) throws ActivationException, ValidationException, ContributionReadException { List dcURIs = getDeployableCompositeURIs(contributionURI); for (String dcURI : dcURIs) { startComposite(contributionURI, dcURI); } return dcURIs; } // TODO: these are used by the shell, should they be on the Node interface? public DomainRegistry getEndpointRegistry() { return domainRegistry; } public ExtensionPointRegistry getExtensionPointRegistry() { return extensionPointRegistry; } public void stop() { for (DeployedComposite dc : startedComposites.values()) { try { dc.stop(); } catch (ActivationException e) { } } startedComposites.clear(); stoppedComposites.clear(); extensionPointRegistry.getExtensionPoint(UtilityExtensionPoint.class).getUtility(ActiveNodes.class).getActiveNodes().remove(this); domainRegistry.removeContributionListener(contributionListener); if (tuscanyRuntime != null) { tuscanyRuntime.stop(); } } @Override public List getNodeNames() { return domainRegistry.getNodeNames(); } @Override public String getLocalNodeName() { return domainRegistry.getLocalNodeName(); } @Override public String getRunningNodeName(String contributionURI, String compositeURI) { return domainRegistry.getRunningNodeName(contributionURI, compositeURI); } public List updateUsingComposites(String contributionURI, String compositeURI) throws ActivationException, ContributionReadException, ValidationException { List updated = new ArrayList(); for (DeployedComposite dc : new ArrayList(startedComposites.values())) { if (dc.uses(contributionURI, compositeURI)) { String dcContributionURI = dc.getContributionURIs().get(0); String dcCompositeURI = dc.getURI(); stopComposite(dcContributionURI, dcCompositeURI); String key = dcContributionURI + "/" + dcCompositeURI; stoppedComposites.remove(key); updated.add(key); startComposite(dcContributionURI, dcCompositeURI); } } if (logger.isLoggable(quietLogging? Level.FINE : Level.INFO)) logger.log(quietLogging? Level.FINE : Level.INFO, "updateUsingComposites", updated); return updated; } public void uninstallContribution(String contributionURI, boolean b) throws ActivationException { uninstallContribution(contributionURI); if (!b) { return; } // stop all started composites using the contribution for (DeployedComposite dc : new ArrayList(startedComposites.values())) { if (dc.getContributionURIs().contains(contributionURI)) { String dcContributionURI = dc.getContributionURIs().get(0); String dcCompositeURI = dc.getURI(); stopComposite(dcContributionURI, dcCompositeURI); String key = dcContributionURI + "/" + dcCompositeURI; stoppedComposites.remove(key); } } // remove all stopped composites using the contribution for (DeployedComposite dc : new ArrayList(stoppedComposites.values())) { if (dc.getContributionURIs().contains(contributionURI)) { stoppedComposites.remove(contributionURI + "/" + dc.getURI()); } } } public boolean getEndpointsIncludeDomainName() { return endpointsIncludeDomainName; } public void setEndpointsIncludeDomainName(boolean b) { endpointsIncludeDomainName = b; } }