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: /** 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: sascha@32: public static final String NO_SUCH_FACTORY = sascha@32: "No such factory"; sascha@32: sascha@32: public static final String NO_SUCH_ARTIFACT = sascha@32: "No such artifact"; sascha@32: sascha@32: public static final String NOT_IN_BACKGROUND = sascha@32: "Not in background"; sascha@32: sascha@32: public static final String INVALID_CALL_STATE = sascha@32: "Invalid after call state"; sascha@32: sascha@32: public static final String CREATION_FAILED = sascha@32: "Creation of artifact failed"; sascha@32: sascha@32: public static final String INTERNAL_ERROR = sascha@32: "Creation of artifact failed"; sascha@32: sascha@70: public static final String NO_SUCH_SERVICE = sascha@70: "No such service"; sascha@70: ingo@79: public static final String DIGEST_ALGORITHM = ingo@79: "SHA-1"; ingo@79: ingo@80: public static final String XPATH_IMPORT_CHECKSUM = ingo@80: "/art:action/art:data/@checksum"; ingo@80: ingo@80: public static final String XPATH_IMPORT_FACTORY = ingo@80: "/art:action/art:data/@factory"; ingo@80: ingo@80: public static final String XPATH_IMPORT_DATA = ingo@80: "/art:action/art:data/text()"; ingo@80: ingo@80: public static final String INVALID_CHECKSUM = ingo@80: "Invalid checksum"; ingo@80: ingo@80: public static final String CHECKSUM_MISMATCH = ingo@80: "Mismatching checksum"; ingo@80: ingo@80: public static final String NO_DATA = ingo@80: "No data"; ingo@80: ingo@80: public static final String INVALID_ARTIFACT = ingo@80: "Invalid artifact"; ingo@80: sascha@32: public class CallContextImpl sascha@32: implements CallContext sascha@32: { sascha@32: protected PersistentArtifact artifact; sascha@32: protected int action; sascha@48: protected CallMeta callMeta; sascha@58: protected HashMap customValues; sascha@32: sascha@48: public CallContextImpl( sascha@48: 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@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@32: logger.warn("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@32: public class DeferredOutputImpl sascha@32: implements DeferredOutput sascha@32: { sascha@32: protected PersistentArtifact artifact; sascha@32: protected Document format; sascha@48: protected CallMeta callMeta; sascha@32: sascha@32: public DeferredOutputImpl() { sascha@32: } sascha@32: 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@32: protected String [][] factoryNamesAndDescription; sascha@32: protected HashMap name2factory; sascha@32: sascha@70: protected String [][] serviceNamesAndDescription; sascha@70: protected HashMap name2service; sascha@70: sascha@32: protected Backend backend; sascha@32: protected Object context; sascha@32: ingo@79: protected byte [] exportSecret; ingo@79: sascha@32: protected HashSet backgroundIds; sascha@13: sascha@13: public ArtifactDatabaseImpl() { sascha@13: } sascha@13: sascha@41: public ArtifactDatabaseImpl(FactoryBootstrap bootstrap) { sascha@41: this(bootstrap, null); sascha@41: } sascha@41: 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@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@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@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@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@32: protected void removeIdFromBackground(int id) { sascha@32: synchronized (backgroundIds) { sascha@32: backgroundIds.remove(Integer.valueOf(id)); sascha@32: } sascha@13: } sascha@13: 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@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@48: 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@55: public Document describe(String identifier, Document data, CallMeta callMeta) sascha@32: 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@48: public Document advance(String identifier, Document target, CallMeta callMeta) sascha@32: 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@48: public DeferredOutput out(String identifier, Document format, CallMeta callMeta) sascha@32: 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( ingo@79: 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: 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@70: 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 :