/** * Copyright (C) 2007 Orbeon, Inc. * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the Free Software Foundation; either version * 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html */ package org.orbeon.oxf.processor; import org.orbeon.oxf.cache.InternalCacheKey; import org.orbeon.oxf.cache.ObjectCache; import org.orbeon.oxf.cache.OutputCacheKey; import org.orbeon.oxf.cache.SimpleOutputCacheKey; import org.orbeon.oxf.common.OXFException; import org.orbeon.oxf.common.ValidationException; import org.orbeon.oxf.pipeline.api.PipelineContext; import org.orbeon.oxf.xml.*; import org.orbeon.oxf.util.DateUtils; import org.orbeon.oxf.xml.dom4j.LocationData; import org.orbeon.saxon.value.DurationValue; import org.xml.sax.Locator; import org.xml.sax.SAXException; import java.util.Date; public class CacheProcessor extends ProcessorImpl { public static String INPUT_KEY = "key"; public static String INPUT_VALIDITY = "validity"; public CacheProcessor() { addInputInfo(new ProcessorInputOutputInfo(INPUT_KEY)); addInputInfo(new ProcessorInputOutputInfo(INPUT_VALIDITY)); addInputInfo(new ProcessorInputOutputInfo(OUTPUT_DATA)); addOutputInfo(new ProcessorInputOutputInfo(OUTPUT_DATA)); } public ProcessorOutput createOutput(String name) { ProcessorOutput output = new ProcessorImpl.ProcessorOutputImpl(getClass(), name) { public void readImpl(PipelineContext pipelineContext, final XMLReceiver receiver) { try { // Get key validity provided by caller final State state = initKeyValidity(pipelineContext); if (state.validity != null) { // Cache data by validity InternalCacheKey internalKey = new InternalCacheKey(CacheProcessor.this, "keyDigest", state.keyDigest); SAXStore dataSaxStore = (SAXStore) ObjectCache.instance().findValid(internalKey, state.validity); if (dataSaxStore == null) { // Can't find data in cache, read it and store it in cache dataSaxStore = new SAXStore(); readInputAsSAX(pipelineContext, INPUT_DATA, dataSaxStore); ObjectCache.instance().add(internalKey, state.validity, dataSaxStore); } dataSaxStore.replay(receiver); } else { // Don't cache readInputAsSAX(pipelineContext, INPUT_DATA, receiver); } } catch (SAXException e) { throw new OXFException(e); } } public OutputCacheKey getKeyImpl(PipelineContext context) { State state = initKeyValidity(context); return new SimpleOutputCacheKey(CacheProcessor.class, OUTPUT_DATA, state.keyDigest); } public Object getValidityImpl(PipelineContext context) { State state = initKeyValidity(context); return state.validity; } }; addOutput(OUTPUT_DATA, output); return output; } private State initKeyValidity(PipelineContext context) { State state = (State) getState(context); if (state.keyDigest == null) { // Get a digest of the key input, if possible from cache state.keyDigest = readCacheInputAsObject(context, getInputByName(INPUT_KEY), new CacheableInputReader<String>() { public String read(PipelineContext context, ProcessorInput input) { DigestContentHandler digestContentHandler = new DigestContentHandler(); readInputAsSAX(context, input, digestContentHandler); return new String(digestContentHandler.getResult()); } }); // Get validity based on date in validity input, if possible from cache CachedValidity validity = readCacheInputAsObject(context, getInputByName(INPUT_VALIDITY), new CacheableInputReader<CachedValidity>() { public CachedValidity read(PipelineContext context, ProcessorInput input) { final StringBuilder validityBuffer = new StringBuilder(); final CachedValidity cachedValidity = new CachedValidity(); readInputAsSAX(context, input, new XMLReceiverAdapter() { Locator locator; public void characters(char[] chars, int start, int length) throws SAXException { // Save location in case we need to use to signal an error if (cachedValidity.locationData == null) cachedValidity.locationData = new LocationData(locator); validityBuffer.append(chars, start, length); } public void setDocumentLocator(Locator locator) { this.locator = locator; } }); cachedValidity.validity = validityBuffer.toString(); return cachedValidity; } }); // Compute data based on validity if ("null".equals(validity.validity) || "none".equals(validity.validity)) { // We won't be caching state.validity = null; } else if (validity.validity.length() == 0) { // No duration specified state.validity = new Long(0); } else if (validity.validity.startsWith("P")) { // Duration specified try { long currentTime = new Date().getTime(); long length = (long) (((DurationValue) DurationValue.makeDuration(validity.validity)).getLengthInSeconds() * 1000.0); state.validity = new Long(currentTime - (currentTime % length)); } catch (Exception e) { throw new ValidationException("Can't parse duration: " + validity.validity, validity.locationData); } } else { // Validity is a date state.validity = new Long(DateUtils.parseISODateOrDateTime(validity.validity)); } } return state; } public void reset(PipelineContext context) { setState(context, new State()); } public static class State { public String keyDigest; public Long validity; } public static class CachedValidity { public String validity; public LocationData locationData; } }