| /* |
| * 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; |
| } |
| |
| } |
| |
| } |