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