Add prototype of a installer support for provisioning models
authorCarsten Ziegeler <cziegeler@apache.org>
Fri, 11 Nov 2016 06:09:36 +0000 (06:09 +0000)
committerCarsten Ziegeler <cziegeler@apache.org>
Fri, 11 Nov 2016 06:09:36 +0000 (06:09 +0000)
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1769248 13f79535-47bb-0310-9956-ffa450edef68

pom.xml [new file with mode: 0644]
src/main/java/org/apache/sling/installer/factory/model/impl/AbstractModelTask.java [new file with mode: 0644]
src/main/java/org/apache/sling/installer/factory/model/impl/InstallModelTask.java [new file with mode: 0644]
src/main/java/org/apache/sling/installer/factory/model/impl/ModelTaskFactory.java [new file with mode: 0644]
src/main/java/org/apache/sling/installer/factory/model/impl/ModelTransformer.java [new file with mode: 0644]
src/main/java/org/apache/sling/installer/factory/model/impl/RepositoryAccess.java [new file with mode: 0644]
src/main/java/org/apache/sling/installer/factory/model/impl/UninstallModelTask.java [new file with mode: 0644]

diff --git a/pom.xml b/pom.xml
new file mode 100644 (file)
index 0000000..87fae4a
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>29</version>
+        <relativePath/>
+    </parent>
+
+    <artifactId>org.apache.sling.installer.factory.model</artifactId>
+    <version>0.1.0-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <name>Apache Sling Installer Provisioning Model Support</name>
+    <description> 
+        Provides support for the provisioning model to the Apache Sling OSGi installer
+    </description>
+
+    <properties>
+        <sling.java.version>8</sling.java.version>
+    </properties>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/installer/factories/model</connection>
+        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/trunk/installer/factories/model</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/installer/factories/model</url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.jcr</groupId>
+            <artifactId>jcr</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.installer.core</artifactId>
+            <version>3.8.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.jcr.api</artifactId>
+            <version>2.4.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.jcr.repoinit</artifactId>
+            <version>1.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.repoinit.parser</artifactId>
+            <version>1.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.settings</artifactId>
+            <version>1.2.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.provisioning.model</artifactId>
+            <version>1.7.0</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/src/main/java/org/apache/sling/installer/factory/model/impl/AbstractModelTask.java b/src/main/java/org/apache/sling/installer/factory/model/impl/AbstractModelTask.java
new file mode 100644 (file)
index 0000000..7fe3151
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * 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.installer.factory.model.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.installer.api.tasks.InstallTask;
+import org.apache.sling.installer.api.tasks.TaskResourceGroup;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract class for the tasks.
+ */
+public abstract class AbstractModelTask extends InstallTask {
+
+    /** Logger. */
+    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    private final BundleContext bundleContext;
+
+    private final Map<ServiceReference<?>, Object> services = new HashMap<>();
+
+    public AbstractModelTask(final TaskResourceGroup group,
+            final BundleContext bundleContext) {
+        super(group);
+        this.bundleContext = bundleContext;
+    }
+
+    protected void cleanup() {
+        for(final ServiceReference<?> r : this.services.keySet()) {
+            this.bundleContext.ungetService(r);
+        }
+        this.services.clear();
+    }
+
+    @SuppressWarnings("unchecked")
+    protected <T> T getService(final Class<T> type) {
+        T service = null;
+        final ServiceReference<T> reference = this.bundleContext.getServiceReference(type);
+        if ( reference != null ) {
+            service = (T)this.services.get(reference);
+            if ( service == null ) {
+                service = this.bundleContext.getService(reference);
+                if ( service != null ) {
+                    this.services.put(reference, service);
+                } else {
+                }
+            }
+        }
+        if ( service == null ) {
+            logger.error("Unable to get OSGi service " + type.getName());
+        }
+        return service;
+    }
+
+    protected String getModelName() {
+        final String url = this.getResource().getURL();
+        final int lastSlash = url.lastIndexOf('/');
+        return lastSlash == -1 ? url : url.substring(lastSlash + 1);
+    }
+}
diff --git a/src/main/java/org/apache/sling/installer/factory/model/impl/InstallModelTask.java b/src/main/java/org/apache/sling/installer/factory/model/impl/InstallModelTask.java
new file mode 100644 (file)
index 0000000..0cf2fac
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * 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.installer.factory.model.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.sling.installer.api.InstallableResource;
+import org.apache.sling.installer.api.OsgiInstaller;
+import org.apache.sling.installer.api.tasks.InstallationContext;
+import org.apache.sling.installer.api.tasks.ResourceState;
+import org.apache.sling.installer.api.tasks.TaskResource;
+import org.apache.sling.installer.api.tasks.TaskResourceGroup;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.jcr.repoinit.JcrRepoInitOpsProcessor;
+import org.apache.sling.provisioning.model.Artifact;
+import org.apache.sling.provisioning.model.ArtifactGroup;
+import org.apache.sling.provisioning.model.Configuration;
+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.Section;
+import org.apache.sling.provisioning.model.Traceable;
+import org.apache.sling.provisioning.model.io.ModelReader;
+import org.apache.sling.repoinit.parser.RepoInitParser;
+import org.apache.sling.repoinit.parser.RepoInitParsingException;
+import org.apache.sling.repoinit.parser.operations.Operation;
+import org.osgi.framework.BundleContext;
+
+/**
+ * This task installs model resources.
+ */
+public class InstallModelTask extends AbstractModelTask {
+
+    private final Set<String> activeRunModes;
+
+    private final SlingRepository repository;
+
+    private final JcrRepoInitOpsProcessor repoInitProcessor;
+
+    private final RepoInitParser repoInitParser;
+
+    public InstallModelTask(final TaskResourceGroup group,
+            final Set<String> runModes,
+            final SlingRepository repository,
+            final JcrRepoInitOpsProcessor repoInitProcessor,
+            final RepoInitParser repoInitParser,
+            final BundleContext bundleContext) {
+        super(group, bundleContext);
+        this.activeRunModes = runModes;
+        this.repository = repository;
+        this.repoInitProcessor = repoInitProcessor;
+        this.repoInitParser = repoInitParser;
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    public void execute(final InstallationContext ctx) {
+        try {
+            final TaskResource resource = this.getResource();
+            final String modelTxt = (String) resource.getAttribute(ModelTransformer.ATTR_MODEL);
+            if ( modelTxt == null ) {
+                ctx.log("Unable to install model resource {} : no model found", this.getResource());
+                this.getResourceGroup().setFinishState(ResourceState.IGNORED);
+            } else {
+                final String name = this.getModelName();
+                final Result result = this.transform(name, modelTxt);
+                if ( result == null ) {
+                    ctx.log("Unable to install model resource {} : unable to create resources", this.getResource());
+                    this.getResourceGroup().setFinishState(ResourceState.IGNORED);
+                } else {
+                    // repo init first
+                    if ( result.repoinit != null ) {
+                        List<Operation> ops = null;
+                        try ( final Reader r = new StringReader(result.repoinit) ) {
+                            ops = this.repoInitParser.parse(r);
+                        } catch (final IOException | RepoInitParsingException e) {
+                            logger.error("Unable to parse repoinit block.", e);
+                            ctx.log("Unable to install model resource {} : unable parse repoinit block.", this.getResource());
+                            this.getResourceGroup().setFinishState(ResourceState.IGNORED);
+                            return;
+                        }
+
+                        // login admin is required for repo init
+                        Session session = null;
+                        try {
+                            session = this.repository.loginAdministrative(null);
+                            this.repoInitProcessor.apply(session, ops);
+                            session.save();
+                        } catch ( final RepositoryException re) {
+                            logger.error("Unable to process repoinit block.", re);
+                            ctx.log("Unable to install model resource {} : unable to process repoinit block.", this.getResource());
+                            this.getResourceGroup().setFinishState(ResourceState.IGNORED);
+                            return;
+
+                        } finally {
+                            if ( session != null ) {
+                                session.logout();
+                            }
+                        }
+                    }
+                    if ( !result.resources.isEmpty() ) {
+                        final OsgiInstaller installer = this.getService(OsgiInstaller.class);
+                        if ( installer != null ) {
+                            installer.registerResources("model-" + name, result.resources.toArray(new InstallableResource[result.resources.size()]));
+                            this.getResourceGroup().setFinishState(ResourceState.INSTALLED);
+                        } else {
+                            ctx.log("Unable to install model resource {} : unable to get OSGi installer", this.getResource());
+                            this.getResourceGroup().setFinishState(ResourceState.IGNORED);
+                        }
+                    }
+                }
+            }
+        } finally {
+            this.cleanup();
+        }
+    }
+
+    public static final class Result {
+        public final List<InstallableResource> resources = new ArrayList<>();
+        public String repoinit;
+    }
+
+    private Result transform(final String name, final String modelText) {
+        try ( final Reader reader = new StringReader(modelText)) {
+            final Model model = ModelUtility.getEffectiveModel(ModelReader.read(reader, name));
+
+            final List<ArtifactDescription> files = new ArrayList<>();
+
+            Map<Traceable, String> errors = collectArtifacts(model, files);
+            if ( errors == null ) {
+                final Result result = new Result();
+                for(final ArtifactDescription desc : files) {
+                    if ( desc.artifactFile != null ) {
+                        final InputStream is = new FileInputStream(desc.artifactFile);
+                        final String digest = String.valueOf(desc.artifactFile.lastModified());
+                        // handle start level
+                        final Dictionary<String, Object> dict = new Hashtable<String, Object>();
+                        if ( desc.startLevel > 0 ) {
+                            dict.put(InstallableResource.BUNDLE_START_LEVEL, desc.startLevel);
+                        }
+                        dict.put(InstallableResource.RESOURCE_URI_HINT, desc.artifactFile.toURI().toString());
+
+                        result.resources.add(new InstallableResource("/" + desc.artifactFile.getName(), is, dict, digest,
+                                                              InstallableResource.TYPE_FILE, null));
+                    } else if ( desc.cfg != null ) {
+                        final String id = (desc.cfg.getFactoryPid() != null ? desc.cfg.getFactoryPid() + "-" + desc.cfg.getPid() : desc.cfg.getPid());
+                        result.resources.add(new InstallableResource("/" + id + ".config",
+                                null,
+                                desc.cfg.getProperties(),
+                                null,
+                                InstallableResource.TYPE_CONFIG, null));
+
+                    } else if ( desc.section != null ) {
+                        result.repoinit = desc.section.getContents();
+                    }
+                }
+                return result;
+            }
+            logger.warn("Errors during parsing model file {} : {}", name, errors.values());
+        } catch ( final IOException ioe) {
+            logger.warn("Unable to read potential model file " + name, ioe);
+        }
+        return null;
+    }
+
+    private static class ArtifactDescription {
+        public int startLevel;
+        public File artifactFile;
+        public Configuration cfg;
+        public Section section;
+    }
+
+    private Map<Traceable, String> collectArtifacts(final Model effectiveModel, final List<ArtifactDescription> files) {
+        final RepositoryAccess repo = new RepositoryAccess();
+        final Map<Traceable, String> errors = new HashMap<>();
+        for(final Feature f : effectiveModel.getFeatures()) {
+            if ( f.isSpecial() ) {
+                continue;
+            }
+            for(final Section section : f.getAdditionalSections()) {
+                final ArtifactDescription desc = new ArtifactDescription();
+                desc.section = section;
+                files.add(desc);
+            }
+            for(final RunMode mode : f.getRunModes()) {
+                if ( mode.isSpecial() ) {
+                    continue;
+                }
+                if ( mode.isActive(this.activeRunModes) ) {
+                    for(final ArtifactGroup group : mode.getArtifactGroups()) {
+                        for(final Artifact artifact : group) {
+                            final File file = repo.get(artifact);
+                            if ( file == null ) {
+                                errors.put(artifact, "Artifact " + artifact.toMvnUrl() + " not found.");
+                            } else {
+                                final ArtifactDescription desc = new ArtifactDescription();
+                                desc.artifactFile = file;
+                                desc.startLevel = group.getStartLevel();
+                                files.add(desc);
+                            }
+                        }
+                    }
+                    for(final Configuration cfg : mode.getConfigurations() ) {
+                        if ( cfg.isSpecial() ) {
+                            continue;
+                        }
+                        final ArtifactDescription desc = new ArtifactDescription();
+                        desc.cfg = cfg;
+                        files.add(desc);
+                    }
+                }
+            }
+        }
+        return errors.isEmpty() ? null : errors;
+    }
+
+    @Override
+    public String getSortKey() {
+        return "30-" + getModelName();
+    }
+}
diff --git a/src/main/java/org/apache/sling/installer/factory/model/impl/ModelTaskFactory.java b/src/main/java/org/apache/sling/installer/factory/model/impl/ModelTaskFactory.java
new file mode 100644 (file)
index 0000000..dfff035
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * 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.installer.factory.model.impl;
+
+
+import org.apache.sling.installer.api.tasks.InstallTask;
+import org.apache.sling.installer.api.tasks.InstallTaskFactory;
+import org.apache.sling.installer.api.tasks.ResourceState;
+import org.apache.sling.installer.api.tasks.TaskResource;
+import org.apache.sling.installer.api.tasks.TaskResourceGroup;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.jcr.repoinit.JcrRepoInitOpsProcessor;
+import org.apache.sling.repoinit.parser.RepoInitParser;
+import org.apache.sling.settings.SlingSettingsService;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This task factory process model resources detected by
+ * the {@link ModelTransformer}.
+ */
+@Component(service = InstallTaskFactory.class)
+public class ModelTaskFactory implements InstallTaskFactory {
+
+    /** Logger. */
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    @Reference
+    private SlingSettingsService settings;
+
+    @Reference
+    private SlingRepository repository;
+
+    @Reference
+    private JcrRepoInitOpsProcessor repoInitProcessor;
+
+    @Reference
+    private RepoInitParser repoInitParser;
+
+    private BundleContext bundleContext;
+
+    @Activate
+    private void activate(final BundleContext ctx) {
+        this.bundleContext = ctx;
+    }
+
+    @Override
+    public InstallTask createTask(final TaskResourceGroup group) {
+        final TaskResource rsrc = group.getActiveResource();
+        if ( !ModelTransformer.TYPE_PROV_MODEL.equals(rsrc.getType()) ) {
+            return null;
+        }
+        if (rsrc.getState() == ResourceState.UNINSTALL ) {
+            logger.info("Uninstalling {}", rsrc.getEntityId());
+
+            return new UninstallModelTask(group, bundleContext);
+        }
+        logger.info("Installing {}", rsrc.getEntityId());
+        return new InstallModelTask(group,
+                this.settings.getRunModes(),
+                this.repository,
+                this.repoInitProcessor,
+                this.repoInitParser,
+                this.bundleContext);
+    }
+}
diff --git a/src/main/java/org/apache/sling/installer/factory/model/impl/ModelTransformer.java b/src/main/java/org/apache/sling/installer/factory/model/impl/ModelTransformer.java
new file mode 100644 (file)
index 0000000..a2c9fa2
--- /dev/null
@@ -0,0 +1,131 @@
+package org.apache.sling.installer.factory.model.impl;/*
+ * 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.
+ */
+
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.sling.installer.api.InstallableResource;
+import org.apache.sling.installer.api.tasks.RegisteredResource;
+import org.apache.sling.installer.api.tasks.ResourceTransformer;
+import org.apache.sling.installer.api.tasks.TransformationResult;
+import org.apache.sling.provisioning.model.Configuration;
+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.Section;
+import org.apache.sling.provisioning.model.Traceable;
+import org.apache.sling.provisioning.model.io.ModelReader;
+import org.apache.sling.provisioning.model.io.ModelWriter;
+import org.osgi.framework.Version;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This transformer detects a file with the ending ".model" containing
+ * a provisioning model.
+ */
+@Component(service = ResourceTransformer.class)
+public class ModelTransformer implements ResourceTransformer {
+
+    public static final String TYPE_PROV_MODEL = "provisioningmodel";
+
+    public static final String ATTR_MODEL = "model";
+
+    /** Logger. */
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    @Override
+    public TransformationResult[] transform(final RegisteredResource resource) {
+        if ( resource.getType().equals(InstallableResource.TYPE_FILE) && resource.getURL().endsWith(".model") ) {
+            try ( final Reader reader = new InputStreamReader(resource.getInputStream(), "UTF-8") ) {
+                final Model model = ModelReader.read(reader, resource.getURL());
+
+                Map<Traceable, String> errors = ModelUtility.validate(model);
+                if ( errors == null ) {
+                    try {
+                        final Model effectiveModel = ModelUtility.getEffectiveModel(model);
+
+                        errors = validate(effectiveModel);
+                        if ( errors == null ) {
+
+                            final Feature f = effectiveModel.getFeatures().get(0);
+                            final TransformationResult tr = new TransformationResult();
+                            tr.setId(f.getName());
+                            tr.setResourceType(TYPE_PROV_MODEL);
+                            tr.setVersion(new Version(f.getVersion()));
+
+                            try ( final StringWriter sw = new StringWriter()) {
+                                ModelWriter.write(sw, effectiveModel);
+                                tr.setAttributes(Collections.singletonMap(ATTR_MODEL, (Object)sw.toString()));
+                            }
+                            return new TransformationResult[] {tr};
+                        }
+                    } catch ( final IllegalArgumentException iae ) {
+                        errors = Collections.singletonMap((Traceable)model, iae.getMessage());
+                    }
+                }
+                if ( errors != null ) {
+                    logger.warn("Errors during parsing model at {} : {}", resource.getURL(), errors.values());
+                }
+
+            } catch ( final IOException ioe) {
+                logger.info("Unable to read model from " + resource.getURL(), ioe);
+            }
+        }
+        return null;
+    }
+
+    private Map<Traceable, String> validate(final Model effectiveModel) {
+        if ( effectiveModel.getFeatures().size() != 1 ) {
+            return Collections.singletonMap((Traceable)effectiveModel, "Model should only contain a single feature.");
+        }
+        final Feature feature = effectiveModel.getFeatures().get(0);
+        if ( feature.isSpecial() ) {
+            return Collections.singletonMap((Traceable)feature, "Feature must not be special.");
+        }
+        if ( feature.getVersion() == null ) {
+            return Collections.singletonMap((Traceable)feature, "Feature must have a version.");
+        }
+        for(final Section section : feature.getAdditionalSections()) {
+            if ( !"repoinit".equals(section.getName()) ) {
+                return Collections.singletonMap((Traceable)section, "Additional section not supported.");
+            }
+        }
+        for(final RunMode mode : feature.getRunModes()) {
+            if ( mode.isSpecial() ) {
+                return Collections.singletonMap((Traceable)mode, "RunMode must not be special.");
+            }
+            for(final Configuration cfg : mode.getConfigurations()) {
+                if ( cfg.isSpecial() ) {
+                    return Collections.singletonMap((Traceable)cfg, "Configuration must not be special.");
+                }
+            }
+        }
+
+        return null;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/installer/factory/model/impl/RepositoryAccess.java b/src/main/java/org/apache/sling/installer/factory/model/impl/RepositoryAccess.java
new file mode 100644 (file)
index 0000000..68792cf
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * 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.installer.factory.model.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.ProcessBuilder.Redirect;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sling.provisioning.model.Artifact;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Helper class for getting maven artifacts.
+ *
+ * It is a simple class assuming that the mvn command is installed
+ * and that the .m2 directory is in the home directory of the current
+ * user.
+ */
+public class RepositoryAccess {
+
+    /**
+     * A logger.
+     */
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    /**
+     * The .m2 directory.
+     */
+    private final String repoHome;
+
+    /**
+     * Create a new instance.
+     */
+    public RepositoryAccess() {
+        this.repoHome = System.getProperty("user.home") + "/.m2/repository/";
+    }
+
+    /**
+     * Get the file for an artifact
+     * @param artifact The artifact
+     * @return The file or {@code null}
+     */
+    public File get(final Artifact artifact) {
+
+        final File artifactFile = this.getArtifact(artifact);
+
+        if ( artifactFile == null ) {
+            return null;
+        }
+        logger.info("Responding for {} with {}", artifact, artifactFile);
+        return artifactFile;
+    }
+
+    private File getArtifact(final Artifact artifact) {
+        logger.info("Requesting {}", artifact);
+
+        final String filePath = (this.repoHome.concat(artifact.getRepositoryPath())).replace('/', File.separatorChar);
+        logger.info("Trying to fetch artifact from {}", filePath);
+        final File f = new File(filePath);
+        if ( !f.exists() || !f.isFile() || !f.canRead() ) {
+            logger.info("Trying to download {}", artifact.getRepositoryPath());
+            try {
+                this.downloadArtifact(artifact);
+            } catch ( final IOException ioe ) {
+                logger.debug("Error downloading file.", ioe);
+            }
+            if ( !f.exists() || !f.isFile() || !f.canRead() ) {
+                logger.info("Artifact not found {}", artifact);
+
+                return null;
+            }
+        }
+        return f;
+    }
+
+    /**
+     * Download artifact from maven
+     * @throws IOException
+     */
+    private void downloadArtifact(final Artifact artifact) throws IOException {
+        // create fake pom
+        final Path dir = Files.createTempDirectory(null);
+        final List<String> lines = new ArrayList<String>();
+        lines.add("<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">");
+        lines.add("    <modelVersion>4.0.0</modelVersion>");
+        lines.add("    <groupId>org.apache.sling</groupId>");
+        lines.add("    <artifactId>temp-artifact</artifactId>");
+        lines.add("    <version>1-SNAPSHOT</version>");
+        lines.add("    <dependencies>");
+        lines.add("        <dependency>");
+        lines.add("            <groupId>" + artifact.getGroupId() + "</groupId>");
+        lines.add("            <artifactId>" + artifact.getArtifactId() + "</artifactId>");
+        lines.add("            <version>" + artifact.getVersion() + "</version>");
+        if ( artifact.getClassifier() != null ) {
+            lines.add("            <classifier>" + artifact.getClassifier() + "</classifier>");
+        }
+        if ( !"bundle".equals(artifact.getType()) && !"jar".equals(artifact.getType()) ) {
+            lines.add("            <type>" + artifact.getType() + "</type>");
+        }
+        lines.add("            <scope>provided</scope>");
+        lines.add("        </dependency>");
+        lines.add("    </dependencies>");
+        lines.add("</project>");
+        logger.info("Writing pom to {}", dir);
+        Files.write(dir.resolve("pom.xml"), lines);
+
+        final File output = dir.resolve("output.txt").toFile();
+        final File error = dir.resolve("error.txt").toFile();
+
+        // invoke maven
+        logger.info("Invoking maven...");
+        final ProcessBuilder pb = new ProcessBuilder("mvn", "verify");
+        pb.directory(dir.toFile());
+        pb.redirectOutput(Redirect.to(output));
+        pb.redirectError(Redirect.to(error));
+
+        final Process p = pb.start();
+        try {
+            p.waitFor();
+        } catch (final InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/installer/factory/model/impl/UninstallModelTask.java b/src/main/java/org/apache/sling/installer/factory/model/impl/UninstallModelTask.java
new file mode 100644 (file)
index 0000000..682c64b
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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.installer.factory.model.impl;
+
+import org.apache.sling.installer.api.OsgiInstaller;
+import org.apache.sling.installer.api.tasks.InstallationContext;
+import org.apache.sling.installer.api.tasks.ResourceState;
+import org.apache.sling.installer.api.tasks.TaskResourceGroup;
+import org.osgi.framework.BundleContext;
+
+/**
+ * This task uninstalls model resources.
+ */
+public class UninstallModelTask extends AbstractModelTask {
+
+    public UninstallModelTask(final TaskResourceGroup group,
+            final BundleContext bundleContext) {
+        super(group, bundleContext);
+    }
+
+    @Override
+    public void execute(final InstallationContext ctx) {
+        try {
+            final OsgiInstaller installer = this.getService(OsgiInstaller.class);
+            if ( installer == null ) {
+                ctx.log("Unable to get OSGi Installer service!");
+            } else {
+                installer.registerResources("model-" + getModelName(), null);
+                this.getResourceGroup().setFinishState(ResourceState.UNINSTALLED);
+            }
+        } finally {
+            this.cleanup();
+        }
+    }
+
+    @Override
+    public String getSortKey() {
+        return "31-" + getModelName();
+    }
+}