/**
*
*/
package melcoe.xacml.util;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import melcoe.xacml.MelcoeXacmlException;
import melcoe.xacml.pdp.MelcoePDPException;
import org.apache.log4j.Logger;
import fedora.common.Constants;
import fedora.common.PID;
import fedora.server.Context;
import fedora.server.ReadOnlyContext;
import fedora.server.Server;
import fedora.server.errors.ObjectNotInLowlevelStorageException;
import fedora.server.errors.ServerException;
import fedora.server.management.Management;
import fedora.server.storage.types.RelationshipTuple;
/**
* A RelationshipResolver that resolves relationships via
* {@link Management#getRelationships(fedora.server.Context, String, String)}.
*
* @author Edwin Shin
*/
public class RelationshipResolverImpl
implements RelationshipResolver {
private static Logger log =
Logger.getLogger(RelationshipResolverImpl.class.getName());
/**
* Designates the repository itself. Policies can apply to the repository,
* but it is a special case, as it is not represented by a PID, and by
* definition, has no parents.
*/
private final static String REPOSITORY = "FedoraRepository";
private static String DEFAULT_RELATIONSHIP =
"info:fedora/fedora-system:def/relations-external#isMemberOf";
private final List<String> relationships;
private Management apim;
private Context fedoraCtx;
public RelationshipResolverImpl() {
this(new HashMap<String, String>());
}
/**
* Constructor that takes a map of parent-child predicates (relationships).
* {@link ContextHandlerImpl} builds the map from the relationship-resolver
* section of config-melcoe-pep.xml (in WEB-INF/classes).
*
* @param options
* @throws MelcoePDPException
*/
public RelationshipResolverImpl(Map<String, String> options) {
relationships = new ArrayList<String>();
if (options.isEmpty()) {
relationships.add(DEFAULT_RELATIONSHIP);
} else {
List<String> keys = new ArrayList<String>(options.keySet());
Collections.sort(keys);
for (String s : keys) {
if (s.startsWith("parent-child-relationship")) {
relationships.add(options.get(s));
}
}
}
}
/*
* (non-Javadoc)
* @see
* melcoe.xacml.pdp.finder.support.RelationshipResolver#buildRESTParentHierarchy
* (java.lang.String)
*/
public String buildRESTParentHierarchy(String pid)
throws MelcoeXacmlException {
Set<String> parents = getParents(pid);
if (parents == null || parents.size() == 0) {
return "/" + pid;
}
String[] parentArray = parents.toArray(new String[parents.size()]);
return buildRESTParentHierarchy(parentArray[0]) + "/" + pid;
}
/*
* (non-Javadoc)
* @see
* melcoe.xacml.pdp.finder.support.RelationshipResolver#getParents(java.
* lang.String)
*/
public Set<String> getParents(String pid) throws MelcoeXacmlException {
if (log.isDebugEnabled()) {
log.debug("Obtaining parents for: " + pid);
}
Set<String> parentPIDs = new HashSet<String>();
if (pid.equalsIgnoreCase(REPOSITORY)) {
return parentPIDs;
}
query: for (String relationship : relationships) {
if (log.isDebugEnabled()) {
log.debug("relationship query: " + pid + ", " + relationship);
}
Map<String, Set<String>> mapping;
try {
mapping = getRelationships(pid, relationship);
} catch (MelcoeXacmlException e) {
Throwable t = e.getCause();
// An object X, may legitimately declare a parent relation to
// another object, Y which does not exist. Therefore, we don't
// want to continue querying for Y's parents.
while (t != null) {
if (t instanceof ObjectNotInLowlevelStorageException) {
if (log.isDebugEnabled()) {
log.debug("Parent, " + pid + ", not found.");
}
break query;
}
}
// Unexpected error, so we throw back the original
throw e;
}
Set<String> parents = mapping.get(relationship);
if (parents != null) {
for (String parent : parents) {
PID parentPID = PID.getInstance(parent);
// we want the parents in demo:123 form, not info:fedora/demo:123
parentPIDs.add(parentPID.toString());
if (log.isDebugEnabled()) {
log.debug("added parent " + parentPID.toString());
}
}
}
}
return parentPIDs;
}
public Map<String, Set<String>> getRelationships(String pid)
throws MelcoeXacmlException {
return getRelationships(pid, null);
}
private Map<String, Set<String>> getRelationships(String pid,
String relationship)
throws MelcoeXacmlException {
PID subject = getNormalizedPID(pid);
RelationshipTuple[] tuples;
try {
tuples =
getApiM().getRelationships(getContext(),
subject.toURI(),
relationship);
// Anticipating searches which fail because the object identified by
// pid may not exist in the Fedora repository, since objects can
// declare parent relationships to objects that do not exist
} catch (ServerException e) {
throw new MelcoeXacmlException(e.getMessage(), e);
}
Map<String, Set<String>> relationships =
new HashMap<String, Set<String>>();
for (RelationshipTuple t : tuples) {
String p = t.predicate;
String o = t.object;
Set<String> values = relationships.get(p);
if (values == null) {
values = new HashSet<String>();
}
values.add(o);
relationships.put(p, values);
}
return relationships;
}
private Management getApiM() {
if (apim != null) {
return apim;
}
Server server;
try {
server = Server.getInstance(new File(Constants.FEDORA_HOME), false);
} catch (Exception e) {
log.error(e.getMessage());
throw new RuntimeException("Failed getting instance of Fedora", e);
}
apim =
(Management) server
.getModule("fedora.server.management.Management");
return apim;
}
private Context getContext() throws MelcoeXacmlException {
if (fedoraCtx != null) {
return fedoraCtx;
}
try {
fedoraCtx =
ReadOnlyContext.getContext(null,
null,
null,
ReadOnlyContext.DO_OP);
} catch (Exception e) {
throw new MelcoeXacmlException(e.getMessage(), e);
}
return fedoraCtx;
}
/**
* Returns a PID object for the requested String. This method will return a
* PID for a variety of pid permutations, e.g. demo:1, info:fedora/demo:1,
* demo:1/DS1, info:fedora/demo:1/sdef:foo/sdep:bar/methodBaz.
*
* @param pid
* @return a PID object
*/
protected PID getNormalizedPID(String pid) {
// strip the leading "info:fedora/" if any
if (pid.startsWith(Constants.FEDORA.uri)) {
pid = pid.substring(Constants.FEDORA.uri.length());
}
// should be left with "demo:foo" or "demo:foo/demo:bar"
return PID.getInstance(pid.split("\\/")[0]);
}
}