/* * Copyright 2007 Macquarie E-Learning Centre Of Excellence * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.fcrepo.server.security.xacml.pdp.data; import java.io.File; import java.net.URISyntaxException; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.fcrepo.server.security.xacml.pdp.finder.policy.PolicyReader; import org.fcrepo.server.security.xacml.util.AttributeBean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.sleepycat.dbxml.XmlDocument; import com.sleepycat.dbxml.XmlDocumentConfig; import com.sleepycat.dbxml.XmlException; import com.sleepycat.dbxml.XmlQueryContext; import com.sleepycat.dbxml.XmlQueryExpression; import com.sleepycat.dbxml.XmlResults; import com.sleepycat.dbxml.XmlValue; import org.jboss.security.xacml.sunxacml.AbstractPolicy; import org.jboss.security.xacml.sunxacml.EvaluationCtx; import org.jboss.security.xacml.sunxacml.ParsingException; import org.jboss.security.xacml.sunxacml.finder.PolicyFinder; /** * Encapsulates indexed access to policies stored in DbXml. * * See DbXmlPolicyStore for CRUD operations on policies in DbXml. * * @author Stephen Bayliss * @version $Id$ */ public class DbXmlPolicyIndex extends XPathPolicyIndex implements PolicyIndex { private static final Logger log = LoggerFactory.getLogger(DbXmlPolicyIndex.class.getName()); private String m_databaseDirectory = null; private String m_container = null; private DbXmlManager m_dbXmlManager = null; private volatile long m_lastUpdate; private PolicyUtils m_utils; private Map<String, XmlQueryExpression> m_queries = null; public DbXmlPolicyIndex(PolicyReader policyReader) throws PolicyIndexException { super(policyReader); } public void setDatabaseDirectory(String databaseDirectory) { m_databaseDirectory = databaseDirectory; } public void setContainer(String container) { m_container = container; } public void init() throws PolicyIndexException { try { m_dbXmlManager = new DbXmlManager(m_databaseDirectory, m_container); m_dbXmlManager.indexMap = this.indexMap; } catch (PolicyStoreException pse) { throw new PolicyIndexException(pse.getMessage(),pse); } m_queries = new ConcurrentHashMap<String, XmlQueryExpression>(); m_utils = new PolicyUtils(); } public void close() { m_dbXmlManager.close(); m_queries.clear(); } /* * (non-Javadoc) * @seemelcoe.xacml.pdp.data.PolicyDataManager#getPolicies(org.jboss.security.xacml.sunxacml. * EvaluationCtx) */ @Override public Map<String, AbstractPolicy> getPolicies(EvaluationCtx eval, PolicyFinder policyFinder) throws PolicyIndexException { long a = 0; long b = 0; long total = 0; Map<String, AbstractPolicy> documents = new HashMap<String, AbstractPolicy>(); XmlQueryExpression qe = null; XmlQueryContext context = null; try { // Get the query (query gets prepared if necesary) a = System.nanoTime(); Map<String, Collection<AttributeBean>> attributeMap = getAttributeMap(eval); context = m_dbXmlManager.manager.createQueryContext(); context.setDefaultCollection(m_dbXmlManager.CONTAINER); for (String prefix : namespaces.keySet()) { context.setNamespace(prefix, namespaces.get(prefix)); } // not clear why this is needed.... but it is used in hashing the queries int resourceComponentCount = 0; Map<String, String> variables = getXpathVariables(attributeMap); for (String variable : variables.keySet()) { context.setVariableValue(variable, new XmlValue(variables.get(variable))); if (variable.equals(XACML_RESOURCE_ID)) { resourceComponentCount++; } } qe = getQuery(attributeMap, context, resourceComponentCount); } catch (XmlException xe) { throw new PolicyIndexException("Error while constructing query", xe); } catch (URISyntaxException e) { throw new PolicyIndexException("Error while constructing query", e); } DbXmlManager.readLock.lock(); try { b = System.nanoTime(); total += b - a; if (log.isDebugEnabled()) { log.debug("Query prep. time: " + (b - a) + "ns"); } // execute the query a = System.nanoTime(); XmlResults results = qe.execute(context); b = System.nanoTime(); total += b - a; if (log.isDebugEnabled()) { log.debug("Query exec. time: " + (b - a) + "ns"); } // process results while (results.hasNext()) { XmlValue value = results.next(); byte[] content = value.asDocument().getContent(); if (content.length > 0) { documents.put(value.asDocument().getName(), handleDocument(m_policyReader.readPolicy(content),policyFinder)); } else { throw new PolicyIndexException("Zero-length result found"); } } results.delete(); } catch (XmlException xe) { log.error("Error getting query results." + xe.getMessage()); throw new PolicyIndexException("Error getting query results." + xe.getMessage(), xe); } catch (ParsingException pe) { log.error("Error getting query results." + pe.getMessage()); throw new PolicyIndexException("Error getting query results." + pe.getMessage(), pe); } finally { DbXmlManager.readLock.unlock(); } if (log.isDebugEnabled()) { log.debug("Total exec. time: " + total + "ns"); } return documents; } /** * Either returns a query that has previously been generated, or generates a * new one if it has not. * * @param attributeMap * the Map of attributes, type and values upon which this query is * based * @param context * the context for the query * @return an XmlQueryExpression that can be executed * @throws XmlException * @throws XmlException * @throws PolicyIndexException */ private XmlQueryExpression getQuery(Map<String, Collection<AttributeBean>> attributeMap, XmlQueryContext context, int r) throws XmlException { // The dimensions for this query. StringBuilder sb = new StringBuilder(64 * attributeMap.size()); for (Collection<AttributeBean> attributeBeans : attributeMap.values()) { sb.append(attributeBeans.size() + ":"); for (AttributeBean bean : attributeBeans) { sb.append(bean.getValues().size() + "-"); } } // If a query of these dimensions already exists, then just return it. String hash = sb.toString() + r; XmlQueryExpression result = m_queries.get(hash); if (result != null) { return result; } // We do not have a query of those dimensions. We must make one. String query = createQuery(attributeMap); if (log.isDebugEnabled()) { log.debug("Query [{}]:\n{}", hash, query); } // Once we have created a query, we can parse it and store the // execution plan. This is an expensive operation that we do // not want to have to do more than once for each dimension result = m_dbXmlManager.manager.prepare(query, context); m_queries.put(hash, result); return result; } /** * Given a set of attributes this method generates a DBXML XPath query based * on those attributes to extract a subset of policies from the database. * * @param attributeMap * the Map of Attributes from which to generate the query * @param r * the number of components in the resource id * @return the query as a String */ private String createQuery(Map<String, Collection<AttributeBean>> attributeMap) { StringBuilder sb = new StringBuilder(256); sb.append("collection('"); sb.append(m_dbXmlManager.CONTAINER); sb.append("')"); getXpath(attributeMap, sb); return sb.toString(); } /* * (non-Javadoc) * @see * org.fcrepo.server.security.xacml.pdp.data.PolicyDataManager#addPolicy * (java.lang.String, java.lang.String) */ @Override public String addPolicy(String name, String document) throws PolicyIndexException { String docName = null; DbXmlManager.writeLock.lock(); try { XmlDocument doc = makeDocument(name, document); docName = doc.getName(); log.debug("Adding document: " + docName); m_dbXmlManager.container.putDocument(doc, m_dbXmlManager.updateContext); setLastUpdate(System.currentTimeMillis()); } catch (XmlException xe) { if (xe.getErrorCode() == XmlException.UNIQUE_ERROR) { throw new PolicyIndexException("Document already exists: " + docName); } else { throw new PolicyIndexException("Error adding policy: " + xe.getMessage(), xe); } } finally { DbXmlManager.writeLock.unlock(); } return docName; } /* * (non-Javadoc) * @see * org.fcrepo.server.security.xacml.pdp.data.PolicyDataManager#deletePolicy * (java.lang.String) */ @Override public boolean deletePolicy(String name) throws PolicyIndexException { log.debug("Deleting document: " + name); DbXmlManager.writeLock.lock(); try { m_dbXmlManager.container.deleteDocument(name, m_dbXmlManager.updateContext); setLastUpdate(System.currentTimeMillis()); } catch (XmlException xe) { // safe delete - only warn if not found if (xe.getDbError() == XmlException.DOCUMENT_NOT_FOUND){ log.warn("Error deleting document: " + name + " - document does not exist"); } else { throw new PolicyIndexException("Error deleting document: " + name + xe.getMessage(), xe); } } finally { DbXmlManager.writeLock.unlock(); } return true; } /* * (non-Javadoc) * @see * org.fcrepo.server.security.xacml.pdp.data.PolicyDataManager#updatePolicy * (java.lang.String, java.lang.String) */ @Override public boolean updatePolicy(String name, String newDocument) throws PolicyIndexException { log.debug("Updating document: " + name); // FIXME: DBXML container.updateDocument is failing to update document metadata (this tested on DBXML ver 2.5.13) // specifically anySubject, anyResource metadata elements are not changing // if Subjects and Resources elements are added/deleted from document. // FIXME: this will acquire and release write locks for each operation // should instead do this just once for an update deletePolicy(name); addPolicy(name, newDocument); // FIXME: code below would also need updating for transactions, deadlocks, single thread updates... /* original code below works apart from metadata update not happening try { utils.validate(newDocument, name); } catch (MelcoePDPException e) { throw new PolicyIndexException(e); } XmlTransaction txn = null; try { try { txn = dbXmlManager.manager.createTransaction(); XmlDocument doc = makeDocument(name, newDocument); dbXmlManager.container.updateDocument(txn, doc, dbXmlManager.updateContext); txn.commit(); setLastUpdate(System.currentTimeMillis()); } catch (XmlException xe) { txn.abort(); throw new PolicyIndexException("Error updating document: " + name, xe); } } catch (XmlException xe) { throw new PolicyIndexException("Error aborting transaction: " + xe.getMessage(), xe); } */ return true; } /* * (non-Javadoc) * @see * org.fcrepo.server.security.xacml.pdp.data.PolicyDataManager#getPolicy * (java.lang.String) */ @Override public AbstractPolicy getPolicy(String name, PolicyFinder policyFinder) throws PolicyIndexException { log.debug("Getting document: " + name); XmlDocument doc = null; DbXmlManager.readLock.lock(); try { doc = m_dbXmlManager.container.getDocument(name); return handleDocument(m_policyReader.readPolicy(doc.getContent()), policyFinder); } catch (XmlException xe) { throw new PolicyIndexException("Error getting Policy: " + name + xe.getMessage() + " - " + xe.getDatabaseException().getMessage(), xe); } catch (ParsingException pe) { throw new PolicyIndexException("Error getting Policy: " + name + pe.getMessage(), pe); } finally { DbXmlManager.readLock.unlock(); } } /** * Check if the policy identified by policyName exists. * * @param policyName * @return true iff the policy store contains a policy identified as * policyName * @throws PolicyIndexException */ @Override public boolean contains(String policyName) throws PolicyIndexException { log.debug("Determining if document exists: " + policyName); DbXmlManager.readLock.lock(); try { m_dbXmlManager.container.getDocument(policyName, new XmlDocumentConfig().setLazyDocs(true)); } catch (XmlException e) { if (e.getErrorCode() == XmlException.DOCUMENT_NOT_FOUND) { return false; } else { throw new PolicyIndexException("Error executing contains. " + e.getMessage(), e); } } finally { DbXmlManager.readLock.unlock(); } return true; } /** * Creates an instance of an XmlDocument for storage in the database. * * @param name * the name of the document (policy) * @param document * the document data as a String * @return the XmlDocument instance * @throws XmlException * @throws PolicyStoreException */ private XmlDocument makeDocument(String name, String document) throws XmlException, PolicyIndexException { Map<String, String> metadata = m_utils.getDocumentMetadata(document .getBytes()); XmlDocument doc = m_dbXmlManager.manager.createDocument(); String docName = name; if (docName == null || docName.isEmpty()) { docName = metadata.get("PolicyId"); } if (docName == null || docName.isEmpty()) { throw new PolicyIndexException("Could not extract PolicyID from document."); } doc.setMetaData("metadata", "PolicyId", new XmlValue(XmlValue.STRING, docName)); doc.setContent(document); doc.setName(docName); // FIXME: // this is probably redundant as the xpath queries now directly query the policy // for the "any" scenarios String item = null; item = metadata.get("anySubject"); if (item != null) { doc.setMetaData("metadata", "anySubject", new XmlValue(XmlValue.STRING, item)); } item = metadata.get("anyResource"); if (item != null) { doc.setMetaData("metadata", "anyResource", new XmlValue(XmlValue.STRING, item)); } item = metadata.get("anyAction"); if (item != null) { doc.setMetaData("metadata", "anyAction", new XmlValue(XmlValue.STRING, item)); } item = metadata.get("anyEnvironment"); if (item != null) { doc.setMetaData("metadata", "anyEnvironment", new XmlValue(XmlValue.STRING, item)); } return doc; } /* * (non-Javadoc) * @see * org.fcrepo.server.security.xacml.pdp.data.PolicyDataManager#getLastUpdate * () */ public long getLastUpdate() { return m_lastUpdate; } /** * @param lastUpdate * the lastUpdate to set */ public void setLastUpdate(long lastUpdate) { this.m_lastUpdate = lastUpdate; } @Override public boolean clear() throws PolicyIndexException { m_dbXmlManager.deleteDatabase(); m_dbXmlManager.close(); m_dbXmlManager = null; // and init will create a new database (by creating a new dbXmlManager) init(); return true; } @SuppressWarnings("unused") private boolean deleteDirectory(String directory) { boolean result = false; if (directory != null) { File file = new File(directory); if (file.exists() && file.isDirectory()) { // 1. delete content of directory: File[] files = file.listFiles(); result = true; //init result flag int count = files.length; for (int i = 0; i < count; i++) { //for each file: File f = files[i]; if (f.isFile()) { result = result && f.delete(); } else if (f.isDirectory()) { result = result && deleteDirectory(f.getAbsolutePath()); } }//next file file.delete(); //finally delete (empty) input directory }//else: input directory does not exist or is not a directory }//else: no input value return result; }//deleteDirectory() }