blob: dc0e838a004a6ebde390b8d3eff2e6184ce86453 [file] [log] [blame]
/*
* 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.online.manipulator;
import static java.lang.String.format;
import static org.apache.felix.ipojo.online.manipulator.Files.dump;
import static org.osgi.service.log.LogService.LOG_DEBUG;
import static org.osgi.service.log.LogService.LOG_WARNING;
import static org.osgi.service.url.URLConstants.URL_HANDLER_PROTOCOL;
import org.apache.felix.ipojo.annotations.Bind;
import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Instantiate;
import org.apache.felix.ipojo.annotations.Invalidate;
import org.apache.felix.ipojo.annotations.Provides;
import org.apache.felix.ipojo.annotations.Requires;
import org.apache.felix.ipojo.annotations.StaticServiceProperty;
import org.apache.felix.ipojo.annotations.Unbind;
import org.apache.felix.ipojo.manipulator.ManipulationVisitor;
import org.apache.felix.ipojo.manipulator.Reporter;
import org.apache.felix.ipojo.manipulator.ResourceStore;
import org.apache.felix.ipojo.manipulator.metadata.CompositeMetadataProvider;
import org.apache.felix.ipojo.manipulator.metadata.FileMetadataProvider;
import org.apache.felix.ipojo.manipulator.reporter.SystemReporter;
import org.apache.felix.ipojo.manipulator.spi.ModuleProvider;
import org.apache.felix.ipojo.manipulator.Pojoization;
import org.apache.felix.ipojo.manipulator.spi.Module;
import org.apache.felix.ipojo.manipulator.spi.provider.CompositeModuleProvider;
import org.apache.felix.ipojo.manipulator.spi.provider.CoreModuleProvider;
import org.apache.felix.ipojo.manipulator.spi.provider.DefaultModuleProvider;
import org.apache.felix.ipojo.manipulator.visitor.check.CheckFieldConsistencyVisitor;
import org.apache.felix.ipojo.manipulator.visitor.writer.ManipulatedResourcesWriter;
import org.osgi.framework.BundleContext;
import org.osgi.service.log.LogService;
import org.osgi.service.url.AbstractURLStreamHandlerService;
import org.osgi.service.url.URLStreamHandlerService;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* iPOJO URL Handler allowing installation time manipulation.
*
* When a bundle is installed with the {@literal ipojo} URL prefix, the referred bundle is
* manipulated by this handler.
* The {@literal metadata.xml} file can either be provided:
* <ul>
* <li>Inside the bundle, this handler will look for {@literal /metadata.xml} (at the root of the bundle)
* or in {@literal /META-INF/metadata.xml}</li>
* <li>Through the URL using the following URL format: {@literal ipojo:URL_BUNDLE!URL_METADATA}, notice
* the {@literal !} used as separator</li>
* </ul>
*
* Examples of valid iPojo's URLs:
* <pre>
* ipojo:file:///tmp/bundle.jar
* ipojo:/http://www.example.org/bundle.jar
* ipojo://mvn://com.acme/bundle/1.0.0 (with Maven Pax Url support installed)
* ipojo:file:///tmp/bundle.jar!file:///tmp2/metadata.xml
* </pre>
*
* Notice that trailing {@literal '/'} (or {@literal '//'}) after {@literal ipojo:} are optional.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
@Component(publicFactory = false,
immediate = true)
@Provides(specifications = URLStreamHandlerService.class,
properties = @StaticServiceProperty(name = URL_HANDLER_PROTOCOL,
value = "ipojo",
type = "java.lang.String"))
@Instantiate
public class IPOJOURLHandler extends AbstractURLStreamHandlerService {
public static final String IPOJO_SCHEME = "ipojo:";
private final BundleContext m_context;
/**
* The directory storing manipulated bundles.
*/
private final File m_temp;
private List<Module> m_modules = new ArrayList<Module>();
//@Requires(optional = true, defaultimplementation = SystemLogService.class)
// TODO Change this once NPE is fixed
private LogService logger = new SystemLogService();
@Bind(optional = true, aggregate = true)
public void bindModule(Module module) {
m_modules.add(module);
}
@Unbind
public void unbindModule(Module module) {
m_modules.remove(module);
}
/**
* Creates a IPOJOURLHandler.
* Gets the bundle context and create the working
* directory.
*
* @param bundleContext the bundle context
*/
public IPOJOURLHandler(BundleContext bundleContext) {
this(bundleContext, bundleContext.getDataFile("temp"));
}
public IPOJOURLHandler(BundleContext context, File work) {
m_context = context;
m_temp = work;
if (!m_temp.exists()) {
m_temp.mkdir();
}
}
/**
* Stops the URL handler:
* Deletes the working directory.
*/
@Invalidate
public void stop() {
File[] files = m_temp.listFiles();
if (files != null) {
for (int i = 0; i < files.length; i++) {
files[i].delete();
}
}
m_temp.delete();
}
/**
* Opens a connection using the ipojo url handler.
* This methods parses the URL and manipulate the given bundle.
*
* @param url the url.
* @return the URL connection on the manipulated bundle
* @throws java.io.IOException occurs when the bundle cannot be either downloaded, or manipulated or
* installed correctly.
* @see org.osgi.service.url.AbstractURLStreamHandlerService#openConnection(java.net.URL)
*/
public URLConnection openConnection(URL url) throws IOException {
logger.log(LOG_DEBUG, format("Processing URL %s", url));
String full = removeScheme(url);
// Now full is like : URL!URL or URL
String[] urls = full.split("!");
URL bundleURL = null;
URL metadataURL = null;
if (urls.length == 1) {
// URL form
bundleURL = new URL(urls[0]);
} else if (urls.length == 2) {
// URL!URL form
bundleURL = new URL(urls[0]);
metadataURL = new URL(urls[1]);
} else {
throw new MalformedURLException("The iPOJO url is not formatted correctly, ipojo:bundle_url[!metadata_url] expected");
}
logger.log(LOG_DEBUG, format("Extracted URL %s", url));
// Dump the referenced bundle on disk
File original = File.createTempFile("original-", ".jar", m_temp);
dump(bundleURL.openStream(), original);
JarFile jf = new JarFile(original);
File metadata = null;
if (metadataURL != null) {
metadata = File.createTempFile("ipojo-", ".xml", m_temp);
dump(metadataURL, metadata);
} else {
// Check that the metadata are in the jar file
metadata = findMetadata(jf);
}
Reporter reporter = new SystemReporter();
File out = File.createTempFile("ipojo-", ".jar", m_temp);
ResourceStore store = new BundleAwareJarFileResourceStore(jf, out, m_context);
CompositeMetadataProvider composite = new CompositeMetadataProvider(reporter);
if (metadata != null) {
FileMetadataProvider provider = new FileMetadataProvider(metadata, reporter);
composite.addMetadataProvider(provider);
}
ClassLoader classloader = new BridgeClassLoader(original, m_context);
// Pojoization
Pojoization pojoizator = new Pojoization(createModuleProvider());
try {
pojoizator.pojoization(store, composite, createVisitor(store, reporter), classloader);
} catch (Exception e) {
if (!pojoizator.getErrors().isEmpty()) {
throw new IOException("Errors occurred during the manipulation : " + pojoizator.getErrors(), e);
}
e.printStackTrace();
throw new IOException("Cannot manipulate the Url: " + url, e);
}
if (!pojoizator.getErrors().isEmpty()) {
throw new IOException("Errors occurred during the manipulation : " + pojoizator.getErrors());
}
if (!pojoizator.getWarnings().isEmpty()) {
logger.log(LOG_WARNING, format("Warnings occurred during the manipulation %s", pojoizator.getWarnings()));
}
logger.log(LOG_DEBUG, format("Manipulation done %s", out.exists()));
// Cleanup
if (metadata != null) {
metadata.delete();
}
original.delete();
out.deleteOnExit();
// Returns the URL Connection
return out.toURI().toURL().openConnection();
}
private String removeScheme(final URL url) {
String full = url.toExternalForm();
// Remove ipojo:
if (full.startsWith(IPOJO_SCHEME)) {
full = full.substring(IPOJO_SCHEME.length());
}
// Remove '/' or '//'
while (full.startsWith("/")) {
full = full.substring(1);
}
return full.trim();
}
private ManipulationVisitor createVisitor(ResourceStore store, Reporter reporter) {
ManipulatedResourcesWriter writer = new ManipulatedResourcesWriter();
writer.setReporter(reporter);
writer.setResourceStore(store);
CheckFieldConsistencyVisitor checkFieldConsistencyVisitor = new CheckFieldConsistencyVisitor(writer);
checkFieldConsistencyVisitor.setReporter(reporter);
return checkFieldConsistencyVisitor;
}
private ModuleProvider createModuleProvider() {
return new CompositeModuleProvider(
new CoreModuleProvider(),
new DefaultModuleProvider(m_modules)
);
}
/**
* Looks for the metadata.xml file in the jar file.
* Two locations are checked:
* <ol>
* <li>the root of the jar file</li>
* <li>the META-INF directory</li>
* </ol>
*
* @param jar the jar file
* @return the found file or <code>null</code> if not found.
* @throws java.io.IOException occurs when the Jar file cannot be read.
*/
private File findMetadata(JarFile jar) throws IOException {
JarEntry je = jar.getJarEntry("metadata.xml");
if (je == null) {
je = jar.getJarEntry("META-INF/metadata.xml");
}
if (je == null) {
logger.log(LOG_DEBUG, "Metadata file not found, use annotations only.");
return null; // Not Found, use annotation only
} else {
logger.log(LOG_DEBUG, format("Metadata file found at %s", je.getName()));
File metadata = File.createTempFile("ipojo-", ".xml", m_temp);
dump(jar.getInputStream(je), metadata);
logger.log(LOG_DEBUG, format("Metadata file saved at %s", metadata));
return metadata;
}
}
}