Java tutorial
/** * This file is part of the Paxle project. * Visit http://www.paxle.net for more information. * Copyright 2007-2010 the original author or authors. * * Licensed under the terms of the Common Public License 1.0 ("CPL 1.0"). * Any use, reproduction or distribution of this program constitutes the recipient's acceptance of this agreement. * The full license text is available under http://www.opensource.org/licenses/cpl1.0.txt * or in the file LICENSE.txt in the root directory of the Paxle distribution. * * Unless required by applicable law or agreed to in writing, this software is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ package org.paxle.parser.impl; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Dictionary; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.ResourceBundle; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.ServiceReference; import org.osgi.service.cm.Configuration; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedService; import org.osgi.service.metatype.AttributeDefinition; import org.osgi.service.metatype.MetaTypeProvider; import org.osgi.service.metatype.ObjectClassDefinition; import org.paxle.core.io.IResourceBundleTool; import org.paxle.core.metadata.IMetaData; import org.paxle.core.metadata.IMetaDataProvider; import org.paxle.core.metadata.IMetaDataService; import org.paxle.parser.ISubParser; import org.paxle.parser.ISubParserManager; public class SubParserManager implements ISubParserManager, MetaTypeProvider, ManagedService, IMetaDataProvider { public static final String PID = ISubParserManager.class.getName(); private static final char SUBPARSER_PID_SEP = '#'; /* ============================================================== * CM properties * ============================================================== */ private static final String ENABLED_MIMETYPES = PID + "." + "enabledMimeTypes"; private static final String ENABLE_DEFAULT = PID + "." + "enableDefault"; /* ============================================================== * Property keys * ============================================================== */ private static final String PROPS_KNOWN_PARSER_PIDS = PID + "." + "knownParserPids"; /** * A {@link HashMap} containing the mime-types that is supported by the sub-parser as key and * the {@link ServiceReference} as value. */ private final HashMap<String, TreeSet<ServiceReference>> subParserList = new HashMap<String, TreeSet<ServiceReference>>(); private final Map<String, ServiceReference> services = new HashMap<String, ServiceReference>(); /** * A list of enabled sub-parsers. The list contains values in the form of * <code>${bundle-symbolic-name}#${service-pid}#${mime-type}</code> or * <code>${bundle-symbolic-name}#${service-class-name}#${mime-type}</code> */ private final Set<String> enabledServices = new HashSet<String>(); private final Set<String> knownServicePids = new HashSet<String>(); /** * For logging */ private final Log logger = LogFactory.getLog(this.getClass()); /** * The CM configuration that belongs to this component */ private final Configuration config; /** * A list of {@link Locale} for which a {@link ResourceBundle} exists * @see MetaTypeProvider#getLocales() */ private final String[] locales; private IResourceBundleTool resourceBundleTool; private boolean enableDefault = true; private IMetaDataService metaDataService = null; private final BundleContext context; private final Properties props; /** * @param config the CM configuration that belongs to this component * @throws IOException * @throws ConfigurationException */ public SubParserManager(Configuration config, IResourceBundleTool resourceBundleTool, final BundleContext context, final Properties props) throws IOException, ConfigurationException { if (config == null) throw new NullPointerException("The CM configuration is null"); this.config = config; this.resourceBundleTool = resourceBundleTool; this.locales = this.resourceBundleTool.getLocaleArray(SubParserManager.PID, Locale.ENGLISH); this.context = context; this.props = props; if (props.get(PROPS_KNOWN_PARSER_PIDS) != null) { final String knownStr = props.getProperty(PROPS_KNOWN_PARSER_PIDS); if (knownStr.length() > 2) for (final String parserPid : knownStr.substring(1, knownStr.length() - 1).split(",")) this.knownServicePids.add(parserPid.trim()); } // initialize CM values if (config.getProperties() == null) { config.update(this.getCMDefaults()); } // update configuration of this component this.updated(config.getProperties()); // getting the meta-data service ServiceReference ref = context.getServiceReference(IMetaDataService.class.getName()); if (ref != null) { this.metaDataService = (IMetaDataService) context.getService(ref); } } public void close() { this.props.put(PROPS_KNOWN_PARSER_PIDS, this.knownServicePids.toString()); } private String[] getMimeTypes(final ServiceReference ref) { final Object mimeTypesObj = ref.getProperty(ISubParser.PROP_MIMETYPES); if (mimeTypesObj instanceof String) { return ((String) mimeTypesObj).split(";|,"); } else if (mimeTypesObj instanceof String[]) { return (String[]) mimeTypesObj; } else { try { final ISubParser p = (ISubParser) context.getService(ref); this.logger.warn(String.format("Parser '%s' registered with no mime-types to the framework", p.getClass().getName())); return null; } finally { context.ungetService(ref); } } } private String keyFor(final String mimeType, final ServiceReference ref) { final String bundle = (String) ref.getBundle().getHeaders().get(Constants.BUNDLE_SYMBOLICNAME); String pid = (String) ref.getProperty(Constants.SERVICE_PID); if (pid == null) pid = context.getService(ref).getClass().getName(); final StringBuilder key = new StringBuilder(mimeType.length() + bundle.length() + pid.length() + 2); key.append(bundle).append(SUBPARSER_PID_SEP).append(pid).append(SUBPARSER_PID_SEP).append(mimeType); return key.toString().intern(); } private String extractMimeType(String servicePID) { return servicePID.substring(servicePID.lastIndexOf(SUBPARSER_PID_SEP) + 1); } private boolean isEnabled(final String mimeType, final ServiceReference ref) { return this.enabledServices.contains(keyFor(mimeType, ref)); } private void setEnabled(final String mimeType, final ServiceReference ref, final boolean enabled) { if (enabled) { this.enabledServices.add(keyFor(mimeType, ref)); } else { this.enabledServices.remove(keyFor(mimeType, ref)); } } @SuppressWarnings("unchecked") public void addSubParser(final ServiceReference ref) { final String[] mimeTypes = getMimeTypes(ref); if (mimeTypes == null) return; for (String mimeType : mimeTypes) { mimeType = mimeType.trim(); TreeSet<ServiceReference> refs = this.subParserList.get(mimeType); if (refs == null) this.subParserList.put(mimeType, refs = new TreeSet<ServiceReference>()); refs.add(ref); final String parserPid = keyFor(mimeType, ref); this.services.put(parserPid, ref); if (!this.knownServicePids.contains(parserPid)) { this.knownServicePids.add(parserPid); setEnabled(mimeType, ref, this.enableDefault); } this.logger.info(String.format("Parser for mimetypes '%s' was installed.", mimeType)); } try { final Dictionary props = config.getProperties(); props.put(ENABLED_MIMETYPES, this.enabledServices.toArray(new String[this.enabledServices.size()])); config.update(props); } catch (IOException e) { logger.error("error updating configuration", e); } } @SuppressWarnings("unchecked") public void removeSubParser(final ServiceReference ref) { final String[] mimeTypes = getMimeTypes(ref); if (mimeTypes == null) return; for (String mimeType : mimeTypes) { mimeType = mimeType.trim(); TreeSet<ServiceReference> refs = this.subParserList.get(mimeType); if (refs == null) continue; this.services.remove(keyFor(mimeType, ref)); refs.remove(ref); this.logger.info(String.format("Parser for mimetypes '%s' was uninstalled.", mimeType)); } try { final Dictionary props = config.getProperties(); props.put(ENABLED_MIMETYPES, this.enabledServices.toArray(new String[this.enabledServices.size()])); config.update(props); } catch (IOException e) { logger.error("error updating configuration", e); } } /** * Getting a {@link ISubParser} which is capable to handle the given mime-type * @param mimeType the mime-type of the document which should be parsed * @return a {@link ISubParser} which is capable to parse a document with the given mime-type */ public ISubParser getSubParser(String mimeType) { if (mimeType == null) return null; mimeType = mimeType.trim(); final TreeSet<ServiceReference> refs = this.subParserList.get(mimeType); if (refs == null) return null; for (final ServiceReference ref : refs) if (isEnabled(mimeType, ref)) return (ISubParser) context.getService(ref); return null; } public Collection<ISubParser> getSubParsers(String mimeType) { if (mimeType == null) return null; mimeType = mimeType.trim(); final TreeSet<ServiceReference> refs = this.subParserList.get(mimeType); if (refs == null) return null; final ArrayList<ISubParser> list = new ArrayList<ISubParser>(refs.size()); for (final ServiceReference ref : refs) { if (isEnabled(mimeType, ref)) { final ISubParser parser = (ISubParser) context.getService(ref); if (parser != null) list.add(parser); } } return list; } /** * Determines if a given mime-type is supported by one of the registered * {@link ISubParser sub-parsers}. * @param mimeType the mime-type * @return <code>true</code> if the given mime-tpye is supported or <code>false</code> otherwise */ public boolean isSupported(String mimeType) { if (mimeType == null) return false; mimeType = mimeType.trim(); final TreeSet<ServiceReference> refs = this.subParserList.get(mimeType); if (refs == null) return false; for (final ServiceReference ref : refs) { if (isEnabled(mimeType, ref)) { return true; } } return false; } /** * @see ISubParserManager#getSubParsers() */ public Map<String, ISubParser> getSubParsers() { final HashMap<String, ISubParser> map = new HashMap<String, ISubParser>(); for (final TreeSet<ServiceReference> refs : this.subParserList.values()) { for (final ServiceReference ref : refs) { final ISubParser parser = (ISubParser) this.context.getService(ref); final String servicePID = (String) ref.getProperty(Constants.SERVICE_PID); map.put(servicePID, parser); } } return map; } /** * @see ISubParserManager#getMimeTypes() */ public Collection<String> getMimeTypes() { Set<String> keySet = this.subParserList.keySet(); String[] keyArray = keySet.toArray(new String[keySet.size()]); return Collections.unmodifiableCollection(Arrays.asList(keyArray)); } /** * @see ISubParserManager#disableMimeType(String) */ @SuppressWarnings("unchecked") public void disableMimeType(String mimeType) { try { if (mimeType == null) return; mimeType = mimeType.toLowerCase(); // update enabled mimetype list final TreeSet<ServiceReference> refs = this.subParserList.get(mimeType.trim()); if (refs != null) for (final ServiceReference ref : refs) setEnabled(mimeType, ref, false); // updating CM Dictionary<String, Object> props = this.config.getProperties(); props.put(ENABLED_MIMETYPES, enabledServices.toArray(new String[enabledServices.size()])); this.config.update(props); } catch (IOException e) { this.logger.error(e); } } /** * @see ISubParserManager#enableMimeType(String) */ @SuppressWarnings("unchecked") public void enableMimeType(String mimeType) { try { if (mimeType == null) return; mimeType = mimeType.toLowerCase(); // updating enabled mimetype list final TreeSet<ServiceReference> refs = this.subParserList.get(mimeType.trim()); if (refs != null) for (final ServiceReference ref : refs) setEnabled(mimeType, ref, true); // updating CM Dictionary<String, Object> props = this.config.getProperties(); props.put(ENABLED_MIMETYPES, enabledServices.toArray(new String[enabledServices.size()])); this.config.update(props); } catch (IOException e) { this.logger.error(e); } } @SuppressWarnings("unchecked") public void enableParser(final String service) { if (service == null) return; try { final String mimeType = this.extractMimeType(service); setEnabled(mimeType, this.services.get(service), true); // updating CM Dictionary<String, Object> props = this.config.getProperties(); props.put(ENABLED_MIMETYPES, enabledServices.toArray(new String[enabledServices.size()])); this.config.update(props); } catch (IOException e) { this.logger.error(e); } } @SuppressWarnings("unchecked") public void disableParser(final String service) { if (service == null) return; try { final String mimeType = this.extractMimeType(service); setEnabled(mimeType, this.services.get(service), false); // updating CM Dictionary<String, Object> props = this.config.getProperties(); props.put(ENABLED_MIMETYPES, enabledServices.toArray(new String[enabledServices.size()])); this.config.update(props); } catch (IOException e) { this.logger.error(e); } } public Set<String> enabledParsers() { final String[] services = this.enabledServices.toArray(new String[this.enabledServices.size()]); return new HashSet<String>(Arrays.asList(services)); } public Map<String, Set<String>> getParsers() { final HashMap<String, Set<String>> r = new HashMap<String, Set<String>>(); for (final Map.Entry<String, ServiceReference> entry : this.services.entrySet()) { final String bundleName = (String) entry.getValue().getBundle().getHeaders().get(Constants.BUNDLE_NAME); Set<String> keys = r.get(bundleName); if (keys == null) r.put(bundleName, keys = new HashSet<String>()); keys.add(entry.getKey()); } return Collections.unmodifiableMap(r); } public Set<String> enabledMimeTypes() { HashSet<String> mimeTypes = new HashSet<String>(); for (final String enabledService : this.enabledServices) { final String mimeType = this.extractMimeType(enabledService); if (this.subParserList.containsKey(mimeType)) mimeTypes.add(mimeType); } return mimeTypes; } /** * @see ISubParserManager#disabledMimeType() */ public Set<String> disabledMimeTypes() { // get all available mime-types and remove enabled mime-types HashSet<String> mimeTypes = new HashSet<String>(this.subParserList.keySet()); mimeTypes.removeAll(enabledMimeTypes()); return mimeTypes; } /** * @see MetaTypeProvider#getLocales() */ public String[] getLocales() { return this.locales == null ? null : this.locales.clone(); } private final class OCD implements ObjectClassDefinition, IMetaData { @SuppressWarnings("unchecked") private final HashMap<String, TreeSet<ServiceReference>> parsers = (HashMap<String, TreeSet<ServiceReference>>) subParserList .clone(); private final String localeStr; private final ResourceBundle rb; public OCD(final String localeStr) { this.localeStr = localeStr; Locale locale = (localeStr == null) ? Locale.ENGLISH : new Locale(localeStr); this.rb = resourceBundleTool.getLocalization(ISubParserManager.class.getSimpleName(), locale); } public AttributeDefinition[] getAttributeDefinitions(int filter) { return new AttributeDefinition[] { // Attribute definition for ENABLE_DEFAULT new AttributeDefinition() { public int getCardinality() { return 0; } public String[] getDefaultValue() { return new String[] { Boolean.TRUE.toString() }; } public String getDescription() { return rb.getString("subparserManager.enableDefault.desc"); } public String getID() { return ENABLE_DEFAULT; } public String getName() { return rb.getString("subparserManager.enableDefault.name"); } public String[] getOptionLabels() { return null; } public String[] getOptionValues() { return null; } public int getType() { return BOOLEAN; } public String validate(String value) { return null; } }, // Attribute definition for ENABLED_MIMETYPES new AttributeDefinition() { private final String[] optionValues, optionLabels; { final TreeMap<String, String> options = new TreeMap<String, String>(); for (final Map.Entry<String, TreeSet<ServiceReference>> entry : parsers.entrySet()) for (final ServiceReference ref : entry.getValue()) { try { // getting the attribute-key to use final String key = keyFor(entry.getKey(), ref); final String PID = (String) ref.getProperty(Constants.SERVICE_PID); // getting the SubParser-service final Object service = context.getService(ref); // getting additional-metadata (if available) IMetaData metadata = null; if (metaDataService != null) { metadata = metaDataService.getMetadata(PID, localeStr); } String name = (metadata != null) ? metadata.getName() : service.getClass().getName(); options.put(key, name + " (" + entry.getKey() + ")"); } finally { context.ungetService(ref); } } optionValues = options.keySet().toArray(new String[options.size()]); optionLabels = options.values().toArray(new String[options.size()]); } public int getCardinality() { return parsers.size(); } public String[] getDefaultValue() { return this.optionValues; } public String getDescription() { return rb.getString("subparserManager.enabledMimeTypes.desc"); } public String getID() { return ENABLED_MIMETYPES; } public String getName() { return rb.getString("subparserManager.enabledMimeTypes.name"); } public String[] getOptionLabels() { return this.optionLabels; } public String[] getOptionValues() { return this.optionValues; } public int getType() { return AttributeDefinition.STRING; } public String validate(String value) { return null; } } }; } public String getDescription() { return rb.getString("subparserManager.desc"); } public String getID() { return PID; } public InputStream getIcon(int size) throws IOException { return (size == 16) ? this.getClass().getResourceAsStream("/OSGI-INF/images/filetypes.png") : null; } public String getName() { return rb.getString("subparserManager.name"); } public String getVersion() { return null; } } /** * @see MetaTypeProvider#getObjectClassDefinition(String, String) */ public ObjectClassDefinition getObjectClassDefinition(String id, String localeStr) { return new OCD(localeStr); } /* * (non-Javadoc) * @see org.paxle.core.metadata.IMetaDataProvider#getMetadata(java.util.Locale) */ public IMetaData getMetadata(String id, String localeStr) { return new OCD(localeStr); } private Hashtable<String, Object> getCMDefaults() { final Hashtable<String, Object> defaults = new Hashtable<String, Object>(); // per default parsing of html and plain-text should be enabled // FIXME: this setting is dependant on the HtmlParser and PlainParser to be installed defaults.put(ENABLED_MIMETYPES, new String[] { "org.paxle.ParserHtml" + SUBPARSER_PID_SEP + "org.paxle.parser.html.impl.HtmlParser" + SUBPARSER_PID_SEP + "text/html", "org.paxle.ParserPlain" + SUBPARSER_PID_SEP + "org.paxle.parser.plain.impl.PlainParser" + SUBPARSER_PID_SEP + "text/plain" }); defaults.put(ENABLE_DEFAULT, Boolean.TRUE); return defaults; } /** * @see ManagedService#updated(Dictionary) */ public void updated(@SuppressWarnings("unchecked") Dictionary properties) throws ConfigurationException { if (properties == null) { logger.warn("updated configuration is null"); /* * Generate default configuration */ properties = this.getCMDefaults(); } // configuring enabled protocols String[] enabledMimeTypes = (String[]) properties.get(ENABLED_MIMETYPES); if (enabledMimeTypes != null) { this.enabledServices.clear(); this.enabledServices.addAll(Arrays.asList(enabledMimeTypes)); } final Object enableDefault = properties.get(ENABLE_DEFAULT); if (enableDefault != null) this.enableDefault = ((Boolean) enableDefault).booleanValue(); } }