| /* |
| * 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.feature.Artifact; |
| import org.apache.sling.feature.ArtifactId; |
| import org.apache.sling.feature.Configuration; |
| import org.apache.sling.feature.Extension; |
| import org.apache.sling.feature.ExtensionType; |
| import org.apache.sling.feature.Feature; |
| import org.apache.sling.feature.builder.ArtifactProvider; |
| import org.apache.sling.feature.io.archive.ArchiveReader; |
| import org.apache.sling.feature.io.artifacts.ArtifactHandler; |
| import org.apache.sling.feature.io.json.FeatureJSONReader; |
| import org.apache.sling.feature.spi.context.ExtensionHandler; |
| import org.apache.sling.feature.spi.context.ExtensionHandlerContext; |
| 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.osgi.framework.BundleContext; |
| |
| import java.io.File; |
| 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.net.MalformedURLException; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.util.ArrayList; |
| import java.util.Dictionary; |
| import java.util.Hashtable; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * This task installs a feature model resources. |
| */ |
| public class InstallFeatureModelTask extends AbstractFeatureModelTask { |
| private final InstallContext installContext; |
| private final List<ExtensionHandler> extensionHandlers; |
| |
| public InstallFeatureModelTask(final TaskResourceGroup group, |
| final InstallContext installContext, final BundleContext bundleContext, |
| final List<ExtensionHandler> extensionHandlers) { |
| super(group, bundleContext); |
| this.installContext = installContext; |
| this.extensionHandlers = extensionHandlers; |
| } |
| |
| @Override |
| public void execute(final InstallationContext ctx) { |
| try { |
| final TaskResource resource = this.getResource(); |
| ctx.log("Installing {}", resource.getEntityId()); |
| final String featureJson = (String) resource.getAttribute(FeatureModelInstallerPlugin.ATTR_MODEL); |
| if (featureJson == null) { |
| ctx.log("Unable to install feature model resource {} : no model found", resource); |
| this.getResourceGroup().setFinishState(ResourceState.IGNORED, null, "No model found"); |
| } else { |
| boolean success = false; |
| final List<InstallableResource> result = this.transform(featureJson, resource); |
| if (result == null) { |
| ctx.log("Unable to install feature model resource {} : unable to create resources", resource); |
| this.getResourceGroup().setFinishState(ResourceState.IGNORED, null, "Unable to create resources"); |
| } else { |
| if (!result.isEmpty()) { |
| final OsgiInstaller installer = this.getService(OsgiInstaller.class); |
| if (installer != null) { |
| installer.registerResources( |
| getScheme(resource), |
| result.toArray(new InstallableResource[result.size()])); |
| } else { |
| ctx.log("Unable to install feature model resource {} : unable to get OSGi installer", |
| resource); |
| this.getResourceGroup().setFinishState(ResourceState.IGNORED, null, "Unable to get OSGi installer"); |
| return; |
| } |
| } |
| this.getResourceGroup().setFinishState(ResourceState.INSTALLED); |
| success = true; |
| } |
| if ( success ) { |
| ctx.log("Installed {}", resource.getEntityId()); |
| } |
| } |
| } finally { |
| this.cleanup(); |
| } |
| } |
| |
| private File getArtifactFile(final File baseDir, final ArtifactId id) { |
| return new File(baseDir, id.toMvnPath().replace('/', File.separatorChar)); |
| } |
| |
| private List<InstallableResource> transform(final String featureJson, |
| final TaskResource rsrc) { |
| Feature feature = null; |
| try (final Reader reader = new StringReader(featureJson)) { |
| feature = FeatureJSONReader.read(reader, null); |
| } catch ( final IOException ioe) { |
| logger.warn("Unable to read feature model file", ioe); |
| } |
| if (feature == null) { |
| return null; |
| } |
| |
| final List<InstallableResource> result = new ArrayList<>(); |
| // configurations |
| for (final Configuration cfg : feature.getConfigurations()) { |
| result.add(new InstallableResource("/".concat(cfg.getPid()).concat(".config"), null, |
| cfg.getConfigurationProperties(), null, InstallableResource.TYPE_CONFIG, null)); |
| } |
| |
| // extract artifacts |
| if (this.installContext.storageDirectory != null) { |
| final byte[] buffer = new byte[1024*1024*256]; |
| |
| try ( final InputStream is = rsrc.getInputStream() ) { |
| ArchiveReader.read(is, new ArchiveReader.ArtifactConsumer() { |
| |
| @Override |
| public void consume(final ArtifactId id, final InputStream is) throws IOException { |
| final File artifactFile = getArtifactFile(installContext.storageDirectory, id); |
| 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 feature model " + feature.getId().toMvnId(), ioe); |
| return null; |
| } |
| } |
| |
| ExtensionHandlerContext context = new ContextImpl(result); |
| |
| for (Extension ext : feature.getExtensions()) { |
| boolean handlerFound = false; |
| for (ExtensionHandler eh : extensionHandlers) { |
| try { |
| handlerFound |= eh.handle(context, ext, feature); |
| } catch (Exception e) { |
| logger.error("Exception while processing extension {} with handler {}", ext, eh, e); |
| } |
| } |
| if (!handlerFound) { |
| if (ExtensionType.ARTIFACTS == ext.getType()) { |
| // Unhandled ARTIFACTS extensions get stored |
| for (final Artifact artifact : ext.getArtifacts()) { |
| addArtifact(artifact, result); |
| } |
| } else { |
| // should this be an error? |
| logger.warn("No extension handler found for mandartory extension " + ext); |
| } |
| } |
| } |
| |
| // bundles |
| for (final Artifact bundle : feature.getBundles()) { |
| if (!addArtifact(bundle, result)) { |
| return null; |
| } |
| } |
| |
| return result; |
| } |
| |
| private boolean addArtifact(final Artifact artifact, |
| final List<InstallableResource> result) { |
| File artifactFile = (this.installContext.storageDirectory == null ? null |
| : getArtifactFile(this.installContext.storageDirectory, artifact.getId())); |
| ArtifactHandler handler; |
| if (artifactFile == null || !artifactFile.exists()) { |
| try { |
| handler = this.installContext.artifactManager.getArtifactHandler(artifact.getId().toMvnUrl()); |
| } catch (final IOException ignore) { |
| return false; |
| } |
| } else { |
| try { |
| handler = new ArtifactHandler(artifactFile); |
| } catch (final MalformedURLException e) { |
| return false; |
| } |
| } |
| if (handler == null) { |
| return false; |
| } |
| try { |
| final URLConnection connection = handler.getLocalURL().openConnection(); |
| connection.connect(); |
| final InputStream is = connection.getInputStream(); |
| final long lastModified = connection.getLastModified(); |
| final String digest = lastModified == 0 ? null : String.valueOf(lastModified); |
| // handle start order |
| final Dictionary<String, Object> dict = new Hashtable<String, Object>(); |
| if (artifact.getStartOrder() > 0) { |
| dict.put(InstallableResource.BUNDLE_START_LEVEL, artifact.getStartOrder()); |
| } |
| dict.put(InstallableResource.RESOURCE_URI_HINT, handler.getLocalURL().toString()); |
| |
| result.add(new InstallableResource("/".concat(artifact.getId().toMvnName()), is, dict, digest, |
| InstallableResource.TYPE_FILE, null)); |
| } catch (final IOException ioe) { |
| logger.warn("Unable to read artifact " + handler.getLocalURL(), ioe); |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public String getSortKey() { |
| return "30-" + getResource().getAttribute(FeatureModelInstallerPlugin.ATTR_ID); |
| } |
| |
| private ArtifactProvider getLocalArtifactProvider() { |
| // TODO share with addArtifact() |
| return new ArtifactProvider() { |
| @Override |
| public URL provide(ArtifactId id) { |
| File artifactFile = (installContext.storageDirectory == null ? null |
| : getArtifactFile(installContext.storageDirectory, id)); |
| ArtifactHandler handler; |
| if (artifactFile == null || !artifactFile.exists()) { |
| try { |
| handler = installContext.artifactManager.getArtifactHandler(id.toMvnUrl()); |
| } catch (final IOException ignore) { |
| return null; |
| } |
| } else { |
| try { |
| handler = new ArtifactHandler(artifactFile); |
| } catch (final MalformedURLException e) { |
| return null; |
| } |
| } |
| if (handler == null) { |
| return null; |
| } |
| return handler.getLocalURL(); |
| } |
| }; |
| } |
| |
| private class ContextImpl implements ExtensionHandlerContext { |
| private final List<InstallableResource> results; |
| |
| public ContextImpl(List<InstallableResource> results) { |
| this.results = results; |
| } |
| |
| @Override |
| public void addBundle(ArtifactId id, URL file, Integer startLevel) { |
| // TODO Auto-generated method stub |
| } |
| |
| @Override |
| public void addInstallableArtifact(ArtifactId id, URL url, Map<String,Object> props) { |
| try { |
| Dictionary <String,Object> dict = new Hashtable<>(); |
| props.entrySet().stream() |
| .filter(e -> e.getValue() != null) |
| .forEach(e -> dict.put(e.getKey(), e.getValue())); |
| |
| InputStream is = url.openStream(); |
| results.add(new InstallableResource("/".concat(id.toMvnName()), is, dict, null /* TODO digest? */, |
| InstallableResource.TYPE_FILE, null)); |
| } catch (IOException e) { |
| logger.warn("Unable to read artifact " + id + " from url " + url, e); |
| } |
| } |
| |
| @Override |
| public void addConfiguration(String pid, String factoryPid, Dictionary<String, Object> properties) { |
| // TODO handler factoryPid, is this ok? |
| String cfgPid = pid; |
| if (factoryPid != null) { |
| cfgPid = factoryPid; |
| } |
| |
| results.add(new InstallableResource("/".concat(cfgPid).concat(".config"), null, |
| properties, null, InstallableResource.TYPE_CONFIG, null)); |
| } |
| |
| @Override |
| public ArtifactProvider getArtifactProvider() { |
| return getLocalArtifactProvider(); |
| } |
| } |
| } |