/*
* 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.felix.ipojo.manipulator.store.builder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import org.apache.felix.ipojo.manipulator.Pojoization;
import org.apache.felix.ipojo.manipulator.QuotedTokenizer;
import org.apache.felix.ipojo.manipulator.render.MetadataRenderer;
import org.apache.felix.ipojo.manipulator.store.ManifestBuilder;
import org.apache.felix.ipojo.manipulator.util.Constants;
import org.apache.felix.ipojo.metadata.Element;
/**
* A {@code DefaultManifestBuilder} handles the knowledge of iPOJO Manifest building.
* It is responsible to update a given Manifest with all gathered (additional)
* referenced packages (from the metadata.xml) + other iPOJO specific additions.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class DefaultManifestBuilder implements ManifestBuilder {
/**
* Referenced packages (by the composites).
*/
private List<String> m_referredPackages = new ArrayList<String>();
/**
* Computed metadatas from the bundle (XML files + annotations).
*/
private List<Element> m_metadata = new ArrayList<Element>();
/**
* The metadata renderer used to print Elements.
*/
private MetadataRenderer m_renderer;
/**
* Add all given package names in the referred packages list
* @param packageNames additional packages
*/
public void addReferredPackage(Set<String> packageNames) {
m_referredPackages.addAll(packageNames);
}
/**
* Add all given metadata
* @param metadatas additional metadata
*/
public void addMetada(Collection<Element> metadatas) {
m_metadata.addAll(metadatas);
}
public void setMetadataRenderer(MetadataRenderer renderer) {
m_renderer = renderer;
}
/**
* Update the given manifest.
* @param original original manifest to be modified
* @return modified manifest
*/
public Manifest build(final Manifest original) {
Attributes att = original.getMainAttributes();
// Set the imports (add ipojo and handler namespaces
setImports(att);
// Add iPOJO-Component
setPOJOMetadata(att);
// Add iPOJO to the creators
setCreatedBy(att);
return original;
}
/**
* Add imports to the given manifest attribute list. This method add ipojo imports and handler imports (if needed).
* @param att : the manifest attribute list to modify.
*/
private void setImports(Attributes att) {
Map<String, Map<String, String>> imports = parseHeader(att.getValue("Import-Package"));
Map<String, String> ver = new TreeMap<String, String>();
ver.put("version", Constants.getPackageImportClause());
if (!imports.containsKey("org.apache.felix.ipojo")) {
imports.put("org.apache.felix.ipojo", ver);
}
if (!imports.containsKey("org.apache.felix.ipojo.architecture")) {
imports.put("org.apache.felix.ipojo.architecture", ver);
}
if (!imports.containsKey("org.osgi.service.cm")) {
Map<String, String> verCM = new TreeMap<String, String>();
verCM.put("version", "1.2");
imports.put("org.osgi.service.cm", verCM);
}
if (!imports.containsKey("org.osgi.service.log")) {
Map<String, String> verCM = new TreeMap<String, String>();
verCM.put("version", "1.3");
imports.put("org.osgi.service.log", verCM);
}
// Add referred imports from the metadata
for (int i = 0; i < m_referredPackages.size(); i++) {
String pack = m_referredPackages.get(i);
imports.put(pack, new TreeMap<String, String>());
}
// Write imports
att.putValue("Import-Package", printClauses(imports, "resolution:"));
}
/**
* Add iPOJO-Components to the given manifest attribute list. This method add the
* {@literal iPOJO-Components} header and its value (according to the metadata)
* to the manifest.
* @param att the manifest attribute list to modify.
*/
private void setPOJOMetadata(Attributes att) {
StringBuilder meta = new StringBuilder();
for (Element metadata : m_metadata) {
meta.append(m_renderer.render(metadata));
}
if (meta.length() != 0) {
att.putValue("iPOJO-Components", meta.toString());
}
}
/**
* Set the create-by in the manifest.
* @param att : manifest attribute.
*/
private void setCreatedBy(Attributes att) {
String prev = att.getValue("Created-By");
if (prev == null) {
att.putValue("Created-By", "iPOJO " + Constants.getVersion());
} else {
if (prev.indexOf("iPOJO") == -1) {
// Avoid appending iPOJO several times
att.putValue("Created-By", prev + " & iPOJO " + Constants.getVersion());
}
}
}
/**
* Standard OSGi header parser. This parser can handle the format
* <pre>
* clauses ::= clause ( ',' clause ) +
* clause ::= name ( ';' name ) (';' key '=' value )
* </pre>
* This is mapped to a Map { name => Map { attr|directive => value } }
*
* @param value String to parse.
* @return parsed map.
*/
protected Map<String, Map<String, String>> parseHeader(String value) {
if (value == null || value.trim().length() == 0) {
return new HashMap<String, Map<String, String>>();
}
Map<String, Map<String, String>> result = new HashMap<String, Map<String, String>>();
QuotedTokenizer qt = new QuotedTokenizer(value, ";=,");
char del;
do {
boolean hadAttribute = false;
Map<String, String> clause = new HashMap<String, String>();
List<String> aliases = new ArrayList<String>();
aliases.add(qt.nextToken());
del = qt.getSeparator();
while (del == ';') {
String adname = qt.nextToken();
if ((del = qt.getSeparator()) != '=') {
if (hadAttribute) {
throw new IllegalArgumentException("Header contains name field after attribute or directive: " + adname + " from " + value);
}
aliases.add(adname);
} else {
String advalue = qt.nextToken();
clause.put(adname, advalue);
del = qt.getSeparator();
hadAttribute = true;
}
}
for (Iterator<String> i = aliases.iterator(); i.hasNext();) {
result.put(i.next(), clause);
}
} while (del == ',');
return result;
}
/**
* Print a standard Map based OSGi header.
*
* @param exports : map { name => Map { attribute|directive => value } }
* @param allowedDirectives list of allowed directives.
* @return the clauses
*/
private String printClauses(Map<String, Map<String, String>> exports, String allowedDirectives) {
StringBuffer sb = new StringBuffer();
String del = "";
for (Iterator i = exports.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Map.Entry) i.next();
String name = (String) entry.getKey();
Map map = (Map) entry.getValue();
sb.append(del);
sb.append(name);
for (Iterator j = map.entrySet().iterator(); j.hasNext();) {
Map.Entry entry2 = (Map.Entry) j.next();
String key = (String) entry2.getKey();
// Skip directives we do not recognize
if (key.endsWith(":") && allowedDirectives.indexOf(key) < 0) {
continue;
}
String value = (String) entry2.getValue();
sb.append(";");
sb.append(key);
sb.append("=");
boolean dirty = value.indexOf(',') >= 0 || value.indexOf(';') >= 0;
if (dirty) {
sb.append("\"");
}
sb.append(value);
if (dirty) {
sb.append("\"");
}
}
del = ", ";
}
return sb.toString();
}
}