/* The contents of this file are subject to the license and copyright terms * detailed in the license directory at the root of the source tree (also * available online at http://fedora-commons.org/license/). */ package org.fcrepo.server.security.xacml.pdp.decorator; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import org.apache.commons.io.IOUtils; import org.fcrepo.server.Server; import org.fcrepo.server.errors.GeneralException; import org.fcrepo.server.errors.InitializationException; import org.fcrepo.server.proxy.AbstractInvocationHandler; import org.fcrepo.server.proxy.ModuleConfiguredInvocationHandler; import org.fcrepo.server.security.xacml.pdp.data.FedoraPolicyStore; import org.fcrepo.server.security.xacml.pdp.data.PolicyIndex; import org.fcrepo.server.security.xacml.pdp.data.PolicyIndexException; import org.fcrepo.server.storage.DOManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A {@link java.lang.reflect.InvocationHandler InvocationHandler} responsible * for updating the FeSL XACML policy index whenever API-M * invocations result in changes to a policy stored in a Fedora object. * * @author Stephen Bayliss * @version $Id$ */ public class PolicyIndexInvocationHandler extends AbstractInvocationHandler implements ModuleConfiguredInvocationHandler { /** Logger for this class. */ private static Logger LOG = LoggerFactory.getLogger(PolicyIndexInvocationHandler.class.getName()); private DOManager m_DOManager = null; // for updating the policy cache private PolicyIndex m_policyIndex = null; public void setPolicyIndex(PolicyIndex policyIndex) { m_policyIndex = policyIndex; } public void setDOManager(DOManager manager) { m_DOManager = manager; } public void init(Server server) throws InitializationException { m_DOManager = (DOManager)server.getBean(DOManager.class.getName()); m_policyIndex = (PolicyIndex)server.getBean(PolicyIndex.class.getName()); if (m_DOManager == null) { LOG.error("DOManager module was not set"); throw new InitializationException("DOManager module was not set"); } if (m_policyIndex == null) { LOG.error("PolicyIndex was not set"); throw new InitializationException("PolicyIndex was not set"); } } /** * {@inheritDoc} */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // get the method parameters and "type" ManagementMethodInvocation managementMethod = new ManagementMethodInvocation(method, args); // if it's not a method that requires policy cache modifications, quit if (managementMethod.action.equals(ManagementMethodInvocation.Action.NA)) return invokeTarget(target, method, args); Object returnValue = null; // holds the state of the Fedora policy object before and after the API method is invoked PolicyObject policyBefore = null; PolicyObject policyAfter = null; // validation note: // validation is carried out as part of the API methods // policy index update occurs after the management method has been invoked // therefore if a validation error occurs the object will not have changed, // and the resulting exception means the policy index won't be updated (and therefore remain in sync) switch (managementMethod.target) { case DIGITALOBJECT: // API methods operating on the object switch (managementMethod.component) { case STATE: // object state change // if the requested object state is null, then there's no state change - no change to policy index if (managementMethod.parameters.objectState != null && managementMethod.parameters.objectState.length() > 0) { // get the "before" object and determine if it was indexed in the cache policyBefore = new PolicyObject(m_DOManager, managementMethod.parameters.context, managementMethod.parameters.pid, null, null, null); boolean indexedBefore = policyBefore.isPolicyActive(); returnValue = invokeTarget(target, method, args); // get the object after the state change and determine if it should be indexed policyAfter = new PolicyObject(m_DOManager, managementMethod.parameters.context, managementMethod.parameters.pid, managementMethod.parameters.objectState, null, null); // if the indexing status has changed, make appropriate changes to the policy cache/index if (indexedBefore != policyAfter.isPolicyActive()) { if (policyAfter.isPolicyActive() ) { addPolicy(managementMethod.parameters.pid, policyAfter.getDsContent()); } else { deletePolicy(managementMethod.parameters.pid); } } } break; case CONTENT: // object ingest or purge switch (managementMethod.action){ case CREATE: // ingest // do the API call returnValue = invokeTarget(target, method, args); // get the ingested policy - note ingested object PID is the return value policyAfter = new PolicyObject(m_DOManager, managementMethod.parameters.context, (String)returnValue, null, null, null); // add to the cache if required if (policyAfter.isPolicyActive()) { addPolicy((String)returnValue, policyAfter.getDsContent()); } break; case DELETE: // purge // get the policy object that existed prior to ingest and see if it was indexed policyBefore = new PolicyObject(m_DOManager, managementMethod.parameters.context, managementMethod.parameters.pid, null, null, null); boolean wasIndexed = policyBefore.isPolicyActive(); // do the API call returnValue = invokeTarget(target, method, args); // if the policy was indexed, delete it from the cache/index if (wasIndexed) deletePolicy(managementMethod.parameters.pid); break; default: } break; default: } break; case DATASTREAM: // operations on datastreams // note, DS ID can be null - server-assigned ID if (managementMethod.parameters.dsID != null && managementMethod.parameters.dsID.equals(FedoraPolicyStore.FESL_POLICY_DATASTREAM)) { switch (managementMethod.component) { case STATE: // datastream state change // get the object prior to the API call and see if it was indexed/cached policyBefore = new PolicyObject(m_DOManager, managementMethod.parameters.context, managementMethod.parameters.pid, null, managementMethod.parameters.dsID, null); boolean wasIndexed = policyBefore.isPolicyActive(); // do the API call returnValue = invokeTarget(target, method, args); // the object after the call policyAfter = new PolicyObject(m_DOManager, managementMethod.parameters.context, managementMethod.parameters.pid, null, managementMethod.parameters.dsID, managementMethod.parameters.dsState); // if indexing status has changed, update the cache/index if (wasIndexed != policyAfter.isPolicyActive()) { if (policyAfter.isPolicyActive()) { addPolicy(managementMethod.parameters.pid, policyAfter.getDsContent()); } else { deletePolicy(managementMethod.parameters.pid); } } break; case CONTENT: // datastream add, modify, purge switch (managementMethod.action){ case CREATE: // do the API call returnValue = invokeTarget(target, method, args); // get the policy object after the method call policyAfter = new PolicyObject(m_DOManager, managementMethod.parameters.context, managementMethod.parameters.pid, null, managementMethod.parameters.dsID, managementMethod.parameters.dsState); if (policyAfter.isPolicyActive()) addPolicy(managementMethod.parameters.pid, policyAfter.getDsContent()); break; case DELETE: // get the policy object before the call and see if it was indexed policyBefore = new PolicyObject(m_DOManager, managementMethod.parameters.context, managementMethod.parameters.pid, null, managementMethod.parameters.dsID, managementMethod.parameters.dsState); wasIndexed = policyBefore.isPolicyActive(); // invoke the method returnValue = invokeTarget(target, method, args); policyAfter = new PolicyObject(m_DOManager, managementMethod.parameters.context, managementMethod.parameters.pid, null, managementMethod.parameters.dsID, null); if (wasIndexed) { //if policy is still active after, only a version was purged and the policy has changed. Update if (policyAfter.isPolicyActive()) { updatePolicy(managementMethod.parameters.pid, policyAfter.getDsContent()); //policy is not active after, then it was deleted and needs to be removed from index. } else { deletePolicy(managementMethod.parameters.pid); } } break; case UPDATE: // do the API call, get the policy object after the call returnValue = invokeTarget(target, method, args); policyAfter = new PolicyObject(m_DOManager, managementMethod.parameters.context, managementMethod.parameters.pid, null, managementMethod.parameters.dsID, null); if (policyAfter.isPolicyActive()) updatePolicy(managementMethod.parameters.pid, policyAfter.getDsContent()); break; default: } break; default: } } break; default: } // if API call not made as part of the above (ie no action was required on policy cache), do it now if (returnValue == null) returnValue = invokeTarget(target,method, args); return returnValue; } /** * Invoke the underlying method, catching any InvocationTargetException and rethrowing the target exception */ private Object invokeTarget(Object target, Method method, Object[] args) throws Throwable { Object returnValue; try { returnValue = method.invoke(target, args); } catch(InvocationTargetException ite) { throw ite.getTargetException(); } return returnValue; } // TODO: invalidate PEP cache on the following operations private void addPolicy(String pid, InputStream dsContent) throws GeneralException { LOG.info("Adding policy " + pid); String policy; try { policy = IOUtils.toString(dsContent); dsContent.close(); m_policyIndex.addPolicy(pid, policy); } catch (IOException e) { throw new GeneralException("Error adding policy " + pid + " to policy index: " + e.getMessage(), e); } catch (PolicyIndexException e) { throw new GeneralException("Error adding policy " + pid + " to policy index: " + e.getMessage(), e); } } private void updatePolicy(String pid, InputStream dsContent) throws GeneralException { LOG.info("Updating policy " + pid); String policy; try { policy = IOUtils.toString(dsContent); dsContent.close(); m_policyIndex.updatePolicy(pid, policy); } catch (IOException e) { throw new GeneralException("Error adding policy " + pid + " to policy index: " + e.getMessage(), e); } catch (PolicyIndexException e) { throw new GeneralException("Error adding policy " + pid + " to policy index: " + e.getMessage(), e); } } /** * Remove the specified policy from the cache * @param pid * @throws PolicyIndexException */ private void deletePolicy(String pid) throws GeneralException { LOG.debug("Deleting policy " + pid); try { // in case the policy doesn't exist (corrupt policy index, failed to add because invalid, etc) m_policyIndex.deletePolicy(pid); } catch (PolicyIndexException e) { throw new GeneralException("Error deleting policy " + pid + " from policy index: " + e.getMessage(), e); } } }