Mercurial > dive4elements > framework
view artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java @ 102:4122dbc9711b
Added a new package artifacts-common for classes and utilities used in several packages.
artifacts/trunk@1285 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author | Ingo Weinzierl <ingo.weinzierl@intevation.de> |
---|---|
date | Wed, 02 Feb 2011 14:37:28 +0000 |
parents | 933bbc9fc11f |
children | 4d725248f8d1 |
line wrap: on
line source
/* * Copyright (c) 2010 by Intevation GmbH * * This program is free software under the LGPL (>=v2.1) * Read the file LGPL.txt coming with the software for details * or visit http://www.gnu.org/licenses/ if it does not exist. */ package de.intevation.artifactdatabase; import de.intevation.artifactdatabase.Backend.PersistentArtifact; import de.intevation.artifacts.Artifact; import de.intevation.artifacts.ArtifactDatabase; import de.intevation.artifacts.ArtifactDatabaseException; import de.intevation.artifacts.ArtifactFactory; import de.intevation.artifacts.ArtifactNamespaceContext; import de.intevation.artifacts.ArtifactSerializer; import de.intevation.artifacts.CallContext; import de.intevation.artifacts.CallMeta; import de.intevation.artifacts.Service; import de.intevation.artifacts.ServiceFactory; import java.io.IOException; import java.io.OutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Hex; import org.apache.log4j.Logger; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * The core implementation of artifact database. This layer exposes * the needed methods to the artifact runtime system which e.g. may * expose them via REST. The concrete persistent representation of the * artifacts is handled by the {@link Backend backend}. * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a> */ public class ArtifactDatabaseImpl implements ArtifactDatabase, Id.Filter, Backend.FactoryLookup { private static Logger logger = Logger.getLogger(ArtifactDatabaseImpl.class); /** * Error message issued if a requested artifact factory * is not registered to this database. */ public static final String NO_SUCH_FACTORY = "No such factory"; /** * Error message issued if a requested artifact is not found * in this database. */ public static final String NO_SUCH_ARTIFACT = "No such artifact"; /** * Error message issued if one tries to remove a requested artifact * from the list of artifacts running in background which is * not in this list. */ public static final String NOT_IN_BACKGROUND = "Not in background"; /** * Error message issued if an artifact wants to translate itself * into a none valid persistent state. */ public static final String INVALID_CALL_STATE = "Invalid after call state"; /** * Error message issued if the creation of an artifact failed. */ public static final String CREATION_FAILED = "Creation of artifact failed"; /** * Error message if an severe internal error occurred. */ public static final String INTERNAL_ERROR = "Creation of artifact failed"; /** * Error message issued if a requested service is not * offered by this database. */ public static final String NO_SUCH_SERVICE = "No such service"; /** * Default digest hash to be used while im-/exporting artifacts. */ public static final String DIGEST_ALGORITHM = "SHA-1"; /** * XPath to get the checksum from an XML representation of * an exported artifact. */ public static final String XPATH_IMPORT_CHECKSUM = "/art:action/art:data/@checksum"; /** * XPath to get the name of the factory which should be * used to revive an antrifact that is going to be imported. */ public static final String XPATH_IMPORT_FACTORY = "/art:action/art:data/@factory"; /** * XPath to get the base64 encoded data of an artifact * that is going to be imported. */ public static final String XPATH_IMPORT_DATA = "/art:action/art:data/text()"; /** * Error message issued if the checksum of an * artifact to be imported has an invalid syntax. */ public static final String INVALID_CHECKSUM = "Invalid checksum"; /** * Error message issued the checksum validation * of an artifact to be imported fails. */ public static final String CHECKSUM_MISMATCH = "Mismatching checksum"; /** * Error message issued if an artifact to be imported * does not have any data. */ public static final String NO_DATA = "No data"; /** * Error message issued if the deserialization of * an artifact to be imported fails. */ public static final String INVALID_ARTIFACT = "Invalid artifact"; /** * Inner class that implements the call context handed * to the methods calls describe(), feed(), etc. of the artifact. */ public class CallContextImpl implements CallContext { /** * The persistence wrapper around the living artifact */ protected PersistentArtifact artifact; /** * The action to be performed after the artifact calls * desribe(), feed(), etc. return. */ protected int action; /** * The meta information of the concrete call * (preferred languages et. al.) */ protected CallMeta callMeta; /** * Map to act like a clipboard when nesting calls * like a proxy artifact. */ protected HashMap customValues; /** * Constructor to create a call context with a given * persistent artifact, a default action and meta informations. * @param artifact The persistent wrapper around a living artifact. * @param action The action to be performed after the concrete * artifact call has returned. * @param callMeta The meta information for this call context. */ public CallContextImpl( PersistentArtifact artifact, int action, CallMeta callMeta ) { this.artifact = artifact; this.action = action; this.callMeta = callMeta; } public void afterCall(int action) { this.action = action; if (action == BACKGROUND) { addIdToBackground(artifact.getId()); } } public void afterBackground(int action) { if (this.action != BACKGROUND) { throw new IllegalStateException(NOT_IN_BACKGROUND); } fromBackground(artifact, action); } public Object globalContext() { return context; } public ArtifactDatabase getDatabase() { return ArtifactDatabaseImpl.this; } public CallMeta getMeta() { return callMeta; } public Long getTimeToLive() { return artifact.getTTL(); } /** * Dispatches and executes the persistence action after * the return of the concrete artifact call. */ public void postCall() { switch (action) { case NOTHING: break; case TOUCH: artifact.touch(); break; case STORE: artifact.store(); break; case BACKGROUND: logger.warn( "BACKGROUND processing is not fully implemented, yet!"); artifact.store(); break; default: logger.error(INVALID_CALL_STATE + ": " + action); throw new IllegalStateException(INVALID_CALL_STATE); } } public Object getContextValue(Object key) { return customValues != null ? customValues.get(key) : null; } public Object putContextValue(Object key, Object value) { if (customValues == null) { customValues = new HashMap(); } return customValues.put(key, value); } } // class CallContextImpl /** * This inner class allows the deferral of writing the output * of the artifact's out() call. */ public class DeferredOutputImpl implements DeferredOutput { /** * The persistence wrapper around a living artifact. */ protected PersistentArtifact artifact; /** * The input document for the artifact's out() call. */ protected Document format; /** * The meta information of the artifact's out() call. */ protected CallMeta callMeta; /** * Default constructor. */ public DeferredOutputImpl() { } /** * Constructor to create a deferred execution unit for * the artifact's out() call given an artifact, an input document * an the meta information. * @param artifact The persistence wrapper around a living artifact. * @param format The input document for the artifact's out() call. * @param callMeta The meta information of the artifact's out() call. */ public DeferredOutputImpl( PersistentArtifact artifact, Document format, CallMeta callMeta ) { this.artifact = artifact; this.format = format; this.callMeta = callMeta; } public void write(OutputStream output) throws IOException { CallContextImpl cc = new CallContextImpl( artifact, CallContext.TOUCH, callMeta); try { artifact.getArtifact().out(format, output, cc); } finally { cc.postCall(); } } } // class DeferredOutputImpl /** * List of name/description pairs needed for * {@link #artifactFactoryNamesAndDescriptions() }. */ protected String [][] factoryNamesAndDescription; /** * Map to access artifact factories by there name. */ protected HashMap name2factory; /** * List of name/description pairs needed for * {@link #serviceNamesAndDescriptions() }. */ protected String [][] serviceNamesAndDescription; /** * Map to access services by there name. */ protected HashMap name2service; /** * Reference to the storage backend. */ protected Backend backend; /** * Reference of the global context of the artifact runtime system. */ protected Object context; /** * The signing secret to be used for ex-/importing artifacts. */ protected byte [] exportSecret; /** * A set of ids of artifact which currently running in background. * This artifacts should not be removed from the database by the * database cleaner. */ protected HashSet backgroundIds; /** * Default constructor. */ public ArtifactDatabaseImpl() { } /** * Constructor to create a artifact database with the given * bootstrap parameters like artifact- and service factories et. al. * Created this way the artifact database has no backend. * @param bootstrap The parameters to start this artifact database. */ public ArtifactDatabaseImpl(FactoryBootstrap bootstrap) { this(bootstrap, null); } /** * Constructor to create a artifact database with the a given * backend and * bootstrap parameters like artifact- and service factories et. al. * @param bootstrap The parameters to start this artifact database. * @param backend The storage backend. */ public ArtifactDatabaseImpl(FactoryBootstrap bootstrap, Backend backend) { backgroundIds = new HashSet(); setupArtifactFactories(bootstrap); setupServices(bootstrap); context = bootstrap.getContext(); exportSecret = bootstrap.getExportSecret(); wireWithBackend(backend); } /** * Used to extract the artifact factories from the bootstrap * parameters and building the internal lookup tables. * @param bootstrap The bootstrap parameters. */ protected void setupArtifactFactories(FactoryBootstrap bootstrap) { name2factory = new HashMap(); ArtifactFactory [] factories = bootstrap.getArtifactFactories(); factoryNamesAndDescription = new String[factories.length][]; for (int i = 0; i < factories.length; ++i) { ArtifactFactory factory = factories[i]; String name = factory.getName(); String description = factory.getDescription(); factoryNamesAndDescription[i] = new String [] { name, description }; name2factory.put(name, factory); } } /** * Used to extract the service factories from the bootstrap * parameters, setting up the services and building the internal * lookup tables. * @param bootstrap The bootstrap parameters. */ protected void setupServices(FactoryBootstrap bootstrap) { name2service = new HashMap(); ServiceFactory [] serviceFactories = bootstrap.getServiceFactories(); serviceNamesAndDescription = new String[serviceFactories.length][]; for (int i = 0; i < serviceFactories.length; ++i) { ServiceFactory factory = serviceFactories[i]; String name = factory.getName(); String description = factory.getDescription(); serviceNamesAndDescription[i] = new String [] { name, description }; name2service.put( name, factory.createService(bootstrap.getContext())); } } /** * Wires a storage backend to this artifact database and * establishes a callback to be able to revive artifacts * via the serializers of this artifact factories. * @param backend The backend to be wired with this artifact database. */ public void wireWithBackend(Backend backend) { if (backend != null) { this.backend = backend; backend.setFactoryLookup(this); } } /** * Called after an backgrounded artifact signals its * will to be written back to the backend. * @param artifact The persistence wrapper around * the backgrounded artifact. * @param action The action to be performed. */ protected void fromBackground(PersistentArtifact artifact, int action) { logger.warn("BACKGROUND processing is not fully implemented, yet!"); switch (action) { case CallContext.NOTHING: break; case CallContext.TOUCH: artifact.touch(); break; case CallContext.STORE: artifact.store(); break; default: logger.warn("operation not allowed in fromBackground"); } removeIdFromBackground(artifact.getId()); } /** * Removes an artifact's database id from the set of backgrounded * artifacts. The database cleaner is now able to remove it safely * from the database again. * @param id The database id of the artifact. */ protected void removeIdFromBackground(int id) { synchronized (backgroundIds) { backgroundIds.remove(Integer.valueOf(id)); } } /** * Adds an artifact's database id to the set of artifacts * running in backgroound. To be in this set prevents the * artifact to be removed from the database by the database cleaner. * @param id The database id of the artifact to be protected * from being removed from the database. */ protected void addIdToBackground(int id) { synchronized (backgroundIds) { backgroundIds.add(Integer.valueOf(id)); } } public List filterIds(List ids) { int N = ids.size(); ArrayList out = new ArrayList(N); synchronized (backgroundIds) { for (int i = 0; i < N; ++i) { Id id = (Id)ids.get(i); // only delete artifact if its not in background. if (!backgroundIds.contains(Integer.valueOf(id.getId()))) { out.add(id); } } } return out; } public String [][] artifactFactoryNamesAndDescriptions() { return factoryNamesAndDescription; } public ArtifactFactory getInternalArtifactFactory(String factoryName) { return getArtifactFactory(factoryName); } public ArtifactFactory getArtifactFactory(String factoryName) { return (ArtifactFactory)name2factory.get(factoryName); } public Document createArtifactWithFactory( String factoryName, CallMeta callMeta, Document data ) throws ArtifactDatabaseException { ArtifactFactory factory = getArtifactFactory(factoryName); if (factory == null) { throw new ArtifactDatabaseException(NO_SUCH_FACTORY); } Artifact artifact = factory.createArtifact( backend.newIdentifier(), context, data); if (artifact == null) { throw new ArtifactDatabaseException(CREATION_FAILED); } PersistentArtifact persistentArtifact; try { persistentArtifact = backend.storeInitially( artifact, factory, factory.timeToLiveUntouched(artifact, context)); } catch (Exception e) { logger.error(e.getLocalizedMessage(), e); throw new ArtifactDatabaseException(CREATION_FAILED); } CallContextImpl cc = new CallContextImpl( persistentArtifact, CallContext.NOTHING, callMeta); try { return artifact.describe(null, cc); } finally { cc.postCall(); } } public Document describe( String identifier, Document data, CallMeta callMeta ) throws ArtifactDatabaseException { // TODO: Handle background tasks PersistentArtifact artifact = backend.getArtifact(identifier); if (artifact == null) { throw new ArtifactDatabaseException(NO_SUCH_ARTIFACT); } CallContextImpl cc = new CallContextImpl( artifact, CallContext.TOUCH, callMeta); try { return artifact.getArtifact().describe(data, cc); } finally { cc.postCall(); } } public Document advance( String identifier, Document target, CallMeta callMeta ) throws ArtifactDatabaseException { // TODO: Handle background tasks PersistentArtifact artifact = backend.getArtifact(identifier); if (artifact == null) { throw new ArtifactDatabaseException(NO_SUCH_ARTIFACT); } CallContextImpl cc = new CallContextImpl( artifact, CallContext.STORE, callMeta); try { return artifact.getArtifact().advance(target, cc); } finally { cc.postCall(); } } public Document feed(String identifier, Document data, CallMeta callMeta) throws ArtifactDatabaseException { // TODO: Handle background tasks PersistentArtifact artifact = backend.getArtifact(identifier); if (artifact == null) { throw new ArtifactDatabaseException(NO_SUCH_ARTIFACT); } CallContextImpl cc = new CallContextImpl( artifact, CallContext.STORE, callMeta); try { return artifact.getArtifact().feed(data, cc); } finally { cc.postCall(); } } public DeferredOutput out( String identifier, Document format, CallMeta callMeta ) throws ArtifactDatabaseException { // TODO: Handle background tasks PersistentArtifact artifact = backend.getArtifact(identifier); if (artifact == null) { throw new ArtifactDatabaseException(NO_SUCH_ARTIFACT); } return new DeferredOutputImpl(artifact, format, callMeta); } public Document exportArtifact(String artifact, CallMeta callMeta) throws ArtifactDatabaseException { final String [] factoryName = new String[1]; byte [] bytes = (byte [])backend.loadArtifact( artifact, new Backend.ArtifactLoader() { public Object load( ArtifactFactory factory, Long ttl, byte [] bytes, int id ) { factoryName[0] = factory.getName(); ArtifactSerializer serializer = factory.getSerializer(); Artifact artifact = serializer.fromBytes(bytes); artifact.cleanup(context); return serializer.toBytes(artifact); } }); if (bytes == null) { throw new ArtifactDatabaseException(NO_SUCH_ARTIFACT); } return createExportDocument( factoryName[0], bytes, exportSecret); } /** * Creates an exteral XML representation of an artifact. * @param factoryName The name of the factory which is responsible * for the serialized artifact. * @param artifact The byte data of the artifact itself. * @param secret The signing secret. * @return An XML document containing the external representation * of the artifact. */ protected static Document createExportDocument( String factoryName, byte [] artifact, byte [] secret ) { Document document = XMLUtils.newDocument(); MessageDigest md; try { md = MessageDigest.getInstance(DIGEST_ALGORITHM); } catch (NoSuchAlgorithmException nsae) { logger.error(nsae.getLocalizedMessage(), nsae); return document; } md.update(artifact); md.update(secret); String checksum = Hex.encodeHexString(md.digest()); XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator( document, ArtifactNamespaceContext.NAMESPACE_URI, ArtifactNamespaceContext.NAMESPACE_PREFIX); Element root = ec.create("action"); document.appendChild(root); Element type = ec.create("type"); ec.addAttr(type, "name", "export"); root.appendChild(type); Element data = ec.create("data"); ec.addAttr(data, "checksum", checksum); ec.addAttr(data, "factory", factoryName); data.setTextContent(Base64.encodeBase64String(artifact)); root.appendChild(data); return document; } public Document importArtifact(Document input, CallMeta callMeta) throws ArtifactDatabaseException { String factoryName = XMLUtils.xpathString( input, XPATH_IMPORT_FACTORY, ArtifactNamespaceContext.INSTANCE); ArtifactFactory factory; if (factoryName == null || (factoryName = factoryName.trim()).length() == 0 || (factory = getArtifactFactory(factoryName)) == null) { throw new ArtifactDatabaseException(NO_SUCH_FACTORY); } String checksumString = XMLUtils.xpathString( input, XPATH_IMPORT_CHECKSUM, ArtifactNamespaceContext.INSTANCE); byte [] checksum; if (checksumString == null || (checksumString = checksumString.trim()).length() == 0 || (checksum = StringUtils.decodeHex(checksumString)) == null ) { throw new ArtifactDatabaseException(INVALID_CHECKSUM); } checksumString = null; String dataString = XMLUtils.xpathString( input, XPATH_IMPORT_DATA, ArtifactNamespaceContext.INSTANCE); if (dataString == null || (dataString = dataString.trim()).length() == 0) { throw new ArtifactDatabaseException(NO_DATA); } byte [] data = Base64.decodeBase64(dataString); dataString = null; MessageDigest md; try { md = MessageDigest.getInstance(DIGEST_ALGORITHM); } catch (NoSuchAlgorithmException nsae) { logger.error(nsae.getLocalizedMessage(), nsae); return XMLUtils.newDocument(); } md.update(data); md.update(exportSecret); byte [] digest = md.digest(); if (!Arrays.equals(checksum, digest)) { throw new ArtifactDatabaseException(CHECKSUM_MISMATCH); } ArtifactSerializer serializer = factory.getSerializer(); Artifact artifact = serializer.fromBytes(data); data = null; if (artifact == null) { throw new ArtifactDatabaseException(INVALID_ARTIFACT); } artifact.setIdentifier(backend.newIdentifier()); PersistentArtifact persistentArtifact; try { persistentArtifact = backend.storeOrReplace( artifact, factory, factory.timeToLiveUntouched(artifact, context)); } catch (Exception e) { logger.error(e.getLocalizedMessage(), e); throw new ArtifactDatabaseException(CREATION_FAILED); } CallContextImpl cc = new CallContextImpl( persistentArtifact, CallContext.NOTHING, callMeta); try { return artifact.describe(input, cc); } finally { cc.postCall(); } } public String [][] serviceNamesAndDescriptions() { return serviceNamesAndDescription; } public Document process( String serviceName, Document input, CallMeta callMeta ) throws ArtifactDatabaseException { Service service = (Service)name2service.get(serviceName); if (service == null) { throw new ArtifactDatabaseException(NO_SUCH_SERVICE); } return service.process(input, context, callMeta); } } // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :