/******************************************************************************* * Copyright (c) 2008 Cambridge Semantics Incorporated. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * File: $Source$ * Created by: Matthew Roy ( <a href="mailto:mroy@cambridgesemantics.com">mroy@cambridgesemantics.com </a>) * Created on: Jul 15, 2008 * Revision: $Id$ * * Contributors: * Cambridge Semantics Incorporated - initial API and implementation *******************************************************************************/ package org.openanzo.datasource.nodecentric.internal; import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.Collection; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArraySet; import org.apache.commons.collections15.multimap.MultiHashMap; import org.openanzo.cache.ICacheProvider; import org.openanzo.datasource.DatasourceDictionary; import org.openanzo.datasource.IAuthorizationService; import org.openanzo.datasource.IDatasource; import org.openanzo.datasource.IDatasourceFactory; import org.openanzo.datasource.nodecentric.ObjectClassDef; import org.openanzo.exceptions.AnzoException; import org.openanzo.exceptions.AnzoRuntimeException; import org.openanzo.exceptions.ExceptionConstants; import org.openanzo.exceptions.LogUtils; import org.openanzo.indexer.lucene.LuceneDictionary; import org.openanzo.jmx.IJMXServiceEndpoint; import org.openanzo.osgi.IServiceTrackerListener; import org.openanzo.osgi.OsgiConfigurationUtils; import org.openanzo.osgi.OsgiServiceTracker; import org.openanzo.osgi.ServiceActivator; import org.openanzo.rdf.utils.SerializationConstants; import org.openanzo.services.IAuthorizationEventListener; import org.openanzo.services.IStatisticsProvider; import org.openanzo.services.ServicesDictionary; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedServiceFactory; import org.osgi.service.metatype.MetaTypeProvider; import org.osgi.service.metatype.ObjectClassDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Activator factory for NodeCentric Datasources * * @author Matthew Roy ( <a href="mailto:mroy@cambridgesemantics.com">mroy@cambridgesemantics.com</a>) * */ public class NodeCentricDatasourceFactory extends ServiceActivator implements IDatasourceFactory, ManagedServiceFactory, MetaTypeProvider { private static final Logger log = LoggerFactory.getLogger(NodeCentricDatasourceFactory.class); private final static ObjectClassDef instanceDef = new ObjectClassDef(); private final MultiHashMap<IDatasource, ServiceRegistration> serviceRegistrations = new MultiHashMap<IDatasource, ServiceRegistration>(); private final ConcurrentMap<String, NodeCentricDatasource> datasources = new ConcurrentHashMap<String, NodeCentricDatasource>(); private OsgiServiceTracker<IAuthorizationEventListener> aclTracker = null; private final Set<IAuthorizationEventListener> aclEventListeners = new CopyOnWriteArraySet<IAuthorizationEventListener>(); private final HashMap<String, Dictionary<? extends Object, ? extends Object>> initializingDatasource = new HashMap<String, Dictionary<? extends Object, ? extends Object>>(); private NodeCentricDatasourcesJMX jmxExposer = null; /** Factory PID for NodecentricDatasource */ public static final String FACTORY_PID = "org.openanzo.datasource.nodecentric.Factory"; private ServiceRegistration jmxRegistration = null; @Override public String getServicePid() { return FACTORY_PID; } @Override public String[] getDependencies() { return new String[] { ICacheProvider.class.getName() }; } @Override protected Collection<String> getServiceClassNames() { HashSet<String> scn = new HashSet<String>(super.getServiceClassNames()); scn.add(ManagedServiceFactory.class.getName()); scn.add(MetaTypeProvider.class.getName()); return scn; } @Override public boolean registerService() { return false; } @Override public void start(BundleContext bundleContext) throws Exception { super.start(bundleContext); aclTracker = new OsgiServiceTracker<IAuthorizationEventListener>(new IServiceTrackerListener<IAuthorizationEventListener>() { public void unregisterService(IAuthorizationEventListener service) { aclEventListeners.remove(service); } public void registerService(IAuthorizationEventListener service) { aclEventListeners.add(service); } public Class<IAuthorizationEventListener> getComponentType() { return IAuthorizationEventListener.class; } }, bundleContext); aclTracker.open(); jmxExposer = new NodeCentricDatasourcesJMX(bundleContext); jmxRegistration = context.registerService(new String[] { IJMXServiceEndpoint.class.getName() }, jmxExposer, null); } @Override public void stop(BundleContext context) throws Exception { super.stop(context); if (aclTracker != null) { aclTracker.close(); aclTracker = null; } } public String getName() { return FACTORY_PID; } @Override public String getExtraStatus(boolean html) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); if (html) { if (datasources.size() > 0) { pw.println("<h4><font color='#00cc00'>NodeCentric Datasources:</font></h4>"); for (NodeCentricDatasource ds : datasources.values()) { try { pw.println("<li>" + URLDecoder.decode(ds.getName(), "UTF-8") + "</li>"); } catch (UnsupportedEncodingException e) { log.error(LogUtils.INTERNAL_MARKER, "Should never happen since UTF-8 must be supported by all JVMs on all platforms.", e); } pw.println("<br/>Configuration Properties: "); pw.println("<table border='1'>"); for (Enumeration<? extends Object> keys = ds.getConfigurationParameters().keys(); keys.hasMoreElements();) { Object key = keys.nextElement(); Object value = ds.getConfigurationParameters().get(key); if (key.toString().toLowerCase().contains("password") && value.toString().startsWith("encrypted:")) { value = "********"; } pw.println("<tr><td>" + key.toString() + "</td><td>" + value.toString() + "</td></tr>"); } pw.println("</table>"); } } if (initializingDatasource.size() > 0) { pw.println("<h4><font color='#FFFF00'>NodeCentric Datasources Starting:</font></h4>"); for (Map.Entry<String, Dictionary<? extends Object, ? extends Object>> entry : initializingDatasource.entrySet()) { pw.println("<li>" + entry.getKey() + "</li>"); pw.println("<br/>Configuration Properties: "); pw.println("<table border='1'>"); for (Enumeration<? extends Object> keys = entry.getValue().keys(); keys.hasMoreElements();) { Object key = keys.nextElement(); Object value = entry.getValue().get(key); pw.println("<tr><td>" + key.toString() + "</td><td>" + value.toString() + "</td></tr>"); } pw.println("</table>"); } } } else { if (datasources.size() > 0) { pw.println("NodeCentric Datasources:"); for (NodeCentricDatasource ds : datasources.values()) { try { pw.println(URLDecoder.decode(ds.getName(), "UTF-8")); } catch (UnsupportedEncodingException e) { log.error(LogUtils.INTERNAL_MARKER, "Should never happen since UTF-8 must be supported by all JVMs on all platforms.", e); } } } if (initializingDatasource.size() > 0) { pw.println("NodeCentric Datasources Starting:"); for (Map.Entry<String, Dictionary<? extends Object, ? extends Object>> entry : initializingDatasource.entrySet()) { pw.println(entry.getKey()); } } } pw.flush(); sw.flush(); return sw.toString(); } @SuppressWarnings("unchecked") public void updated(String pid, Dictionary configProperties) throws ConfigurationException { try { lock.lockInterruptibly(); if (!datasources.containsKey(pid)) { boolean enabled = ServicesDictionary.getEnabled(configProperties); if (enabled) { try { OsgiConfigurationUtils.validateConfiguration(instanceDef, configProperties); OsgiConfigurationUtils.updateConfigProperties(configProperties, context); String indexLocation = LuceneDictionary.getIndexLocation(configProperties); if (indexLocation == null) { File root = context.getDataFile(pid); if (!root.exists()) { root.mkdirs(); } File indexLocationFile = new File(root, "index"); if (!indexLocationFile.exists()) { indexLocationFile.mkdirs(); } LuceneDictionary.setIndexLocation(configProperties, indexLocationFile.getAbsolutePath()); } initializingDatasource.put(pid, configProperties); try { NodeCentricDatasource datasource = new NodeCentricDatasource(context, configProperties, getDependency(ICacheProvider.class), aclEventListeners, eventAdmin); Dictionary<String, String> properties = new Hashtable<String, String>(); properties.put(SerializationConstants.isPrimary, Boolean.toString(datasource.isPrimary())); properties.put(DatasourceDictionary.KEY_DATASOURCE_URI, datasource.getInstanceURI().toString()); serviceRegistrations.put(datasource, context.registerService(new String[] { NodeCentricDatasource.class.getName(), IDatasource.class.getName(), IStatisticsProvider.class.getName() }, datasource, properties)); IAuthorizationService auth = datasource.getAuthorizationService(); if (auth instanceof IAuthorizationEventListener) { serviceRegistrations.put(datasource, context.registerService(IAuthorizationEventListener.class.getName(), auth, null)); } datasources.put(pid, datasource); } finally { initializingDatasource.remove(pid); } } catch (AnzoException ae) { log.error(LogUtils.LIFECYCLE_MARKER, "Error creating new nodecentric datasource", ae); throw new AnzoRuntimeException(ae); } } } } catch (InterruptedException e) { throw new AnzoRuntimeException(ExceptionConstants.OSGI.INTERNAL_COMPONENT_ERROR, e); } finally { lock.unlock(); } } public void deleted(String pid) { try { lock.lockInterruptibly(); NodeCentricDatasource datasource = datasources.remove(pid); if (datasource != null) { try { datasource.close(false); } catch (AnzoException ae) { log.error(LogUtils.LIFECYCLE_MARKER, "Error closing nodecentric datasource", ae); } Collection<ServiceRegistration> regs = serviceRegistrations.remove(datasource); if (regs != null) { for (ServiceRegistration reg : regs) { reg.unregister(); } } } } catch (InterruptedException e) { throw new AnzoRuntimeException(ExceptionConstants.OSGI.INTERNAL_COMPONENT_ERROR, e); } finally { lock.unlock(); } } @Override public void start() { Properties props = new Properties(); props.put(org.osgi.framework.Constants.SERVICE_PID, getServicePid()); props.put(org.osgi.framework.Constants.SERVICE_DESCRIPTION, getBundleDescription()); context.registerService(getServiceClassNames().toArray(new String[0]), this, props); } @Override public void stop(boolean bundleStopping) { aclTracker.close(); for (NodeCentricDatasource ds : datasources.values()) { try { ds.close(bundleStopping); Collection<ServiceRegistration> regs = serviceRegistrations.remove(ds); if (regs != null) { for (ServiceRegistration reg : regs) { if (!bundleStopping) { reg.unregister(); } } regs.clear(); } } catch (AnzoException ae) { log.error(LogUtils.LIFECYCLE_MARKER, "Error closing nodecentric datasource", ae); } } datasources.clear(); if (bundleStopping && jmxRegistration != null) { jmxRegistration.unregister(); jmxRegistration = null; } } public String[] getLocales() { return null; } public ObjectClassDefinition getObjectClassDefinition(String pid, String locale) { return instanceDef; } }