view artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java @ 91:730ff077a58c

More javadoc, some code formatting. artifacts/trunk@847 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Sun, 28 Mar 2010 10:01:03 +0000
parents b2e0cb83631c
children 73d0ebae81d7
line wrap: on
line source
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;

/**
 * @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);

    public static final String NO_SUCH_FACTORY =
        "No such factory";

    public static final String NO_SUCH_ARTIFACT =
        "No such artifact";

    public static final String NOT_IN_BACKGROUND =
        "Not in background";

    public static final String INVALID_CALL_STATE =
        "Invalid after call state";

    public static final String CREATION_FAILED =
        "Creation of artifact failed";

    public static final String INTERNAL_ERROR =
        "Creation of artifact failed";

    public static final String NO_SUCH_SERVICE =
        "No such service";

    public static final String DIGEST_ALGORITHM =
        "SHA-1";

    public static final String XPATH_IMPORT_CHECKSUM =
        "/art:action/art:data/@checksum";

    public static final String XPATH_IMPORT_FACTORY =
        "/art:action/art:data/@factory";

    public static final String XPATH_IMPORT_DATA =
        "/art:action/art:data/text()";

    public static final String INVALID_CHECKSUM =
        "Invalid checksum";

    public static final String CHECKSUM_MISMATCH =
        "Mismatching checksum";

    public static final String NO_DATA =
        "No data";

    public static final String INVALID_ARTIFACT =
        "Invalid artifact";

    public class CallContextImpl
    implements   CallContext
    {
        protected PersistentArtifact artifact;
        protected int                action;
        protected CallMeta           callMeta;
        protected HashMap            customValues;

        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();
        }

        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

    public class DeferredOutputImpl
    implements   DeferredOutput
    {
        protected PersistentArtifact artifact;
        protected Document           format;
        protected CallMeta           callMeta;

        public DeferredOutputImpl() {
        }

        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

    protected String [][] factoryNamesAndDescription;
    protected HashMap     name2factory;

    protected String [][] serviceNamesAndDescription;
    protected HashMap     name2service;

    protected Backend     backend;
    protected Object      context;

    protected byte []     exportSecret;

    protected HashSet     backgroundIds;

    public ArtifactDatabaseImpl() {
    }

    public ArtifactDatabaseImpl(FactoryBootstrap bootstrap) {
        this(bootstrap, null);
    }

    public ArtifactDatabaseImpl(FactoryBootstrap bootstrap, Backend backend) {

        backgroundIds = new HashSet();

        setupArtifactFactories(bootstrap);
        setupServices(bootstrap);

        context      = bootstrap.getContext();
        exportSecret = bootstrap.getExportSecret();

        wireWithBackend(backend);
    }

    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);
        }
    }

    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()));
        }

    }

    public void wireWithBackend(Backend backend) {
        if (backend != null) {
            this.backend = backend;
            backend.setFactoryLookup(this);
        }
    }

    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());
    }

    protected void removeIdFromBackground(int id) {
        synchronized (backgroundIds) {
            backgroundIds.remove(Integer.valueOf(id));
        }
    }

    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);
                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);
    }

    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 :

http://dive4elements.wald.intevation.org