/** * Copyright (C) 2010 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.impl; import org.orbeon.oxf.cache.*; import org.orbeon.oxf.pipeline.api.PipelineContext; import org.orbeon.oxf.processor.ProcessorImpl; import org.orbeon.oxf.util.NumberUtils; import java.util.Arrays; /** * Implementation of a caching transformer output that assumes that an output simply depends on * all the inputs plus optional local information that can be digested. */ public abstract class DigestTransformerOutputImpl extends CacheableTransformerOutputImpl { private static final Long DEFAULT_VALIDITY = 0L; public DigestTransformerOutputImpl(ProcessorImpl processor, String name) { super(processor, name); } @Override protected final boolean supportsLocalKeyValidity() { return true; } @Override protected CacheKey getLocalKey(PipelineContext pipelineContext) { for (final String inputName : getProcessor(pipelineContext).getInputNames()) { if (!getProcessor(pipelineContext).isInputInCache(pipelineContext, inputName))// NOTE: We don't really support multiple inputs with the same name. return null; } return getFilledOutState(pipelineContext).key; } @Override protected final Object getLocalValidity(PipelineContext pipelineContext) { for (final String inputName : getProcessor(pipelineContext).getInputNames()) { if (!getProcessor(pipelineContext).isInputInCache(pipelineContext, inputName))// NOTE: We don't really support multiple inputs with the same name. return null; } return getFilledOutState(pipelineContext).validity; } /** * Fill-out user data into the state, if needed. Return caching information. * * @param pipelineContext the current PipelineContext * @param digestState state set during processor start() or reset() * @return false if private information is known that requires disabling caching, true otherwise */ protected abstract boolean fillOutState(PipelineContext pipelineContext, DigestState digestState); /** * Compute a digest of the internal document on which the output depends. * * @param digestState state set during processor start() or reset() * @return the digest */ protected abstract byte[] computeDigest(PipelineContext pipelineContext, DigestState digestState); protected final DigestState getFilledOutState(PipelineContext pipelineContext) { // This is called from both readImpl and getLocalValidity. Based on the assumption that // a getKeyImpl will be followed soon by a readImpl if it fails, we compute key, // validity, and user-defined data. final DigestState state = (DigestState) getProcessor(pipelineContext).getState(pipelineContext); // Create request document final boolean allowCaching = fillOutState(pipelineContext, state); // Compute key and validity if possible if ((state.validity == null || state.key == null) && allowCaching) { // Compute digest if (state.digest == null) { state.digest = computeDigest(pipelineContext, state); } // Compute local key if (state.key == null) { state.key = new InternalCacheKey(getProcessor(pipelineContext), "requestHash", NumberUtils.toHexString(state.digest)); } // Compute local validity if (state.validity == null) { state.validity = DEFAULT_VALIDITY; // HACK so we don't recurse at the next line final OutputCacheKey outputCacheKey = getKeyImpl(pipelineContext); if (outputCacheKey != null) { final Cache cache = ObjectCache.instance(); final DigestValidity digestValidity = (DigestValidity) cache.findValid(outputCacheKey, DEFAULT_VALIDITY); if (digestValidity != null && Arrays.equals(state.digest, digestValidity.digest)) { state.validity = digestValidity.lastModified; } else { final Long currentValidity = new Long(System.currentTimeMillis()); cache.add(outputCacheKey, DEFAULT_VALIDITY, new DigestValidity(state.digest, currentValidity)); state.validity = currentValidity; } } else { state.validity = null; // HACK restore } } } return state; } }