This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.provisioning.model-1.8.0 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-provisioning-model.git
commit ab4c7d8e9caf7cfbf2257f232e5dcba8a7a39b52 Author: Carsten Ziegeler <cziege...@apache.org> AuthorDate: Sat Nov 12 15:09:17 2016 +0000 SLING-6278 : Provide tooling to create an archive with the provisioning model and all artifacts git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/tooling/support/provisioning-model@1769384 13f79535-47bb-0310-9956-ffa450edef68 --- .../sling/provisioning/model/ModelUtility.java | 52 ++++++-- .../provisioning/model/io/ModelArchiveReader.java | 103 ++++++++++++++++ .../provisioning/model/io/ModelArchiveWriter.java | 137 +++++++++++++++++++++ .../sling/provisioning/model/io/package-info.java | 2 +- 4 files changed, 284 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/apache/sling/provisioning/model/ModelUtility.java b/src/main/java/org/apache/sling/provisioning/model/ModelUtility.java index 0503f97..968bfce 100644 --- a/src/main/java/org/apache/sling/provisioning/model/ModelUtility.java +++ b/src/main/java/org/apache/sling/provisioning/model/ModelUtility.java @@ -167,14 +167,14 @@ public abstract class ModelUtility { for(final Feature feature : model.getFeatures() ) { // validate feature if ( feature.getName() == null || feature.getName().isEmpty() ) { - errors.put(feature, "Name is required for a feature."); + addError(errors, feature, "Name is required for a feature."); } // version should be a valid version if ( feature.getVersion() != null ) { try { new Version(feature.getVersion()); } catch ( final IllegalArgumentException iae) { - errors.put(feature, "Version is not a valid version: " + feature.getVersion()); + addError(errors, feature, "Version is not a valid version: " + feature.getVersion()); } } for(final RunMode runMode : feature.getRunModes()) { @@ -193,12 +193,12 @@ public abstract class ModelUtility { hasSpecial = 2; } else { hasSpecial = 2; - errors.put(runMode, "Invalid modes " + Arrays.toString(rm)); + addError(errors, runMode, "Invalid modes " + Arrays.toString(rm)); break; } } else { hasSpecial++; - errors.put(runMode, "Invalid modes " + Arrays.toString(rm)); + addError(errors, runMode, "Invalid modes " + Arrays.toString(rm)); break; } @@ -212,7 +212,7 @@ public abstract class ModelUtility { for(final ArtifactGroup sl : runMode.getArtifactGroups()) { if ( sl.getStartLevel() < 0 ) { - errors.put(sl, "Invalid start level " + sl.getStartLevel()); + addError(errors, sl, "Invalid start level " + sl.getStartLevel()); } for(final Artifact a : sl) { String error = null; @@ -229,7 +229,7 @@ public abstract class ModelUtility { error = (error != null ? error + ", " : "") + "type missing"; } if (error != null) { - errors.put(a, error); + addError(errors, a, error); } } } @@ -246,12 +246,12 @@ public abstract class ModelUtility { error = (error != null ? error + ", " : "") + "configuration properties missing"; } if (error != null) { - errors.put(c, error); + addError(errors, c, error); } } } } - if ( errors.size() == 0 ) { + if ( errors.isEmpty()) { return null; } return errors; @@ -260,7 +260,7 @@ public abstract class ModelUtility { /** * Applies a set of variables to the given model. * All variables that are referenced anywhere within the model are detected and passed to the given variable resolver. - * The variable resolver may look up variables on it's own, or fallback to the variables already defined for the feature. + * The variable resolver may look up variables on it's own, or fall back to the variables already defined for the feature. * All resolved variable values are collected and put to the "variables" section of the resulting model. * @param model Original model * @param resolver Variable resolver @@ -346,4 +346,38 @@ public abstract class ModelUtility { return versionUpdater.process(model); } + /** + * Validates the model and checks that each feature has a valid version. + * + * This method first calls {@link #validate(Model)} and then checks + * that each feature has a version. + * + * @param model The model to validate + * @return A map with errors or {@code null} if valid. + * @since 1.9 + */ + public static Map<Traceable, String> validateIncludingVersion(final Model model) { + Map<Traceable, String> errors = validate(model); + for(final Feature feature : model.getFeatures()) { + if ( feature.getVersion() == null ) { + if ( errors == null ) { + errors = new HashMap<>(); + } + addError(errors, feature, "Feature must have a version."); + } + } + return errors; + } + + /** + * Add an error for the {@code Traceable} to the error map + * @param errors The map of errors + * @param object The traceable object + * @param error The error message + * @since 1.9 + */ + private static void addError(final Map<Traceable, String> errors, final Traceable object, final String error) { + String value = errors.get(object); + errors.put(object, (value == null ? error : value + " " + error)); + } } diff --git a/src/main/java/org/apache/sling/provisioning/model/io/ModelArchiveReader.java b/src/main/java/org/apache/sling/provisioning/model/io/ModelArchiveReader.java new file mode 100644 index 0000000..38d0230 --- /dev/null +++ b/src/main/java/org/apache/sling/provisioning/model/io/ModelArchiveReader.java @@ -0,0 +1,103 @@ +/* + * 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.sling.provisioning.model.io; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.jar.Manifest; + +import org.apache.sling.provisioning.model.Artifact; +import org.apache.sling.provisioning.model.Model; +import org.apache.sling.provisioning.model.ModelUtility; + +/** + * The model archive reader can be used to read an archive based on a model + * The archive contains the model file and all artifacts. + * @since 1.3 + */ +public class ModelArchiveReader { + + public interface ArtifactConsumer { + + /** + * Consume the artifact from the archive + * The input stream must not be closed by the consumer. + * @param artifact The artifact + * @param is The input stream for the artifact + * @throws IOException If the artifact can't be consumed + */ + void consume(Artifact artifact, final InputStream is) throws IOException; + } + + /** + * Read a model archive. + * The input stream is not closed. It is up to the caller to close the input stream. + * @param in The input stream to read from. + * @return The model + * @throws IOException If anything goes wrong + */ + @SuppressWarnings("resource") + public static Model read(final InputStream in, + final ArtifactConsumer consumer) + throws IOException { + Model model = null; + + final JarInputStream jis = new JarInputStream(in); + + // check manifest + final Manifest manifest = jis.getManifest(); + if ( manifest == null ) { + throw new IOException("Not a model archive - manifest is missing."); + } + // check manifest header + final String version = manifest.getMainAttributes().getValue(ModelArchiveWriter.MANIFEST_HEADER); + if ( version == null ) { + throw new IOException("Not a model archive - manifest header is missing."); + } + // validate manifest header + try { + final int number = Integer.valueOf(version); + if ( number < 1 || number > ModelArchiveWriter.ARCHIVE_VERSION ) { + throw new IOException("Not a model archive - invalid manifest header value: " + version); + } + } catch (final NumberFormatException nfe) { + throw new IOException("Not a model archive - invalid manifest header value: " + version); + } + + // read contents + JarEntry entry = null; + while ( ( entry = jis.getNextJarEntry() ) != null ) { + if ( ModelArchiveWriter.MODEL_NAME.equals(entry.getName()) ) { + model = ModelUtility.getEffectiveModel(ModelReader.read(new InputStreamReader(jis, "UTF-8"), null)); + } else if ( !entry.isDirectory() && entry.getName().startsWith(ModelArchiveWriter.ARTIFACTS_PREFIX) ) { // artifact + final Artifact artifact = Artifact.fromMvnUrl("mvn:" + entry.getName().substring(ModelArchiveWriter.ARTIFACTS_PREFIX.length())); + consumer.consume(artifact, jis); + } + jis.closeEntry(); + } + if ( model == null ) { + throw new IOException("Not a model archive - model file is missing."); + } + + // TODO - we could check whether all artifacts from the model are in the archive + + return model; + } +} diff --git a/src/main/java/org/apache/sling/provisioning/model/io/ModelArchiveWriter.java b/src/main/java/org/apache/sling/provisioning/model/io/ModelArchiveWriter.java new file mode 100644 index 0000000..7c6d9e9 --- /dev/null +++ b/src/main/java/org/apache/sling/provisioning/model/io/ModelArchiveWriter.java @@ -0,0 +1,137 @@ +/* + * 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.sling.provisioning.model.io; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +import org.apache.sling.provisioning.model.Artifact; +import org.apache.sling.provisioning.model.ArtifactGroup; +import org.apache.sling.provisioning.model.Feature; +import org.apache.sling.provisioning.model.Model; +import org.apache.sling.provisioning.model.ModelUtility; +import org.apache.sling.provisioning.model.RunMode; +import org.apache.sling.provisioning.model.Traceable; + +/** + * The model archive writer can be used to create an archive based on a model + * The archive contains the model file and all artifacts. + * @since 1.3 + */ +public class ModelArchiveWriter { + + /** The manifest header marking an archive as a model archive. */ + public static final String MANIFEST_HEADER = "Model-Archive-Version"; + + /** Current support version of the model archive. */ + public static final int ARCHIVE_VERSION = 1; + + /** Default extension for model archives. */ + public static final String DEFAULT_EXTENSION = "mar"; + + /** Model name. */ + public static final String MODEL_NAME = "models/feature.model"; + + /** Artifacts prefix. */ + public static final String ARTIFACTS_PREFIX = "artifacts/"; + + public interface ArtifactProvider { + + /** + * Provide an input stream for the artifact. + * The input stream will be closed by the caller. + * @param artifact The artifact + * @return The input stream + * @throws IOException If the input stream can't be provided + */ + InputStream getInputStream(Artifact artifact) throws IOException; + } + + /** + * Create a model archive. + * The output stream will not be closed by this method. The caller + * must call {@link JarOutputStream#close()} or {@link JarOutputStream#finish()} + * on the return output stream. The caller can add additional files through + * the return stream. + * + * In order to create an archive for a model, each feature in the model must + * have a name and a version and the model must be valid, therefore {@link ModelUtility#validateIncludingVersion(Model)} + * is called first. If the model is invalid an {@code IOException} is thrown. + * + * @param out The output stream to write to + * @param model The model to write + * @param baseManifest Optional base manifest used for creating the manifest. + * @param provider The artifact provider + * @return The jar output stream. + * @throws IOException If anything goes wrong + */ + public static JarOutputStream write(final OutputStream out, + final Model model, + final Manifest baseManifest, + final ArtifactProvider provider) + throws IOException { + // check model + final Map<Traceable, String> errors = ModelUtility.validate(model); + if ( errors != null ) { + throw new IOException("Model is not valid: " + errors); + } + + // create manifest + final Manifest manifest = (baseManifest == null ? new Manifest() : new Manifest(baseManifest)); + manifest.getMainAttributes().putValue("Manifest-Version", "1.0"); + manifest.getMainAttributes().putValue(MANIFEST_HEADER, String.valueOf(ARCHIVE_VERSION)); + + // create archive + final JarOutputStream jos = new JarOutputStream(out, manifest); + + // write model first + final JarEntry entry = new JarEntry(MODEL_NAME); + jos.putNextEntry(entry); + final Writer writer = new OutputStreamWriter(jos, "UTF-8"); + ModelWriter.write(writer, model); + writer.flush(); + jos.closeEntry(); + + final byte[] buffer = new byte[1024*1024*256]; + for(final Feature f : model.getFeatures() ) { + for(final RunMode rm : f.getRunModes()) { + for(final ArtifactGroup g : rm.getArtifactGroups()) { + for(final Artifact a : g) { + final JarEntry artifactEntry = new JarEntry(ARTIFACTS_PREFIX + a.getRepositoryPath()); + jos.putNextEntry(artifactEntry); + + try (final InputStream is = provider.getInputStream(a)) { + int l = 0; + while ( (l = is.read(buffer)) > 0 ) { + jos.write(buffer, 0, l); + } + } + jos.closeEntry(); + } + } + } + } + return jos; + } +} diff --git a/src/main/java/org/apache/sling/provisioning/model/io/package-info.java b/src/main/java/org/apache/sling/provisioning/model/io/package-info.java index a100458..748b2f7 100644 --- a/src/main/java/org/apache/sling/provisioning/model/io/package-info.java +++ b/src/main/java/org/apache/sling/provisioning/model/io/package-info.java @@ -17,7 +17,7 @@ * under the License. */ -@org.osgi.annotation.versioning.Version("1.2") +@org.osgi.annotation.versioning.Version("1.3") package org.apache.sling.provisioning.model.io; -- To stop receiving notification emails like this one, please contact "commits@sling.apache.org" <commits@sling.apache.org>.