/* * 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.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.fcrepo.common.MalformedPIDException; import org.fcrepo.common.PID; import org.fcrepo.server.security.xacml.pdp.MelcoePDP; import org.fcrepo.server.security.xacml.pdp.finder.policy.PolicyReader; import org.fcrepo.server.security.xacml.util.DataFileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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; /** * Implements PolicyIndex for a filesystem policy index, cached in memory * * FIXME: there is no indexing; all policies will be returned by getPolicies() * * @author nishen@melcoe.mq.edu.au */ class FilePolicyIndex extends PolicyIndexBase implements PolicyIndex { private static final Logger logger = LoggerFactory.getLogger(FilePolicyIndex.class.getName()); private String DB_HOME = null; // contains the cached policies. one and only one of these private static Map<String, byte[]> policies = null; // protects concurrent access to the policies (particularly the files in the cache directory) private static final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); public static final Lock readLock = rwl.readLock(); public static final Lock writeLock = rwl.writeLock(); protected FilePolicyIndex(PolicyReader policyReader) throws PolicyIndexException { super(policyReader); indexed = false; // this implementation returns all policies when queried - no indexing/filtering logger.info("Starting FilePolicyIndex"); } public void setPolicyDirectoryPath(String dbHome) throws PolicyIndexException { if (logger.isDebugEnabled()) { Runtime runtime = Runtime.getRuntime(); logger.debug("Total memory: " + runtime.totalMemory() / 1024); logger.debug("Free memory: " + runtime.freeMemory() / 1024); logger.debug("Max memory: " + runtime.maxMemory() / 1024); } DB_HOME = MelcoePDP.PDP_HOME.getAbsolutePath() + dbHome; File db_home = new File(DB_HOME); if (!db_home.exists()) { try { db_home.mkdirs(); } catch (Exception e) { throw new PolicyIndexException("Could not create DB directory: " + db_home.getAbsolutePath()); } } } public void init() throws PolicyIndexException { loadPolicies(DB_HOME); } /* * (non-Javadoc) * @seemelcoe.xacml.pdp.data.Index#getPolicies(org.jboss.security.xacml.sunxacml. * EvaluationCtx) */ @Override public Map<String, AbstractPolicy> getPolicies(EvaluationCtx eval, PolicyFinder policyFinder) throws PolicyIndexException { // no indexing, return everything // return a copy, otherwise the map could change during evaluation if policies are added, deleted etc readLock.lock(); try { Map<String, AbstractPolicy> result = new ConcurrentHashMap<String, AbstractPolicy>(); for(String id:policies.keySet()){ AbstractPolicy policy = handleDocument(m_policyReader.readPolicy(policies.get(id)),policyFinder); result.put(id, policy); } return result; } catch (ParsingException pe) { throw new PolicyIndexException(pe.getMessage(),pe); }finally { readLock.unlock(); } } /** * Convert a policy name to a filename that can be used to persist the policy. * Policy names must be valid PIDs (the DO managing the policy) * @param policyName * @return * @throws PolicyIndexException */ private File nameToFile(String policyName) throws PolicyIndexException { PID pid; try { pid = new PID(policyName); } catch (MalformedPIDException e) { throw new PolicyIndexException("Invalid policy name. Policy name must be a valid PID - " + policyName); } return new File(DB_HOME + "/" + pid.toFilename() + ".xml"); } /** * Determine name of policy from file. .xml prefix is removed, and name is converted to a PID. * Policy names must be valid PIDs. * @param policyFile * @return * @throws PolicyIndexException */ private String fileToName(File policyFile) throws PolicyIndexException { try { if (!policyFile.getName().endsWith(".xml")) throw new PolicyIndexException("Invalid policy file name. Policy files must end in .xml - " + policyFile.getName()); return PID.fromFilename(policyFile.getName().substring(0, policyFile.getName().lastIndexOf(".xml"))).toString(); } catch (MalformedPIDException e) { throw new PolicyIndexException("Invalid policy file name. Filename cannot be converted to a valid PID - " + policyFile.getName()); } } /* * (non-Javadoc) * @see org.fcrepo.server.security.xacml.pdp.data.PolicyIndex#addPolicy(java.lang.String, * java.lang.String) */ @Override public String addPolicy(String name, String document) throws PolicyIndexException { writeLock.lock(); try { logger.debug("Adding policy named: " + name); return doAdd(name, document); } finally { writeLock.unlock(); } } /* * (non-Javadoc) * @see * org.fcrepo.server.security.xacml.pdp.data.PolicyDataManager#deletePolicy(java.lang.String) */ @Override public boolean deletePolicy(String name) throws PolicyIndexException { writeLock.lock(); try { logger.debug("Deleting policy named: " + name); return doDelete(name); } finally { writeLock.unlock(); } } /* * (non-Javadoc) * @see * org.fcrepo.server.security.xacml.pdp.data.PolicyIndex#updatePolicy(java.lang.String, * java.lang.String) */ @Override public boolean updatePolicy(String name, String newDocument) throws PolicyIndexException { writeLock.lock(); try { logger.debug("Updating policy named: " + name); if (doDelete(name)) { doAdd(name, newDocument); return true; } else { // delete failed return false; } } finally { writeLock.unlock(); } } // the actual add and delete methods; these are not protected by locks // as locking should take place on the public methods (especially we don't want // separate add/delete locks for an update private String doAdd(String name, String document) throws PolicyIndexException { String filename = nameToFile(name).getAbsolutePath(); if (policies.put(name, document.getBytes()) != null) { throw new PolicyIndexException("Attempting to add policy " + name + " but it already exists"); } try { logger.debug("Saving policy file in index: " + filename); DataFileUtils.saveDocument(filename, document.getBytes()); } catch (Exception e) { throw new PolicyIndexException("Failed to save policy file " + filename); } return name; } private boolean doDelete(String name) throws PolicyIndexException { if (policies.remove(name) == null) { throw new PolicyIndexException("Attempting to delete non-existent policy " + name); } File policy = nameToFile(name); logger.debug("Removing policy file from index: " + policy); if (!policy.exists()) { logger.error("Policy " + name + " removed from cache, but no corresponding file found in policy cache directory"); } else { // delete the file if (!policy.delete()) { policy.deleteOnExit(); // just in case logger.error("Failed to delete policy file " + policy.getName() + ". Marked for deletion on VM exit"); } } 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 { readLock.lock(); try { logger.debug("Getting policy named: " + name); if (policies.containsKey(name)) { return handleDocument(m_policyReader.readPolicy(policies.get(name)), policyFinder); } else { throw new PolicyIndexException("Attempting to get non-existent policy " + name); } } catch (ParsingException pe) { throw new PolicyIndexException(pe.getMessage(),pe); } finally { readLock.unlock(); } } @Override public boolean contains(String name) throws PolicyIndexException { readLock.lock(); try { return policies.containsKey(name); } finally { readLock.unlock(); } } @Override public boolean clear() throws PolicyIndexException { writeLock.lock(); try { logger.debug("Clearing file policy index"); // remove the policy files File policyDir = new File(DB_HOME); for (File policyFile : policyDir.listFiles()) { if (!policyFile.delete()) { throw new PolicyIndexException("Could not clear policy index. Failed to delete policy file " + policyFile.getAbsolutePath()); } } // clear the cache policies = new ConcurrentHashMap<String, byte[]>(); return true; } finally { writeLock.unlock(); } } private void loadPolicies(String policyDir) throws PolicyIndexException { // stop any reads whilst we're starting up writeLock.lock(); try { if (policies == null) { logger.info("Populating FeSL File policy index cache from " + policyDir); policies = new ConcurrentHashMap<String, byte[]>(); File policyHome = new File(policyDir); if (!policyHome.exists()) { throw new PolicyIndexException("Policy directory does not exist: " + policyHome.getAbsolutePath()); } File[] pf = policyHome.listFiles(); for (File f : pf) { if (!f.getName().endsWith(".xml")) { // should not happen... all files should be .xml throw new PolicyIndexException("Non .xml files found in policy index cache directory"); } try { logger.info("Loading FeSL policy from cache directory: " + f.getAbsolutePath()); byte[] doc = DataFileUtils.loadFile(f); String policyName = fileToName(f); logger.debug("Adding policy file to cache, policy name: " + policyName); policies.put(policyName, doc); } catch (Exception e) { logger.error("Error loading document: " + f.getName(), e); throw new PolicyIndexException("Error loading document: " + f.getName(),e); } } logger.info("Populated cache with " + pf.length + " files"); } } finally { writeLock.unlock(); } } }