summaryrefslogtreecommitdiffstats
path: root/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/processor/impl/FolderContributionProcessor.java156
-rw-r--r--sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/processor/impl/JarContributionProcessor.java123
-rw-r--r--sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/impl/ContributionRepositoryImpl.java399
-rw-r--r--sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/impl/ContributionServiceImpl.java545
-rw-r--r--sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/impl/PackageTypeDescriberImpl.java120
-rw-r--r--sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/util/FileHelper.java701
-rw-r--r--sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/util/IOHelper.java190
-rw-r--r--sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/resources/META-INF/services/org.apache.tuscany.sca.contribution.processor.PackageProcessor19
-rw-r--r--sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/resources/contribution-impl-validation-messages.properties28
9 files changed, 2281 insertions, 0 deletions
diff --git a/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/processor/impl/FolderContributionProcessor.java b/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/processor/impl/FolderContributionProcessor.java
new file mode 100644
index 0000000000..ab056e3214
--- /dev/null
+++ b/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/processor/impl/FolderContributionProcessor.java
@@ -0,0 +1,156 @@
+/*
+ * 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.contribution.processor.impl;
+
+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.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.tuscany.sca.contribution.PackageType;
+import org.apache.tuscany.sca.contribution.processor.PackageProcessor;
+import org.apache.tuscany.sca.contribution.service.ContributionException;
+import org.apache.tuscany.sca.contribution.service.ContributionReadException;
+
+/**
+ * Folder contribution package processor.
+ *
+ * @version $Rev$ $Date$
+ */
+public class FolderContributionProcessor implements PackageProcessor {
+
+ public FolderContributionProcessor() {
+ }
+
+ public String getPackageType() {
+ return PackageType.FOLDER;
+ }
+
+ /**
+ * Recursively traverse a root directory
+ *
+ * @param fileList
+ * @param file
+ * @param root
+ * @throws IOException
+ */
+ private static void traverse(List<URI> fileList, final File file, final File root) throws IOException {
+ // Allow privileged access to test file. Requires FilePermissions in security policy file.
+ Boolean isFile = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
+ public Boolean run() {
+ return file.isFile();
+ }
+ });
+ if (isFile) {
+ fileList.add(AccessController.doPrivileged(new PrivilegedAction<URI>() {
+ public URI run() {
+ return root.toURI().relativize(file.toURI());
+ }
+ }));
+ } else {
+ // Allow privileged access to test file. Requires FilePermissions in security policy
+ // file.
+ Boolean isDirectory = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
+ public Boolean run() {
+ return file.isDirectory();
+ }
+ });
+ if (isDirectory) {
+ String uri = AccessController.doPrivileged(new PrivilegedAction<URI>() {
+ public URI run() {
+ return root.toURI().relativize(file.toURI());
+ }
+ }).toString();
+
+ if (uri.endsWith("/")) {
+ uri = uri.substring(0, uri.length() - 1);
+ }
+ fileList.add(URI.create(uri));
+
+ // Allow privileged access to list files. Requires FilePermission in security
+ // policy.
+ File[] files = AccessController.doPrivileged(new PrivilegedAction<File[]>() {
+ public File[] run() {
+ return file.listFiles();
+ }
+ });
+ for (File f : files) {
+ if (!f.getName().startsWith(".")) {
+ traverse(fileList, f, root);
+ }
+ }
+ }
+ }
+ }
+
+ public URL getArtifactURL(URL sourceURL, URI artifact) throws MalformedURLException {
+ return new URL(sourceURL, artifact.toString());
+ }
+
+ public List<URI> getArtifacts(URL packageSourceURL, InputStream inputStream) throws ContributionException,
+ IOException {
+ if (packageSourceURL == null) {
+ throw new IllegalArgumentException("Invalid null package source URL.");
+ }
+
+ List<URI> artifacts = new ArrayList<URI>();
+
+ try {
+ // Assume the root is a jar file
+ final File rootFolder = new File(packageSourceURL.toURI());
+ // Allow privileged access to test file. Requires FilePermissions in security policy
+ // file.
+ Boolean isDirectory = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
+ public Boolean run() {
+ return rootFolder.isDirectory();
+ }
+ });
+ if (isDirectory) {
+ // Allow privileged access to test file. Requires FilePermissions in security policy
+ // file.
+ Boolean folderExists = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
+ public Boolean run() {
+ return rootFolder.exists();
+ }
+ });
+ if (!folderExists) {
+ throw new ContributionReadException(rootFolder.getAbsolutePath());
+ }
+
+ // Security consideration. This method gathers URIs of enclosed
+ // artifacts. The URIs are protected by the policy when a user
+ // yries to open those URLs.
+ traverse(artifacts, rootFolder, rootFolder);
+ }
+
+ } catch (URISyntaxException e) {
+ throw new ContributionReadException(packageSourceURL.toExternalForm(), e);
+ }
+
+ return artifacts;
+ }
+}
diff --git a/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/processor/impl/JarContributionProcessor.java b/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/processor/impl/JarContributionProcessor.java
new file mode 100644
index 0000000000..3df275a7a1
--- /dev/null
+++ b/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/processor/impl/JarContributionProcessor.java
@@ -0,0 +1,123 @@
+/*
+ * 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.contribution.processor.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+import org.apache.tuscany.sca.contribution.PackageType;
+import org.apache.tuscany.sca.contribution.processor.PackageProcessor;
+import org.apache.tuscany.sca.contribution.service.ContributionException;
+
+/**
+ * Jar Contribution package processor.
+ *
+ * @version $Rev$ $Date$
+ */
+public class JarContributionProcessor implements PackageProcessor {
+
+ public JarContributionProcessor() {
+ }
+
+ public String getPackageType() {
+ return PackageType.JAR;
+ }
+
+ public URL getArtifactURL(URL sourceURL, URI artifact) throws MalformedURLException {
+ if (sourceURL.toString().startsWith("jar:")) {
+ return new URL(sourceURL, artifact.toString());
+ } else {
+ return new URL("jar:" + sourceURL.toExternalForm() + "!/" + artifact);
+ }
+ }
+
+ public List<URI> getArtifacts(URL packageSourceURL, InputStream inputStream) throws ContributionException,
+ IOException {
+ if (packageSourceURL == null) {
+ throw new IllegalArgumentException("Invalid null package source URL.");
+ }
+
+ if (inputStream == null) {
+ throw new IllegalArgumentException("Invalid null source inputstream.");
+ }
+
+ // Assume the root is a jar file
+ JarInputStream jar = new JarInputStream(inputStream);
+ try {
+ Set<String> names = new HashSet<String>();
+ while (true) {
+ JarEntry entry = jar.getNextJarEntry();
+ if (entry == null) {
+ // EOF
+ break;
+ }
+
+ // FIXME: Maybe we should externalize the filter as a property
+ String name = entry.getName();
+ if (!name.startsWith(".")) {
+
+ // Trim trailing /
+ if (name.endsWith("/")) {
+ name = name.substring(0, name.length() - 1);
+ }
+
+ // Add the entry name
+ if (!names.contains(name)) {
+ names.add(name);
+
+ // Add parent folder names to the list too
+ for (;;) {
+ int s = name.lastIndexOf('/');
+ if (s == -1) {
+ name = "";
+ } else {
+ name = name.substring(0, s);
+ }
+ if (!names.contains(name)) {
+ names.add(name);
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Return list of URIs
+ List<URI> artifacts = new ArrayList<URI>();
+ for (String name: names) {
+ artifacts.add(URI.create(name));
+ }
+ return artifacts;
+
+ } finally {
+ jar.close();
+ }
+ }
+}
diff --git a/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/impl/ContributionRepositoryImpl.java b/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/impl/ContributionRepositoryImpl.java
new file mode 100644
index 0000000000..9375cb917f
--- /dev/null
+++ b/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/impl/ContributionRepositoryImpl.java
@@ -0,0 +1,399 @@
+/*
+ * 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.contribution.service.impl;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamReader;
+
+import org.apache.tuscany.sca.assembly.builder.impl.ProblemImpl;
+import org.apache.tuscany.sca.contribution.Contribution;
+import org.apache.tuscany.sca.contribution.service.ContributionRepository;
+import org.apache.tuscany.sca.contribution.service.util.FileHelper;
+import org.apache.tuscany.sca.contribution.service.util.IOHelper;
+import org.apache.tuscany.sca.monitor.Monitor;
+import org.apache.tuscany.sca.monitor.Problem;
+import org.apache.tuscany.sca.monitor.Problem.Severity;
+
+/**
+ * The default implementation of ContributionRepository
+ *
+ * @version $Rev$ $Date$
+ */
+public class ContributionRepositoryImpl implements ContributionRepository {
+ private static final String NS = "http://tuscany.apache.org/xmlns/1.0-SNAPSHOT";
+ private static final String DOMAIN_INDEX_FILENAME = "sca-domain.xml";
+ private final File rootFile;
+ private Map<String, String> contributionLocations = new HashMap<String, String>();
+
+ private Map<String, Contribution> contributionMap = new HashMap<String, Contribution>();
+ private List<Contribution> contributions = new ArrayList<Contribution>();
+
+ private URI domain;
+ private XMLInputFactory factory;
+ private Monitor monitor;
+
+ /**
+ * Marshals warnings into the monitor
+ *
+ * @param message
+ * @param model
+ * @param messageParameters
+ */
+ protected void warning(String message, Object model, String... messageParameters) {
+ if (monitor != null){
+ Problem problem = new ProblemImpl(this.getClass().getName(), "contribution-impl-validation-messages", Severity.WARNING, model, message, (Object[])messageParameters);
+ monitor.problem(problem);
+ }
+ }
+
+ /**
+ * Marshals errors into the monitor
+ *
+ * @param problems
+ * @param message
+ * @param model
+ */
+ protected void error(String message, Object model, Object... messageParameters) {
+ if (monitor != null) {
+ Problem problem = new ProblemImpl(this.getClass().getName(), "contribution-impl-validation-messages", Severity.ERROR, model, message, (Object[])messageParameters);
+ monitor.problem(problem);
+ }
+ }
+
+ /**
+ * Marshals exceptions into the monitor
+ *
+ * @param problems
+ * @param message
+ * @param model
+ */
+ protected void error(String message, Object model, Exception ex) {
+ if (monitor != null) {
+ Problem problem = new ProblemImpl(this.getClass().getName(), "contribution-impl-validation-messages", Severity.ERROR, model, message, ex);
+ monitor.problem(problem);
+ }
+ }
+
+ /**
+ * Constructor with repository root
+ *
+ * @param repository
+ * @param factory
+ */
+ public ContributionRepositoryImpl(final String repository, XMLInputFactory factory, Monitor monitor) throws IOException {
+ this.monitor = monitor;
+ String root = repository;
+ if (repository == null) {
+ root = AccessController.doPrivileged(new PrivilegedAction<String>() {
+ public String run() {
+ // Default to <user.home>/.tuscany/domains/local/
+ String userHome = System.getProperty("user.home");
+ String slash = File.separator;
+ return userHome + slash + ".tuscany" + slash + "domains" + slash + "local" + slash;
+ }
+ });
+ }
+
+ // Allow privileged access to File. Requires FilePermission in security policy file.
+ final String finalRoot = root;
+ this.rootFile = AccessController.doPrivileged(new PrivilegedAction<File>() {
+ public File run() {
+ return new File(finalRoot);
+ }
+ });
+
+ // Allow privileged access to File. Requires FilePermission in security policy file.
+ this.domain = AccessController.doPrivileged(new PrivilegedAction<URI>() {
+ public URI run() {
+ return rootFile.toURI();
+ }
+ });
+
+ // Allow privileged access to mkdir. Requires FilePermission in security policy file.
+ try {
+ AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
+ public Object run() throws IOException {
+ FileHelper.forceMkdir(rootFile);
+ return null;
+ }
+ });
+ } catch (PrivilegedActionException e) {
+ error("PrivilegedActionException", rootFile, (IOException)e.getException());
+ throw (IOException)e.getException();
+ }
+
+ // Allow privileged access to test file. Requires FilePermissions in security policy file.
+ Boolean notDirectory = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
+ public Boolean run() {
+ return (!rootFile.exists() || !rootFile.isDirectory() || !rootFile.canRead());
+ }
+ });
+ if (notDirectory) {
+ error("RootNotDirectory", rootFile, repository);
+ throw new IOException("The root is not a directory: " + repository);
+ }
+ this.factory = factory;
+ }
+
+ public URI getDomain() {
+ return domain;
+ }
+
+ /**
+ * Resolve contribution location in the repository -> root repository /
+ * contribution file -> contribution group id / artifact id / version
+ *
+ * @param contribution
+ * @return
+ */
+ private File mapToFile(URL sourceURL) {
+ String fileName = FileHelper.toFile(sourceURL).getName();
+ return new File(rootFile, "contributions" + File.separator + fileName);
+ }
+
+ /**
+ * Write a specific source InputStream to a file on disk
+ *
+ * @param source contents of the file to be written to disk
+ * @param target file to be written
+ * @throws IOException
+ */
+ public static void copy(InputStream source, File target) throws IOException {
+ BufferedOutputStream out = null;
+ BufferedInputStream in = null;
+
+ try {
+ out = new BufferedOutputStream(new FileOutputStream(target));
+ in = new BufferedInputStream(source);
+ IOHelper.copy(in, out);
+ } finally {
+ IOHelper.closeQuietly(out);
+ IOHelper.closeQuietly(in);
+ }
+ }
+
+ public URL store(final String contribution, URL sourceURL, InputStream contributionStream) throws IOException {
+ // where the file should be stored in the repository
+ final File location = mapToFile(sourceURL);
+ FileHelper.forceMkdir(location.getParentFile());
+
+ copy(contributionStream, location);
+
+ // add contribution to repositoryContent
+ // Allow ability to read user.dir property. Requires PropertyPermission in security policy.
+ URL contributionURL;
+ try {
+ contributionURL= AccessController.doPrivileged(new PrivilegedExceptionAction<URL>() {
+ public URL run() throws IOException {
+ URL contributionURL = location.toURL();
+ URI relative = rootFile.toURI().relativize(location.toURI());
+ contributionLocations.put(contribution, relative.toString());
+ return contributionURL;
+ }
+ });
+ } catch (PrivilegedActionException e) {
+ error("PrivilegedActionException", location, (IOException)e.getException());
+ throw (IOException)e.getException();
+ }
+ saveMap();
+
+ return contributionURL;
+ }
+
+ public URL store(String contribution, URL sourceURL) throws IOException {
+ // where the file should be stored in the repository
+ File location = mapToFile(sourceURL);
+ File source = FileHelper.toFile(sourceURL);
+ if (source == null || source.isFile()) {
+ URLConnection connection = sourceURL.openConnection();
+ connection.setUseCaches(false);
+ InputStream is = connection.getInputStream();
+ try {
+ return store(contribution, sourceURL, is);
+ } finally {
+ IOHelper.closeQuietly(is);
+ }
+ }
+
+ FileHelper.forceMkdir(location);
+ FileHelper.copyDirectory(source, location);
+
+ // add contribution to repositoryContent
+ URI relative = rootFile.toURI().relativize(location.toURI());
+ contributionLocations.put(contribution, relative.toString());
+ saveMap();
+
+ return location.toURL();
+ }
+
+ public URL find(String contribution) {
+ if (contribution == null) {
+ return null;
+ }
+ String location = contributionLocations.get(contribution);
+ if (location == null) {
+ return null;
+ }
+ try {
+ return new File(rootFile, location).toURL();
+ } catch (MalformedURLException e) {
+ // Should not happen
+ error("MalformedURLException", location, new AssertionError(e));
+ throw new AssertionError(e);
+ }
+ }
+
+ public void remove(String contribution) {
+ URL contributionURL = this.find(contribution);
+ if (contributionURL != null) {
+ // remove
+ try {
+ FileHelper.forceDelete(FileHelper.toFile(contributionURL));
+ this.contributionLocations.remove(contribution);
+ saveMap();
+ } catch (IOException ioe) {
+ // handle file could not be removed
+ }
+ }
+ }
+
+ public List<String> list() {
+ return new ArrayList<String>(contributionLocations.keySet());
+ }
+
+ public void init() {
+ File domainFile = new File(rootFile, "sca-domain.xml");
+ if (!domainFile.isFile()) {
+ return;
+ }
+ FileInputStream is;
+ try {
+ is = new FileInputStream(domainFile);
+ } catch (FileNotFoundException e) {
+ warning("DomainFileNotFound", domainFile, domainFile.getAbsolutePath());
+ return;
+ }
+ try {
+ XMLStreamReader reader = factory.createXMLStreamReader(new InputStreamReader(is, "UTF-8"));
+ while (reader.hasNext()) {
+ switch (reader.getEventType()) {
+ case XMLStreamConstants.START_ELEMENT:
+ String name = reader.getName().getLocalPart();
+ if ("domain".equals(name)) {
+ String uri = reader.getAttributeValue(null, "uri");
+ if (uri != null) {
+ domain = URI.create(uri);
+ }
+ }
+ if ("contribution".equals(name)) {
+ String uri = reader.getAttributeValue(null, "uri");
+ String location = reader.getAttributeValue(null, "location");
+ contributionLocations.put(uri, location);
+ }
+ break;
+ default:
+ break;
+ }
+ reader.next();
+ }
+ } catch (Exception e) {
+ // Ignore
+ } finally {
+ IOHelper.closeQuietly(is);
+ }
+ }
+
+ private void saveMap() {
+ File domainFile = new File(rootFile, DOMAIN_INDEX_FILENAME);
+ FileOutputStream os = null;
+ try {
+ os = new FileOutputStream(domainFile);
+ PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, "UTF-8"));
+ writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+ writer.println("<domain uri=\"" + getDomain() + "\" xmlns=\"" + NS + "\">");
+ for (Map.Entry<String, String> e : contributionLocations.entrySet()) {
+ writer.println(" <contribution uri=\"" + e.getKey() + "\" location=\"" + e.getValue() + "\"/>");
+ }
+ writer.println("</domain>");
+ writer.flush();
+ } catch (IOException e) {
+ IllegalArgumentException ae = new IllegalArgumentException(e);
+ error("IllegalArgumentException", os, ae);
+ throw ae;
+ } finally {
+ IOHelper.closeQuietly(os);
+ }
+ }
+
+ public void destroy() {
+ }
+
+ public void addContribution(Contribution contribution) {
+ contributionMap.put(contribution.getURI(), contribution);
+ contributions.add(contribution);
+ }
+
+ public void removeContribution(Contribution contribution) {
+ contributionMap.remove(contribution.getURI());
+ contributions.remove(contribution);
+ }
+
+ public void updateContribution(Contribution contribution) {
+ Contribution oldContribution = contributionMap.remove(contribution.getURI());
+ contributions.remove(oldContribution);
+ contributionMap.put(contribution.getURI(), contribution);
+ contributions.add(contribution);
+ }
+
+ public Contribution getContribution(String uri) {
+ return contributionMap.get(uri);
+ }
+
+ public List<Contribution> getContributions() {
+ return Collections.unmodifiableList(contributions);
+ }
+
+}
diff --git a/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/impl/ContributionServiceImpl.java b/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/impl/ContributionServiceImpl.java
new file mode 100644
index 0000000000..0e39174497
--- /dev/null
+++ b/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/impl/ContributionServiceImpl.java
@@ -0,0 +1,545 @@
+/*
+ * 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.contribution.service.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.net.URLConnection;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+
+import org.apache.tuscany.sca.assembly.AssemblyFactory;
+import org.apache.tuscany.sca.assembly.Composite;
+import org.apache.tuscany.sca.assembly.builder.impl.ProblemImpl;
+import org.apache.tuscany.sca.contribution.Artifact;
+import org.apache.tuscany.sca.contribution.Contribution;
+import org.apache.tuscany.sca.contribution.ContributionFactory;
+import org.apache.tuscany.sca.contribution.ContributionMetadata;
+import org.apache.tuscany.sca.contribution.ModelFactoryExtensionPoint;
+import org.apache.tuscany.sca.contribution.processor.PackageProcessor;
+import org.apache.tuscany.sca.contribution.processor.StAXArtifactProcessor;
+import org.apache.tuscany.sca.contribution.processor.URLArtifactProcessor;
+import org.apache.tuscany.sca.contribution.resolver.ExtensibleModelResolver;
+import org.apache.tuscany.sca.contribution.resolver.ModelResolver;
+import org.apache.tuscany.sca.contribution.resolver.ModelResolverExtensionPoint;
+import org.apache.tuscany.sca.contribution.service.ContributionException;
+import org.apache.tuscany.sca.contribution.service.ContributionRepository;
+import org.apache.tuscany.sca.contribution.service.ContributionService;
+import org.apache.tuscany.sca.contribution.service.ExtensibleContributionListener;
+import org.apache.tuscany.sca.contribution.service.util.IOHelper;
+import org.apache.tuscany.sca.contribution.xml.ContributionMetadataDocumentProcessor;
+import org.apache.tuscany.sca.definitions.SCADefinitions;
+import org.apache.tuscany.sca.policy.Intent;
+import org.apache.tuscany.sca.policy.IntentAttachPointType;
+import org.apache.tuscany.sca.policy.PolicySet;
+import org.apache.tuscany.sca.monitor.Monitor;
+import org.apache.tuscany.sca.monitor.Problem;
+import org.apache.tuscany.sca.monitor.Problem.Severity;
+
+/**
+ * Service interface that manages artifacts contributed to a Tuscany runtime.
+ *
+ * @version $Rev$ $Date$
+ */
+public class ContributionServiceImpl implements ContributionService {
+
+ /**
+ * Repository where contributions are stored. Usually set by injection.
+ */
+ private ContributionRepository contributionRepository;
+
+ /**
+ * Registry of available package processors.
+ */
+ private PackageProcessor packageProcessor;
+
+ /**
+ * Registry of available artifact processors
+ */
+
+ private URLArtifactProcessor artifactProcessor;
+
+ /**
+ * Registry of available StAX processors,
+ * used for loading contribution metadata in a extensible way
+ */
+ private StAXArtifactProcessor staxProcessor;
+
+ /**
+ * Event listener for contribution operations
+ */
+ private ExtensibleContributionListener contributionListener;
+
+ /**
+ * Registry of available model resolvers
+ */
+
+ private ModelResolverExtensionPoint modelResolvers;
+
+ /**
+ * Model factory extension point
+ */
+
+ private ModelFactoryExtensionPoint modelFactories;
+
+ /**
+ * XML factory used to create reader instance to load contribution metadata
+ */
+ private XMLInputFactory xmlFactory;
+
+ /**
+ * Assembly factory
+ */
+ private AssemblyFactory assemblyFactory;
+
+ /**
+ * Contribution model factory
+ */
+ private ContributionFactory contributionFactory;
+
+
+ private ModelResolver policyDefinitionsResolver;
+
+ private List policyDefinitions;
+
+ private Monitor monitor;
+
+ private String COMPOSITE_FILE_EXTN = ".composite";
+
+ public ContributionServiceImpl(ContributionRepository repository,
+ PackageProcessor packageProcessor,
+ URLArtifactProcessor documentProcessor,
+ StAXArtifactProcessor staxProcessor,
+ ExtensibleContributionListener contributionListener,
+ ModelResolver policyDefinitionsResolver,
+ ModelResolverExtensionPoint modelResolvers,
+ ModelFactoryExtensionPoint modelFactories,
+ AssemblyFactory assemblyFactory,
+ ContributionFactory contributionFactory,
+ XMLInputFactory xmlFactory,
+ List<SCADefinitions> policyDefinitions,
+ Monitor monitor) {
+ super();
+ this.contributionRepository = repository;
+ this.packageProcessor = packageProcessor;
+ this.artifactProcessor = documentProcessor;
+ this.staxProcessor = staxProcessor;
+ this.contributionListener = contributionListener;
+ this.modelResolvers = modelResolvers;
+ this.modelFactories = modelFactories;
+ this.xmlFactory = xmlFactory;
+ this.assemblyFactory = assemblyFactory;
+ this.contributionFactory = contributionFactory;
+ this.policyDefinitionsResolver = policyDefinitionsResolver;
+ this.policyDefinitions = policyDefinitions;
+ this.monitor = monitor;
+ }
+
+ /**
+ * Report a error.
+ *
+ * @param problems
+ * @param message
+ * @param model
+ */
+ private void error(String message, Object model, Object... messageParameters) {
+ if (monitor != null) {
+ Problem problem = new ProblemImpl(this.getClass().getName(), "contribution-impl-validation-messages", Severity.ERROR, model, message, (Object[])messageParameters);
+ monitor.problem(problem);
+ }
+ }
+
+ public Contribution contribute(String contributionURI, URL sourceURL, boolean storeInRepository)
+ throws ContributionException, IOException {
+ if (contributionURI == null) {
+ error("ContributionURINull", contributionURI);
+ throw new IllegalArgumentException("URI for the contribution is null");
+ }
+ if (sourceURL == null) {
+ error("SourceURLNull", sourceURL);
+ throw new IllegalArgumentException("Source URL for the contribution is null");
+ }
+ return addContribution(contributionURI, sourceURL, null, null, storeInRepository);
+ }
+
+ public Contribution contribute(String contributionURI,
+ URL sourceURL,
+ ModelResolver modelResolver,
+ boolean storeInRepository) throws ContributionException, IOException {
+ if (contributionURI == null) {
+ error("ContributionURINull", contributionURI);
+ throw new IllegalArgumentException("URI for the contribution is null");
+ }
+ if (sourceURL == null) {
+ error("SourceURLNull", sourceURL);
+ throw new IllegalArgumentException("Source URL for the contribution is null");
+ }
+
+ return addContribution(contributionURI, sourceURL, null, modelResolver, storeInRepository);
+ }
+
+ public Contribution contribute(String contributionURI, URL sourceURL, InputStream input)
+ throws ContributionException, IOException {
+
+ return addContribution(contributionURI, sourceURL, input, null, true);
+ }
+
+ public Contribution contribute(String contributionURI, URL sourceURL, InputStream input, ModelResolver modelResolver)
+ throws ContributionException, IOException {
+
+ return addContribution(contributionURI, sourceURL, input, modelResolver, true);
+ }
+
+ public Contribution getContribution(String uri) {
+ return this.contributionRepository.getContribution(uri);
+ }
+
+ /**
+ * Remove a contribution and notify listener that contribution was removed
+ */
+ public void remove(String uri) throws ContributionException {
+ Contribution contribution = contributionRepository.getContribution(uri);
+ this.contributionRepository.removeContribution(contribution);
+ this.contributionListener.contributionRemoved(this.contributionRepository, contribution);
+ }
+
+ /**
+ * Add a composite model to the contribution
+ */
+ public void addDeploymentComposite(Contribution contribution, Composite composite) throws ContributionException {
+ Artifact artifact = this.contributionFactory.createArtifact();
+ artifact.setURI(composite.getURI());
+ artifact.setModel(composite);
+
+ contribution.getArtifacts().add(artifact);
+
+ contribution.getDeployables().add(composite);
+ }
+
+ /**
+ * Utility/Helper methods for contribution service
+ */
+
+ /**
+ * Perform read of the contribution metadata loader (sca-contribution.xml and sca-contribution-generated.xml)
+ * When the two metadata files are available, the information provided are merged, and the sca-contribution has priorities
+ *
+ * @param sourceURL
+ * @return Contribution
+ * @throws ContributionException
+ */
+ private Contribution readContributionMetadata(URL sourceURL) throws ContributionException {
+ Contribution contributionMetadata = contributionFactory.createContribution();
+
+ ContributionMetadataDocumentProcessor metadataDocumentProcessor =
+ new ContributionMetadataDocumentProcessor(modelFactories, staxProcessor, monitor);
+
+ final URL[] urls = {sourceURL};
+ // Allow access to create classloader. Requires RuntimePermission in security policy.
+ URLClassLoader cl = AccessController.doPrivileged(new PrivilegedAction<URLClassLoader>() {
+ public URLClassLoader run() {
+ return new URLClassLoader(urls, null);
+ }
+ });
+ for (String path: new String[]{
+ Contribution.SCA_CONTRIBUTION_GENERATED_META,
+ Contribution.SCA_CONTRIBUTION_META}) {
+ URL url = cl.getResource(path);
+ if (url != null) {
+ ContributionMetadata contribution = metadataDocumentProcessor.read(sourceURL, URI.create(path), url);
+ contributionMetadata.getImports().addAll(contribution.getImports());
+ contributionMetadata.getExports().addAll(contribution.getExports());
+ contributionMetadata.getDeployables().addAll(contribution.getDeployables());
+ }
+ }
+
+ // For debugging purposes, write it back to XML
+ // if (contributionMetadata != null) {
+ // try {
+ // ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ // XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
+ // outputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, Boolean.TRUE);
+ // staxProcessor.write(contributionMetadata, outputFactory.createXMLStreamWriter(bos));
+ // Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(bos.toByteArray()));
+ // OutputFormat format = new OutputFormat();
+ // format.setIndenting(true);
+ // format.setIndent(2);
+ // XMLSerializer serializer = new XMLSerializer(System.out, format);
+ // serializer.serialize(document);
+ // } catch (Exception e) {
+ // e.printStackTrace();
+ // }
+ // }
+
+ return contributionMetadata;
+ }
+
+ /**
+ * Note:
+ *
+ * @param contributionURI ContributionID
+ * @param sourceURL contribution location
+ * @param contributionStream contribution content
+ * @param storeInRepository flag if we store the contribution into the
+ * repository or not
+ * @return the contribution model representing the contribution
+ * @throws IOException
+ * @throws DeploymentException
+ */
+ private Contribution addContribution(String contributionURI,
+ URL sourceURL,
+ InputStream contributionStream,
+ ModelResolver modelResolver,
+ boolean storeInRepository) throws IOException, ContributionException {
+
+ if (contributionStream == null && sourceURL == null) {
+ error("ContributionContentNull", contributionStream);
+ throw new IllegalArgumentException("The content of the contribution is null.");
+ }
+
+ // store the contribution in the contribution repository
+ URL locationURL = sourceURL;
+ if (contributionRepository != null && storeInRepository) {
+ if (contributionStream == null) {
+ locationURL = contributionRepository.store(contributionURI, sourceURL);
+ } else {
+ locationURL = contributionRepository.store(contributionURI, sourceURL, contributionStream);
+ }
+ }
+
+ //initialize contribution based on it's metadata if available
+ Contribution contribution = readContributionMetadata(locationURL);
+
+ // Create contribution model resolver
+ if (modelResolver == null) {
+ //FIXME Remove this domain resolver, visibility of policy declarations should be handled by
+ // the contribution import/export mechanism instead of this domainResolver hack.
+ modelResolver = new ExtensibleModelResolver(contribution, modelResolvers, modelFactories, policyDefinitionsResolver);
+ }
+
+ //set contribution initial information
+ contribution.setURI(contributionURI);
+ contribution.setLocation(locationURL.toString());
+ contribution.setModelResolver(modelResolver);
+
+ List<URI> contributionArtifacts = null;
+
+ //NOTE: if a contribution is stored on the repository
+ //the stream would be consumed at this point
+ if (storeInRepository || contributionStream == null) {
+ URLConnection connection = sourceURL.openConnection();
+ connection.setUseCaches(false);
+ // Allow access to open URL stream. Add FilePermission to added to security policy file.
+ final URLConnection finalConnection = connection;
+ try {
+ contributionStream = AccessController.doPrivileged(new PrivilegedExceptionAction<InputStream>() {
+ public InputStream run() throws IOException {
+ return finalConnection.getInputStream();
+ }
+ });
+ } catch (PrivilegedActionException e) {
+ throw (IOException)e.getException();
+ }
+
+ try {
+ // process the contribution
+ contributionArtifacts = this.packageProcessor.getArtifacts(locationURL, contributionStream);
+ } finally {
+ IOHelper.closeQuietly(contributionStream);
+ contributionStream = null;
+ }
+ } else {
+ // process the contribution
+ contributionArtifacts = this.packageProcessor.getArtifacts(locationURL, contributionStream);
+ }
+
+ // Read all artifacts in the contribution
+ try {
+ // Allow access to read system properties. Requires PropertyPermission in security policy.
+ // Any security exceptions are caught and wrapped as ContributionException.
+ processReadPhase(contribution, contributionArtifacts);
+ } catch ( Exception e ) {
+ throw new ContributionException(e);
+ }
+
+ //
+ this.contributionListener.contributionAdded(this.contributionRepository, contribution);
+
+ // Resolve them
+ processResolvePhase(contribution);
+
+ // Add all composites under META-INF/sca-deployables to the
+ // list of deployables
+ String prefix = Contribution.SCA_CONTRIBUTION_DEPLOYABLES;
+ for (Artifact artifact : contribution.getArtifacts()) {
+ if (artifact.getModel() instanceof Composite) {
+ if (artifact.getURI().startsWith(prefix)) {
+ Composite composite = (Composite)artifact.getModel();
+ if (!contribution.getDeployables().contains(composite)) {
+ contribution.getDeployables().add(composite);
+ }
+ }
+ }
+ }
+
+ // store the contribution on the registry
+ this.contributionRepository.addContribution(contribution);
+
+ return contribution;
+ }
+
+ /**
+ * This utility method process each artifact and delegates to proper
+ * artifactProcessor to read the model and generate the in-memory representation
+ *
+ * @param contribution
+ * @param artifacts
+ * @throws ContributionException
+ * @throws MalformedURLException
+ */
+ private void processReadPhase(Contribution contribution, List<URI> artifacts) throws ContributionException,
+ MalformedURLException, XMLStreamException {
+
+ ModelResolver modelResolver = contribution.getModelResolver();
+ URL contributionURL = new URL(contribution.getLocation());
+
+ List<URI> compositeUris = new ArrayList<URI>();
+
+ Object model = null;
+ for (URI anArtifactUri : artifacts) {
+ if ( anArtifactUri.toString().endsWith(COMPOSITE_FILE_EXTN)) {
+ compositeUris.add(anArtifactUri);
+ } else {
+ URL artifactURL = packageProcessor.getArtifactURL(new URL(contribution.getLocation()), anArtifactUri);
+
+ // Add the deployed artifact model to the resolver
+ Artifact artifact = this.contributionFactory.createArtifact();
+ artifact.setURI(anArtifactUri.toString());
+ artifact.setLocation(artifactURL.toString());
+ contribution.getArtifacts().add(artifact);
+ modelResolver.addModel(artifact);
+
+ model = this.artifactProcessor.read(contributionURL, anArtifactUri, artifactURL);
+
+ if (model != null) {
+ artifact.setModel(model);
+
+ // Add the loaded model to the model resolver
+ modelResolver.addModel(model);
+
+ // Add policy definitions to the list of policy definitions
+ if (model instanceof SCADefinitions) {
+ policyDefinitions.add(model);
+
+ SCADefinitions definitions = (SCADefinitions)model;
+ for (Intent intent : definitions.getPolicyIntents() ) {
+ policyDefinitionsResolver.addModel(intent);
+ }
+
+ for (PolicySet policySet : definitions.getPolicySets() ) {
+ policyDefinitionsResolver.addModel(policySet);
+ }
+
+ for (IntentAttachPointType attachPointType : definitions.getBindingTypes() ) {
+ policyDefinitionsResolver.addModel(attachPointType);
+ }
+
+ for (IntentAttachPointType attachPointType : definitions.getImplementationTypes() ) {
+ policyDefinitionsResolver.addModel(attachPointType);
+ }
+ }
+ }
+ }
+ }
+
+ for (URI anArtifactUri : compositeUris) {
+ URL artifactURL = packageProcessor.getArtifactURL(new URL(contribution.getLocation()), anArtifactUri);
+
+ // Add the deployed artifact model to the resolver
+ Artifact artifact = this.contributionFactory.createArtifact();
+ artifact.setURI(anArtifactUri.toString());
+ artifact.setLocation(artifactURL.toString());
+ contribution.getArtifacts().add(artifact);
+ modelResolver.addModel(artifact);
+
+ model = this.artifactProcessor.read(contributionURL, anArtifactUri, artifactURL);
+ if (model != null) {
+ artifact.setModel(model);
+ // Add the loaded model to the model resolver
+ modelResolver.addModel(model);
+ }
+ }
+ }
+
+ /**
+ * This utility method process each artifact and delegates to proper
+ * artifactProcessor to resolve the model references
+ *
+ * @param contribution
+ * @throws ContributionException
+ */
+ @SuppressWarnings("unchecked")
+ private void processResolvePhase(Contribution contribution) throws ContributionException {
+ List<Artifact> composites = new ArrayList<Artifact>();
+
+ // for each artifact that was processed on the contribution
+ for (Artifact artifact : contribution.getArtifacts()) {
+ //leave the composites to be resolved at the end
+ if (artifact.getURI().endsWith(".composite")) {
+ composites.add(artifact);
+ } else {
+ // resolve the model object
+ if (artifact.getModel() != null) {
+ // System.out.println("Processing Resolve Phase : " + artifact.getURI());
+ this.artifactProcessor.resolve(artifact.getModel(), contribution.getModelResolver());
+ }
+ }
+ }
+
+ //process each composite file
+ for (Artifact artifact : composites) {
+ // resolve the model object
+ if (artifact.getModel() != null) {
+ this.artifactProcessor.resolve(artifact.getModel(), contribution.getModelResolver());
+ }
+ }
+
+ //resolve deployables from contribution metadata
+ List<Composite> resolvedDeployables = new ArrayList<Composite>();
+ for (Composite deployableComposite : contribution.getDeployables()) {
+ Composite resolvedDeployable =
+ contribution.getModelResolver().resolveModel(Composite.class, deployableComposite);
+
+ resolvedDeployables.add(resolvedDeployable);
+ }
+ contribution.getDeployables().clear();
+ contribution.getDeployables().addAll(resolvedDeployables);
+ }
+}
diff --git a/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/impl/PackageTypeDescriberImpl.java b/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/impl/PackageTypeDescriberImpl.java
new file mode 100644
index 0000000000..9f04c4af27
--- /dev/null
+++ b/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/impl/PackageTypeDescriberImpl.java
@@ -0,0 +1,120 @@
+/*
+ * 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.contribution.service.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.tuscany.sca.contribution.PackageType;
+import org.apache.tuscany.sca.contribution.service.TypeDescriber;
+import org.apache.tuscany.sca.contribution.service.util.FileHelper;
+
+/**
+ * Implementation of the content describer for contribution packages
+ *
+ * @version $Rev$ $Date$
+ */
+public class PackageTypeDescriberImpl implements TypeDescriber {
+ private final Map<String, String> contentTypeRegistry = new HashMap<String, String>();
+
+ public PackageTypeDescriberImpl() {
+ super();
+ init();
+ }
+
+ /**
+ * Initialize contentType registry with know types based on known file extensions
+ */
+ private void init() {
+ contentTypeRegistry.put("JAR", PackageType.JAR);
+ contentTypeRegistry.put("WAR", PackageType.JAR);
+ }
+
+ protected String resolveContentyTypeByExtension(URL resourceURL) {
+ String artifactExtension = FileHelper.getExtension(resourceURL.getPath());
+ if (artifactExtension == null) {
+ return null;
+ }
+ return contentTypeRegistry.get(artifactExtension.toUpperCase());
+ }
+
+ /**
+ * Build contentType for a specific resource. We first check if the file is a supported one
+ * (looking into our registry based on resource extension) If not found, we try to check file
+ * contentType Or we return defaultContentType provided
+ *
+ * @param resourceURL The artifact URL
+ * @param defaultContentType The default content type if we can't find the correct one
+ * @return The content type
+ */
+ public String getType(URL resourceURL, String defaultContentType) {
+ URLConnection connection = null;
+ String contentType = defaultContentType;
+ final String urlProtocol = resourceURL.getProtocol();
+
+ if (urlProtocol.equals("file")) {
+ final File fileOrDir = FileHelper.toFile(resourceURL);
+ // Allow privileged access to test file. Requires FilePermissions in security policy.
+ Boolean isDirectory = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
+ public Boolean run() {
+ return fileOrDir.isDirectory();
+ }
+ });
+ if (isDirectory) {
+ // Special case : contribution is a folder
+ contentType = PackageType.FOLDER;
+ }
+
+ String type = resolveContentyTypeByExtension(resourceURL);
+ if (type != null) {
+ return type;
+ }
+ } else if (urlProtocol.equals("bundle") || urlProtocol.equals("bundleresource")) {
+ contentType = PackageType.BUNDLE;
+ } else {
+ contentType = resolveContentyTypeByExtension(resourceURL);
+ if (contentType == null) {
+ try {
+ connection = resourceURL.openConnection();
+ connection.setUseCaches(false);
+ contentType = connection.getContentType();
+
+ if (contentType == null || contentType.equals("content/unknown")) {
+ // here we couldn't figure out from our registry or from URL and it's not a
+ // special file
+ // return defaultContentType if provided
+ contentType = defaultContentType;
+ }
+ } catch (IOException io) {
+ // could not access artifact, just ignore and we will return
+ // null contentType
+ }
+ }
+ }
+ return contentType == null ? defaultContentType : contentType;
+ }
+
+}
diff --git a/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/util/FileHelper.java b/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/util/FileHelper.java
new file mode 100644
index 0000000000..cd5e87ca42
--- /dev/null
+++ b/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/util/FileHelper.java
@@ -0,0 +1,701 @@
+/*
+ * 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.contribution.service.util;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.regex.Pattern;
+
+public class FileHelper {
+ /**
+ * The extension separator character.
+ */
+ private static final char EXTENSION_SEPARATOR = '.';
+
+ /**
+ * The Unix separator character.
+ */
+ private static final char UNIX_SEPARATOR = '/';
+
+ /**
+ * The Windows separator character.
+ */
+ private static final char WINDOWS_SEPARATOR = '\\';
+
+ /**
+ * Returns the index of the last directory separator character.
+ * <p>
+ * This method will handle a file in either Unix or Windows format. The
+ * position of the last forward or backslash is returned.
+ * <p>
+ * The output will be the same irrespective of the machine that the code is
+ * running on.
+ *
+ * @param filename the filename to find the last path separator in, null
+ * returns -1
+ * @return the index of the last separator character, or -1 if there is no
+ * such character
+ */
+ public static int indexOfLastSeparator(String filename) {
+ if (filename == null) {
+ return -1;
+ }
+ int lastUnixPos = filename.lastIndexOf(UNIX_SEPARATOR);
+ int lastWindowsPos = filename.lastIndexOf(WINDOWS_SEPARATOR);
+ return Math.max(lastUnixPos, lastWindowsPos);
+ }
+
+ /**
+ * Returns the index of the last extension separator character, which is a
+ * dot.
+ * <p>
+ * This method also checks that there is no directory separator after the
+ * last dot. To do this it uses {@link #indexOfLastSeparator(String)} which
+ * will handle a file in either Unix or Windows format.
+ * <p>
+ * The output will be the same irrespective of the machine that the code is
+ * running on.
+ *
+ * @param filename the filename to find the last path separator in, null
+ * returns -1
+ * @return the index of the last separator character, or -1 if there is no
+ * such character
+ */
+ public static int indexOfExtension(String filename) {
+ if (filename == null) {
+ return -1;
+ }
+ int extensionPos = filename.lastIndexOf(EXTENSION_SEPARATOR);
+ int lastSeparator = indexOfLastSeparator(filename);
+ return lastSeparator > extensionPos ? -1 : extensionPos;
+ }
+
+ /**
+ * Gets the name minus the path from a full filename.
+ * <p>
+ * This method will handle a file in either Unix or Windows format. The text
+ * after the last forward or backslash is returned.
+ *
+ * <pre>
+ * a/b/c.txt --&gt; c.txt
+ * a.txt --&gt; a.txt
+ * a/b/c --&gt; c
+ * a/b/c/ --&gt; &quot;&quot;
+ * </pre>
+ *
+ * <p>
+ * The output will be the same irrespective of the machine that the code is
+ * running on.
+ *
+ * @param fileName the filename to query, null returns null
+ * @return the name of the file without the path, or an empty string if none
+ * exists
+ */
+ public static String getName(String fileName) {
+ if (fileName == null) {
+ return null;
+ }
+ int index = indexOfLastSeparator(fileName);
+ return fileName.substring(index + 1);
+ }
+
+ /**
+ * Gets the extension of a filename.
+ * <p>
+ * This method returns the textual part of the filename after the last dot.
+ * There must be no directory separator after the dot.
+ *
+ * <pre>
+ * foo.txt --&gt; &quot;txt&quot;
+ * a/b/c.jpg --&gt; &quot;jpg&quot;
+ * a/b.txt/c --&gt; &quot;&quot;
+ * a/b/c --&gt; &quot;&quot;
+ * </pre>
+ *
+ * <p>
+ * The output will be the same irrespective of the machine that the code is
+ * running on.
+ *
+ * @param filename the filename to retrieve the extension of.
+ * @return the extension of the file or an empty string if none exists.
+ */
+ public static String getExtension(String filename) {
+ if (filename == null) {
+ return null;
+ }
+ int index = indexOfExtension(filename);
+ if (index == -1) {
+ return "";
+ } else {
+ return filename.substring(index + 1);
+ }
+ }
+
+ /**
+ * Make a directory, including any necessary but nonexistent parent
+ * directories. If there already exists a file with specified name or the
+ * directory cannot be created then an exception is thrown.
+ *
+ * @param directory directory to create, not null
+ * @throws NullPointerException if the directory is null
+ * @throws IOException if the directory cannot be created
+ */
+ public static void forceMkdir(File directory) throws IOException {
+ if (directory.exists()) {
+ if (directory.isFile()) {
+ String message =
+ "File " + directory + " exists and is " + "not a directory. Unable to create directory.";
+ throw new IOException(message);
+ }
+ } else {
+ if (!directory.mkdirs()) {
+ String message = "Unable to create directory " + directory;
+ throw new IOException(message);
+ }
+ }
+ }
+
+ /**
+ * Delete a file. If file is a directory, delete it and all sub-directories.
+ * <p>
+ * The difference between File.delete() and this method are:
+ * <ul>
+ * <li>A directory to be deleted does not have to be empty.</li>
+ * <li>You get exceptions when a file or directory cannot be deleted.
+ * (java.io.File methods returns a boolean)</li>
+ * </ul>
+ *
+ * @param file file or directory to delete, not null
+ * @throws NullPointerException if the directory is null
+ * @throws IOException in case deletion is unsuccessful
+ */
+ public static void forceDelete(File file) throws IOException {
+ if (file.isDirectory()) {
+ deleteDirectory(file);
+ } else {
+ if (!file.exists()) {
+ throw new FileNotFoundException("File does not exist: " + file);
+ }
+ if (!file.delete()) {
+ String message = "Unable to delete file: " + file;
+ throw new IOException(message);
+ }
+ }
+ }
+
+ /**
+ * Convert from a <code>URL</code> to a <code>File</code>.
+ * <p>
+ * From version 1.1 this method will decode the URL. Syntax such as
+ * <code>file:///my%20docs/file.txt</code> will be correctly decoded to
+ * <code>/my docs/file.txt</code>.
+ *
+ * @param url the file URL to convert, null returns null
+ * @return the equivalent <code>File</code> object, or <code>null</code>
+ * if the URL's protocol is not <code>file</code>
+ * @throws IllegalArgumentException if the file is incorrectly encoded
+ */
+ public static File toFile(URL url) {
+ if (url == null || !url.getProtocol().equals("file")) {
+ return null;
+ } else {
+ String filename = url.getFile().replace('/', File.separatorChar);
+ int pos = 0;
+ while ((pos = filename.indexOf('%', pos)) >= 0) { // NOPMD
+ if (pos + 2 < filename.length()) {
+ String hexStr = filename.substring(pos + 1, pos + 3);
+ char ch = (char)Integer.parseInt(hexStr, 16);
+ filename = filename.substring(0, pos) + ch + filename.substring(pos + 3);
+ }
+ }
+ return new File(filename);
+ }
+ }
+
+ public static FileFilter getFileFilter(String regExp, boolean ignoreCase) {
+ return new RegExpFilter(regExp, ignoreCase);
+ }
+
+ /**
+ * A regular-expression based resource filter
+ */
+ public static class RegExpFilter implements FileFilter {
+ private Pattern pattern;
+
+ public RegExpFilter(Pattern pattern) {
+ this.pattern = pattern;
+ }
+
+ public RegExpFilter(String patternStr, boolean ignoreCase) {
+ this.pattern = Pattern.compile(patternStr, ignoreCase ? Pattern.CASE_INSENSITIVE : 0);
+ }
+
+ public boolean accept(File file) {
+ return pattern.matcher(file.getName()).matches();
+ }
+
+ /**
+ * Convert wildcard into a regex pattern
+ *
+ * @param str
+ * @return
+ */
+ public static RegExpFilter getWildcardFilter(String str, boolean ignoreCase) {
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < str.length(); i++) {
+ char ch = str.charAt(i);
+ if (ch == '?') {
+ buffer.append('.');
+ } else if (ch == '*') {
+ buffer.append(".*");
+ } else {
+ buffer.append(ch);
+ }
+ }
+ return new RegExpFilter(buffer.toString(), ignoreCase);
+ }
+
+ }
+
+ /**
+ * Clean a directory without deleting it.
+ *
+ * @param directory directory to clean
+ * @throws IOException in case cleaning is unsuccessful
+ */
+ public static void cleanDirectory(File directory) throws IOException {
+ if (!directory.exists()) {
+ String message = directory + " does not exist";
+ throw new IllegalArgumentException(message);
+ }
+
+ if (!directory.isDirectory()) {
+ String message = directory + " is not a directory";
+ throw new IllegalArgumentException(message);
+ }
+
+ File[] files = directory.listFiles();
+ if (files == null) { // null if security restricted
+ throw new IOException("Failed to list contents of " + directory);
+ }
+
+ IOException exception = null;
+ for (int i = 0; i < files.length; i++) {
+ File file = files[i];
+ try {
+ forceDelete(file);
+ } catch (IOException ioe) {
+ exception = ioe;
+ }
+ }
+
+ if (null != exception) {
+ throw exception;
+ }
+ }
+
+ /**
+ * Clean a directory without deleting it.
+ *
+ * @param directory directory to clean, must not be <code>null</code>
+ * @throws NullPointerException if the directory is <code>null</code>
+ * @throws IOException in case cleaning is unsuccessful
+ */
+ private static void cleanDirectoryOnExit(File directory) throws IOException {
+ if (!directory.exists()) {
+ String message = directory + " does not exist";
+ throw new IllegalArgumentException(message);
+ }
+
+ if (!directory.isDirectory()) {
+ String message = directory + " is not a directory";
+ throw new IllegalArgumentException(message);
+ }
+
+ File[] files = directory.listFiles();
+ if (files == null) { // null if security restricted
+ throw new IOException("Failed to list contents of " + directory);
+ }
+
+ IOException exception = null;
+ for (int i = 0; i < files.length; i++) {
+ File file = files[i];
+ try {
+ forceDeleteOnExit(file);
+ } catch (IOException ioe) {
+ exception = ioe;
+ }
+ }
+
+ if (null != exception) {
+ throw exception;
+ }
+ }
+
+ /**
+ * Copies a whole directory to a new location preserving the file dates.
+ * <p>
+ * This method copies the specified directory and all its child directories
+ * and files to the specified destination. The destination is the new
+ * location and name of the directory.
+ * <p>
+ * The destination directory is created if it does not exist. If the
+ * destination directory did exist, then this method merges the source with
+ * the destination, with the source taking precedence.
+ *
+ * @param srcDir an existing directory to copy, must not be
+ * <code>null</code>
+ * @param destDir the new directory, must not be <code>null</code>
+ * @throws NullPointerException if source or destination is
+ * <code>null</code>
+ * @throws IOException if source or destination is invalid
+ * @throws IOException if an IO error occurs during copying
+ * @since Commons IO 1.1
+ */
+ public static void copyDirectory(File srcDir, File destDir) throws IOException {
+ copyDirectory(srcDir, destDir, true);
+ }
+
+ /**
+ * Copies a whole directory to a new location.
+ * <p>
+ * This method copies the contents of the specified source directory to
+ * within the specified destination directory.
+ * <p>
+ * The destination directory is created if it does not exist. If the
+ * destination directory did exist, then this method merges the source with
+ * the destination, with the source taking precedence.
+ *
+ * @param srcDir an existing directory to copy, must not be
+ * <code>null</code>
+ * @param destDir the new directory, must not be <code>null</code>
+ * @param preserveFileDate true if the file date of the copy should be the
+ * same as the original
+ * @throws NullPointerException if source or destination is
+ * <code>null</code>
+ * @throws IOException if source or destination is invalid
+ * @throws IOException if an IO error occurs during copying
+ * @since Commons IO 1.1
+ */
+ public static void copyDirectory(File srcDir, File destDir, boolean preserveFileDate) throws IOException {
+ if (srcDir == null) {
+ throw new NullPointerException("Source must not be null");
+ }
+ if (destDir == null) {
+ throw new NullPointerException("Destination must not be null");
+ }
+ if (!srcDir.exists()) {
+ throw new FileNotFoundException("Source '" + srcDir + "' does not exist");
+ }
+ if (!srcDir.isDirectory()) {
+ throw new IOException("Source '" + srcDir + "' exists but is not a directory");
+ }
+ if (srcDir.getCanonicalPath().equals(destDir.getCanonicalPath())) {
+ throw new IOException("Source '" + srcDir + "' and destination '" + destDir + "' are the same");
+ }
+ doCopyDirectory(srcDir, destDir, preserveFileDate);
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Copies a directory to within another directory preserving the file dates.
+ * <p>
+ * This method copies the source directory and all its contents to a
+ * directory of the same name in the specified destination directory.
+ * <p>
+ * The destination directory is created if it does not exist. If the
+ * destination directory did exist, then this method merges the source with
+ * the destination, with the source taking precedence.
+ *
+ * @param srcDir an existing directory to copy, must not be
+ * <code>null</code>
+ * @param destDir the directory to place the copy in, must not be
+ * <code>null</code>
+ * @throws NullPointerException if source or destination is
+ * <code>null</code>
+ * @throws IOException if source or destination is invalid
+ * @throws IOException if an IO error occurs during copying
+ * @since Commons IO 1.2
+ */
+ public static void copyDirectoryToDirectory(File srcDir, File destDir) throws IOException {
+ if (srcDir == null) {
+ throw new NullPointerException("Source must not be null");
+ }
+ if (!(srcDir.exists() && srcDir.isDirectory())) {
+ throw new IllegalArgumentException("Source '" + destDir + "' is not a directory");
+ }
+ if (destDir == null) {
+ throw new NullPointerException("Destination must not be null");
+ }
+ if (!(destDir.exists() && destDir.isDirectory())) {
+ throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory");
+ }
+ copyDirectory(srcDir, new File(destDir, srcDir.getName()), true);
+ }
+
+ /**
+ * Copies a file to a new location preserving the file date.
+ * <p>
+ * This method copies the contents of the specified source file to the
+ * specified destination file. The directory holding the destination file is
+ * created if it does not exist. If the destination file exists, then this
+ * method will overwrite it.
+ *
+ * @param srcFile an existing file to copy, must not be <code>null</code>
+ * @param destFile the new file, must not be <code>null</code>
+ * @throws NullPointerException if source or destination is
+ * <code>null</code>
+ * @throws IOException if source or destination is invalid
+ * @throws IOException if an IO error occurs during copying
+ * @see #copyFileToDirectory(File, File)
+ */
+ public static void copyFile(File srcFile, File destFile) throws IOException {
+ copyFile(srcFile, destFile, true);
+ }
+
+ /**
+ * Copies a file to a new location.
+ * <p>
+ * This method copies the contents of the specified source file to the
+ * specified destination file. The directory holding the destination file is
+ * created if it does not exist. If the destination file exists, then this
+ * method will overwrite it.
+ *
+ * @param srcFile an existing file to copy, must not be <code>null</code>
+ * @param destFile the new file, must not be <code>null</code>
+ * @param preserveFileDate true if the file date of the copy should be the
+ * same as the original
+ * @throws NullPointerException if source or destination is
+ * <code>null</code>
+ * @throws IOException if source or destination is invalid
+ * @throws IOException if an IO error occurs during copying
+ * @see #copyFileToDirectory(File, File, boolean)
+ */
+ public static void copyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException {
+ if (srcFile == null) {
+ throw new NullPointerException("Source must not be null");
+ }
+ if (destFile == null) {
+ throw new NullPointerException("Destination must not be null");
+ }
+ if (!srcFile.exists()) {
+ throw new FileNotFoundException("Source '" + srcFile + "' does not exist");
+ }
+ if (srcFile.isDirectory()) {
+ throw new IOException("Source '" + srcFile + "' exists but is a directory");
+ }
+ if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) {
+ throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same");
+ }
+ if (!(destFile.getParentFile() != null && destFile.getParentFile().exists())) {
+ if (!destFile.getParentFile().mkdirs()) { //NOPMD
+ throw new IOException("Destination '" + destFile + "' directory cannot be created");
+ }
+ }
+ if (!(destFile.exists() && destFile.canWrite())) {
+ throw new IOException("Destination '" + destFile + "' exists but is read-only");
+ }
+ doCopyFile(srcFile, destFile, preserveFileDate);
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Copies a file to a directory preserving the file date.
+ * <p>
+ * This method copies the contents of the specified source file to a file of
+ * the same name in the specified destination directory. The destination
+ * directory is created if it does not exist. If the destination file
+ * exists, then this method will overwrite it.
+ *
+ * @param srcFile an existing file to copy, must not be <code>null</code>
+ * @param destDir the directory to place the copy in, must not be
+ * <code>null</code>
+ * @throws NullPointerException if source or destination is null
+ * @throws IOException if source or destination is invalid
+ * @throws IOException if an IO error occurs during copying
+ * @see #copyFile(File, File, boolean)
+ */
+ public static void copyFileToDirectory(File srcFile, File destDir) throws IOException {
+ copyFileToDirectory(srcFile, destDir, true);
+ }
+
+ /**
+ * Copies a file to a directory optionally preserving the file date.
+ * <p>
+ * This method copies the contents of the specified source file to a file of
+ * the same name in the specified destination directory. The destination
+ * directory is created if it does not exist. If the destination file
+ * exists, then this method will overwrite it.
+ *
+ * @param srcFile an existing file to copy, must not be <code>null</code>
+ * @param destDir the directory to place the copy in, must not be
+ * <code>null</code>
+ * @param preserveFileDate true if the file date of the copy should be the
+ * same as the original
+ * @throws NullPointerException if source or destination is
+ * <code>null</code>
+ * @throws IOException if source or destination is invalid
+ * @throws IOException if an IO error occurs during copying
+ * @see #copyFile(File, File, boolean)
+ * @since Commons IO 1.3
+ */
+ public static void copyFileToDirectory(File srcFile, File destDir, boolean preserveFileDate) throws IOException {
+ if (destDir == null) {
+ throw new NullPointerException("Destination must not be null");
+ }
+ if (!(destDir.exists() && destDir.isDirectory())) {
+ throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory");
+ }
+ copyFile(srcFile, new File(destDir, srcFile.getName()), preserveFileDate);
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Recursively delete a directory.
+ *
+ * @param directory directory to delete
+ * @throws IOException in case deletion is unsuccessful
+ */
+ public static void deleteDirectory(File directory) throws IOException {
+ if (!directory.exists()) {
+ return;
+ }
+
+ cleanDirectory(directory);
+ if (!directory.delete()) {
+ String message = "Unable to delete directory " + directory + ".";
+ throw new IOException(message);
+ }
+ }
+
+ /**
+ * Recursively schedule directory for deletion on JVM exit.
+ *
+ * @param directory directory to delete, must not be <code>null</code>
+ * @throws NullPointerException if the directory is <code>null</code>
+ * @throws IOException in case deletion is unsuccessful
+ */
+ private static void deleteDirectoryOnExit(File directory) throws IOException {
+ if (!directory.exists()) {
+ return;
+ }
+
+ cleanDirectoryOnExit(directory);
+ directory.deleteOnExit();
+ }
+
+ /**
+ * Internal copy directory method.
+ *
+ * @param srcDir the validated source directory, must not be
+ * <code>null</code>
+ * @param destDir the validated destination directory, must not be
+ * <code>null</code>
+ * @param preserveFileDate whether to preserve the file date
+ * @throws IOException if an error occurs
+ * @since Commons IO 1.1
+ */
+ private static void doCopyDirectory(File srcDir, File destDir, boolean preserveFileDate) throws IOException {
+ if (destDir.exists()) {
+ if (!destDir.isDirectory()) {
+ throw new IOException("Destination '" + destDir + "' exists but is not a directory");
+ }
+ } else {
+ if (!destDir.mkdirs()) {
+ throw new IOException("Destination '" + destDir + "' directory cannot be created");
+ }
+ if (preserveFileDate) {
+ destDir.setLastModified(srcDir.lastModified());
+ }
+ }
+ if (!destDir.canWrite()) {
+ throw new IOException("Destination '" + destDir + "' cannot be written to");
+ }
+ // recurse
+ File[] files = srcDir.listFiles();
+ if (files == null) { // null if security restricted
+ throw new IOException("Failed to list contents of " + srcDir);
+ }
+ for (int i = 0; i < files.length; i++) {
+ File copiedFile = new File(destDir, files[i].getName());
+ if (files[i].isDirectory()) {
+ doCopyDirectory(files[i], copiedFile, preserveFileDate);
+ } else {
+ doCopyFile(files[i], copiedFile, preserveFileDate);
+ }
+ }
+ }
+
+ /**
+ * Internal copy file method.
+ *
+ * @param srcFile the validated source file, must not be <code>null</code>
+ * @param destFile the validated destination file, must not be
+ * <code>null</code>
+ * @param preserveFileDate whether to preserve the file date
+ * @throws IOException if an error occurs
+ */
+ private static void doCopyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException {
+ if (destFile.exists() && destFile.isDirectory()) {
+ throw new IOException("Destination '" + destFile + "' exists but is a directory");
+ }
+
+ FileInputStream input = new FileInputStream(srcFile);
+ try {
+ FileOutputStream output = new FileOutputStream(destFile);
+ try {
+ IOHelper.copy(input, output);
+ } finally {
+ IOHelper.closeQuietly(output);
+ }
+ } finally {
+ IOHelper.closeQuietly(input);
+ }
+
+ if (srcFile.length() != destFile.length()) {
+ throw new IOException("Failed to copy full contents from '" + srcFile + "' to '" + destFile + "'");
+ }
+ if (preserveFileDate) {
+ destFile.setLastModified(srcFile.lastModified());
+ }
+ }
+
+ /**
+ * Schedule a file to be deleted when JVM exits. If file is directory delete
+ * it and all sub-directories.
+ *
+ * @param file file or directory to delete, must not be <code>null</code>
+ * @throws NullPointerException if the file is <code>null</code>
+ * @throws IOException in case deletion is unsuccessful
+ */
+ public static void forceDeleteOnExit(File file) throws IOException {
+ if (file.isDirectory()) {
+ deleteDirectoryOnExit(file);
+ } else {
+ file.deleteOnExit();
+ }
+ }
+
+}
diff --git a/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/util/IOHelper.java b/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/util/IOHelper.java
new file mode 100644
index 0000000000..630d1f6bf4
--- /dev/null
+++ b/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/java/org/apache/tuscany/sca/contribution/service/util/IOHelper.java
@@ -0,0 +1,190 @@
+/*
+ * 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.contribution.service.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.jar.JarFile;
+
+public class IOHelper {
+ /**
+ * The default buffer size to use.
+ */
+ private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
+
+ /**
+ * Unconditionally close an <code>InputStream</code>.
+ * <p>
+ * Equivalent to {@link InputStream#close()}, except any exceptions will be ignored.
+ * This is typically used in finally blocks.
+ *
+ * @param input the InputStream to close, may be null or already closed
+ */
+ public static void closeQuietly(InputStream input) {
+ try {
+ if (input != null) {
+ input.close();
+ }
+ } catch (IOException ioe) {
+ // ignore
+ }
+ }
+
+ /**
+ * Unconditionally close an <code>OutputStream</code>.
+ * <p>
+ * Equivalent to {@link OutputStream#close()}, except any exceptions will be ignored.
+ * This is typically used in finally blocks.
+ *
+ * @param output the OutputStream to close, may be null or already closed
+ */
+ public static void closeQuietly(OutputStream output) {
+ try {
+ if (output != null) {
+ output.close();
+ }
+ } catch (IOException ioe) {
+ // ignore
+ }
+ }
+
+ /**
+ * Copy bytes from an <code>InputStream</code> to an
+ * <code>OutputStream</code>.
+ * <p>
+ * This method buffers the input internally, so there is no need to use a
+ * <code>BufferedInputStream</code>.
+ *
+ * @param input the <code>InputStream</code> to read from
+ * @param output the <code>OutputStream</code> to write to
+ * @return the number of bytes copied
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since Commons IO 1.1
+ */
+ public static int copy(InputStream input, OutputStream output) throws IOException {
+ byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
+ int count = 0;
+ int n = 0;
+ while (-1 != (n = input.read(buffer))) { // NOPMD
+ output.write(buffer, 0, n);
+ count += n;
+ }
+ return count;
+ }
+
+ public static InputStream getInputStream(URL url) throws IOException {
+ return new SafeURLInputStream(url);
+ }
+
+ /**
+ * This class is a workaround for URL stream issue as illustrated below.
+ * InputStream is=url.getInputStream(); is.close(); // This line doesn't close
+ * the JAR file if the URL is a jar entry like "jar:file:/a.jar!/my.composite" We
+ * also need to turn off the JarFile cache.
+ *
+ * @see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4950148
+ *
+ * @version $Rev$ $Date$
+ */
+ public static class SafeURLInputStream extends InputStream {
+ private JarFile jarFile;
+ private InputStream is;
+
+ public SafeURLInputStream(URL url) throws IOException {
+ String protocol = url.getProtocol();
+ if (protocol != null && (protocol.equals("jar"))) {
+ JarURLConnection connection = (JarURLConnection)url.openConnection();
+ // We cannot use cache
+ connection.setUseCaches(false);
+ try {
+ is = connection.getInputStream();
+ } catch (IOException e) {
+ throw e;
+ }
+ jarFile = connection.getJarFile();
+ } else {
+ URLConnection connection = url.openConnection();
+ connection.setUseCaches(false);
+ is = connection.getInputStream();
+ }
+ }
+
+ public SafeURLInputStream(JarURLConnection connection) throws IOException {
+ // We cannot use cache
+ connection.setUseCaches(false);
+ is = connection.getInputStream();
+ jarFile = connection.getJarFile();
+ }
+
+ @Override
+ public int available() throws IOException {
+ return is.available();
+ }
+
+ @Override
+ public void close() throws IOException {
+ is.close();
+ // We need to close the JAR file
+ if (jarFile != null) {
+ jarFile.close();
+ }
+ }
+
+ @Override
+ public synchronized void mark(int readlimit) {
+ is.mark(readlimit);
+ }
+
+ @Override
+ public boolean markSupported() {
+ return is.markSupported();
+ }
+
+ @Override
+ public int read() throws IOException {
+ return is.read();
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ return is.read(b, off, len);
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ return is.read(b);
+ }
+
+ @Override
+ public synchronized void reset() throws IOException {
+ is.reset();
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ return is.skip(n);
+ }
+ }
+} \ No newline at end of file
diff --git a/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/resources/META-INF/services/org.apache.tuscany.sca.contribution.processor.PackageProcessor b/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/resources/META-INF/services/org.apache.tuscany.sca.contribution.processor.PackageProcessor
new file mode 100644
index 0000000000..b644709266
--- /dev/null
+++ b/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/resources/META-INF/services/org.apache.tuscany.sca.contribution.processor.PackageProcessor
@@ -0,0 +1,19 @@
+# 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.
+
+org.apache.tuscany.sca.contribution.processor.impl.FolderContributionProcessor;type=application/vnd.tuscany.folder
+org.apache.tuscany.sca.contribution.processor.impl.JarContributionProcessor;type=application/x-compressed
diff --git a/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/resources/contribution-impl-validation-messages.properties b/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/resources/contribution-impl-validation-messages.properties
new file mode 100644
index 0000000000..158ee2102d
--- /dev/null
+++ b/sandbox/sebastien/java/sca-node/modules/contribution-impl/src/main/resources/contribution-impl-validation-messages.properties
@@ -0,0 +1,28 @@
+#
+#
+# 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.
+#
+#
+ContributionURINull =URI for the contribution is null
+SourceURLNull = Source URL for the contribution is null
+ContributionContentNull = The content of the contribution is null
+PrivilegedActionException = Exception occured due to FilePermissions in security policy file
+RootNotDirectory = The root is not a directory {0}
+IllegalArgumentException = IllegalArgumentException occured due to :
+DomainFileNotFound = Domain file "sca-domain.xml" not found ({0})
+MalformedURLException = MalformedURLException occured due to : \ No newline at end of file