This is an automated email from the ASF dual-hosted git repository. davidb pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-javax-activation.git
commit 299695b75d3c3fcf9a755140aaf0683231d24a6c Author: Carsten Ziegeler <[email protected]> AuthorDate: Tue Apr 23 10:41:40 2013 +0000 SLING-2835 : Addition of OSGi-aware javax.activation bundle. Apply contribution from Robert Munteanu git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1470883 13f79535-47bb-0310-9956-ffa450edef68 --- pom.xml | 88 +++++++ .../sling/javax/activation/internal/Activator.java | 130 ++++++++++ .../activation/internal/OsgiMailcapCommandMap.java | 279 +++++++++++++++++++++ 3 files changed, 497 insertions(+) diff --git a/pom.xml b/pom.xml new file mode 100755 index 0000000..988cf05 --- /dev/null +++ b/pom.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.sling</groupId> + <artifactId>sling</artifactId> + <version>15</version> + </parent> + + <artifactId>org.apache.sling.javax.activation</artifactId> + <packaging>bundle</packaging> + <version>0.0.1-SNAPSHOT</version> + + <name>Apache Sling javax.activation bundle</name> + <description>The Apache Sling javax.activation bundle contributes an OSGi-compatible activation bundle.</description> + + <properties> + <javax.activation.version>1.1.1</javax.activation.version> + <site.javadoc.exclude>**.impl.**</site.javadoc.exclude> + </properties> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <instructions> + <Bundle-Activator>org.apache.sling.javax.activation.internal.Activator</Bundle-Activator> + <Export-Package>javax.activation;version=${javax.activation.version}</Export-Package> + <Embed-Dependency>*;scope=compile</Embed-Dependency> + </instructions> + </configuration> + </plugin> + </plugins> + </build> + <dependencies> + <dependency> + <groupId>javax.activation</groupId> + <artifactId>activation</artifactId> + <version>${javax.activation.version}</version> + </dependency> + <!-- Compendium 4.2.0 is needed to access BundleTracker --> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + <version>4.2.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.core</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <scope>provided</scope> + </dependency> + + <!-- Test dependencies --> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + </dependencies> +</project> diff --git a/src/main/java/org/apache/sling/javax/activation/internal/Activator.java b/src/main/java/org/apache/sling/javax/activation/internal/Activator.java new file mode 100755 index 0000000..626613a --- /dev/null +++ b/src/main/java/org/apache/sling/javax/activation/internal/Activator.java @@ -0,0 +1,130 @@ +/* + * 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.sling.javax.activation.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import javax.activation.CommandMap; +import javax.activation.MailcapCommandMap; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.util.tracker.BundleTracker; +import org.osgi.util.tracker.BundleTrackerCustomizer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The <tt>Activator</tt> locates <tt>activation</tt>-related services defined in <tt>mailcap</tt> files and registers + * them so they can be used by the Java Activation Framework + * + * <p> + * The mailcap entries are expected to be found in a <tt>/META-INF/mailcap</tt> file inside the bundle. + * </p> + * + * <p> + * This implementation does not support the full lookup algorithm specified by the {@link MailcapCommandMap}. + * </p> + * + */ +public class Activator implements BundleActivator { + + private static final String MAILCAP_FILE_NAME = "/META-INF/mailcap"; + private static final Logger log = LoggerFactory.getLogger(Activator.class); + + private BundleTracker bundleTracker; + private OsgiMailcapCommandMap commandMap; + + public void start(BundleContext context) throws Exception { + + commandMap = new OsgiMailcapCommandMap(); + + for (Bundle bundle : context.getBundles()) + registerBundleMailcapEntries(bundle); + + CommandMap.setDefaultCommandMap(commandMap); + + bundleTracker = new BundleTracker(context, Bundle.ACTIVE | Bundle.UNINSTALLED | Bundle.STOP_TRANSIENT, + new BundleTrackerCustomizer() { + + public void removedBundle(Bundle bundle, BundleEvent event, Object object) { + unregisterBundleMailcapEntries(bundle); + } + + public void modifiedBundle(Bundle bundle, BundleEvent event, Object object) { + unregisterBundleMailcapEntries(bundle); + registerBundleMailcapEntries(bundle); + } + + public Object addingBundle(Bundle bundle, BundleEvent event) { + registerBundleMailcapEntries(bundle); + return bundle; + } + }); + + bundleTracker.open(); + } + + private void registerBundleMailcapEntries(Bundle bundle) { + + if (bundle.getState() != Bundle.ACTIVE) + return; + + URL mailcapEntry = bundle.getEntry(MAILCAP_FILE_NAME); + if (mailcapEntry == null) + return; + + InputStream input = null; + + try { + input = mailcapEntry.openStream(); + + commandMap.addMailcapEntries(input, bundle); + + } catch (IOException e) { + log.warn("Failed loading " + MAILCAP_FILE_NAME + " from bundle " + bundle, e); + } finally { + try { + input.close(); + } catch (IOException e) { + // don't care + } + } + } + + private void unregisterBundleMailcapEntries(Bundle bundle) { + + commandMap.removeMailcapEntriesForBundle(bundle); + } + + public void stop(BundleContext context) throws Exception { + + if (bundleTracker != null) { + bundleTracker.close(); + bundleTracker = null; + } + + CommandMap.setDefaultCommandMap(null); + } + +} diff --git a/src/main/java/org/apache/sling/javax/activation/internal/OsgiMailcapCommandMap.java b/src/main/java/org/apache/sling/javax/activation/internal/OsgiMailcapCommandMap.java new file mode 100755 index 0000000..e8ff2b9 --- /dev/null +++ b/src/main/java/org/apache/sling/javax/activation/internal/OsgiMailcapCommandMap.java @@ -0,0 +1,279 @@ +/* + * 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.sling.javax.activation.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.activation.CommandInfo; +import javax.activation.CommandMap; +import javax.activation.DataContentHandler; + +import org.osgi.framework.Bundle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.sun.activation.registries.MailcapFile; + +/** + * The <tt>OsgiMailcapCommandMap</tt> is a <tt>CommandMap</tt> which ensures that {@link DataCommandHandler} classes are + * loaded by their containing bundles. + * + * <p> + * This allows the javax.activation bundle to obey classloading contraints in an OSGi environment, while preserving most + * of the functionality available in an unmodified version of the bundle. Notably, this implementation does not support + * loading <tt>mailcap</tt> files which are not placed inside a bundle. + * </p> + * + */ +public class OsgiMailcapCommandMap extends CommandMap { + + private static final Logger log = LoggerFactory.getLogger(OsgiMailcapCommandMap.class); + + private final Map<Bundle, MailcapFile> db = new HashMap<Bundle, MailcapFile>(); + private final Object sync = new Object(); + + public void addMailcapEntries(InputStream mailcapFile, Bundle originatingBundle) throws IOException { + + synchronized (sync) { + db.put(originatingBundle, new MailcapFile(mailcapFile)); + } + + log.debug("Added mailcap entries from bundle {}", originatingBundle); + } + + public void removeMailcapEntriesForBundle(Bundle bundle) { + + boolean removed; + synchronized (sync) { + removed = db.remove(bundle) != null; + } + + if (removed) { + log.debug("Removed mailcap entries from bundle {}", bundle); + } + } + + @Override + public CommandInfo[] getPreferredCommands(String mimeType) { + + List<CommandInfo> commands = new ArrayList<CommandInfo>(); + + if (mimeType != null) { + mimeType = mimeType.toLowerCase(Locale.ENGLISH); + } + + synchronized (sync) { + getPreferredCommands(mimeType, commands, false); + getPreferredCommands(mimeType, commands, true); + } + + return commands.toArray(new CommandInfo[commands.size()]); + } + + private void getPreferredCommands(String mimeType, List<CommandInfo> accumulator, boolean fallback) { + + for (Map.Entry<Bundle, MailcapFile> entry : db.entrySet()) { + Map<?, ?> commandMap = fallback ? entry.getValue().getMailcapFallbackList(mimeType) : entry.getValue() + .getMailcapList(mimeType); + + if (commandMap == null) { + continue; + } + + for (Object verbObject : commandMap.keySet()) { + String verb = (String) verbObject; + + if (!commandsHaveVerb(accumulator, verb)) { + List<?> commands = (List<?>) commandMap.get(verb); + String className = (String) commands.get(0); + accumulator.add(new CommandInfo(verb, className)); + } + } + } + } + + @Override + public CommandInfo[] getAllCommands(String mimeType) { + List<CommandInfo> commands = new ArrayList<CommandInfo>(); + if (mimeType != null) { + mimeType = mimeType.toLowerCase(Locale.ENGLISH); + } + + synchronized (sync) { + getAllCommands(mimeType, commands, false); + getAllCommands(mimeType, commands, true); + } + + return commands.toArray(new CommandInfo[commands.size()]); + } + + private void getAllCommands(String mimeType, List<CommandInfo> accumulator, boolean fallback) { + for (Map.Entry<Bundle, MailcapFile> entry : db.entrySet()) { + Map<?, ?> commandMap = fallback ? entry.getValue().getMailcapFallbackList(mimeType) : + entry.getValue() .getMailcapList(mimeType); + + if (commandMap == null) { + continue; + } + + for (Object verbAsObject : commandMap.keySet()) { + String verb = (String) verbAsObject; + + List<?> commands = (List<?>) commandMap.get(verb); + + for (Object command : commands) { + accumulator.add(new CommandInfo(verb, (String) command)); + } + + } + } + } + + @Override + public CommandInfo getCommand(String mimeType, String cmdName) { + if (mimeType != null) { + mimeType = mimeType.toLowerCase(Locale.ENGLISH); + } + + CommandInfo command = null; + + synchronized (sync) { + command = getCommand(mimeType, cmdName, false); + if (command != null) { + return command; + } + + command = getCommand(mimeType, cmdName, true); + } + + return command; + } + + private CommandInfo getCommand(String mimeType, String commandName, boolean fallback) { + + for (Map.Entry<Bundle, MailcapFile> entry : db.entrySet()) { + Map<?, ?> commandMap = fallback ? entry.getValue().getMailcapFallbackList(mimeType) + : entry.getValue().getMailcapList(mimeType); + if (commandMap != null) { + List<?> commands = (List<?>) commandMap.get(commandName); + if (commands == null) { + continue; + } + + String cmdClassName = (String) commands.get(0); + + if (cmdClassName != null) { + return new CommandInfo(commandName, cmdClassName); + } + } + } + + return null; + } + + @Override + public DataContentHandler createDataContentHandler(String mimeType) { + if (mimeType != null) { + mimeType = mimeType.toLowerCase(Locale.ENGLISH); + } + + synchronized (sync) { + DataContentHandler dch = findDataContentHandler(mimeType, false); + + if (dch != null) { + return dch; + } + + return findDataContentHandler(mimeType, true); + } + } + + private DataContentHandler findDataContentHandler(String mimeType, boolean fallback) { + + for (Map.Entry<Bundle, MailcapFile> entry : db.entrySet()) { + Map<?, ?> commandMap = fallback ? entry.getValue().getMailcapFallbackList(mimeType) : entry.getValue() + .getMailcapList(mimeType); + if (commandMap != null) { + List<?> v = (List<?>) commandMap.get("content-handler"); + if (v == null) { + continue; + } + + String name = (String) v.get(0); + DataContentHandler dch = getDataContentHandler(name, entry.getKey()); + if (dch != null) { + return dch; + } + } + } + + return null; + } + + public String[] getMimeTypes() { + List<String> mimeTypesList = new ArrayList<String>(); + + synchronized (sync) { + for (Map.Entry<Bundle, MailcapFile> entry : db.entrySet()) { + String[] mimeTypes = entry.getValue().getMimeTypes(); + for (String mimeType : mimeTypes) { + if (!mimeTypesList.contains(mimeType)) { + mimeTypesList.add(mimeType); + } + } + } + } + + return mimeTypesList.toArray(new String[mimeTypesList.size()]); + } + + private DataContentHandler getDataContentHandler(String name, Bundle bundle) { + try { + return (DataContentHandler) bundle.loadClass(name).newInstance(); + } catch (InstantiationException e) { + log.warn("Unable to instantiate " + DataContentHandler.class.getSimpleName() + " class ' " + name + + " ' from bundle " + bundle, e); + } catch (IllegalAccessException e) { + log.warn("Unable to instantiate " + DataContentHandler.class.getSimpleName() + " class ' " + name + + " ' from bundle " + bundle, e); + } catch (ClassNotFoundException e) { + log.warn("Unable to instantiate " + DataContentHandler.class.getSimpleName() + " class ' " + name + + " ' from bundle " + bundle, e); + } + + return null; + } + + private boolean commandsHaveVerb(List<CommandInfo> commands, String verb) { + + for (CommandInfo commandInfo : commands) { + if (commandInfo.getCommandName().equals(verb)) { + return true; + } + } + + return false; + } +}
