diff options
Diffstat (limited to '')
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 --> c.txt + * a.txt --> a.txt + * a/b/c --> c + * a/b/c/ --> "" + * </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 --> "txt" + * a/b/c.jpg --> "jpg" + * a/b.txt/c --> "" + * a/b/c --> "" + * </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 |