/*******************************************************************************
* 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: Aug 19, 2008
* Revision: $Id$
*
* Contributors:
* Cambridge Semantics Incorporated - initial API and implementation
*******************************************************************************/
package org.openanzo.datasource.manager.internal;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.StringTokenizer;
import java.util.concurrent.locks.ReentrantLock;
import org.openanzo.client.AnzoClient;
import org.openanzo.client.pool.AnzoClientPool;
import org.openanzo.client.pool.RestrictedAnzoClient;
import org.openanzo.datasource.DatasourceDictionary;
import org.openanzo.datasource.IDatasourceListener;
import org.openanzo.exceptions.AnzoException;
import org.openanzo.exceptions.AnzoRuntimeException;
import org.openanzo.exceptions.ExceptionConstants;
import org.openanzo.exceptions.LogUtils;
import org.openanzo.exceptions.Messages;
import org.openanzo.osgi.IServiceTrackerListener;
import org.openanzo.osgi.IStatusProvider;
import org.openanzo.osgi.OsgiConfigurationUtils;
import org.openanzo.osgi.OsgiServiceTracker;
import org.openanzo.osgi.ServiceLifecycleState;
import org.openanzo.osgi.registry.IRegistryProvider;
import org.openanzo.osgi.registry.RegistryDataset;
import org.openanzo.rdf.RDFFormat;
import org.openanzo.rdf.Statement;
import org.openanzo.rdf.URI;
import org.openanzo.rdf.utils.ReadWriteUtils;
import org.openanzo.rdf.utils.SmartEncodingInputStream;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceRegistration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Matthew Roy ( <a href="mailto:mroy@cambridgesemantics.com">mroy@cambridgesemantics.com</a>)
*
*/
public class InitResourceListener implements BundleListener, IDatasourceListener, IStatusProvider {
private static final Logger log = LoggerFactory.getLogger(InitResourceListener.class);
private static final String REGISTRY_INITIALIZATION_RESOURCE = "Instance-Init-Resources";
private static final String TEMPLATE_FILE = "template.trig";
private OsgiServiceTracker<AnzoClientPool> clientPoolTracker = null;
private final BundleContext context;
AnzoClientPool clientPool = null;
private HashSet<Long> bundles = new HashSet<Long>();
private static final String FILE_EXTENSION = ".trig";
protected final ReentrantLock lock = new ReentrantLock();
protected ServiceLifecycleState state = ServiceLifecycleState.CREATED;
protected Thread startThread = null;
private final StartRunner startRunner = new StartRunner();
String currentlyLoading = null;
private final ArrayList<String> loadedFiles = new ArrayList<String>();
ServiceRegistration reg = null;
private RegistryDataset registry = null;
private boolean testing = false;
InitResourceListener(BundleContext context, IRegistryProvider registryProvider) {
this.context = context;
testing = context.getProperty("regressionTesting") != null;
if (!testing) {
try {
this.registry = registryProvider.openRegistry(org.openanzo.rdf.Constants.OSGI.OSGI, "InitResourcesManifestLoader");
this.registry.addNamedGraph(org.openanzo.rdf.Constants.OSGI.OSGI);
} catch (AnzoException ae) {
log.error(LogUtils.DATASOURCE_MARKER, "Error creating init resources manifest loader's registry", ae);
throw new AnzoRuntimeException(ae);
}
}
IServiceTrackerListener<AnzoClientPool> listener = new IServiceTrackerListener<AnzoClientPool>() {
public void unregisterService(AnzoClientPool service) {
if (clientPool != null) {
clientPool.unregisterDatasourceListener(InitResourceListener.this);
}
clientPool = service;
stopLocked(false);
}
public void registerService(AnzoClientPool service) {
if (clientPool == null) {
clientPool = service;
startLocked();
}
}
public Class<AnzoClientPool> getComponentType() {
return AnzoClientPool.class;
}
};
String filterString = "(&(" + Constants.OBJECTCLASS + "=" + listener.getComponentType().getName() + ")(!(" + DatasourceDictionary.KEY_DATASOURCE_URI + " =*)))";
try {
Filter filter = context.createFilter(filterString);
clientPoolTracker = new OsgiServiceTracker<AnzoClientPool>(listener, filter, context);
clientPoolTracker.open();
} catch (InvalidSyntaxException ise) {
log.error(LogUtils.LIFECYCLE_MARKER, Messages.formatString(ExceptionConstants.OSGI.INVALID_SERVICE_SYNTAX, filterString), ise);
}
reg = context.registerService(IStatusProvider.class.getName(), this, null);
}
void stop(boolean bundleStopping) {
state = ServiceLifecycleState.STOPPING;
if (!bundleStopping) {
context.removeBundleListener(this);
reg.unregister();
reg = null;
}
clientPoolTracker.close();
state = ServiceLifecycleState.STOPPED;
}
public String getCurrentStatus(boolean html) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
if (html) {
String statusString = null;
switch (state) {
case CREATED:
statusString = "<font color='#0000cc'>" + state.toString() + "</font>";
break;
case STARTED:
statusString = "<font color='#00cc00'>" + state.toString() + "</font>";
break;
case STARTING:
statusString = "<font color='#FFFF00'>" + state.toString() + "</font>";
break;
case STOPPED:
statusString = "<font color='#cc0000'>" + state.toString() + "</font>";
break;
case STOPPING:
statusString = "<font color='#FF9900'>" + state.toString() + "</font>";
break;
}
pw.println("<div id='initResourceProvider'>");
pw.println("<h2>org.openanzo.datasource.manager.InitResourceDataLoader" + " - " + statusString + "</h2><div id='subInitResourceProvider'> Loads init resource data into server");
pw.println("<br/>Bundle: [" + context.getBundle().getBundleId() + "] " + context.getBundle().getLocation());
if (currentlyLoading != null) {
pw.println("<br/>Currently Loading:<br/>");
pw.println(currentlyLoading);
}
if (loadedFiles.size() > 0) {
pw.println("<br/>Loaded Files:<br/>");
for (String file : loadedFiles) {
pw.println("<li>" + file + "</li>");
}
}
pw.println("</div></div><hr width='100%' size='1' />");
} else {
pw.println("**********************************************************");
pw.println("InitResourceListener:");
pw.println(getState());
if (currentlyLoading != null) {
pw.println("Currently Loading:\n");
pw.println(currentlyLoading);
}
if (loadedFiles.size() > 0) {
pw.println("Loaded Files:\n");
for (String file : loadedFiles) {
pw.println(file + "\n");
}
}
}
pw.flush();
sw.flush();
return sw.toString();
}
public ServiceLifecycleState getState() {
return state;
}
private void start() {
state = ServiceLifecycleState.STARTING;
clientPool.registerDatasourceListener(this);
processBundles();
context.addBundleListener(this);
state = ServiceLifecycleState.STARTED;
}
public void reset() throws AnzoException {
//clear the bundles list, since data has been reset
bundles.clear();
}
public void resetStarting() throws AnzoException {
}
public void resetFinished() throws AnzoException {
}
public void postReset() throws AnzoException {
this.registry.addNamedGraph(org.openanzo.rdf.Constants.OSGI.OSGI);
processBundles();
}
public void bundleChanged(BundleEvent event) {
Bundle bundle = event.getBundle();
if (Bundle.ACTIVE == bundle.getState()) {
try {
registry.beginUpdatingRegistry();
addingBundle(bundle);
registry.commitRegistry();
} catch (AnzoException ae) {
log.error(LogUtils.LIFECYCLE_MARKER, "Error processing bundle changed information", ae);
}
}
}
private void processBundles() {
try {
registry.beginUpdatingRegistry();
Bundle[] bundles = context.getBundles();
for (Bundle bundle : bundles) {
if (Bundle.ACTIVE == bundle.getState()) {
addingBundle(bundle);
}
}
registry.commitRegistry();
} catch (AnzoException ae) {
log.error(LogUtils.LIFECYCLE_MARKER, "Error processing bundle information", ae);
}
}
static final URI loaded = org.openanzo.rdf.Constants.valueFactory.createURI("http://openanzo.org/internal/initResourcesManifest#isLoaded");
static final URI fileLoaded = org.openanzo.rdf.Constants.valueFactory.createURI("http://openanzo.org/internal/registryManifest#fileLoaded");
static final URI irm = org.openanzo.rdf.Constants.valueFactory.createURI("http://openanzo.org/internal/initResourcesManifest");
@SuppressWarnings("unchecked")
void addingBundle(Bundle bundle) {
if (!bundles.contains(bundle.getBundleId())) {
bundles.add(bundle.getBundleId());
try {
Dictionary<String, String> headers = bundle.getHeaders();
String initPath = headers.get(REGISTRY_INITIALIZATION_RESOURCE);
if (initPath != null) {
String symbolicName = bundle.getSymbolicName();
URI uri = org.openanzo.rdf.Constants.valueFactory.createURI(org.openanzo.rdf.Constants.OSGI.BUNDLE + symbolicName);
if (registry.contains(irm, loaded, uri, org.openanzo.rdf.Constants.OSGI.OSGI)) {
return;
}
RestrictedAnzoClient client = this.clientPool.getAnzoClient(false, "InitResourceListener");
StringTokenizer st = new StringTokenizer(initPath, ",");
try {
Collection<Statement> stmts = new HashSet<Statement>();
Collection<Statement> templateStatements = new HashSet<Statement>();
while (st.hasMoreTokens()) {
String token = st.nextToken();
token = OsgiConfigurationUtils.preprocessString(token, bundle.getBundleContext());
if (token.endsWith(".trig")) {
log.info(LogUtils.LIFECYCLE_MARKER, "Loading instance data in file '{}' for bundle {}.", token, bundle.getSymbolicName());
processFile(bundle, token, stmts, templateStatements);
log.info(LogUtils.LIFECYCLE_MARKER, "Done loading instance data in file '{}' for bundle {}.", token, bundle.getSymbolicName());
} else if (token.endsWith("/")) {
log.info(LogUtils.LIFECYCLE_MARKER, "Loading instance data from directory '{}' (excluding subdirectories) for bundle {}.", token, bundle.getSymbolicName());
processDirectory(bundle, token, false, client);
log.info(LogUtils.LIFECYCLE_MARKER, "Done loading instance data from directory '{}' (excluding subdirectories) for bundle {}.", token, bundle.getSymbolicName());
} else if (token.endsWith("/*")) {
log.info(LogUtils.LIFECYCLE_MARKER, "Loading instance data from directory '{}' (including subdirectories) for bundle {}.", token, bundle.getSymbolicName());
processDirectory(bundle, token, true, client);
log.info(LogUtils.LIFECYCLE_MARKER, "Done loading instance data from directory '{}' (including subdirectories) for bundle {}.", token, bundle.getSymbolicName());
} else {
log.warn(LogUtils.LIFECYCLE_MARKER, "Ignoring entry in {} property for bundle {}. Entry must be a file with .trig extension, a directory ending in '/', or a directory ending in '/*'.", REGISTRY_INITIALIZATION_RESOURCE, bundle.getSymbolicName());
}
}
importStatements(client, stmts, templateStatements);
registry.add(irm, loaded, uri, org.openanzo.rdf.Constants.OSGI.OSGI);
} finally {
this.clientPool.returnAnzoClient(client);
currentlyLoading = null;
}
}
} catch (AnzoException ae) {
log.error(LogUtils.LIFECYCLE_MARKER, "Error adding bundle inforation to registry", ae);
}
}
}
private void importStatements(AnzoClient client, Collection<Statement> statements, Collection<Statement> templateStatements) throws AnzoException {
if (templateStatements == null || templateStatements.size() == 0) {
client.importStatements(statements, AnzoClient.REVISIONED_NAMED_GRAPH);
} else {
client.importStatements(statements, templateStatements);
}
}
@SuppressWarnings("unchecked")
private void processDirectory(Bundle bundle, String directoryName, boolean subDirs, AnzoClient client) throws AnzoException {
Enumeration entries = bundle.getEntryPaths(directoryName);
if (entries != null) {
Collection<Statement> stmts = new HashSet<Statement>();
Collection<Statement> templateStatements = new HashSet<Statement>();
while (entries.hasMoreElements()) {
String entry = (String) entries.nextElement();
if (entry.endsWith(FILE_EXTENSION)) {
processFile(bundle, entry, stmts, templateStatements);
} else if (subDirs && entry.endsWith("/")) {
processDirectory(bundle, entry, subDirs, client);
}
}
importStatements(client, stmts, templateStatements);
}
}
private void processFile(Bundle bundle, String file, Collection<Statement> stmts, Collection<Statement> templateStatements) {
try {
currentlyLoading = file;
URL initResource = bundle.getEntry(file);
if (initResource != null) {
if (file.endsWith(TEMPLATE_FILE)) {
templateStatements.addAll(ReadWriteUtils.loadStatements(SmartEncodingInputStream.createSmartReader(initResource.openStream()), RDFFormat.forFileName(file), ""));
} else {
stmts.addAll(ReadWriteUtils.loadStatements(SmartEncodingInputStream.createSmartReader(initResource.openStream()), RDFFormat.forFileName(file), ""));
}
loadedFiles.add(file);
registry.add(irm, fileLoaded, org.openanzo.rdf.Constants.valueFactory.createURI(initResource.toURI().toString()), org.openanzo.rdf.Constants.OSGI.OSGI);
}
} catch (URISyntaxException ae) {
log.error(LogUtils.LIFECYCLE_MARKER, "IOException while loading " + file, ae);
} catch (IOException ae) {
log.error(LogUtils.LIFECYCLE_MARKER, "IOException while loading " + file, ae);
} catch (AnzoException ae) {
log.error(LogUtils.LIFECYCLE_MARKER, "AnzoException loading " + file, ae);
} finally {
currentlyLoading = null;
}
}
final protected void startLocked() {
lock.lock();
try {
if (startThread == null) {
startThread = new Thread(startRunner, "ServiceInitializer: InitResourceListener");
startThread.start();
}
} finally {
lock.unlock();
}
}
final protected void stopLocked(boolean bundleStopping) {
StopRunner runner = new StopRunner(bundleStopping);
runner.run();
}
class StartRunner implements Runnable {
public void run() {
lock.lock();
ServiceLifecycleState prevState = state;
try {
if (state == ServiceLifecycleState.STARTED) {
} else if (state != ServiceLifecycleState.STARTING) {
state = ServiceLifecycleState.STARTING;
start();
state = ServiceLifecycleState.STARTED;
}
} catch (Throwable t) {
state = prevState;
} finally {
startThread = null;
lock.unlock();
}
}
}
class StopRunner implements Runnable {
boolean bundleStopping = false;
StopRunner(boolean bundleStopping) {
this.bundleStopping = bundleStopping;
}
public void run() {
lock.lock();
try {
if (state == ServiceLifecycleState.STARTED) {
state = ServiceLifecycleState.STOPPING;
stop(bundleStopping);
}
} finally {
state = ServiceLifecycleState.STOPPED;
lock.unlock();
}
}
}
}