| /* |
| * 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.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| 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.Iterator; |
| 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.ModelArchiveReader; |
| 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(); |
| ctx.log("Installing {}", resource.getEntityId()); |
| final String modelTxt = (String) resource.getAttribute(ModelTransformer.ATTR_MODEL); |
| final Integer featureIndex = (Integer) resource.getAttribute(ModelTransformer.ATTR_FEATURE_INDEX); |
| final String name = (String) resource.getAttribute(ModelTransformer.ATTR_FEATURE_NAME); |
| if ( modelTxt == null || featureIndex == null || name == null ) { |
| ctx.log("Unable to install model resource {} : no model found", resource); |
| this.getResourceGroup().setFinishState(ResourceState.IGNORED); |
| } else { |
| final String path = (String) resource.getAttribute(ModelTransformer.ATTR_BASE_PATH); |
| final File baseDir = (path == null ? null : new File(path)); |
| |
| boolean success = false; |
| try { |
| final Result result = this.transform(name, modelTxt, featureIndex, resource, baseDir); |
| if ( result == null ) { |
| ctx.log("Unable to install model resource {} : unable to create resources", resource); |
| 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.", resource); |
| 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.", resource); |
| 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()])); |
| } else { |
| ctx.log("Unable to install model resource {} : unable to get OSGi installer", resource); |
| this.getResourceGroup().setFinishState(ResourceState.IGNORED); |
| return; |
| } |
| } |
| this.getResourceGroup().setFinishState(ResourceState.INSTALLED); |
| success = true; |
| } |
| } finally { |
| if ( !success && baseDir != null ) { |
| this.deleteDirectory(baseDir); |
| } |
| } |
| if ( success ) { |
| ctx.log("Installed {}", resource.getEntityId()); |
| } |
| } |
| } 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, |
| final int featureIndex, |
| final TaskResource rsrc, |
| final File baseDir) { |
| Model model = null; |
| try ( final Reader reader = new StringReader(modelText)) { |
| model = ModelUtility.getEffectiveModel(ModelReader.read(reader, name)); |
| } catch ( final IOException ioe) { |
| logger.warn("Unable to read model file for feature " + name, ioe); |
| } |
| if ( model == null ) { |
| return null; |
| } |
| int index = 0; |
| final Iterator<Feature> iter = model.getFeatures().iterator(); |
| while ( iter.hasNext() ) { |
| iter.next(); |
| if ( index != featureIndex ) { |
| iter.remove(); |
| } |
| index++; |
| } |
| |
| if ( baseDir != null ) { |
| final List<Artifact> artifacts = new ArrayList<>(); |
| final Feature feature = model.getFeatures().get(0); |
| for(final RunMode rm : feature.getRunModes()) { |
| for(final ArtifactGroup group : rm.getArtifactGroups()) { |
| for(final Artifact a : group) { |
| artifacts.add(a); |
| } |
| } |
| } |
| |
| // extract artifacts |
| final byte[] buffer = new byte[1024*1024*256]; |
| |
| try ( final InputStream is = rsrc.getInputStream() ) { |
| ModelArchiveReader.read(is, new ModelArchiveReader.ArtifactConsumer() { |
| |
| @Override |
| public void consume(final Artifact artifact, final InputStream is) throws IOException { |
| if ( artifacts.contains(artifact) ) { |
| final File artifactFile = new File(baseDir, artifact.getRepositoryPath().replace('/', File.separatorChar)); |
| if ( !artifactFile.exists() ) { |
| artifactFile.getParentFile().mkdirs(); |
| try (final OutputStream os = new FileOutputStream(artifactFile)) { |
| int l = 0; |
| while ( (l = is.read(buffer)) > 0 ) { |
| os.write(buffer, 0, l); |
| } |
| } |
| } |
| } |
| } |
| }); |
| } catch ( final IOException ioe) { |
| logger.warn("Unable to extract artifacts from model " + name, ioe); |
| return null; |
| } |
| } |
| |
| final List<ArtifactDescription> files = new ArrayList<>(); |
| |
| Map<Traceable, String> errors = collectArtifacts(model, files, baseDir); |
| if ( errors == null ) { |
| final Result result = new Result(); |
| for(final ArtifactDescription desc : files) { |
| if ( desc.artifactFile != null ) { |
| try { |
| 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)); |
| } catch ( final IOException ioe ) { |
| logger.warn("Unable to read artifact " + desc.artifactFile, ioe); |
| return 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()); |
| |
| 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 File baseDir) { |
| 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) { |
| File file = (baseDir == null ? null : new File(baseDir, artifact.getRepositoryPath().replace('/', File.separatorChar))); |
| if ( file == null || !file.exists() ) { |
| 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-" + getResource().getAttribute(ModelTransformer.ATTR_FEATURE_NAME); |
| } |
| } |