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@158: import de.intevation.artifacts.ArtifactCollection; ingo@155: import de.intevation.artifacts.ArtifactCollectionFactory; 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@149: import de.intevation.artifacts.User; ingo@130: import de.intevation.artifacts.UserFactory; 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; ingo@168: import java.util.Date; tim@75: import java.util.HashMap; tim@75: import java.util.HashSet; tim@75: import java.util.List; tim@75: ingo@160: import javax.xml.xpath.XPathConstants; ingo@160: 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; ingo@160: import org.w3c.dom.Node; 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 Sascha L. Teichmann 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: ingo@153: /** Message that is returned if an operation was successful.*/ ingo@153: public static final String OPERATION_SUCCESSFUL = ingo@153: "SUCCESS"; ingo@153: ingo@153: /** Message that is returned if an operation failed.*/ ingo@153: public static final String OPERATION_FAILURE = ingo@153: "FAILURE"; ingo@153: 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: ingo@149: ingo@149: // User constants ingo@149: ingo@149: /** ingo@149: * Error message issued if the creation of a user failed. ingo@149: */ ingo@149: public static final String USER_CREATION_FAILED = ingo@149: "Creation of user failed."; ingo@149: ingo@149: /** XPath to figure out the name of a new user.*/ ingo@149: public static final String XPATH_USERNAME = ingo@149: "/art:action/art:user/@name"; ingo@149: ingo@149: /** XPath to figure out the role of a new user.*/ ingo@149: public static final String XPATH_USERROLE = ingo@149: "/art:action/art:user/art:role"; ingo@149: ingo@152: /** Error message if a specified user does not exist.*/ ingo@152: public static final String NO_SUCH_USER = ingo@152: "No such user"; ingo@152: ingo@149: /** Error message if no username is given for user creation.*/ ingo@149: public static final String NO_USERNAME = ingo@149: "Invalid username"; ingo@149: ingo@158: // Collection constants ingo@158: ingo@158: /** ingo@158: * Error message issued if the creation of a collection failed. ingo@158: */ ingo@158: public static final String COLLECTION_CREATION_FAILED = ingo@158: "Creation of collection failed"; ingo@158: ingo@162: /** ingo@162: * XPath to figure out the name of a collection described in the incoming ingo@162: * document. ingo@162: */ ingo@162: public static final String XPATH_COLLECTION_NAME = ingo@162: "/art:action/art:type/art:collection/@name"; ingo@162: ingo@149: 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: /** ingo@155: * The factory that is used to create new artifact collections. ingo@155: */ ingo@155: protected ArtifactCollectionFactory collectionFactory; ingo@155: ingo@155: /** ingo@127: * The factory that is used to create and list users. ingo@127: */ ingo@127: protected UserFactory userFactory; ingo@127: ingo@127: /** 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: ingo@155: setupArtifactCollectionFactory(bootstrap); sascha@70: setupArtifactFactories(bootstrap); sascha@70: setupServices(bootstrap); ingo@127: setupUserFactory(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: /** ingo@155: * Used to extract the artifact collection factory from bootstrap. ingo@155: * ingo@155: * @param bootstrap The bootstrap parameters. ingo@155: */ ingo@155: protected void setupArtifactCollectionFactory(FactoryBootstrap bootstrap) { ingo@155: collectionFactory = bootstrap.getArtifactCollectionFactory(); ingo@155: } ingo@155: ingo@155: /** 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: ingo@127: ingo@127: /** ingo@127: * Used to extract the user factory from the bootstrap. ingo@127: */ ingo@127: protected void setupUserFactory(FactoryBootstrap bootstrap) { ingo@127: userFactory = bootstrap.getUserFactory(); ingo@127: } ingo@127: 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: ingo@149: public UserFactory getUserFactory() { ingo@149: return userFactory; ingo@149: } ingo@149: ingo@158: public ArtifactCollectionFactory getArtifactCollectionFactory() { ingo@158: return collectionFactory; ingo@158: } ingo@158: 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@117: sascha@118: // User API sascha@117: sascha@117: public Document listUsers(CallMeta callMeta) ingo@150: throws ArtifactDatabaseException ingo@150: { ingo@150: UserFactory factory = getUserFactory(); sascha@117: ingo@150: if (factory == null) { ingo@150: throw new ArtifactDatabaseException(NO_SUCH_FACTORY); ingo@150: } ingo@150: sascha@157: User [] users = backend.getUsers(factory, context); ingo@150: ingo@150: if (users != null) { ingo@150: logger.debug(users.length + " users found in the backend."); ingo@150: } ingo@150: ingo@150: Document result = XMLUtils.newDocument(); ingo@150: ingo@150: XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator( ingo@150: result, ingo@150: ArtifactNamespaceContext.NAMESPACE_URI, ingo@150: ArtifactNamespaceContext.NAMESPACE_PREFIX); ingo@150: ingo@150: Element root = ec.create("users"); ingo@150: result.appendChild(root); ingo@150: ingo@150: for (User user: users) { ingo@150: Element ue = ec.create("user"); ingo@152: ec.addAttr(ue, "uuid", user.identifier()); ingo@150: ec.addAttr(ue, "name", user.getName()); ingo@150: ingo@160: Document role = user.getRole(); ingo@160: ingo@160: if (role != null) { ingo@160: ue.appendChild(result.importNode(role.getFirstChild(), true)); ingo@160: } ingo@160: ingo@150: root.appendChild(ue); ingo@160: ingo@150: } ingo@150: ingo@150: return result; sascha@117: } sascha@117: sascha@117: public Document createUser(Document data, CallMeta callMeta) ingo@149: throws ArtifactDatabaseException ingo@149: { ingo@149: UserFactory factory = getUserFactory(); ingo@149: ingo@149: if (factory == null) { ingo@149: throw new ArtifactDatabaseException(NO_SUCH_FACTORY); ingo@149: } ingo@149: ingo@149: String name = XMLUtils.xpathString( ingo@149: data, XPATH_USERNAME, ArtifactNamespaceContext.INSTANCE); ingo@149: ingo@149: if (name == null || name.length() == 0) { ingo@149: logger.warn("User without username not accepted!"); ingo@149: throw new ArtifactDatabaseException(NO_USERNAME); ingo@149: } ingo@149: ingo@160: Node tmp = (Node) XMLUtils.xpath( ingo@160: data, ingo@160: XPATH_USERROLE, ingo@160: XPathConstants.NODE, ingo@160: ArtifactNamespaceContext.INSTANCE); ingo@160: ingo@149: Document role = XMLUtils.newDocument(); ingo@149: ingo@160: if (tmp != null) { ingo@160: Node clone = role.importNode(tmp, true); ingo@160: role.appendChild(clone); ingo@160: } ingo@160: ingo@149: User newUser = null; ingo@149: ingo@149: try { sascha@157: newUser = backend.createUser(name, role, userFactory, context); ingo@149: } ingo@149: catch (Exception e) { ingo@149: logger.error(e.getMessage(), e); ingo@149: throw new ArtifactDatabaseException(USER_CREATION_FAILED); ingo@149: } ingo@149: ingo@149: Document result = XMLUtils.newDocument(); ingo@149: ingo@149: XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator( ingo@149: result, ingo@149: ArtifactNamespaceContext.NAMESPACE_URI, ingo@149: ArtifactNamespaceContext.NAMESPACE_PREFIX); ingo@149: ingo@149: Element root = ec.create("result"); ingo@149: ingo@149: if (newUser != null) { ingo@153: root.setTextContent(OPERATION_SUCCESSFUL); ingo@149: } ingo@149: else { ingo@153: root.setTextContent(OPERATION_FAILURE); ingo@149: } ingo@149: ingo@149: result.appendChild(root); ingo@149: ingo@149: return result; sascha@117: } sascha@117: sascha@117: public Document deleteUser(String userId, CallMeta callMeta) ingo@152: throws ArtifactDatabaseException ingo@152: { ingo@153: logger.debug("Delete user: " + userId); ingo@152: ingo@153: Document result = XMLUtils.newDocument(); ingo@153: ingo@153: XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator( ingo@153: result, ingo@153: ArtifactNamespaceContext.NAMESPACE_URI, ingo@153: ArtifactNamespaceContext.NAMESPACE_PREFIX); ingo@153: ingo@153: Element root = ec.create("result"); ingo@153: result.appendChild(root); ingo@153: sascha@157: boolean success = backend.deleteUser(userId); sascha@154: sascha@154: root.setTextContent(success ? OPERATION_SUCCESSFUL: OPERATION_FAILURE); ingo@152: ingo@153: return result; sascha@117: } sascha@117: sascha@117: sascha@117: // Collection API sascha@117: sascha@117: public Document listCollections(String userId, CallMeta callMeta) ingo@164: throws ArtifactDatabaseException ingo@164: { ingo@164: ArtifactCollectionFactory acf = getArtifactCollectionFactory(); sascha@167: UserFactory uf = getUserFactory(); ingo@164: sascha@167: if (acf == null || uf == null) { ingo@164: throw new ArtifactDatabaseException(NO_SUCH_FACTORY); ingo@164: } ingo@164: ingo@164: logger.debug("Fetch the list of collection for user: " + userId); ingo@164: sascha@167: ArtifactCollection [] ac = backend.listCollections( sascha@167: userId, sascha@167: null, // XXX: fetch from REST sascha@167: acf, uf, sascha@167: context); ingo@164: ingo@164: Document result = XMLUtils.newDocument(); ingo@164: ingo@164: XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator( ingo@164: result, ingo@164: ArtifactNamespaceContext.NAMESPACE_URI, ingo@164: ArtifactNamespaceContext.NAMESPACE_PREFIX); ingo@164: ingo@164: Element root = ec.create("artifact-collections"); ingo@164: result.appendChild(root); ingo@164: ingo@164: if (ac == null || ac.length == 0) { ingo@164: logger.debug("No collections for the user existing."); ingo@164: ingo@164: return result; ingo@164: } ingo@164: ingo@164: logger.debug("Found " + ac.length + " collections of the user."); ingo@164: ingo@164: for (ArtifactCollection c: ac) { ingo@164: Element collection = ec.create("artifact-collection"); ingo@164: ec.addAttr(collection, "name", c.getName()); ingo@164: ec.addAttr(collection, "uuid", c.identifier()); ingo@168: ingo@168: Date creationTime = c.getCreationTime(); ingo@168: String creation = creationTime != null ingo@168: ? Long.toString(creationTime.getTime()) ingo@168: : ""; ingo@168: ingo@168: ec.addAttr(collection, "creation", creation); ingo@164: ingo@164: root.appendChild(collection); ingo@164: } ingo@164: ingo@164: return result; sascha@117: } sascha@117: sascha@117: public Document createCollection(String ownerId, Document data, sascha@117: CallMeta callMeta) ingo@158: throws ArtifactDatabaseException ingo@158: { ingo@158: ArtifactCollectionFactory acf = getArtifactCollectionFactory(); ingo@158: ingo@158: if (acf == null) { ingo@158: throw new ArtifactDatabaseException(NO_SUCH_FACTORY); ingo@158: } ingo@158: ingo@162: String name = XMLUtils.xpathString( ingo@162: data, XPATH_COLLECTION_NAME, ArtifactNamespaceContext.INSTANCE); ingo@162: ingo@162: logger.debug("Create new collection with name: " + name); sascha@159: ingo@158: ArtifactCollection ac = backend.createCollection( sascha@159: ownerId, name, acf, data, context); ingo@158: ingo@158: if (ac == null) { ingo@158: throw new ArtifactDatabaseException(COLLECTION_CREATION_FAILED); ingo@158: } ingo@158: ingo@158: Document result = XMLUtils.newDocument(); ingo@158: ingo@158: XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator( ingo@158: result, ingo@158: ArtifactNamespaceContext.NAMESPACE_URI, ingo@158: ArtifactNamespaceContext.NAMESPACE_PREFIX); ingo@158: ingo@158: Element root = ec.create("result"); ingo@158: result.appendChild(root); ingo@158: ingo@169: Element acElement = ec.create("artifact-collection"); ingo@169: ec.addAttr(acElement, "uuid", ac.identifier()); ingo@169: ingo@169: root.appendChild(acElement); ingo@158: ingo@158: return result; sascha@117: } sascha@117: sascha@117: public Document deleteCollection(String collectionId, CallMeta callMeta) ingo@162: throws ArtifactDatabaseException ingo@162: { ingo@162: logger.debug("Delete collection: " + collectionId); ingo@162: ingo@162: Document result = XMLUtils.newDocument(); ingo@162: ingo@162: XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator( ingo@162: result, ingo@162: ArtifactNamespaceContext.NAMESPACE_URI, ingo@162: ArtifactNamespaceContext.NAMESPACE_PREFIX); ingo@162: ingo@162: Element root = ec.create("result"); ingo@162: result.appendChild(root); ingo@162: ingo@162: boolean success = backend.deleteCollection(collectionId); ingo@162: ingo@162: root.setTextContent(success ? OPERATION_SUCCESSFUL: OPERATION_FAILURE); ingo@162: ingo@162: return result; sascha@117: } sascha@117: sascha@117: public Document getCollectionAttribute(String collectionId, String artifactId, sascha@117: CallMeta callMeta) throws ArtifactDatabaseException { sascha@117: throw new ArtifactDatabaseException("Not implemented, yet!"); sascha@117: } sascha@117: sascha@117: public Document setCollectionAttribute(String collectionId, String artifactId, sascha@117: Document attribute, CallMeta callMeta) sascha@117: throws ArtifactDatabaseException { sascha@117: throw new ArtifactDatabaseException("Not implemented, yet!"); sascha@117: } sascha@117: sascha@117: public Document addCollectionArtifact(String collectionId, String artifactId, ingo@166: CallMeta callMeta) throws ArtifactDatabaseException ingo@166: { ingo@166: logger.debug( ingo@166: "Add artifact '" + artifactId + "' collection '" +collectionId+"'"); ingo@166: ingo@166: boolean success = backend.addCollectionArtifact( sascha@176: collectionId, sascha@176: artifactId, sascha@176: null); // XXX: Needed to be extracted from input ingo@166: ingo@166: Document result = XMLUtils.newDocument(); ingo@166: ingo@166: XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator( ingo@166: result, ingo@166: ArtifactNamespaceContext.NAMESPACE_URI, ingo@166: ArtifactNamespaceContext.NAMESPACE_PREFIX); ingo@166: ingo@166: Element root = ec.create("result"); ingo@166: result.appendChild(root); ingo@166: ingo@166: root.setTextContent(success ? OPERATION_SUCCESSFUL: OPERATION_FAILURE); ingo@166: ingo@166: return result; sascha@117: } sascha@117: sascha@117: public Document removeCollectionArtifact(String collectionId, String artifactId, sascha@117: CallMeta callMeta) throws ArtifactDatabaseException { sascha@117: throw new ArtifactDatabaseException("Not implemented, yet!"); sascha@117: } sascha@117: sascha@117: public Document listCollectionArtifacts(String collectionId, sascha@117: CallMeta callMeta) throws ArtifactDatabaseException { sascha@117: throw new ArtifactDatabaseException("Not implemented, yet!"); sascha@117: } sascha@117: sascha@117: public DeferredOutput outCollection(String collectionId, sascha@117: Document format, CallMeta callMeta) sascha@117: throws ArtifactDatabaseException{ sascha@117: throw new ArtifactDatabaseException("Not implemented, yet!"); sascha@117: } sascha@13: } ingo@79: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :