| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.ace.builder; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.jar.Attributes; |
| import java.util.jar.JarInputStream; |
| import java.util.jar.JarOutputStream; |
| import java.util.jar.Manifest; |
| import java.util.zip.ZipEntry; |
| |
| import aQute.bnd.annotation.ConsumerType; |
| |
| /** |
| * Builder for deployment packages. Can handle bundles, resource processors and artifacts. Uses |
| * the builder pattern: |
| * <pre> |
| * OutputStream out = new FileOutputStream("first.dp"); |
| * DeploymentPackageBuilder.createDeploymentPackage("mydp", "1.0") |
| * .addBundle(new URL("http://repository/api-1.1.0.jar")) |
| * .addBundle(new URL("http://repository/impl-1.1.3.jar")) |
| * .addResourceProcessor(new URL("http://repository/rp-1.0.2.jar")) |
| * .addArtifact(new URL("http://artifacts/config/v1.jar"), "rp.pid") |
| * .addArtifact(new URL("http://artifacts/data/v3.jar"), "rp.pid") |
| * .generate(out); |
| * </pre> |
| * For bundles and resource processors, you can simply point to a valid URL and it will |
| * be queried for all required metadata. For artifacts, you need to specify both the URL |
| * and the PID of the resource processor. The builder will use the order you specify for |
| * bundles, resource processors and artifacts, but you don't have to specify all bundles |
| * and resource processors first and then all artifacts. |
| */ |
| @ConsumerType |
| public class DeploymentPackageBuilder { |
| private static final String PREFIX_BUNDLE = "bundle-"; |
| private static final String PREFIX_ARTIFACT = "artifact-"; |
| private static final int BUFFER_SIZE = 32 * 1024; |
| private final String m_symbolicName; |
| private final String m_version; |
| private final List<ArtifactData> m_bundles = new ArrayList<ArtifactData>(); |
| private final List<ArtifactData> m_processors = new ArrayList<ArtifactData>(); |
| private final List<ArtifactData> m_artifacts = new ArrayList<ArtifactData>(); |
| private int m_id = 1; |
| |
| private DeploymentPackageBuilder(String symbolicName, String version) { |
| m_symbolicName = symbolicName; |
| m_version = version; |
| } |
| |
| /** |
| * Creates a new deployment package. |
| * |
| * @param name the name of the deployment package |
| * @param version the version of the deployment package |
| * @return a builder to further add data to the deployment package |
| */ |
| public static DeploymentPackageBuilder createDeploymentPackage(String name, String version) { |
| return new DeploymentPackageBuilder(name, version); |
| } |
| |
| /** |
| * Adds a bundle to the deployment package. |
| * |
| * @param url a url that refers to the bundle |
| * @return a builder to further add data to the deployment package |
| * @throws Exception if something goes wrong while building |
| */ |
| public DeploymentPackageBuilder addBundle(URL url) throws Exception { |
| return addBundleArtifact(url, false); |
| } |
| |
| /** |
| * Adds a resource processor to the deployment package. A resource processor is a special |
| * type of bundle. |
| * |
| * @param url a url that refers to the resource processor |
| * @return a builder to further add data to the deployment package |
| * @throws Exception if something goes wrong while building |
| */ |
| public DeploymentPackageBuilder addResourceProcessor(URL url) throws Exception { |
| return addBundleArtifact(url, true); |
| } |
| |
| /** |
| * Adds an artifact to the deployment package. |
| * |
| * @param url a url that refers to the artifact |
| * @param processorPID the PID of the processor for this artifact |
| * @return a builder to further add data to the deployment package |
| * @throws Exception if something goes wrong while building |
| */ |
| public DeploymentPackageBuilder addArtifact(URL url, String processorPID) throws Exception { |
| String path = url.getPath(); |
| int i = path.lastIndexOf('/'); |
| if (i > 0 && i < (path.length() - 1)) { |
| path = path.substring(i + 1); |
| } |
| String name = PREFIX_ARTIFACT + getUniqueID() + "-" + path; |
| m_artifacts.add(ArtifactData.createArtifact(url, name, processorPID)); |
| return this; |
| } |
| |
| /** |
| * Generates a deployment package and streams it to the output stream you provide. Before |
| * it starts generating, it will first validate that you have actually specified a |
| * resource processor for each type of artifact you provided. |
| * |
| * @param output the output stream to write to |
| * @throws Exception if something goes wrong while validating or generating |
| */ |
| public void generate(OutputStream output) throws Exception { |
| validateArtifacts(); |
| List<ArtifactData> artifacts = new ArrayList<ArtifactData>(); |
| artifacts.addAll(m_bundles); |
| artifacts.addAll(m_processors); |
| artifacts.addAll(m_artifacts); |
| Manifest m = createManifest(artifacts); |
| writeStream(artifacts, m, output); |
| } |
| |
| /** Returns the symbolic name of the deployment package. */ |
| public String getSymbolicName() { |
| return m_symbolicName; |
| } |
| |
| /** Returns the version of the deployment package. */ |
| public String getVersion() { |
| return m_version; |
| } |
| |
| private DeploymentPackageBuilder addBundleArtifact(URL url, boolean isResourceProcessor) throws Exception { |
| JarInputStream jis = null; |
| try { |
| jis = new JarInputStream(url.openStream()); |
| Manifest bundleManifest = jis.getManifest(); |
| if (bundleManifest == null) { |
| throw new Exception("Not a valid manifest in: " + url); |
| } |
| Attributes attributes = bundleManifest.getMainAttributes(); |
| String bundleSymbolicName = getRequiredHeader(attributes, "Bundle-SymbolicName"); |
| String bundleVersion = getRequiredHeader(attributes, "Bundle-Version"); |
| String name = PREFIX_BUNDLE + bundleSymbolicName + "-" + bundleVersion; |
| int i = name.lastIndexOf('/'); |
| if (i > 0 && i < (name.length() - 1)) { |
| name = name.substring(i + 1); |
| } |
| if (isResourceProcessor) { |
| if (!"true".equals(getRequiredHeader(attributes, "DeploymentPackage-Customizer"))) { |
| throw new IOException("Invalid DeploymentPackage-Customizer header in: " + url); |
| } |
| String processorPID = getRequiredHeader(attributes, "Deployment-ProvidesResourceProcessor"); |
| m_processors.add(ArtifactData.createResourceProcessor(url, name, bundleSymbolicName, bundleVersion, processorPID)); |
| } |
| else { |
| m_bundles.add(ArtifactData.createBundle(url, name, bundleSymbolicName, bundleVersion)); |
| } |
| } |
| finally { |
| if (jis != null) { |
| jis.close(); |
| } |
| } |
| return this; |
| } |
| |
| private void validateArtifacts() throws Exception { |
| for (ArtifactData data : m_artifacts) { |
| String pid = data.getProcessorPid(); |
| boolean found = false; |
| for (ArtifactData processor : m_processors) { |
| if (pid.equals(processor.getProcessorPid())) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| throw new Exception("No resource processor found for artifact " + data.getURL() + " with processor PID " + pid); |
| } |
| } |
| } |
| |
| private String getRequiredHeader(Attributes mainAttributes, String headerName) throws Exception { |
| String value = mainAttributes.getValue(headerName); |
| if (value == null || value.equals("")) { |
| throw new Exception("Missing or invalid " + headerName + " header."); |
| } |
| return value; |
| } |
| |
| private Manifest createManifest(List<ArtifactData> files) throws Exception { |
| Manifest manifest = new Manifest(); |
| Attributes main = manifest.getMainAttributes(); |
| main.putValue("Manifest-Version", "1.0"); |
| main.putValue("DeploymentPackage-SymbolicName", m_symbolicName); |
| main.putValue("DeploymentPackage-Version", m_version); |
| |
| for (ArtifactData file : files) { |
| if (file.isBundle()) { |
| Attributes a = new Attributes(); |
| a.putValue("Bundle-SymbolicName", file.getSymbolicName()); |
| a.putValue("Bundle-Version", file.getVersion()); |
| if (file.isCustomizer()) { |
| a.putValue("DeploymentPackage-Customizer", "true"); |
| a.putValue("Deployment-ProvidesResourceProcessor", file.getProcessorPid()); |
| } |
| manifest.getEntries().put(file.getFilename(), a); |
| } |
| else { |
| Attributes a = new Attributes(); |
| a.putValue("Resource-Processor", file.getProcessorPid()); |
| manifest.getEntries().put(file.getFilename(), a); |
| } |
| } |
| return manifest; |
| } |
| |
| private void writeStream(List<ArtifactData> files, Manifest manifest, OutputStream outputStream) throws Exception { |
| JarOutputStream output = null; |
| InputStream fis = null; |
| try { |
| output = new JarOutputStream(outputStream, manifest); |
| byte[] buffer = new byte[BUFFER_SIZE]; |
| for (ArtifactData file : files) { |
| output.putNextEntry(new ZipEntry(file.getFilename())); |
| fis = file.getURL().openStream(); |
| int bytes = fis.read(buffer); |
| while (bytes != -1) { |
| output.write(buffer, 0, bytes); |
| bytes = fis.read(buffer); |
| } |
| fis.close(); |
| output.closeEntry(); |
| fis = null; |
| } |
| } |
| finally { |
| if (fis != null) { |
| fis.close(); |
| } |
| if (output != null) { |
| output.close(); |
| } |
| } |
| } |
| private synchronized int getUniqueID() { |
| return m_id++; |
| } |
| } |