/*
* Copyright (C) 2011 Everit Kft. (http://everit.org)
*
* Licensed 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.everit.osgi.dev.maven.util;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.apache.maven.plugin.MojoExecutionException;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.everit.osgi.dev.maven.EOSGiConstants;
/**
* Creates a bundle execution plan on the OSGi container based on the old and new settings and the
* content of the bundle files.
*/
public class BundleExecutionPlan {
/**
* Temporary data used for calculation.
*/
private static class TemporaryBundleMaps {
Set<DistributableArtifact> higherStartLevelOnBundles = new HashSet<>();
Set<DistributableArtifact> ifInitialChanges = new HashSet<>();
Map<String, DistributableArtifact> installBundles = new HashMap<>();
Set<DistributableArtifact> lowerStartLevelOnBundles = new HashSet<>();
Map<DistributableArtifact, Integer> setInitialStartLevelOnBundles = new HashMap<>();
Set<DistributableArtifact> setStartLevelFromInitialBundles = new HashSet<>();
Set<DistributableArtifact> startStoppedBundles = new HashSet<>();
Set<DistributableArtifact> stopStartedBundles = new HashSet<>();
Set<DistributableArtifact> updateBundles = new HashSet<>();
}
public final Collection<DistributableArtifact> changeStartLevelIfInitialBundleStartLevelChangesOnBundles; // CS_DISABLE_LINE_LENGTH
public final Collection<DistributableArtifact> higherStartLevelOnBundles;
public final Collection<DistributableArtifact> installBundles;
public final Collection<DistributableArtifact> lowerStartLevelOnBundles;
/**
* The lowest start level that was assigned to any of the bundles that change state in the plan or
* <code>null</code> if none of these bundles had a specific startLevel.
*/
public final Integer lowestStartLevel;
/**
* Map of bundle location and the old startlevel of the bundles that should have their start
* levels reset to the initial one.
*/
public final Map<DistributableArtifact, Integer> setInitialStartLevelOnBundles;
public final Collection<DistributableArtifact> setStartLevelFromInitialBundles;
public final Collection<DistributableArtifact> startStoppedBundles;
public final Collection<DistributableArtifact> stopStartedBundles;
public final Collection<DistributableArtifact> uninstallBundles;
public final Collection<DistributableArtifact> updateBundles;
/**
* Constructor.
*
* @param existingArtifacts
* The artifacts that were existing in the environment previously.
* @param newArtifacts
* The artifact list of the new distribution process.
* @param environmentRootFolder
* The root folder of the environment where the artifacts will be copied.
* @param artifactResolver
* Resolves the artifacts.
* @throws MojoExecutionException
* If an exception happens during generating the execution plan.
*/
public BundleExecutionPlan(final String environmentId,
final Collection<DistributableArtifact> existingArtifacts,
final Collection<DistributableArtifact> newArtifacts, final File environmentRootFolder,
final PredefinedRepoArtifactResolver artifactResolver)
throws MojoExecutionException {
Map<String, DistributableArtifact> existingArtifactsByBundleLocation =
createExistingBundleLocationWithArtifactProperties(existingArtifacts);
TemporaryBundleMaps tmp = new TemporaryBundleMaps();
for (DistributableArtifact newArtifact : newArtifacts) {
String newBundleAction = newArtifact.properties.get("bundle.action");
if (newBundleAction != null
&& !EOSGiConstants.BUNDLE_ACTION_NONE.equalsIgnoreCase(newBundleAction)) {
String bundleLocation = newArtifact.properties.get("bundle.location");
if (bundleLocation != null) {
DistributableArtifact existingArtifact =
existingArtifactsByBundleLocation.remove(bundleLocation);
if (existingArtifact == null) {
if (tmp.installBundles.containsKey(bundleLocation)) {
throw new MojoExecutionException(
"Bundle location '" + bundleLocation + "' exists twice in environment '"
+ environmentId + "'");
}
tmp.installBundles.put(bundleLocation, newArtifact);
} else {
if (bundleContentChanged(newArtifact, environmentRootFolder, artifactResolver)) {
tmp.updateBundles.add(newArtifact);
} else {
if (bundleBecameStarted(existingArtifact.properties, newArtifact.properties)) {
tmp.startStoppedBundles.add(newArtifact);
} else if (bundleBecameStopped(existingArtifact.properties, newArtifact.properties)) {
tmp.stopStartedBundles.add(newArtifact);
}
}
fillStartLevelChangeWhereNecessary(newArtifact, existingArtifact, tmp);
}
}
}
}
this.uninstallBundles = new HashSet<>(existingArtifactsByBundleLocation.values());
this.installBundles = new HashSet<>(tmp.installBundles.values());
this.updateBundles = tmp.updateBundles;
this.startStoppedBundles = tmp.startStoppedBundles;
this.stopStartedBundles = tmp.stopStartedBundles;
this.lowerStartLevelOnBundles = tmp.lowerStartLevelOnBundles;
this.higherStartLevelOnBundles = tmp.higherStartLevelOnBundles;
this.setInitialStartLevelOnBundles = tmp.setInitialStartLevelOnBundles;
this.setStartLevelFromInitialBundles = tmp.setStartLevelFromInitialBundles;
this.changeStartLevelIfInitialBundleStartLevelChangesOnBundles = tmp.ifInitialChanges;
this.lowestStartLevel = resolveLowestStartLevel(tmp);
}
private boolean bundleBecameStarted(final Map<String, String> existingArtifactProperties,
final Map<String, String> newArtifactProperties) {
String existingBundleAction = existingArtifactProperties.get("bundle.action");
String newBundleAction = newArtifactProperties.get("bundle.action");
return EOSGiConstants.BUNDLE_ACTION_INSTALL.equalsIgnoreCase(existingBundleAction)
&& EOSGiConstants.BUNDLE_ACTION_START.equalsIgnoreCase(newBundleAction);
}
private boolean bundleBecameStopped(final Map<String, String> existingArtifactProperties,
final Map<String, String> newArtifactProperties) {
String existingBundleAction = existingArtifactProperties.get("bundle.action");
String newBundleAction = newArtifactProperties.get("bundle.action");
return EOSGiConstants.BUNDLE_ACTION_START.equalsIgnoreCase(existingBundleAction)
&& EOSGiConstants.BUNDLE_ACTION_INSTALL.equalsIgnoreCase(newBundleAction);
}
private boolean bundleContentChanged(final DistributableArtifact newArtifact,
final File environmentRootFolder,
final PredefinedRepoArtifactResolver artifactResolver) throws MojoExecutionException {
Artifact resolvedArtifact =
resolveArtifact(artifactResolver, newArtifact);
File newArtifactFile = resolvedArtifact.getFile();
File oldArtifactFile =
PluginUtil.resolveArtifactAbsoluteFile(newArtifact, resolvedArtifact,
environmentRootFolder);
return contentDifferent(newArtifactFile, oldArtifactFile);
}
private boolean contentDifferent(final File newArtifactFile, final File oldArtifactFile)
throws MojoExecutionException {
if (!oldArtifactFile.exists()
|| (newArtifactFile.lastModified() == oldArtifactFile.lastModified()
&& newArtifactFile.length() == oldArtifactFile.length())) {
return false;
}
try (InputStream newIn = new BufferedInputStream(new FileInputStream(newArtifactFile));
InputStream oldIn = new BufferedInputStream(new FileInputStream(oldArtifactFile))) {
int oldR = 0;
int newR = 0;
boolean changed = false;
while (oldR >= 0 && newR >= 0 && !changed) {
oldR = oldIn.read();
newR = newIn.read();
if (oldR != newR) {
changed = true;
}
}
return changed;
} catch (IOException e) {
throw new MojoExecutionException(
"Error during diff check on files: " + oldArtifactFile + ", " + newArtifactFile, e);
}
}
private Map<String, DistributableArtifact> createExistingBundleLocationWithArtifactProperties(
final Collection<DistributableArtifact> existingArtifacts) {
Map<String, DistributableArtifact> result = new HashMap<>();
if (existingArtifacts == null) {
return result;
}
for (DistributableArtifact artifact : existingArtifacts) {
String bundleAction = artifact.properties.get("bundle.action");
if (bundleAction != null
&& !EOSGiConstants.BUNDLE_ACTION_NONE.equalsIgnoreCase(bundleAction)) {
String bundleLocation = artifact.properties.get("bundle.location");
result.put(bundleLocation, artifact);
}
}
return result;
}
private void fillStartLevelChangeWhereNecessary(
final DistributableArtifact newArtifact,
final DistributableArtifact existingArtifact,
final TemporaryBundleMaps tmp) {
Integer newStartLevel = resolveStartLevel(newArtifact.properties);
Integer existingStartLevel = resolveStartLevel(existingArtifact.properties);
if (!Objects.equals(newStartLevel, existingStartLevel)) {
if (newStartLevel == null) {
tmp.setInitialStartLevelOnBundles.put(newArtifact, existingStartLevel);
} else if (existingStartLevel == null) {
tmp.setStartLevelFromInitialBundles.add(newArtifact);
} else if (newStartLevel.compareTo(existingStartLevel) > 0) {
tmp.higherStartLevelOnBundles.add(newArtifact);
} else {
tmp.lowerStartLevelOnBundles.add(newArtifact);
}
} else if (newStartLevel == null) {
tmp.ifInitialChanges.add(newArtifact);
}
}
private Artifact resolveArtifact(final PredefinedRepoArtifactResolver artifactResolver,
final DistributableArtifact artifact)
throws MojoExecutionException {
ArtifactRequest artifactRequest = new ArtifactRequest();
Artifact aetherArtifact = new DefaultArtifact(artifact.coordinates);
artifactRequest.setArtifact(aetherArtifact);
Artifact resolvedArtifact = artifactResolver.resolve(artifactRequest);
return resolvedArtifact;
}
private Integer resolveLowestStartLevel(final Collection<DistributableArtifact> artifacts,
final Integer pLowestStartLevel) {
Integer result = pLowestStartLevel;
for (DistributableArtifact artifact : artifacts) {
Integer startLevel = resolveStartLevel(artifact.properties);
if (startLevel != null && (result == null || startLevel.compareTo(result) < 0)) {
result = startLevel;
}
}
return result;
}
private Integer resolveLowestStartLevel(final TemporaryBundleMaps tmp) {
Integer result = resolveLowestStartLevel(tmp.higherStartLevelOnBundles, null);
result = resolveLowestStartLevel(tmp.ifInitialChanges, null);
result = resolveLowestStartLevel(tmp.installBundles.values(), null);
result = resolveLowestStartLevel(tmp.lowerStartLevelOnBundles, null);
result = resolveLowestStartLevel(tmp.setInitialStartLevelOnBundles.keySet(), null);
result = resolveLowestStartLevel(tmp.setStartLevelFromInitialBundles, null);
result = resolveLowestStartLevel(tmp.startStoppedBundles, null);
result = resolveLowestStartLevel(tmp.stopStartedBundles, null);
result = resolveLowestStartLevel(tmp.updateBundles, null);
return result;
}
private Integer resolveStartLevel(final Map<String, String> artifactProperties) {
if (artifactProperties == null) {
return null;
}
String startLevelString = artifactProperties.get("bundle.startLevel");
if (startLevelString == null) {
return null;
}
return Integer.parseInt(startLevelString);
}
}