Java tutorial
/* Copyright 2010 University of Cambridge * Licensed under the Educational Community License (ECL), Version 2.0. You may not use this file except in * compliance with this License. * * You may obtain a copy of the ECL 2.0 License at https://source.collectionspace.org/collection-space/LICENSE.txt */ package org.collectionspace.chain.csp.persistence.services.relation; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.collectionspace.chain.csp.persistence.services.connection.ConnectionException; import org.collectionspace.chain.csp.persistence.services.connection.RequestMethod; import org.collectionspace.chain.csp.persistence.services.connection.ReturnedDocument; import org.collectionspace.chain.csp.persistence.services.connection.ReturnedMultipartDocument; import org.collectionspace.chain.csp.persistence.services.connection.ReturnedURL; import org.collectionspace.chain.csp.persistence.services.connection.ServicesConnection; import org.collectionspace.chain.csp.schema.Record; import org.collectionspace.chain.csp.schema.Relationship; import org.collectionspace.chain.csp.schema.Spec; import org.collectionspace.chain.util.xtmpl.InvalidXTmplException; import org.collectionspace.csp.api.core.CSPRequestCache; import org.collectionspace.csp.api.core.CSPRequestCredentials; import org.collectionspace.csp.api.persistence.ExistException; import org.collectionspace.csp.api.persistence.UnderlyingStorageException; import org.collectionspace.csp.api.persistence.UnimplementedException; import org.collectionspace.csp.helper.persistence.ContextualisedStorage; import org.collectionspace.services.common.api.RefName; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Node; import org.jaxen.JaxenException; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /* /relate/main/ POST ::: {'src': src-type/src, 'type': type, 'dst': dst-type/dst} ::: id * /relate/main/<id> PUT ::: {'src': src-type/src, 'type': type, 'dst': dst-type/dst} ::: * /relate/main/<id> DELETE ::: ::: * /relate/main/<id> GET ::: ::: {'src': src-type/src, 'type': type, 'dst': dst-type/dst} * */ // XXX some hacks here because services don't seem to support multiple search criteria on relationships. // XXX need to implement CSPACE-1080 public class ServicesRelationStorage implements ContextualisedStorage { private static final Logger log = LoggerFactory.getLogger(ServicesRelationStorage.class); private ServicesConnection conn; private Spec spec; private RelationFactory factory; private Map<String, String> type_to_surl = new HashMap<String, String>(); private Map<String, String> surl_to_type = new HashMap<String, String>(); private static Map<String, Relationship> types = new HashMap<String, Relationship>(); static { //needs to be set thr CSPACE-2557 // types.add("affects"); // types.add("new"); // XXX Only one type is bad for testing. remove when there's a second real one } public ServicesRelationStorage(ServicesConnection conn, Spec spec) throws JaxenException, InvalidXTmplException, DocumentException, IOException { this.conn = conn; this.spec = spec; for (Relationship rel : spec.getAllRelations()) { types.put(rel.getID(), rel); } for (Record r : spec.getAllRecords()) { type_to_surl.put(r.getID(), r.getServicesURL()); surl_to_type.put(r.getServicesURL(), r.getID()); } factory = new RelationFactory(); } private String[] splitTypeFromId(String path) throws UnderlyingStorageException { String[] out = path.split("/"); if (out[0].equals("")) { path = path.substring(1); out = path.split("/"); } if (out.length != 2) throw new UnderlyingStorageException("Path must be two components, not " + path); return out; } private Relation dataToRelation(CSPRequestCache cache, String id, JSONObject data) throws JSONException, UnderlyingStorageException { String[] src = splitTypeFromId(data.getString("src")); String[] dst = splitTypeFromId(data.getString("dst")); if (type_to_surl.containsKey(src[0])) { src[0] = type_to_surl.get(src[0]); } if (type_to_surl.containsKey(dst[0])) { dst[0] = type_to_surl.get(dst[0]); } String type = data.getString("type"); if (types.containsKey(type)) { Relationship rel = types.get(type); if (!rel.hasDestinationType(dst[0]) && !rel.hasDestinationType("all")) { throw new UnderlyingStorageException("type " + type + " is undefined for destination:" + dst[0]); } if (!rel.hasSourceType("all") && !rel.hasSourceType(src[0])) { throw new UnderlyingStorageException("type " + type + " is undefined for source: " + src[0]); } } else { throw new UnderlyingStorageException("type " + type + " is undefined"); } return factory.create(id, src[0], src[1], type, dst[0], dst[1]); } private JSONObject relationToData(CSPRequestCache cache, Relation r) throws JSONException { JSONObject out = new JSONObject(); String srct = r.getSourceType(); String dstt = r.getDestinationType(); if (surl_to_type.containsKey(r.getSourceType())) { srct = surl_to_type.get(r.getSourceType()); } if (surl_to_type.containsKey(r.getDestinationType())) { dstt = surl_to_type.get(r.getDestinationType()); } out.put("src", srct + "/" + r.getSourceId()); out.put("dst", dstt + "/" + r.getDestinationId()); out.put("type", r.getRelationshipType()); out.put("csid", r.getID()); return out; } private Boolean isPathType(String in, String[] prefixes, int var) throws UnderlyingStorageException { if (in == null) return false; if (in.startsWith("/")) in = in.substring(1); if (in.endsWith("/")) in = in.substring(0, in.length() - 1); String[] split = in.split("/"); if (split.length != prefixes.length + var) return false; for (int i = 0; i < prefixes.length; i++) { if (!prefixes[i].equals(split[i])) { return false; } } return true; } // XXX refactor private String[] extractPaths(String in, String[] prefixes, int var) throws UnderlyingStorageException { if (in == null) throw new UnderlyingStorageException("null is not a path"); if (in.startsWith("/")) in = in.substring(1); if (in.endsWith("/")) in = in.substring(0, in.length() - 1); String[] split = in.split("/"); if (split.length != prefixes.length + var) throw new UnderlyingStorageException( "Path is incorrect length (should be " + (prefixes.length + var) + " but is " + split.length); for (int i = 0; i < prefixes.length; i++) if (!prefixes[i].equals(split[i])) throw new UnderlyingStorageException( "Path component " + i + " must be " + prefixes[i] + " but is " + split[i]); if (var == 0) return new String[0]; String[] ret = new String[var]; System.arraycopy(split, prefixes.length, ret, 0, var); return ret; } @Override public String autocreateJSON(ContextualisedStorage root, CSPRequestCredentials creds, CSPRequestCache cache, String filePath, JSONObject data, JSONObject restrictions) throws ExistException, UnimplementedException, UnderlyingStorageException { try { extractPaths(filePath, new String[] { "main" }, 0); Map<String, Document> in = new HashMap<String, Document>(); Document datapath = dataToRelation(cache, null, data).toDocument(); //log.info("AUTOCREATE"+datapath.asXML()); in.put("relations_common", datapath); ReturnedURL out = conn.getMultipartURL(RequestMethod.POST, "/relations/", in, creds, cache); if (out.getStatus() > 299) throw new UnderlyingStorageException("Could not add relation status=" + out.getStatus(), out.getStatus(), "/relations/"); return out.getURLTail(); } catch (ConnectionException e) { throw new UnderlyingStorageException("Could not add relation" + e.getLocalizedMessage(), e.getStatus(), e.getUrl(), e); } catch (JSONException e) { throw new UnderlyingStorageException("Could not retrieve data" + e.getLocalizedMessage()); } } public void createJSON(ContextualisedStorage root, CSPRequestCredentials creds, CSPRequestCache cache, String filePath, JSONObject jsonObject) throws ExistException, UnimplementedException, UnderlyingStorageException { throw new UnimplementedException("Cannot create relations to path"); } public void transitionWorkflowJSON(ContextualisedStorage root, CSPRequestCredentials creds, CSPRequestCache cache, String filePath, String workflowTransition) throws ExistException, UnimplementedException, UnderlyingStorageException { throw new UnimplementedException("Workflow not supported on relations"); } public void deleteJSON(ContextualisedStorage root, CSPRequestCredentials creds, CSPRequestCache cache, String filePath) throws ExistException, UnimplementedException, UnderlyingStorageException { try { String[] parts = extractPaths(filePath, new String[] { "main" }, 1); //log.info("DELETE"+parts[0]); int status = conn.getNone(RequestMethod.DELETE, "/relations/" + parts[0], null, creds, cache); if (status > 299) throw new UnderlyingStorageException("Could not delete relation, status=" + status, status, "/relations/" + parts[0]); } catch (ConnectionException e) { throw new UnderlyingStorageException("Could not delete relation" + e.getLocalizedMessage(), e.getStatus(), e.getUrl(), e); } } private String searchPath(JSONObject in) throws UnderlyingStorageException, JSONException { if (in == null) return ""; StringBuffer out = new StringBuffer(); if (in.has("src")) { String[] src = splitTypeFromId(in.getString("src")); out.append("&sbj=" + src[1]); } if (in.has("srcType")) { String srctype = in.getString("srcType"); out.append("&srcType=" + srctype); } if (in.has("dst")) { String[] dst = splitTypeFromId(in.getString("dst")); out.append("&obj=" + dst[1]); } if (in.has("dstType")) { String dsttype = in.getString("dstType"); out.append("&objType=" + dsttype); } if (in.has("type")) { out.append("&prd=" + in.getString("type")); } String ret = out.toString(); if (ret.startsWith("&")) ret = ret.substring(1); return ret; } // Needed because of CSPACE-1080 //XXX is this still needed?CSPACE-1080 has been resolved... private boolean post_filter(CSPRequestCredentials creds, CSPRequestCache cache, JSONObject restrictions, Node candidate) throws ExistException, UnderlyingStorageException, ConnectionException, JSONException { if (restrictions == null) return true; // Subject String src_csid = candidate.selectSingleNode("subjectCsid").getText(); String rest_src = restrictions.optString("src"); if (rest_src != null && !"".equals(rest_src)) { String[] data = rest_src.split("/"); if (data[0].equals("")) { rest_src = rest_src.substring(1); data = rest_src.split("/"); } if (!src_csid.equals(rest_src.split("/")[1])) return false; } String dst_csid = candidate.selectSingleNode("objectCsid").getText(); String rest_dst = restrictions.optString("dst"); if (rest_dst != null && !"".equals(rest_dst)) { String[] data2 = rest_dst.split("/"); if (data2[0].equals("")) { rest_dst = rest_dst.substring(1); data2 = rest_dst.split("/"); } if (!dst_csid.equals(rest_dst.split("/")[1])) return false; } // Retrieve the relation (CSPACE-1081) ReturnedMultipartDocument rel = conn.getMultipartXMLDocument(RequestMethod.GET, candidate.selectSingleNode("uri").getText(), null, creds, cache); if (rel.getStatus() == 404) throw new ExistException("Not found"); Document rel_doc = rel.getDocument("relations_common"); if (rel_doc == null) throw new UnderlyingStorageException("Could not retrieve relation, missing relations_common"); String type = rel_doc.selectSingleNode("relations_common/relationshipType").getText(); if (restrictions.has("type") && !type.equals(restrictions.optString("type"))) return false; return true; } @SuppressWarnings("unchecked") public JSONObject getPathsJSON(ContextualisedStorage root, CSPRequestCredentials creds, CSPRequestCache cache, String rootPath, JSONObject restrictions) throws ExistException, UnimplementedException, UnderlyingStorageException { JSONObject out = new JSONObject(); Boolean isHierarchical = false; if (isPathType(rootPath, new String[] { "main" }, 0)) { extractPaths(rootPath, new String[] { "main" }, 0); } else if (isPathType(rootPath, new String[] { "hierarchical" }, 0)) { extractPaths(rootPath, new String[] { "hierarchical" }, 0); isHierarchical = true; } try { JSONObject moredata = new JSONObject(); List<String> list = new ArrayList<String>(); ReturnedDocument data = conn.getXMLDocument(RequestMethod.GET, "/relations?" + searchPath(restrictions), null, creds, cache); Document doc = data.getDocument(); if (doc == null) throw new UnderlyingStorageException("Could not retrieve relation, missing relations_common"); JSONObject pagination = new JSONObject(); String xmlroot = "relations-common-list"; List<Node> nodes = doc.getDocument().selectNodes("/" + xmlroot + "/*"); for (Node node : nodes) { if (node.matches("/" + xmlroot + "/relation-list-item")) { //if(post_filter(creds,cache,restrictions,node)) list.add(node.selectSingleNode("csid").getText()); if (isHierarchical) { JSONObject hdata = new JSONObject(); Node subjectNode = node.selectSingleNode("subject"); Node objectNode = node.selectSingleNode("object"); hdata.put("subjecturi", subjectNode.selectSingleNode("uri").getText()); hdata.put("objecturi", objectNode.selectSingleNode("uri").getText()); hdata.put("subjectcsid", subjectNode.selectSingleNode("csid").getText()); hdata.put("objectcsid", objectNode.selectSingleNode("csid").getText()); findNameUnderNode(hdata, "subjectname", "subjectrefname", subjectNode); findNameUnderNode(hdata, "objectname", "objectrefname", objectNode); hdata.put("type", node.selectSingleNode("predicate").getText()); hdata.put("csid", node.selectSingleNode("csid").getText()); moredata.put(node.selectSingleNode("csid").getText(), hdata); } } else { pagination.put(node.getName(), node.getText()); } } out.put("pagination", pagination); out.put("listItems", list.toArray(new String[0])); out.put("moredata", moredata); return out; } catch (ConnectionException e) { throw new UnderlyingStorageException("Could not retrieve relation" + e.getLocalizedMessage(), e.getStatus(), e.getUrl()); } catch (JSONException e) { throw new UnderlyingStorageException("Could not retrieve relation", e); } } private void findNameUnderNode(JSONObject out, String nameKey, String refNameKey, Node itemNode) throws JSONException { // Look for something to put into the subjectname. Start with refName, // then name, then number Node itemRefName = itemNode.selectSingleNode("refName"); String nameValue = null; if (itemRefName != null) { String refNameValue = itemRefName.getText(); out.put(refNameKey, refNameValue); RefName.AuthorityItem item = RefName.AuthorityItem.parse(refNameValue); if (item != null) { nameValue = item.displayName; } else { RefName.Authority authority = RefName.Authority.parse(refNameValue); if (authority != null) { nameValue = authority.displayName; } } } // If no displayName from refName, then try name element if (nameValue == null) { Node itemNameNode = itemNode.selectSingleNode("name"); if (itemNameNode != null) { nameValue = itemNameNode.getText(); } } // Still nothing? try number element if (nameValue == null) { Node itemNumberNode = itemNode.selectSingleNode("number"); if (itemNumberNode != null) { nameValue = itemNumberNode.getText(); } } if (nameValue == null) { nameValue = "MISSING DATA"; } out.put(nameKey, nameValue); } @SuppressWarnings("unchecked") public String[] getPaths(ContextualisedStorage root, CSPRequestCredentials creds, CSPRequestCache cache, String rootPath, JSONObject restrictions) throws ExistException, UnimplementedException, UnderlyingStorageException { extractPaths(rootPath, new String[] { "main" }, 0); try { List<String> out = new ArrayList<String>(); ReturnedDocument data = conn.getXMLDocument(RequestMethod.GET, "/relations/" + searchPath(restrictions), null, creds, cache); Document doc = data.getDocument(); if (doc == null) throw new UnderlyingStorageException("Could not retrieve relation, missing relations_common"); List<Node> objects = doc.getDocument().selectNodes("relations-common-list/relation-list-item"); for (Node object : objects) { if (post_filter(creds, cache, restrictions, object)) out.add(object.selectSingleNode("csid").getText()); } return out.toArray(new String[0]); } catch (ConnectionException e) { throw new UnderlyingStorageException("Could not retrieve relation" + e.getLocalizedMessage(), e.getStatus(), e.getUrl(), e); } catch (JSONException e) { throw new UnderlyingStorageException("Could not retrieve relation" + e.getLocalizedMessage()); } } public JSONObject retrieveJSON(ContextualisedStorage root, CSPRequestCredentials creds, CSPRequestCache cache, String filePath, JSONObject restrictions) throws ExistException, UnimplementedException, UnderlyingStorageException { try { Boolean isHierarchical = false; String[] parts = null; if (isPathType(filePath, new String[] { "main" }, 1)) { parts = extractPaths(filePath, new String[] { "main" }, 1); } else if (isPathType(filePath, new String[] { "hierarchical" }, 1)) { parts = extractPaths(filePath, new String[] { "hierarchical" }, 1); isHierarchical = true; } ReturnedMultipartDocument out = conn.getMultipartXMLDocument(RequestMethod.GET, "/relations/" + parts[0], null, creds, cache); if (out.getStatus() == 404) throw new UnderlyingStorageException("Could not retrieve relation", out.getStatus(), "/relations/" + parts[0]); Document doc = out.getDocument("relations_common"); if (doc == null) throw new UnderlyingStorageException("Could not retrieve relation, missing relations_common", out.getStatus(), "/relations/" + parts[0]); return relationToData(cache, factory.load(parts[0], doc)); } catch (ConnectionException e) { throw new UnderlyingStorageException("Could not retrieve relation" + e.getLocalizedMessage(), e.getStatus(), e.getUrl(), e); } catch (JaxenException e) { throw new UnderlyingStorageException("Could not retrieve relation" + e.getLocalizedMessage(), e); } catch (JSONException e) { throw new UnderlyingStorageException("Could not retrieve relation" + e.getLocalizedMessage(), e); } } public void updateJSON(ContextualisedStorage root, CSPRequestCredentials creds, CSPRequestCache cache, String filePath, JSONObject data, JSONObject restrictions) throws ExistException, UnimplementedException, UnderlyingStorageException { try { String[] parts = extractPaths(filePath, new String[] { "main" }, 1); Map<String, Document> in = new HashMap<String, Document>(); Document datapath = dataToRelation(cache, parts[0], data).toDocument(); in.put("relations_common", datapath); //log.info("UPDATE"+datapath.asXML()); //log.info("UPDATE"+"/relations/"+parts[0]); ReturnedMultipartDocument out = conn.getMultipartXMLDocument(RequestMethod.PUT, "/relations/" + parts[0], in, creds, cache); if (out.getStatus() == 404) throw new ExistException("Not found"); if (out.getStatus() > 299) throw new UnderlyingStorageException("Could not update relation", out.getStatus(), "/relations/" + parts[0]); } catch (ConnectionException e) { throw new UnderlyingStorageException("Could not update relation" + e.getLocalizedMessage(), e.getStatus(), e.getUrl(), e); } catch (JSONException e) { throw new UnderlyingStorageException("Could not retrieve data" + e.getLocalizedMessage(), e); } } }