/*
* 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 WARRANTIESOR 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.aries.application.utils.manifest;
import java.io.File;
import java.util.Map;
import java.util.jar.Manifest;
import org.apache.aries.application.utils.AppConstants;
import org.apache.aries.util.filesystem.FileSystem;
import org.apache.aries.util.filesystem.IDirectory;
import org.apache.aries.util.filesystem.IFile;
import org.apache.aries.util.manifest.BundleManifest;
import org.apache.aries.util.manifest.ManifestProcessor;
import org.osgi.framework.Version;
public class ManifestDefaultsInjector
{
/**
* Quick adapter to update a Manifest, using content of a Zip File.
* <p>
* This is really a wrapper of updateManifest(Manifest,String,IDirectory), with the
* IDirectory being being created from the Zip File. This method avoids other Bundles
* requiring IDirectory solely for calling updateManifest.
* <p>
* @param mf Manifest to be updated
* @param appName The name to use for this app, if the name contains
* a '_' char then the portion after the '_' is used, if possible, as
* the application version.
* @param zip Content to use for application.
* @return true if manifest modified, false otherwise.
*/
public static boolean updateManifest(Manifest mf, String appName, File zip){
IDirectory appPathIDir = FileSystem.getFSRoot(zip);
boolean result = updateManifest(mf, appName, appPathIDir);
return result;
}
/**
* Tests the supplied manifest for the presence of expected
* attributes, and where missing, adds them, and defaults
* their values appropriately.
*
* @param mf The manifest to test & update if needed.
* @param appName The name to use for this app, if the name contains
* a '_' char then the portion after the '_' is used, if possible, as
* the application version.
* @param appDir The IDirectory to scan to build application content
* property
* @return true if manifest modified, false otherwise.
*/
public static boolean updateManifest(Manifest mf, String appName, IDirectory appDir){
Map<String, String> props = ManifestProcessor.readManifestIntoMap(mf);
String extracted[] = extractAppNameAndVersionFromNameIfPossible(appName);
String name = extracted[0];
String version = extracted[1];
boolean updated = false;
updated |= defaultAppSymbolicName(mf, props, name);
updated |= defaultAppName(mf, props, name);
updated |= defaultVersion(mf, props, version);
updated |= defaultAppScope(mf, props, name, version);
updated |= defaultAppContent(mf, props, appDir);
return updated;
}
/**
* Takes a compound name_version string, and returns the Name & Version information.
* <p>
* @param name Contains name data related to this app. Expected format is name_version
* @return Array of String, index 0 is appName, index 1 is Version.
* <br> Name will be the appname retrieved from the 'name' argument, Version will be the
* version if found and valid, otherwise will be defaulted.
*/
private static String[] extractAppNameAndVersionFromNameIfPossible(String name){
String retval[] = new String[2];
String appName = name;
String defaultedVersion;
int index = name.indexOf('_');
if (index != -1) {
appName = name.substring(0, index);
defaultedVersion = name.substring(index + 1);
try {
new Version(defaultedVersion);
} catch (IllegalArgumentException e) {
// this is not an error condition
defaultedVersion = AppConstants.DEFAULT_VERSION;
}
} else {
defaultedVersion = AppConstants.DEFAULT_VERSION;
}
retval[0] = appName;
retval[1] = defaultedVersion;
return retval;
}
/**
* Sets the app symbolic name into the manifest, if not already present.
*
* @param mf manifest to update
* @param props parsed manifest used to test if already present.
* @param appName used for name if missing
* @return true if manifest is modified, false otherwise.
*/
private static boolean defaultAppSymbolicName(Manifest mf, Map<String, String> props, String appName){
boolean updated = false;
if (!props.containsKey(AppConstants.APPLICATION_SYMBOLIC_NAME)) {
mf.getMainAttributes().putValue(AppConstants.APPLICATION_SYMBOLIC_NAME, appName);
updated = true;
}
return updated;
}
/**
* Sets the app name into the manifest, if not already present.
*
* @param mf manifest to update
* @param props parsed manifest used to test if already present.
* @param appName used for name if missing
* @return true if manifest is modified, false otherwise.
*/
private static boolean defaultAppName(Manifest mf, Map<String, String> props, String appName){
boolean updated = false;
if (!props.containsKey(AppConstants.APPLICATION_NAME)) {
mf.getMainAttributes().putValue(AppConstants.APPLICATION_NAME, appName);
updated = true;
}
return updated;
}
/**
* Sets the app version into the manifest, if not already present.
*
* @param mf manifest to update
* @param props parsed manifest used to test if already present.
* @param appVersion used for version if missing
* @return true if manifest is modified, false otherwise.
*/
private static boolean defaultVersion(Manifest mf, Map<String, String> props, String appVersion){
boolean updated = false;
if (!props.containsKey(AppConstants.APPLICATION_VERSION)) {
mf.getMainAttributes().putValue(AppConstants.APPLICATION_VERSION, appVersion);
updated = true;
}
return updated;
}
/**
* Sets the app scope into the manifest, if not already present.
*
* @param mf manifest to update
* @param props parsed manifest used to test if already present.
* @param name used to build appScope if app symbolic name not set.
* @param version used to build appScope if app version missing.
* @return true if manifest is modified, false otherwise.
*/
private static boolean defaultAppScope(Manifest mf, Map<String, String> props, String name, String version){
boolean updated = false;
if (!props.containsKey(AppConstants.APPLICATION_SCOPE)) {
String appSymbolicName;
if (props.containsKey(AppConstants.APPLICATION_SYMBOLIC_NAME)) {
appSymbolicName = props.get(AppConstants.APPLICATION_SYMBOLIC_NAME);
} else {
appSymbolicName = name;
}
String appVersion;
if (props.containsKey(AppConstants.APPLICATION_VERSION)) {
appVersion = props.get(AppConstants.APPLICATION_VERSION);
} else {
appVersion = version;
}
String appScope = appSymbolicName + '_' + appVersion;
mf.getMainAttributes().putValue(AppConstants.APPLICATION_SCOPE, appScope);
updated = true;
}
return updated;
}
/**
* Sets the app content into the manifest, if not already present.
* <p>
* This method will NOT set the appcontent if it is unable to build it.
* This is important, as the absence of appcontent is used by some callers
* to test if a manifest contains all required content.
*
* @param mf manifest to update
* @param props parsed manifest used to test if already present.
* @param appDir used to build app content if missing.
* @return true if manifest is modified, false otherwise.
*/
private static boolean defaultAppContent(Manifest mf, Map<String, String> props, IDirectory appDir){
boolean updated = false;
if (!props.containsKey(AppConstants.APPLICATION_CONTENT)) {
String appContent = calculateAppContent(appDir);
if (appContent != null) {
mf.getMainAttributes().putValue(AppConstants.APPLICATION_CONTENT, appContent);
updated = true;
}
}
return updated;
}
/**
* Processes an IDirectory to find targets that require adding to the application content attrib.
*
* @param appDir The IDirectory to scan
* @return AppContent string, or null if no content was found.
*/
private static String calculateAppContent(IDirectory appDir){
StringBuilder builder = new StringBuilder();
for (IFile file : appDir) {
processPossibleBundle(file, builder);
}
String returnVal = null;
if (builder.length() > 0) {
builder.deleteCharAt(builder.length() - 1);
returnVal = builder.toString();
}
return returnVal;
}
/**
* This method works out if the given IFile represents an OSGi bundle and if
* it is then we append a matching rule to the String builder.
*
* @param file to file to check.
* @param builder the builder to append to.
*/
private static void processPossibleBundle(IFile file, StringBuilder builder)
{
if (file.isDirectory() || (file.isFile() && (file.getName().endsWith(".jar") || file.getName().endsWith(".war")))) {
BundleManifest bundleMf = BundleManifest.fromBundle(file);
if (bundleMf != null) {
String manifestVersion = bundleMf.getManifestVersion();
String name = bundleMf.getSymbolicName();
String version = bundleMf.getVersion().toString();
// if the bundle manifest version is 2 AND a symbolic name is specified we have a valid bundle
if ("2".equals(manifestVersion) && name != null) {
builder.append(name);
// bundle version is not a required manifest header
if (version != null) {
builder.append(";version=\"[");
builder.append(version);
builder.append(',');
builder.append(version);
builder.append("]\"");
}
// the last comma will be removed once all content has been added
builder.append(",");
}
}
}
}
}