package org.apache.slide.projector.application;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.slide.projector.Context;
import org.apache.slide.projector.Projector;
import org.apache.slide.projector.SystemContext;
import org.apache.slide.projector.URI;
import org.apache.slide.projector.descriptor.ValueFactory;
import org.apache.slide.projector.descriptor.ValueFactoryManager;
import org.apache.slide.projector.engine.ProcessorManager;
import org.apache.slide.projector.engine.ProjectorClassLoader;
import org.apache.slide.projector.engine.Scheduler;
import org.apache.slide.projector.i18n.MessageManager;
import org.apache.slide.projector.value.ArrayValue;
import org.apache.slide.projector.value.StreamableValue;
import org.apache.slide.projector.value.URIValue;
import org.apache.slide.projector.value.Value;
import org.apache.webdav.lib.Subscriber;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import de.zeigermann.xml.simpleImporter.DefaultSimpleImportHandler;
import de.zeigermann.xml.simpleImporter.SimpleImporter;
import de.zeigermann.xml.simpleImporter.SimplePath;
public class ApplicationManager {
private final static Logger logger = Logger.getLogger(ApplicationManager.class.getName());
private final static String APPLICATION_CONFIG = "application.xml";
private final static Context context = new SystemContext();
private final static String CLASSES_DIR = "classes/";
private static ApplicationManager applicationManager;
private List applicationListeners = new ArrayList();
private Map installedApplications = new HashMap(); // URI -> Application
// FIXME: Should be used from applications classpath
private ProjectorClassLoader factoryClassLoader = new ProjectorClassLoader(this.getClass().getClassLoader(), new URIValue(Projector.getProjectorDir()+CLASSES_DIR));
private ApplicationManager() {
logger.log(Level.INFO, "Starting application manager");
Projector.getRepository().subscribe("Update/newmember", new URIValue(Projector.getApplicationsDir()), 1,
new Subscriber() {
public void notify(String uri, Map information) {
logger.log(Level.FINE, "Package manager received add event");
applicationManager.installApplications();
}
}, context.getCredentials());
Projector.getRepository().subscribe("Delete", new URIValue(Projector.getApplicationsDir()), 1,
new Subscriber() {
public void notify(String uri, Map information) {
logger.log(Level.FINE, "Package manager received delete event");
applicationManager.installApplications();
}
}, context.getCredentials());
applicationListeners.add(ProcessorManager.getInstance());
applicationListeners.add(MessageManager.getInstance());
applicationListeners.add(Scheduler.getInstance());
installApplications();
}
private synchronized void installApplications() {
Value[] applicationUris;
List applicationsToInstall = new ArrayList();
List applicationsToRemove = new ArrayList();
try {
List removedApplications = new ArrayList();
removedApplications.addAll(installedApplications.keySet());
applicationUris = ((ArrayValue)Projector.getRepository().getChildren(new URIValue(Projector.getApplicationsDir()), context.getCredentials())).getArray();
for ( int i = 0; i < applicationUris.length; i++ ) {
String applicationUri = applicationUris[i].toString();
if ( !applicationUri.endsWith("/") ) {
applicationUri = applicationUri + "/";
}
/* FIXME: Is this needed or can it be fixed in getChildren() ?
if ( applicationUri.indexOf(Constants.REPOSITORY_DOMAIN) != -1 ) {
applicationUri = applicationUri.substring(applicationUri.indexOf(Constants.REPOSITORY_DOMAIN)+Constants.REPOSITORY_DOMAIN.length());
}
*/
if ( !installedApplications.containsKey(applicationUri) ) {
Application installedApplication = parseApplication(new URIValue(applicationUri));
if ( installedApplication != null ) {
applicationsToInstall.add(installedApplication);
}
} else {
logger.log(Level.FINE, "Application '"+applicationUri+"' already installed");
removedApplications.remove(applicationUri);
}
}
for ( Iterator i = removedApplications.iterator(); i.hasNext(); ) {
Application removedApplication = (Application)installedApplications.get((URI)i.next());
applicationsToRemove.add(removedApplication);
}
// install applications sorted by application dependencies
List sortedApplications = sortApplications(applicationsToInstall);
for ( Iterator i = sortedApplications.iterator(); i.hasNext(); ) {
Application application = (Application)i.next();
Projector.getRepository().subscribe("Update", application.getUri(), 0,
new Subscriber() {
public void notify(String uri, Map information) {
applicationManager.updateApplication(uri);
}
}, context.getCredentials());
install(Application.MESSAGES, application);
install(Application.PROCESSORS, application);
}
Scheduler.getInstance().install(new URIValue(Projector.getWorkDir() + Scheduler.JOBS), true);
for ( Iterator i = sortedApplications.iterator(); i.hasNext(); ) {
Application application = (Application)i.next();
install(Application.JOBS, application);
}
Scheduler.getInstance().saveJobs();
} catch (IOException e) {
logger.log(Level.SEVERE, "Could not determine installed applications!", e);
}
}
public static ApplicationManager getInstance() {
if ( applicationManager == null ) {
applicationManager = new ApplicationManager();
}
return applicationManager;
}
private List sortApplications(List applicationsToInstall) {
List sortedApplications = new ArrayList();
for ( Iterator i = applicationsToInstall.iterator(); i.hasNext(); ) {
Application application = (Application)i.next();
if ( !sortedApplications.contains(application) ) {
logger.log(Level.FINE, "Try to install '"+application.getName()+"'");
addRequiredApplicationsFirst(sortedApplications, applicationsToInstall, application);
}
}
return sortedApplications;
}
private void addRequiredApplicationsFirst(List sortedApplications, List applicationsToInstall, Application application) {
// FIXME: Check application versions
logger.log(Level.FINE, "Checking for dependencies...");
for ( Iterator i = application.getDependencies().iterator(); i.hasNext(); ) {
Dependency dependency = (Dependency)i.next();
logger.log(Level.FINE, "Dependency on application '"+dependency.getRequiredApplication()+"' found!");
Application requiredApplication = getApplicationByName(applicationsToInstall, dependency.getRequiredApplication());
if ( requiredApplication == null ) {
// check if application is already installed
requiredApplication = getApplicationByName(installedApplications.entrySet(), dependency.getRequiredApplication());
if ( requiredApplication == null ) {
// FIXME: Throw exception and abort startup
logger.log(Level.SEVERE, "Required application '"+dependency.getRequiredApplication()+"' not found!");
}
} else {
logger.log(Level.FINE, "Required application '"+requiredApplication.getName()+"' not installed but available, so install it first");
addRequiredApplicationsFirst(sortedApplications, applicationsToInstall, requiredApplication);
}
}
if ( !sortedApplications.contains(application) ) {
logger.log(Level.FINE, "Adding '"+application.getName()+"' to installation process");
sortedApplications.add(application);
}
}
private Application getApplicationByName(Collection applications, String name) {
for ( Iterator i = applications.iterator(); i.hasNext(); ) {
Application application = (Application)i.next();
if ( application.getName().equals(name)) {
return application;
}
}
return null;
}
private Application parseApplication(URI applicationUri) {
try {
SimpleImporter importer = new SimpleImporter();
URI applicationDefinition = new URIValue(applicationUri.toString()+APPLICATION_CONFIG);
StreamableValue applicationDefinitionResouce = ((StreamableValue)Projector.getRepository().getResource(applicationDefinition, context.getCredentials()));
if ( applicationDefinitionResouce != null ) {
InputStream configuration = applicationDefinitionResouce.getInputStream();
ConfigurationHandler handler = new ConfigurationHandler(applicationUri);
importer.addSimpleImportHandler(handler);
importer.parse(new InputSource(configuration));
return handler.getApplication();
} else {
logger.log(Level.SEVERE, "Application definition (application.xml) not found in directory '"+applicationUri+"'. Application will not be installed!");
}
} catch (ParserConfigurationException e) {
logger.log(Level.SEVERE, "Exception while parsing application configuration. Skipping installation...", e);
} catch (SAXException e) {
logger.log(Level.SEVERE, "Exception while parsing application configuration. Skipping installation...", e);
} catch (IOException e) {
logger.log(Level.SEVERE, "Could not get application information. Skipping installation...", e);
}
return null;
}
private Application getApplication(List applications, URI applicationUri) {
for ( Iterator i = applications.iterator(); i.hasNext(); ) {
Application application = (Application)i.next();
if ( application.getUri().equals(applicationUri) ) return application;
}
return null;
}
private synchronized void updateApplication(String uri) {
URI applicationUri = new URIValue(uri);
logger.log(Level.FINE, "Updating application '"+applicationUri+"'");
// Compare newly parsed application with previously installed and send diffs
Application installedApplication = (Application)installedApplications.get(applicationUri);
Application updatedApplication = parseApplication(applicationUri);
for ( Iterator i = installedApplication.getContent().entrySet().iterator(); i.hasNext(); ) {
Map.Entry entry = (Map.Entry)i.next();
List removed = new ArrayList();
removed.addAll((List)entry.getValue());
List updated = updatedApplication.getContent((String)entry.getKey());
if ( updated != null ) {
removed.removeAll(updated);
}
for ( Iterator j = removed.iterator(); j.hasNext(); ) {
for ( Iterator k = applicationListeners.iterator(); k.hasNext(); ) {
((ApplicationListener)k.next()).uninstall((String)entry.getKey(), updatedApplication.getUri(), (URI)j.next());
}
}
}
for ( Iterator i = updatedApplication.getContent().entrySet().iterator(); i.hasNext(); ) {
Map.Entry entry = (Map.Entry)i.next();
List added = new ArrayList();
added.addAll((List)entry.getValue());
List installed = installedApplication.getContent((String)entry.getKey());
if ( installed != null ) {
added.removeAll(installed);
}
for ( Iterator j = added.iterator(); j.hasNext(); ) {
for ( Iterator k = applicationListeners.iterator(); k.hasNext(); ) {
((ApplicationListener)k.next()).install((String)entry.getKey(), updatedApplication.getUri(), (URI)j.next());
}
}
}
}
private void install(String type, Application application) {
logger.log(Level.FINE, "Installing "+type+" of application '"+application.getUri()+"'");
List contents = (List)application.getContent().get(type);
if ( contents != null ) {
for ( Iterator j = contents.iterator(); j.hasNext(); ) {
URI uri = (URI)j.next();
for ( Iterator k = applicationListeners.iterator(); k.hasNext(); ) {
((ApplicationListener)k.next()).install(type, application.getUri(), uri);
}
}
}
installedApplications.put(application.getUri(), application);
}
private void uninstall(String type, Application application) {
logger.log(Level.FINE, "Uninstall "+type+" of application '"+application.getUri()+"'");
for ( Iterator i = application.getContent().entrySet().iterator(); i.hasNext(); ) {
Map.Entry entry = (Map.Entry)i.next();
for ( Iterator j = ((List)entry.getValue()).iterator(); j.hasNext(); ) {
URI uri = (URI)j.next();
for ( Iterator k = applicationListeners.iterator(); k.hasNext(); ) {
((ApplicationListener)k.next()).uninstall((String)entry.getKey(), application.getUri(), uri);
}
}
}
// FIXME: Remove subscriber
installedApplications.remove(application.getUri());
}
private final class ConfigurationHandler extends DefaultSimpleImportHandler {
private Application application;
private URI applicationUri;
private ConfigurationHandler(URI applicationUri) {
this.applicationUri = applicationUri;
}
private Application getApplication() {
return application;
}
public void startElement(SimplePath path, String name, AttributesImpl attributes, String leadingCDdata) {
if (path.matches("application")) {
application = new Application(applicationUri);
} else if ( path.matches("application/name") ) {
application.setName(leadingCDdata);
} else if ( path.matches("application/display-name") ) {
application.setDisplayName(leadingCDdata);
} else if ( path.matches("application/vendor") ) {
application.setVendor(leadingCDdata);
} else if ( path.matches("application/description") ) {
application.setDescription(leadingCDdata);
} else if ( path.matches("application/version") ) {
application.setVersion(leadingCDdata);
} else if ( path.matches("application/dependencies/requires") ) {
Dependency dependency = new Dependency(attributes.getValue("application"), attributes.getValue("version"));
application.addDependency(dependency);
} else if ( path.matches("application/resource-types/resource-type") ) {
String resourceTypeName = attributes.getValue("name");
String clazz = attributes.getValue("class");
try {
ValueFactory descriptorFactory = (ValueFactory)factoryClassLoader.loadClass(clazz).getConstructor(new Class[0]).newInstance(new Object[0]);
ValueFactoryManager.getInstance().registerDescriptorFactory(descriptorFactory);
logger.log(Level.FINE, "Successfully registered descriptor factory " + clazz);
} catch (Exception e) {
logger.log(Level.SEVERE, "Descriptor factory " + clazz + " could not loaded!", e);
}
} else if ( path.matches("application/content/processors") ) {
String uri = attributes.getValue("uri");
application.addContent(Application.PROCESSORS, new URIValue(applicationUri+attributes.getValue("uri")));
} else if ( path.matches("application/content/messages") ) {
application.addContent(Application.MESSAGES, new URIValue(applicationUri+attributes.getValue("uri")));
} else if ( path.matches("application/content/jobs") ) {
application.addContent(Application.JOBS, new URIValue(applicationUri+attributes.getValue("uri")));
}
}
}
}