blob: 4e3382559e83ca1e484df9f69ff89e5719c46659 [file] [log] [blame]
/*
* 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++;
}
}