Return-Path: X-Original-To: apmail-ace-commits-archive@www.apache.org Delivered-To: apmail-ace-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 60C61F82F for ; Thu, 4 Apr 2013 09:52:46 +0000 (UTC) Received: (qmail 59893 invoked by uid 500); 4 Apr 2013 09:44:52 -0000 Delivered-To: apmail-ace-commits-archive@ace.apache.org Received: (qmail 59723 invoked by uid 500); 4 Apr 2013 09:44:47 -0000 Mailing-List: contact commits-help@ace.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@ace.apache.org Delivered-To: mailing list commits@ace.apache.org Received: (qmail 59011 invoked by uid 99); 4 Apr 2013 09:44:17 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 04 Apr 2013 09:44:17 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 04 Apr 2013 09:44:06 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 5E0842388A36; Thu, 4 Apr 2013 09:43:43 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1464402 [2/11] - in /ace/trunk: org.apache.ace.deployment.api/ org.apache.ace.deployment.deploymentadmin/ org.apache.ace.deployment.itest/ org.apache.ace.deployment.itest/src/org/apache/ace/it/deployment/ org.apache.ace.deployment.provider... Date: Thu, 04 Apr 2013 09:43:37 -0000 To: commits@ace.apache.org From: marrs@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20130404094343.5E0842388A36@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Added: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/BaseRepositoryHandler.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/BaseRepositoryHandler.java?rev=1464402&view=auto ============================================================================== --- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/BaseRepositoryHandler.java (added) +++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/BaseRepositoryHandler.java Thu Apr 4 09:43:34 2013 @@ -0,0 +1,293 @@ +/* + * 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.ace.deployment.provider.repositorybased; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +import org.osgi.framework.Version; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Provides a SAX-based push parser for obtaining all versions of the deployment packages of a certain target. + */ +class BaseRepositoryHandler extends DefaultHandler { + + private final String m_targetID; + + /** Denotes the current tag in the XML structure. */ + private XmlTag m_currentTag; + /** Denotes the current version of the found target. */ + private Version m_currentVersion; + /** Denotes whether or not the requested target is found. */ + private boolean m_targetFound; + /** Denotes the current deployment artifact. */ + private XmlDeploymentArtifact m_currentArtifact; + /** Denotes the directive key of the current deployment artifact. */ + private String m_currentDirectiveKey; + + /** + * Creates a new {@link BaseRepositoryHandler} instance. + * + * @param targetID the target ID to search for, cannot be null. + */ + public BaseRepositoryHandler(String targetID) { + m_targetID = targetID; + m_currentTag = XmlTag.unknown; + } + + /** + * Parses the given text as {@link Version}. + * + * @param text the text to parse as version, can not be null. + * @return a {@link Version} if the given text represent a correct version, never null. + */ + static final Version parseVersion(String text) { + try { + if (text != null) { + return Version.parseVersion(text); + } + } + catch (Exception e) { + // Ignore; simply return an empty version to denote this invalid version... + } + return Version.emptyVersion; + } + + @Override + public void startDocument() throws SAXException { + m_currentTag = XmlTag.unknown; + m_currentVersion = null; + m_targetFound = false; + m_currentArtifact = null; + m_currentDirectiveKey = null; + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + if (XmlTag.targetID.equals(m_currentTag)) { + // verify whether we're in the DP for the requested target... + m_targetFound = m_targetID.equals(new String(ch, start, length)); + } + else if (XmlTag.version.equals(m_currentTag)) { + // Don't assume we've got the desired version (yet)... + m_currentVersion = null; + + if (m_targetFound) { + m_currentVersion = parseAsVersion(new String(ch, start, length)); + } + } + else if (XmlTag.url.equals(m_currentTag)) { + try { + URL artifactUrl = new URL(new String(ch, start, length)); + m_currentArtifact = new XmlDeploymentArtifact(artifactUrl); + } + catch (MalformedURLException e) { + throw new SAXException("Unexpected URL!", e); + } + } + else if (XmlTag.directives.equals(m_currentTag)) { + if (m_currentArtifact == null) { + throw new SAXException("Unexpected directive tag!"); + } + String value = new String(ch, start, length).trim(); + if (m_currentDirectiveKey != null && !value.equals("")) { + m_currentArtifact.m_directives.put(m_currentDirectiveKey, value); + } + } + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + XmlTag tag = XmlTag.asXmlTag(qName); + // If the given element is an expected child of the current tag, we + // traverse deeper into the XML-hierarchy; otherwise, consider it an + // "unknown"/uninteresting child and keep the current tag as-is... + if (m_currentTag.isExpectedChild(tag)) { + m_currentTag = tag; + } + + m_currentDirectiveKey = null; + // If we're parsing the directives of an artifact, take the name for + // later use (the literal text in this tag will be used as value)... + if (XmlTag.directives.equals(m_currentTag)) { + m_currentDirectiveKey = qName; + } + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + XmlTag tag = XmlTag.asXmlTag(qName); + // When we're ending the current tag, traverse up to its parent... + if (!XmlTag.unknown.equals(tag) && (m_currentTag == tag)) { + m_currentTag = tag.getParent(); + } + + // Invoke the callbacks for events we're interested in... + if (XmlTag.version.equals(tag)) { + if (m_currentVersion != null && !Version.emptyVersion.equals(m_currentVersion)) { + // Let the version be handled (if needed)... + handleVersion(m_currentVersion); + } + + // Let the currentVersion field as-is! We want to reuse it for the artifacts... + } + else if (XmlTag.deploymentArtifact.equals(tag)) { + if (m_currentArtifact != null) { + // push out the current deployment artifact... + handleArtifact(m_currentVersion, m_currentArtifact); + } + + m_currentArtifact = null; + } + else if (XmlTag.directives.equals(tag)) { + m_currentDirectiveKey = null; + } + } + + /** + * Allows subclasses to handle the given version of a target's deployment package. + *

By default, this method does nothing.

+ * + * @param version the version found, never null. + */ + protected void handleVersion(Version version) { + // NO-op + } + + /** + * Allows subclasses to handle the given deployment artifact for the given version of the deployment package. + *

By default, this method does nothing.

+ * + * @param version the version of the deployment package; + * @param artifact the deployment artifact itself. + */ + protected void handleArtifact(Version version, XmlDeploymentArtifact artifact) { + // NO-op + } + + /** + * Parses the given text as {@link Version}. + * + * @param text the text to parse as version, can not be null. + * @return a {@link Version} if the given text represent a correct version, + * can be null in case of an incorrect/empty version.. + */ + protected final Version parseAsVersion(String text) { + Version result = parseVersion(text); + if (Version.emptyVersion.equals(result)) { + return null; + } + return result; + } + + /** + * Helper class to store a pair of URL and directive, in which the directive may be empty. + */ + public static class XmlDeploymentArtifact { + final private URL m_url; + final private Map m_directives; + + private XmlDeploymentArtifact(URL url) { + m_url = url; + m_directives = new HashMap(); + } + + public URL getUrl() { + return m_url; + } + + public Map getDirective() { + return m_directives; + } + } + + /** + * Defines the structure of our XML (only the parts we're interested in). + */ + public static enum XmlTag { + targetID, + version, + attributes(targetID, version), + url, + directives, + deploymentArtifact(url, directives), + artifacts(deploymentArtifact), + tags, + deploymentversion(attributes, tags, artifacts), + deploymentversions(deploymentversion), + repository(deploymentversions), + unknown(repository); + + private final XmlTag[] m_children; + private XmlTag m_parent; + + private XmlTag(XmlTag... possibleChildren) { + m_children = possibleChildren; + // Update the children's parent... + for (int i = 0; i < m_children.length; i++) { + m_children[i].m_parent = this; + } + } + + /** + * Returns the parent tag of this tag. + * + * @return a parent tag, can be null. + */ + public XmlTag getParent() { + return m_parent; + } + + /** + * Returns whether the given XML tag is an expected child of this tag. + * + * @param xmlTag the XML tag to test, cannot be null. + * @return true if the given tag is an expected child of this tag, false otherwise. + */ + public boolean isExpectedChild(XmlTag xmlTag) { + for (int i = 0; i < m_children.length; i++) { + if (xmlTag.equals(m_children[i])) { + return true; + } + } + return false; + } + + /** + * Provides a "safe" way of representing the given tag name as instance of this enum. If the given name does not represent a defined tag, "unknown" will be returned. + * + * @param name the XML tag-name to represent as enum value, cannot be null. + * @return a {@link XmlTag} representation of the given tag-name, never null. + */ + public static XmlTag asXmlTag(String name) { + XmlTag[] values = { artifacts, attributes, deploymentArtifact, deploymentversion, deploymentversions, directives, repository, tags, targetID, url, version }; + for (int i = 0; i < values.length; i++) { + if (values[i].name().equals(name)) { + return values[i]; + } + } + return XmlTag.unknown; + } + } +} \ No newline at end of file Added: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/DeploymentArtifactCollector.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/DeploymentArtifactCollector.java?rev=1464402&view=auto ============================================================================== --- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/DeploymentArtifactCollector.java (added) +++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/DeploymentArtifactCollector.java Thu Apr 4 09:43:34 2013 @@ -0,0 +1,93 @@ +/* + * 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.ace.deployment.provider.repositorybased; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.osgi.framework.Version; + +/** + * Provides {@link BaseRepositoryHandler} implementation that gathers all deployment + * artifacts of deployment packages for a specific target with a specific version. + */ +public class DeploymentArtifactCollector extends BaseRepositoryHandler { + + private final List m_expectedVersions; + private final Map> m_artifacts; + + /** + * @param targetID the identification of the target to gather all artifacts for; + * @param versions the version of the deployment package to gather all artifacts for. + */ + public DeploymentArtifactCollector(String targetID, String... versions) { + super(targetID); + + m_artifacts = new HashMap>(); + + m_expectedVersions = new ArrayList(versions.length); + for (int i = 0; i < versions.length; i++) { + Version v = parseVersion(versions[i]); + if (Version.emptyVersion.equals(v)) { + throw new IllegalArgumentException("Expected real version for " + versions[i]); + } + m_expectedVersions.add(v); + } + } + + /** + * Returns all deployment artifacts of the requested target's deployment package. + * + * @return an array with lists of all found deployment artifacts, never null. + * The array contains the deployment artifacts per requested version, in the same + * order as given in the class constructor. + */ + public List[] getArtifacts() { + List[] result = new List[m_expectedVersions.size()]; + int i = 0; + for (Version version : m_expectedVersions) { + List list = m_artifacts.get(version); + if (list == null) { + throw new IllegalArgumentException("No artifacts found for version " + version); + } + result[i++] = list; + } + return result; + } + + @Override + protected void handleVersion(Version version) { + if (m_expectedVersions.contains(version)) { + List artifacts = m_artifacts.get(version); + if (artifacts == null) { + artifacts = new ArrayList(); + m_artifacts.put(version, artifacts); + } + } + } + + @Override + protected void handleArtifact(Version version, XmlDeploymentArtifact artifact) { + if (m_expectedVersions.contains(version)) { + m_artifacts.get(version).add(artifact); + } + } +} Added: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/DeploymentPackageVersionCollector.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/DeploymentPackageVersionCollector.java?rev=1464402&view=auto ============================================================================== --- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/DeploymentPackageVersionCollector.java (added) +++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/DeploymentPackageVersionCollector.java Thu Apr 4 09:43:34 2013 @@ -0,0 +1,55 @@ +/* + * 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.ace.deployment.provider.repositorybased; + +import java.util.ArrayList; +import java.util.List; + +import org.osgi.framework.Version; + +/** + * Provides {@link BaseRepositoryHandler} implementation that gathers all versions of deployment packages for a specific target. + */ +public class DeploymentPackageVersionCollector extends BaseRepositoryHandler { + + private final List m_versions; + + /** + * @param targetID the target to gather all deployment package versions for. + */ + public DeploymentPackageVersionCollector(String targetID) { + super(targetID); + + m_versions = new ArrayList(); + } + + /** + * Returns a list of all found deployment package versions. + * + * @return a list of {@link Version}s, never null. + */ + public List getVersions() { + return m_versions; + } + + @Override + protected void handleVersion(Version version) { + m_versions.add(version); + } +} Added: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/LRUMap.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/LRUMap.java?rev=1464402&view=auto ============================================================================== --- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/LRUMap.java (added) +++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/LRUMap.java Thu Apr 4 09:43:34 2013 @@ -0,0 +1,36 @@ +/* + * 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.ace.deployment.provider.repositorybased; + +import java.util.LinkedHashMap; + +public class LRUMap extends LinkedHashMap { + private static final int INITIAL = 64; + private static final int MAX = 1024; + private static final float LOADFACTOR = 0.75f; + + public LRUMap() { + super(INITIAL, LOADFACTOR, true); + } + + @Override + protected boolean removeEldestEntry(java.util.Map.Entry eldest) { + return size() > MAX; + } +} Added: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProvider.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProvider.java?rev=1464402&view=auto ============================================================================== --- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProvider.java (added) +++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProvider.java Thu Apr 4 09:43:34 2013 @@ -0,0 +1,479 @@ +/* + * 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.ace.deployment.provider.repositorybased; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Dictionary; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.xml.parsers.SAXParserFactory; + +import org.apache.ace.deployment.provider.ArtifactData; +import org.apache.ace.deployment.provider.DeploymentProvider; +import org.apache.ace.deployment.provider.impl.ArtifactDataImpl; +import org.apache.ace.deployment.provider.repositorybased.BaseRepositoryHandler.XmlDeploymentArtifact; +import org.apache.ace.connectionfactory.ConnectionFactory; +import org.apache.ace.range.RangeIterator; +import org.apache.ace.repository.Repository; +import org.apache.ace.repository.ext.BackupRepository; +import org.apache.ace.repository.ext.CachedRepository; +import org.apache.ace.repository.ext.impl.CachedRepositoryImpl; +import org.apache.ace.repository.ext.impl.FilebasedBackupRepository; +import org.apache.ace.repository.ext.impl.RemoteRepository; +import org.apache.felix.dm.DependencyManager; +import org.osgi.framework.Constants; +import org.osgi.framework.Version; +import org.osgi.service.cm.ConfigurationException; +import org.osgi.service.cm.ManagedService; +import org.osgi.service.log.LogService; + +/** + * The RepositoryBasedProvider provides version information and bundle data by the DeploymentProvider interface. It uses a + * Repository to get its information from, which it parses using a SAX parser. + */ +public class RepositoryBasedProvider implements DeploymentProvider, ManagedService { + private static final String URL = "url"; + private static final String NAME = "name"; + private static final String CUSTOMER = "customer"; + + /** + * Key, intended to be used for artifacts which are bundles and will publish + * a resource processor (see OSGi compendium section 114.10). + */ + public static final String DIRECTIVE_ISCUSTOMIZER = "DeploymentPackage-Customizer"; + + /** + * Key, intended to be used for resources which require a resource processor + * (see OSGi compendium section 114.10). + */ + public static final String DIRECTIVE_KEY_PROCESSORID = "Resource-Processor"; + + /** + * Key, intended to be used for artifacts which have a resourceID that's different + * from their generated name (based on URL). + */ + public static final String DIRECTIVE_KEY_RESOURCE_ID = "Resource-ID"; + + /** + * Key, intended to be used for matching processed (see ArtifactPreprocessor) to their + * 'original' one. + */ + public static final String DIRECTIVE_KEY_BASEURL = "Base-Url"; + + public static final String REPOSITORY_PATH = "ACE-RepositoryPath"; + + public static final String KEY_SYMBOLICNAME = Constants.BUNDLE_SYMBOLICNAME; + public static final String KEY_NAME = Constants.BUNDLE_NAME; + public static final String KEY_VERSION = Constants.BUNDLE_VERSION; + public static final String KEY_VENDOR = Constants.BUNDLE_VENDOR; + public static final String KEY_RESOURCE_PROCESSOR_PID = "Deployment-ProvidesResourceProcessor"; + + public static final String MIMETYPE = "application/vnd.osgi.bundle"; + + + private volatile LogService m_log; + + /** This variable is volatile since it can be changed by the Updated() method. */ + private volatile CachedRepository m_cachedRepository; + + /** + * This variable is volatile since it can be changed by the updated() method. Furthermore, it will be used to inject a + * custom repository in the integration test. + */ + private volatile Repository m_directRepository; + private volatile DependencyManager m_manager; + + private final SAXParserFactory m_saxParserFactory; + private final Map> m_cachedVersionLists; + + public RepositoryBasedProvider() { + m_saxParserFactory = SAXParserFactory.newInstance(); + m_cachedVersionLists = new LRUMap>(); + } + + public List getBundleData(String targetId, String version) throws IllegalArgumentException, IOException { + return getBundleData(targetId, null, version); + } + + public List getBundleData(String targetId, String versionFrom, String versionTo) throws IllegalArgumentException, IOException { + try { + if (versionFrom != null) { + Version.parseVersion(versionFrom); + } + Version.parseVersion(versionTo); + } + catch (NumberFormatException nfe) { + throw new IllegalArgumentException(nfe); + } + + InputStream input = null; + List dataVersionTo = null; + List dataVersionFrom = null; + + List[] pairs = null; + try { + // ACE-240: do NOT allow local/remote repositories to be empty. If we're + // asking for real artifacts, it means we must have a repository... + input = getRepositoryStream(true /* fail */); + if (versionFrom == null) { + pairs = getDeploymentArtifactPairs(input, targetId, new String[] { versionTo }); + } + else { + pairs = getDeploymentArtifactPairs(input, targetId, new String[] { versionFrom, versionTo }); + } + } + catch (IOException ioe) { + m_log.log(LogService.LOG_WARNING, "Problem parsing source version.", ioe); + throw ioe; + } + finally { + if (input != null) { + try { + input.close(); + } + catch (IOException e) { + m_log.log(LogService.LOG_DEBUG, "Error closing stream", e); + } + } + } + + if ((pairs != null) && (pairs.length > 1)) { + dataVersionFrom = getAllArtifactData(pairs[0]); + dataVersionTo = getAllArtifactData(pairs[1]); + Iterator it = dataVersionTo.iterator(); + while (it.hasNext()) { + ArtifactDataImpl bundleDataVersionTo = (ArtifactDataImpl) it.next(); + // see if there was previously a version of this bundle, and update the 'changed' property accordingly. + if (bundleDataVersionTo.isBundle()) { + ArtifactData bundleDataVersionFrom = getArtifactData(bundleDataVersionTo.getSymbolicName(), dataVersionFrom); + bundleDataVersionTo.setChanged(!bundleDataVersionTo.equals(bundleDataVersionFrom)); + } + else { + ArtifactData bundleDataVersionFrom = getArtifactData(bundleDataVersionTo.getUrl(), dataVersionFrom); + bundleDataVersionTo.setChanged(bundleDataVersionFrom == null); + } + } + } + else { + dataVersionTo = getAllArtifactData(pairs[0]); + } + + return dataVersionTo != null ? dataVersionTo : new ArrayList(); + } + + @SuppressWarnings("unchecked") + public List getVersions(String targetId) throws IllegalArgumentException, IOException { + // check if cache is up to date + if (isCacheUpToDate()) { + List result = m_cachedVersionLists.get(targetId); + if (result != null) { + return result; + } + } + else { + m_cachedVersionLists.clear(); + } + + List stringVersionList = new ArrayList(); + InputStream input = null; + + try { + // ACE-240: allow local/remote repositories to be empty; as the target + // might be new & unregistered, it can have no repository yet... + input = getRepositoryStream(false /* fail */); + List versionList = getAvailableVersions(input, targetId); + if (versionList.isEmpty()) { + m_log.log(LogService.LOG_DEBUG, "No versions found for target: " + targetId); + } + else { + // now sort the list of versions and convert all values to strings. + Collections.sort(versionList); + Iterator it = versionList.iterator(); + while (it.hasNext()) { + String version = (it.next()).toString(); + stringVersionList.add(version); + } + } + } + catch (IllegalArgumentException iae) { + // just move on. + } + catch (IOException ioe) { + m_log.log(LogService.LOG_DEBUG, "Problem parsing DeploymentRepository", ioe); + throw ioe; + } + finally { + if (input != null) { + try { + input.close(); + } + catch (IOException e) { + m_log.log(LogService.LOG_DEBUG, "Error closing stream", e); + } + } + } + + m_log.log(LogService.LOG_DEBUG, "Cache added for " + targetId); + + m_cachedVersionLists.put(targetId, stringVersionList); + return stringVersionList; + } + + /** + * Helper method to get the bundledata given an inputstream to a repository xml file + * + * @param input An input stream to the XML data to be parsed. + * @return A list of ArtifactData object representing this version. + */ + private List getAllArtifactData(List deploymentArtifacts) throws IllegalArgumentException { + List result = new ArrayList(); + + // get the bundledata for each URL + for (XmlDeploymentArtifact pair : deploymentArtifacts) { + Map directives = pair.getDirective(); + + if (directives.get(DIRECTIVE_KEY_PROCESSORID) == null) { + // this is a bundle. + String symbolicName = directives.get(KEY_SYMBOLICNAME); + String bundleVersion = directives.get(KEY_VERSION); + if (symbolicName != null) { + // it is the right symbolic name + if (symbolicName.trim().equals("")) { + m_log.log(LogService.LOG_WARNING, "Invalid bundle:" + pair.toString() + " the symbolic name is empty."); + } + else { + result.add(new ArtifactDataImpl(pair.getUrl(), directives, symbolicName, bundleVersion, true)); + } + } + } + else { + // it is an artifact. + String filename = directives.get(DIRECTIVE_KEY_RESOURCE_ID); + result.add(new ArtifactDataImpl(filename, pair.getUrl(), directives, true)); + } + + } + return result; + } + + /** + * Helper method check for the existence of artifact data in the collection for a bundle with the given url. + * + * @param url The url to be found. + * @return The ArtifactData object that has this url, or null if none can be + * found. + */ + private ArtifactData getArtifactData(URL url, Collection data) { + ArtifactData bundle = null; + URI uri = null; + try { + uri = url.toURI(); + } + catch (URISyntaxException e) { + m_log.log(LogService.LOG_ERROR, "Could not convert URL " + url + " to a URI"); + return null; + } + Iterator it = data.iterator(); + while (it.hasNext()) { + bundle = it.next(); + try { + if (uri.equals(bundle.getUrl().toURI())) { + return bundle; + } + } + catch (URISyntaxException e) { + m_log.log(LogService.LOG_ERROR, "Could not convert bundle URL for " + bundle.getFilename() + " to a URI"); + } + } + return null; + } + + /** + * Helper method check for the existence of artifact data in the collection for a bundle with the given symbolic name. + * + * @param symbolicName The symbolic name to be found. + * @return The ArtifactData object that has this symbolicName, or null if none + * can be found. + */ + private ArtifactData getArtifactData(String symbolicName, Collection data) { + ArtifactData bundle = null; + Iterator it = data.iterator(); + while (it.hasNext()) { + bundle = it.next(); + String bsn = bundle.getSymbolicName(); + if ((bsn != null) && bsn.equals(symbolicName)) { + return bundle; + } + } + return null; + } + + /** + * Returns the available deployment versions for a target + * + * @param input A dom document representation of the repository + * @param targetId The target identifier + * @return A list of available versions + */ + private List getAvailableVersions(InputStream input, String targetId) throws IllegalArgumentException { + DeploymentPackageVersionCollector collector = new DeploymentPackageVersionCollector(targetId); + + try { + m_saxParserFactory.newSAXParser().parse(input, collector); + + return collector.getVersions(); + } + catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Helper method to retrieve urls and directives for a target-version combination. + * + * @param input An input stream from which an XML representation of a deployment repository can be read. + * @param targetId The target identifier to be used + * @param versions An array of versions. + * @return An array of lists of URLDirectivePairs. For each version in versions, a separate list will be + * created; the index of a version in the versions array is equal to the index of its result in the + * result array. + * @throws IllegalArgumentException if the targetId or versions cannot be found in the input stream, or if + * input does not contain an XML stream. + */ + private List[] getDeploymentArtifactPairs(InputStream input, String targetId, String[] versions) throws IllegalArgumentException { + final DeploymentArtifactCollector collector = new DeploymentArtifactCollector(targetId, versions); + + try { + m_saxParserFactory.newSAXParser().parse(input, collector); + + return collector.getArtifacts(); + } + catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Helper to get an input stream to the currently used deployment repository. + * + * @return An input stream to the repository document. Will return an empty stream if none can be found. + * @throws java.io.IOException if there is a problem communicating with the local or remote repository. + */ + private InputStream getRepositoryStream(boolean fail) throws IOException { + // cache the repositories, since we do not want them to change while we're in this method. + CachedRepository cachedRepository = m_cachedRepository; + Repository repository = m_directRepository; + InputStream result; + + if (cachedRepository != null) { + // we can use the cached repository + if (cachedRepository.isCurrent()) { + result = cachedRepository.getLocal(fail); + } + else { + result = cachedRepository.checkout(fail); + } + } + else { + RangeIterator ri = repository.getRange().iterator(); + long resultVersion = 0; + while (ri.hasNext()) { + resultVersion = ri.next(); + } + if (resultVersion != 0) { + result = repository.checkout(resultVersion); + } + else { + throw new IllegalArgumentException("There is no deployment information available."); + } + } + + return result; + } + + private boolean isCacheUpToDate() { + CachedRepository cachedRepository = m_cachedRepository; + try { + return (cachedRepository != null && cachedRepository.isCurrent()); + } + catch (IOException ioe) { + m_log.log(LogService.LOG_WARNING, "Failed to check if cache is current. Assuming it's not.", ioe); + return false; + } + } + + public void updated(Dictionary settings) throws ConfigurationException { + if (settings != null) { + String url = getNotNull(settings, URL, "DeploymentRepository URL not configured."); + String name = getNotNull(settings, NAME, "RepositoryName not configured."); + String customer = getNotNull(settings, CUSTOMER, "RepositoryCustomer not configured."); + + // create the remote repository and set it. + try { + BackupRepository backup = new FilebasedBackupRepository(File.createTempFile("currentrepository", null), File.createTempFile("backuprepository", null)); + + // We always create the remote repository. If we can create a backup repository, we will wrap a CachedRepository + // around it. + m_directRepository = new RemoteRepository(new URL(url), customer, name); + + m_manager.add(m_manager.createComponent() + .setImplementation(m_directRepository) + .add(m_manager.createServiceDependency() + .setService(ConnectionFactory.class) + .setRequired(true))); + + m_cachedRepository = null; + if (backup != null) { + m_cachedRepository = new CachedRepositoryImpl(m_directRepository, backup, CachedRepositoryImpl.UNCOMMITTED_VERSION); + } + } + catch (IllegalArgumentException e) { + throw new ConfigurationException("Authentication", e.getMessage()); + } + catch (MalformedURLException mue) { + throw new ConfigurationException(URL, mue.getMessage()); + } + catch (IOException e) { + m_log.log(LogService.LOG_WARNING, "Unable to create temporary files for FilebasedBackupRepository"); + } + } + } + + /** + * Convenience method for getting settings from a configuration dictionary. + */ + private String getNotNull(Dictionary settings, String id, String errorMessage) throws ConfigurationException { + String result = (String) settings.get(id); + if (result == null) { + throw new ConfigurationException(id, errorMessage); + } + return result; + } +} \ No newline at end of file Added: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/service/DeploymentService.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/service/DeploymentService.java?rev=1464402&view=auto ============================================================================== --- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/service/DeploymentService.java (added) +++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/service/DeploymentService.java Thu Apr 4 09:43:34 2013 @@ -0,0 +1,82 @@ +/* + * 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.ace.deployment.service; + +import java.io.IOException; +import java.util.SortedSet; + +import org.osgi.framework.Version; + +/** + * Deployment service can be used to talk to the management agent about deployment packages, + * versions and updates, and to actually perform them. This interface coexists with the + * tasks that are also published by the management agent and that are probably more convenient + * if you just want to schedule (checks for) updates. + */ +public interface DeploymentService { + + /** + * Returns the highest version that is available locally (already installed). + * + * @return The highest installed version, can be null if no version is locally available. + */ + Version getHighestLocalVersion(); + + /** + * Returns the highest version that is available remotely. + * + * @param url The URL to be used to retrieve the versions available on the remote. + * @return The highest version available on the remote or null if no versions were available or the remote could not be reached. + * @throws IOException in case of I/O problems obtaining the remote version. + */ + Version getHighestRemoteVersion() throws IOException; + + /** + * Returns all versions that are available remotely. + * + * @return the remote versions, sorted, can be null. + * @throws IOException in case of I/O problems obtaining the remote versions. + */ + SortedSet getRemoteVersions() throws IOException; + + /** + * Installs the version specified by the highestRemoteVersion. + * + * @param remoteVersion the version to retrieve and install; + * @param localVersion the current (local) version, can be null in case of no version is yet installed. + * @throws IOException in case of I/O problems installing the version; + * @throws Exception in case of other problems installing the version. + */ + void installVersion(Version remoteVersion, Version localVersion) throws IOException, Exception; + + /** + * Updates from the current local version to the given remote version. + *

+ * This method is the same as calling: + *

+     * installVersion(toVersion, getHighestLocalVersion());
+     * 
+ *

+ * + * @param toVersion the (remote) version to update to, cannot be null. + * @throws Exception in case of other problems updating to the requested version. + */ + void update(Version toVersion) throws Exception; + +} Added: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/service/impl/Activator.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/service/impl/Activator.java?rev=1464402&view=auto ============================================================================== --- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/service/impl/Activator.java (added) +++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/service/impl/Activator.java Thu Apr 4 09:43:34 2013 @@ -0,0 +1,154 @@ +/* + * 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.ace.deployment.service.impl; + +import java.util.Dictionary; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.ace.connectionfactory.ConnectionFactory; +import org.apache.ace.deployment.Deployment; +import org.apache.ace.deployment.service.DeploymentService; +import org.apache.ace.discovery.Discovery; +import org.apache.ace.identification.Identification; +import org.apache.felix.dm.Component; +import org.apache.felix.dm.DependencyActivatorBase; +import org.apache.felix.dm.DependencyManager; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.service.cm.ConfigurationException; +import org.osgi.service.cm.ManagedServiceFactory; +import org.osgi.service.event.EventAdmin; +import org.osgi.service.log.LogService; + +/** + * Provides an activator for the deployment service. + */ +public class Activator extends DependencyActivatorBase implements ManagedServiceFactory { + + private static final String PID_NAME = "org.apache.ace.deployment.task.base.factory"; + + private static final String MA_NAME = "ma"; + private static final String DEFAULT_MA_NAME = null; + + private final Map m_instances = new ConcurrentHashMap(); + + private volatile DependencyManager m_manager; + + /** + * @see org.osgi.service.cm.ManagedServiceFactory#deleted(java.lang.String) + */ + public void deleted(String pid) { + Component component; + synchronized (m_instances) { + component = m_instances.remove(pid); + } + + if (component != null) { + m_manager.remove(component); + } + } + + /** + * @see org.apache.felix.dm.DependencyActivatorBase#destroy(org.osgi.framework.BundleContext, org.apache.felix.dm.DependencyManager) + */ + public void destroy(BundleContext context, DependencyManager manager) throws Exception { + // do nothing + } + + /** + * @see org.osgi.service.cm.ManagedServiceFactory#getName() + */ + public String getName() { + return "Deployment Service - base"; + } + + /** + * @see org.apache.felix.dm.DependencyActivatorBase#init(org.osgi.framework.BundleContext, org.apache.felix.dm.DependencyManager) + */ + public void init(BundleContext context, DependencyManager manager) throws Exception { + m_manager = manager; + + // Create a default deployment service instance... + m_manager.add(createService(DEFAULT_MA_NAME)); + + Properties props = new Properties(); + props.put(Constants.SERVICE_PID, PID_NAME); + + m_manager.add(createComponent() + .setInterface(ManagedServiceFactory.class.getName(), props) + .setImplementation(this) + ); + } + + /** + * @see org.osgi.service.cm.ManagedServiceFactory#updated(java.lang.String, java.util.Dictionary) + */ + public void updated(String pid, Dictionary dict) throws ConfigurationException { + final String ma = (String) dict.get(MA_NAME); + + Component component = m_instances.get(pid); + if (component == null) { + component = createService(ma); + synchronized (m_instances) { + m_instances.put(pid, component); + } + m_manager.add(component); + } + else { + // TODO do we want to deal with changes here? + } + } + + /** + * Creates the {@link DeploymentService} component for the given management agent name. + * + * @param ma the name of the management agent to create the service for, can be null. + * @return a {@link Component} instance for the {@link DeploymentService}, never null. + */ + private Component createService(String ma) { + Dictionary deploymentProperties = new Properties(); + + String identificationFilter = "(" + Constants.OBJECTCLASS + "=" + Identification.class.getName() + ")"; + String discoveryFilter = "(" + Constants.OBJECTCLASS + "=" + Discovery.class.getName() + ")"; + + if (ma == null || "".equals(ma.trim())) { + identificationFilter = String.format("(&%s(!(%s=*)))", identificationFilter, MA_NAME); + discoveryFilter = String.format("(&%s(!(%s=*)))", discoveryFilter, MA_NAME); ; + } + else { + identificationFilter = String.format("(&%s(%s=%s))", identificationFilter, MA_NAME, ma); + discoveryFilter = String.format("(&%s(%s=%s))", discoveryFilter, MA_NAME, ma); + deploymentProperties.put(MA_NAME, ma); + } + + DeploymentServiceImpl deploymentService = new DeploymentServiceImpl(); + + return createComponent() + .setInterface(DeploymentService.class.getName(), deploymentProperties) + .setImplementation(deploymentService) + .add(createServiceDependency().setService(Deployment.class).setRequired(true)) + .add(createServiceDependency().setService(ConnectionFactory.class).setRequired(true)) + .add(createServiceDependency().setService(Identification.class, identificationFilter).setRequired(true)) + .add(createServiceDependency().setService(Discovery.class, discoveryFilter).setRequired(true)) + .add(createServiceDependency().setService(EventAdmin.class).setRequired(false)) + .add(createServiceDependency().setService(LogService.class).setRequired(false)); + } +} \ No newline at end of file Added: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/service/impl/DeploymentServiceImpl.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/service/impl/DeploymentServiceImpl.java?rev=1464402&view=auto ============================================================================== --- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/service/impl/DeploymentServiceImpl.java (added) +++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/service/impl/DeploymentServiceImpl.java Thu Apr 4 09:43:34 2013 @@ -0,0 +1,300 @@ +/* + * 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.ace.deployment.service.impl; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.apache.ace.connectionfactory.ConnectionFactory; +import org.apache.ace.deployment.Deployment; +import org.apache.ace.deployment.service.DeploymentService; +import org.apache.ace.discovery.Discovery; +import org.apache.ace.identification.Identification; +import org.osgi.framework.Version; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventAdmin; +import org.osgi.service.log.LogService; + +/** + * Provides an implementation for {@link DeploymentService}. + */ +public class DeploymentServiceImpl implements DeploymentService { + + private final String TOPIC_DEPLOYMENTPACKAGE_INSTALL = "org/apache/ace/deployment/INSTALL"; + + // injected by dependencymanager + protected volatile Deployment m_deployer; + protected volatile Identification m_identification; + protected volatile Discovery m_discovery; + protected volatile LogService m_log; + protected volatile EventAdmin m_eventAdmin; + protected volatile ConnectionFactory m_connectionFactory; + + /** + * @see org.apache.ace.deployment.service.DeploymentService#getHighestLocalVersion() + */ + public Version getHighestLocalVersion() { + Object[] installedPackages = m_deployer.list(); + List versions = new ArrayList(); + for (int i = 0; i < installedPackages.length; i++) { + if (m_deployer.getName(installedPackages[i]).equals(m_identification.getID())) { + versions.add(m_deployer.getVersion(installedPackages[i])); + } + } + return getHighestVersion(versions); + } + + /** + * @see org.apache.ace.deployment.service.DeploymentService#getHighestRemoteVersion() + */ + public Version getHighestRemoteVersion() throws IOException { + SortedSet versions = getRemoteVersions(getURL()); + return ((versions == null) || versions.isEmpty()) ? null : versions.last(); + } + + /** + * @see org.apache.ace.deployment.service.DeploymentService#getRemoteVersions() + */ + public SortedSet getRemoteVersions() throws IOException { + return getRemoteVersions(getURL()); + } + + /** + * @see org.apache.ace.deployment.service.DeploymentService#installVersion(org.osgi.framework.Version, org.osgi.framework.Version) + */ + public void installVersion(Version highestRemoteVersion, Version highestLocalVersion) throws IOException, Exception { + InputStream inputStream = null; + + m_log.log(LogService.LOG_INFO, "Installing version: " + highestRemoteVersion); + + try { + String version = highestRemoteVersion.toString(); + URL baseURL = getURL(); + boolean isFileBasedProtocol = "file".equals(baseURL.getProtocol()); + if (highestLocalVersion != null && !isFileBasedProtocol) { + version += "?current=" + highestLocalVersion.toString(); + } + URL dataURL = new URL(baseURL, version); + if (isFileBasedProtocol) { + File file = urlToFile(dataURL); + inputStream = new FileInputStream(file); + } + else { + inputStream = getContents(dataURL); + } + + // Post event for auditlog + m_eventAdmin.postEvent(createEvent(version, dataURL)); + + m_deployer.install(inputStream); + } + finally { + if (inputStream != null) { + try { + inputStream.close(); + } + catch (Exception ex) { + // Not much we can do. + } + } + } + } + + /** + * @see org.apache.ace.deployment.service.DeploymentService#update(org.osgi.framework.Version) + */ + public void update(Version toVersion) throws Exception { + installVersion(toVersion, getHighestLocalVersion()); + } + + /** + * @param url + * @return + * @throws IOException + */ + final SortedSet getRemoteVersions(URL url) throws IOException { + if (url == null) { + return null; + } + + if ("file".equals(url.getProtocol())) { + return getVersionsFromDirectory(url); + } + else { + return getVersionsFromServer(url); + } + } + + /** + * @param version + * @param dataURL + * @return + */ + private Event createEvent(String version, URL dataURL) { + Dictionary properties = new Properties(); + properties.put("deploymentpackage.url", dataURL.toString()); + properties.put("deploymentpackage.version", version); + Event event = new Event(TOPIC_DEPLOYMENTPACKAGE_INSTALL, properties); + return event; + } + + /** + * @param versions + * @return + */ + private Version getHighestVersion(List versions) { + Version highestVersion = null; + for (Iterator i = versions.iterator(); i.hasNext();) { + Version version = (Version) i.next(); + if (highestVersion == null) { + highestVersion = version; + } + else if (version.compareTo(highestVersion) > 0) { + highestVersion = version; + } + } + return highestVersion; + } + + /** + * @return + */ + private URL getURL() { + URL host = m_discovery.discover(); + if (host == null) { + return null; + } + try { + return new URL(host, "deployment/" + m_identification.getID() + "/versions/"); + } + catch (MalformedURLException e) { + m_log.log(LogService.LOG_WARNING, "Malformed URL", e); + return null; + } + } + + /** + * @param url + * @return + */ + private SortedSet getVersionsFromDirectory(URL url) { + File file = urlToFile(url); + if (!file.isDirectory()) { + return null; + } + + final File[] files = file.listFiles(); + SortedSet versions = new TreeSet(); + for (File f : files) { + try { + Version version = Version.parseVersion(f.getName()); + if (version != Version.emptyVersion) { + versions.add(version); + } + } + catch (IllegalArgumentException e) { + // if the file is not a valid version, we skip it + } + } + return versions; + } + + /** + * @param url + * @return + */ + private SortedSet getVersionsFromServer(URL url) { + BufferedReader bufReader = null; + try { + bufReader = new BufferedReader(new InputStreamReader(getContents(url))); + SortedSet versions = new TreeSet(); + + String versionString; + while ((versionString = bufReader.readLine()) != null) { + try { + Version version = Version.parseVersion(versionString); + if (version != Version.emptyVersion) { + versions.add(version); + } + } + catch (IllegalArgumentException iae) { + m_log.log(LogService.LOG_WARNING, "Received malformed version, ignoring: " + versionString); + } + } + + return versions; + } + catch (IOException ioe) { + m_log.log(LogService.LOG_DEBUG, "I/O error accessing server!", ioe); + return null; + } + finally { + if (bufReader != null) { + try { + bufReader.close(); + } + catch (Exception ex) { + // not much we can do + } + } + } + } + + /** + * @param url + * @return + */ + private File urlToFile(URL url) { + File file; + // See: http://weblogs.java.net/blog/kohsuke/archive/2007/04/how_to_convert.html + // makes a best effort to convert a file URL to a File + try { + file = new File(url.toURI()); + } + catch (URISyntaxException e) { + file = new File(url.getPath()); + } + return file; + } + + /** + * @param url the remote URL to connect to, cannot be null. + * @return an {@link InputStream} to the remote URL, never null. + * @throws IOException in case of I/O problems opening the remote connection. + */ + private InputStream getContents(URL url) throws IOException { + URLConnection conn = m_connectionFactory.createConnection(url); + return conn.getInputStream(); + } +} \ No newline at end of file Added: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/service/packageinfo URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/service/packageinfo?rev=1464402&view=auto ============================================================================== --- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/service/packageinfo (added) +++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/service/packageinfo Thu Apr 4 09:43:34 2013 @@ -0,0 +1 @@ +version 1.0 \ No newline at end of file Added: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/AceRestException.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/AceRestException.java?rev=1464402&view=auto ============================================================================== --- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/AceRestException.java (added) +++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/AceRestException.java Thu Apr 4 09:43:34 2013 @@ -0,0 +1,46 @@ +/* + * 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.ace.deployment.servlet; + +import java.io.IOException; +import javax.servlet.http.HttpServletResponse; + +/** + * Handle common rest problems here. + * This can be thrown by services (unaware of how to handle it in the end) and handeled inside the catching servlet. + */ +public class AceRestException extends Exception { + private final int m_statusCode; + private final String m_description; + + public AceRestException(int statusCode, String description) { + super(statusCode + ":" + description); + m_statusCode = statusCode; + m_description = description; + } + + /** + * handling code where we turn this into http error. + * + * @param response + */ + public void handleAsHttpError(HttpServletResponse response) throws IOException { + response.sendError(m_statusCode, m_description); + } +} \ No newline at end of file Added: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/Activator.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/Activator.java?rev=1464402&view=auto ============================================================================== --- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/Activator.java (added) +++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/Activator.java Thu Apr 4 09:43:34 2013 @@ -0,0 +1,51 @@ +/* + * 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.ace.deployment.servlet; + +import javax.servlet.Servlet; + +import org.apache.ace.deployment.processor.DeploymentProcessor; +import org.apache.ace.deployment.provider.DeploymentProvider; +import org.apache.ace.deployment.streamgenerator.StreamGenerator; +import org.apache.felix.dm.DependencyActivatorBase; +import org.apache.felix.dm.DependencyManager; +import org.osgi.framework.BundleContext; +import org.osgi.service.log.LogService; + +public class Activator extends DependencyActivatorBase { + public static final String PID = "org.apache.ace.deployment.servlet"; + + @Override + public void init(BundleContext context, DependencyManager manager) throws Exception { + manager.add(createComponent() + .setInterface(Servlet.class.getName(), null) + .setImplementation(DeploymentServlet.class) + .add(createConfigurationDependency().setPropagate(true).setPid(PID)) + .add(createServiceDependency().setService(StreamGenerator.class).setRequired(true)) + .add(createServiceDependency().setService(DeploymentProvider.class).setRequired(true)) + .add(createServiceDependency().setService(DeploymentProcessor.class).setRequired(false).setCallbacks("addProcessor", "removeProcessor")) + .add(createServiceDependency().setService(LogService.class).setRequired(false)) + ); + } + + @Override + public void destroy(BundleContext context, DependencyManager manager) throws Exception { + // do nothing + } +} \ No newline at end of file Added: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/DeploymentServlet.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/DeploymentServlet.java?rev=1464402&view=auto ============================================================================== --- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/DeploymentServlet.java (added) +++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/DeploymentServlet.java Thu Apr 4 09:43:34 2013 @@ -0,0 +1,311 @@ +/* + * 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.ace.deployment.servlet; + +import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Dictionary; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.ace.authentication.api.AuthenticationService; +import org.apache.ace.deployment.processor.DeploymentProcessor; +import org.apache.ace.deployment.provider.DeploymentProvider; +import org.apache.ace.deployment.streamgenerator.StreamGenerator; +import org.apache.felix.dm.Component; +import org.apache.felix.dm.DependencyManager; +import org.osgi.framework.ServiceReference; +import org.osgi.service.cm.ConfigurationException; +import org.osgi.service.cm.ManagedService; +import org.osgi.service.log.LogService; +import org.osgi.service.useradmin.User; + +/** + * The DeploymentServlet class provides in a list of versions available for a target and a stream + * of data containing the DeploymentPackage (or fix package) for a specific target and version. + */ +public class DeploymentServlet extends HttpServlet implements ManagedService { + private static final long serialVersionUID = 1L; + + /** A boolean denoting whether or not authentication is enabled. */ + private static final String KEY_USE_AUTHENTICATION = "authentication.enabled"; + + public static final String CURRENT = "current"; + public static final String PROCESSOR = "processor"; + public static final String VERSIONS = "versions"; + public static final String DP_MIMETYPE = "application/vnd.osgi.dp"; + public static final String TEXT_MIMETYPE = "text/plain"; + + private final ConcurrentMap m_processors = new ConcurrentHashMap(); + + // injected by Dependency Manager + private volatile DependencyManager m_dm; + private volatile LogService m_log; + private volatile StreamGenerator m_streamGenerator; + private volatile DeploymentProvider m_provider; + private volatile AuthenticationService m_authService; + + private volatile boolean m_useAuth = false; + + /** + * Responds to GET requests sent to this endpoint, the response depends on the requested path: + *
  • http://host/endpoint/targetid/versions/ returns a list of versions available for the specified target + *
  • http://host/endpoint/targetid/versions/x.y.z returns a deployment package stream for the specified target and version + * + * The status code of the response can be one of the following: + *
  • HttpServletResponse.SC_BAD_REQUEST - If no target is specified or the request is malformed in a different way. + *
  • HttpServletResponse.SC_NOT_FOUND - If the specified target or version does not exist. + *
  • HttpServletResponse.SC_INTERNAL_SERVER_ERROR - If there was a problem processing the request. + *
  • HttpServletResponse.SC_OK - If all went fine + */ + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { + try { + String[] pathElements = verifyAndGetPathElements(request.getPathInfo()); + String targetID = pathElements[1]; + List versions = getVersions(targetID); + int numberOfElements = pathElements.length; + + if (numberOfElements == 3) { + handleVersionsRequest(versions, response); + } + else { + String version = pathElements[3]; + handlePackageDelivery(targetID, version, versions, request, response); + } + } + catch (AceRestException e) { + m_log.log(LogService.LOG_WARNING, e.getMessage(), e); + e.handleAsHttpError(response); + } + } + + /** + * Called by Dependency Manager upon initialization of this component. + * + * @param comp the component to initialize, cannot be null. + */ + protected void init(Component comp) { + comp.add(m_dm.createServiceDependency() + .setService(AuthenticationService.class) + .setRequired(m_useAuth) + .setInstanceBound(true) + ); + } + + /** + * {@inheritDoc} + */ + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + if (!authenticate(req)) { + // Authentication failed; don't proceed with the original request... + resp.sendError(SC_UNAUTHORIZED); + } else { + // Authentication successful, proceed with original request... + super.service(req, resp); + } + } + + /** + * Authenticates, if needed the user with the information from the given request. + * + * @param request the request to obtain the credentials from, cannot be null. + * @return true if the authentication was successful, false otherwise. + */ + private boolean authenticate(HttpServletRequest request) { + if (m_useAuth) { + User user = m_authService.authenticate(request); + if (user == null) { + m_log.log(LogService.LOG_INFO, "Authentication failure!"); + } + return (user != null); + } + return true; + } + + /** + * Serve the case where requested path is like: + * http://host/endpoint/targetid/versions/ returns a list of versions available for the specified target + * + * @param versions versions to be put into response + * @param response response object. + */ + private void handleVersionsRequest(List versions, HttpServletResponse response) throws AceRestException { + ServletOutputStream output = null; + + response.setContentType(TEXT_MIMETYPE); + try { + output = response.getOutputStream(); + for (String version : versions) { + output.print(version); + output.print("\n"); + } + } + catch (IOException e) { + throw new AceRestException(HttpServletResponse.SC_BAD_REQUEST, "Request URI is invalid"); + } + finally { + tryClose(output); + } + } + + private void handlePackageDelivery(final String targetID, final String version, final List versions, final HttpServletRequest request, final HttpServletResponse response) throws AceRestException { + ServletOutputStream output = null; + + try { + if (!versions.contains(version)) { + throw new AceRestException(HttpServletResponse.SC_NOT_FOUND, "Unknown version (" + version + ")"); + } + String current = request.getParameter(CURRENT); + String processor = request.getParameter(PROCESSOR); + + InputStream inputStream; + if (current != null) { + inputStream = m_streamGenerator.getDeploymentPackage(targetID, current, version); + } + else { + inputStream = m_streamGenerator.getDeploymentPackage(targetID, version); + } + + if (processor != null) { + DeploymentProcessor deploymentProcessor = m_processors.get(processor); + if (deploymentProcessor != null) { + deploymentProcessor.process(inputStream, request, response); + return; + } + else { + throw new AceRestException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not find a deployment processor called: " + processor); + } + } + response.setContentType(DP_MIMETYPE); + output = response.getOutputStream(); + byte[] buffer = new byte[1024 * 32]; + for (int bytesRead = inputStream.read(buffer); bytesRead != -1; bytesRead = inputStream.read(buffer)) { + output.write(buffer, 0, bytesRead); + } + } + catch (IllegalArgumentException e) { + throw (AceRestException) new AceRestException(HttpServletResponse.SC_BAD_REQUEST, "Request URI is invalid").initCause(e); + } + catch (IOException e) { + throw (AceRestException) new AceRestException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not deliver package").initCause(e); + } + finally { + tryClose(output); + } + } + + private List getVersions(String targetID) throws AceRestException { + try { + return m_provider.getVersions(targetID); + } + catch (IllegalArgumentException iae) { + throw new AceRestException(HttpServletResponse.SC_NOT_FOUND, "Unknown target (" + targetID + ")"); + } + catch (IOException ioe) { + m_log.log(LogService.LOG_WARNING, "Error getting available versions.", ioe); + throw new AceRestException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error getting available versions."); + } + } + + private void tryClose(OutputStream output) { + try { + if (output != null) { + output.close(); + } + } + catch (IOException e) { + m_log.log(LogService.LOG_WARNING, "Exception trying to close stream after request. ", e); + throw new RuntimeException(e); + } + } + + /** + * Make sure the path is valid. + * Also returns the splited version of #path. + * + * @param path http request path + * + * @return splitted version of #path. Split delim is "/" + * + * @throws org.apache.ace.deployment.servlet.AceRestException if path is not valid or cannot be processed. + */ + private String[] verifyAndGetPathElements(String path) throws AceRestException { + if (path == null) { + throw new AceRestException(HttpServletResponse.SC_BAD_REQUEST, "Request URI is invalid"); + } + String[] elements = path.split("/"); + int numberOfElements = elements.length; + + if ((numberOfElements < 3) || (numberOfElements > 4) || !VERSIONS.equals(elements[2])) { + throw new AceRestException(HttpServletResponse.SC_BAD_REQUEST, "Request URI is invalid"); + } + return elements; + } + + @Override + public String getServletInfo() { + return "Ace Deployment Servlet Endpoint"; + } + + public void updated(Dictionary settings) throws ConfigurationException { + if (settings != null) { + String useAuthString = (String) settings.get(KEY_USE_AUTHENTICATION); + if (useAuthString == null + || !("true".equalsIgnoreCase(useAuthString) || "false".equalsIgnoreCase(useAuthString))) { + throw new ConfigurationException(KEY_USE_AUTHENTICATION, "Missing or invalid value!"); + } + boolean useAuth = Boolean.parseBoolean(useAuthString); + + m_useAuth = useAuth; + } + else { + m_useAuth = false; + } + } + + public void addProcessor(ServiceReference ref, DeploymentProcessor processor) { + String key = (String) ref.getProperty(PROCESSOR); + if (key == null) { + m_log.log(LogService.LOG_WARNING, "Deployment processor ignored, required service property '" + PROCESSOR + "' is missing."); + return; + } + m_processors.putIfAbsent(key, processor); + } + + public void removeProcessor(ServiceReference ref, DeploymentProcessor processor) { + String key = (String) ref.getProperty(PROCESSOR); + if (key == null) { + // we do not log this here again, we already did so in 'addProcessor' + return; + } + m_processors.remove(key); + } +} \ No newline at end of file Added: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/streamgenerator/StreamGenerator.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/streamgenerator/StreamGenerator.java?rev=1464402&view=auto ============================================================================== --- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/streamgenerator/StreamGenerator.java (added) +++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/streamgenerator/StreamGenerator.java Thu Apr 4 09:43:34 2013 @@ -0,0 +1,47 @@ +/* + * 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.ace.deployment.streamgenerator; + +import java.io.IOException; +import java.io.InputStream; + +public interface StreamGenerator +{ + + /** + * Returns an input stream with the requested deployment package. + * + * @param id the ID of the package + * @param version the version of the package + * @return an input stream + * @throws java.io.IOException when the stream could not be generated + */ + public InputStream getDeploymentPackage(String id, String version) throws IOException; + + /** + * Returns an input stream with the requested deployment fix package. + * + * @param id the ID of the package. + * @param fromVersion the version of the target. + * @param toVersion the version the target should be in after applying the package. + * @return an input stream. + * @throws java.io.IOException when the stream could not be generated. + */ + public InputStream getDeploymentPackage(String id, String fromVersion, String toVersion) throws IOException; +} \ No newline at end of file Added: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/streamgenerator/impl/Activator.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/streamgenerator/impl/Activator.java?rev=1464402&view=auto ============================================================================== --- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/streamgenerator/impl/Activator.java (added) +++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/streamgenerator/impl/Activator.java Thu Apr 4 09:43:34 2013 @@ -0,0 +1,48 @@ +/* + * 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.ace.deployment.streamgenerator.impl; + +import org.apache.ace.connectionfactory.ConnectionFactory; +import org.apache.ace.deployment.provider.DeploymentProvider; +import org.apache.ace.deployment.streamgenerator.StreamGenerator; +import org.apache.felix.dm.DependencyActivatorBase; +import org.apache.felix.dm.DependencyManager; +import org.osgi.framework.BundleContext; + +public class Activator extends DependencyActivatorBase { + @Override + public void init(BundleContext context, DependencyManager manager) throws Exception { + manager.add(createComponent() + .setInterface(StreamGenerator.class.getName(), null) + .setImplementation(StreamGeneratorImpl.class) + .add(createServiceDependency() + .setService(DeploymentProvider.class) + .setRequired(true) + ) + .add(createServiceDependency() + .setService(ConnectionFactory.class) + .setRequired(true) + ) + ); + } + + @Override + public void destroy(BundleContext context, DependencyManager manager) throws Exception { + } +} \ No newline at end of file