From f2a51820c596c9cc7dae84ca64719666763bebcb Mon Sep 17 00:00:00 2001 From: remillet Date: Fri, 19 Jan 2018 17:39:27 -0800 Subject: [PATCH] Adding support for POST and PUT vocabulary payloads containing term lists. Adding XMLReplay tests and some changes to XMLReply itself. --- .../IntegrationTests/xmlreplay/XmlReplay.java | 89 +++--- .../xmlreplay/XmlReplayEval.java | 71 +++-- .../xmlreplay/XmlReplayTransport.java | 183 +++++++----- .../responses/DeleteVocabJustItems.res.xml | 25 ++ .../DeleteVocabWithItems/objectExit.xml | 13 + .../replaceWithItems-vocab.xml | 26 ++ .../responses/ReplaceVocabItems.res.xml | 25 ++ .../{res => responses}/showVocab.res.xml | 0 .../showVocabWithItems.res.xml | 0 .../showVocabWithItemsLastPage.res.xml | 0 .../showVocabWithItemsPaged.res.xml | 0 .../ShowItems/showItems-item-template.xml | 1 - .../GetVocabularyItems.res.xml | 0 .../vocabulary/vocab-Item-template.xml | 11 + .../xmlreplay/vocabulary/vocab-Template.xml | 13 + .../xmlreplay/vocabulary/vocabulary.xml | 278 ++++++++++++++++-- .../common/vocabulary/AuthorityResource.java | 201 +++++++++---- .../document/DocumentReferenceException.java | 2 +- .../vocabulary/VocabularyResource.java | 159 +++++++++- 19 files changed, 867 insertions(+), 230 deletions(-) create mode 100644 services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/DeleteVocabJustItems/responses/DeleteVocabJustItems.res.xml create mode 100644 services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/DeleteVocabWithItems/objectExit.xml create mode 100644 services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ReplaceVocabItems/replaceWithItems-vocab.xml create mode 100644 services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ReplaceVocabItems/responses/ReplaceVocabItems.res.xml rename services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/{res => responses}/showVocab.res.xml (100%) rename services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/{res => responses}/showVocabWithItems.res.xml (100%) rename services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/{res => responses}/showVocabWithItemsLastPage.res.xml (100%) rename services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/{res => responses}/showVocabWithItemsPaged.res.xml (100%) rename services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/{res => responses}/GetVocabularyItems.res.xml (100%) create mode 100644 services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/vocab-Item-template.xml create mode 100644 services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/vocab-Template.xml diff --git a/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/XmlReplay.java b/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/XmlReplay.java index b9ae62c5c..587a41906 100644 --- a/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/XmlReplay.java +++ b/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/XmlReplay.java @@ -3,6 +3,7 @@ package org.collectionspace.services.IntegrationTests.xmlreplay; import org.apache.commons.cli.*; import org.apache.commons.io.FileUtils; +import org.apache.commons.jexl2.JexlContext; import org.apache.commons.jexl2.JexlEngine; import org.collectionspace.services.common.api.Tools; import org.dom4j.*; @@ -247,7 +248,7 @@ public class XmlReplay { for (ServiceResult pr : serviceResultsMap.values()) { try { if (pr.autoDelete == true && Tools.notEmpty(pr.deleteURL)){ - ServiceResult deleteResult = XmlReplayTransport.doDELETE(pr.deleteURL, defaultAuths.getDefaultAuth(), pr.testID, "[autodelete:"+logName+"]"); + ServiceResult deleteResult = XmlReplayTransport.doDELETE(pr.deleteURL, pr.adminAuth, pr.testID, "[autodelete:"+logName+"]"); if (deleteResult.gotExpectedResult() == false || deleteResult.responseCode != 200) { reattemptList.put(REATTEMPT_KEY + deleteFailures++, pr); // We need to try again after our dependents have been deleted. cow() } @@ -347,21 +348,21 @@ public class XmlReplay { String startElement = ""; String label = ""; - public static PartsStruct readParts(Node testNode, final String testID, String xmlReplayBaseDir){ - PartsStruct resultPartsStruct = new PartsStruct(); - resultPartsStruct.responseFilename = testNode.valueOf("filename"); - resultPartsStruct.startElement = testNode.valueOf("startElement"); - resultPartsStruct.label = testNode.valueOf("label"); - String responseFilename = testNode.valueOf("filename"); - if (Tools.notEmpty(responseFilename)){ - resultPartsStruct.responseFilename = xmlReplayBaseDir + '/' + responseFilename; - List varNodes = testNode.selectNodes("vars/var"); - readVars(testNode, varNodes, resultPartsStruct); - } - return resultPartsStruct; - } + public static PartsStruct readParts(Node testNode, final String testID, String xmlReplayBaseDir) { + PartsStruct resultPartsStruct = new PartsStruct(); + resultPartsStruct.responseFilename = testNode.valueOf("filename"); + resultPartsStruct.startElement = testNode.valueOf("startElement"); + resultPartsStruct.label = testNode.valueOf("label"); + String responseFilename = testNode.valueOf("filename"); + if (Tools.notEmpty(responseFilename)) { + resultPartsStruct.responseFilename = xmlReplayBaseDir + '/' + responseFilename; + List varNodes = testNode.selectNodes("vars/var"); + readVars(testNode, varNodes, resultPartsStruct); + } + return resultPartsStruct; + } - private static void readVars(Node testNode, List varNodes, PartsStruct resultPartsStruct){ + private static void readVars(Node testNode, List varNodes, PartsStruct resultPartsStruct) { Map vars = new HashMap(); resultPartsStruct.varsList.add(vars); //System.out.println("### vars: "+vars.size()+" ########"); @@ -476,7 +477,7 @@ public class XmlReplay { public static List runXmlReplayFile(String xmlReplayBaseDir, String controlFileName, - String testGroupID, + String targetedTestGroupID, String oneTestID, Map serviceResultsMap, boolean param_autoDeletePOSTS, @@ -519,12 +520,12 @@ public class XmlReplay { authsMapINFO = "Using AuthsMap from control file: "+authsMap; } - report.addTestGroup(testGroupID, controlFileName); //controlFileName is just the short name, without the full path. + report.addTestGroup(targetedTestGroupID, controlFileName); //controlFileName is just the short name, without the full path. String xmlReplayHeader = "========================================================================" +"\r\nXmlReplay running:" +"\r\n controlFile: "+ (new File(controlFile).getCanonicalPath()) +"\r\n protoHostPort: "+protoHostPort - +"\r\n testGroup: "+testGroupID + +"\r\n testGroup: "+targetedTestGroupID + (Tools.notEmpty(oneTestID) ? "\r\n oneTestID: "+oneTestID : "") +"\r\n AuthsMap: "+authsMapINFO +"\r\n param_autoDeletePOSTS: "+param_autoDeletePOSTS @@ -536,9 +537,10 @@ public class XmlReplay { System.out.println(xmlReplayHeader); String autoDeletePOSTS = ""; + String authIDForCleanup = ""; List testgroupNodes; - if (Tools.notEmpty(testGroupID)){ - testgroupNodes = document.selectNodes("//testGroup[@ID='"+testGroupID+"']"); + if (Tools.notEmpty(targetedTestGroupID)){ + testgroupNodes = document.selectNodes("//testGroup[@ID='"+targetedTestGroupID+"']"); } else { testgroupNodes = document.selectNodes("//testGroup"); } @@ -549,11 +551,25 @@ public class XmlReplay { evalStruct.jexl = jexl; for (Node testgroup : testgroupNodes) { - + String testGroupID = testgroup.valueOf("@autoDeletePOSTS"); XmlReplayEval.MapContextWKeys jc = new XmlReplayEval.MapContextWKeys();//MapContext(); //Get a new JexlContext for each test group. evalStruct.jc = jc; - autoDeletePOSTS = testgroup.valueOf("@autoDeletePOSTS"); + // + // Decide which auth to use for POST request cleanups + // + String authForCleanup = null; + authIDForCleanup = testgroup.valueOf("@authForCleanup"); + if (Tools.isEmpty(authIDForCleanup) == false) { + authForCleanup = authsMap.map.get(authIDForCleanup); + if (Tools.isEmpty(authForCleanup)) { + String msg = String.format("The 'authForCleanup' attribute value '%s' declared for test group '%s' is not defined.", + authIDForCleanup, testGroupID); + throw new Exception(msg); + } + } + authForCleanup = authForCleanup != null ? authForCleanup : defaultAuths.getDefaultAuth(); + List tests; if (Tools.notEmpty(oneTestID)){ tests = testgroup.selectNodes("test[@ID='"+oneTestID+"']"); @@ -588,9 +604,13 @@ public class XmlReplay { String currentAuthForTest = null; String authIDForTest = testNode.valueOf("@auth"); - - if (Tools.notEmpty(authIDForTest)){ + if (Tools.notEmpty(authIDForTest)) { currentAuthForTest = authsMap.map.get(authIDForTest); + if (currentAuthForTest == null) { + String msg = String.format("The 'auth' attribute value '%s' declared for test '%s' is not defined.", + authIDForTest, testIDLabel); + throw new Exception(msg); + } } else { String tokenAuthExpression = testNode.valueOf("@tokenauth"); if (Tools.notEmpty(tokenAuthExpression)){ @@ -598,12 +618,12 @@ public class XmlReplay { } } - if (Tools.notEmpty(currentAuthForTest)){ + if (Tools.notEmpty(currentAuthForTest)) { authForTest = currentAuthForTest; //else just run with current from last loop; } - if (Tools.isEmpty(authForTest)){ + if (Tools.isEmpty(authForTest)) { authForTest = defaultAuths.getDefaultAuth(); - } + } if (uri.indexOf("$")>-1){ uri = XmlReplayEval.eval(uri, serviceResultsMap, null, jexl, jc); @@ -699,6 +719,7 @@ public class XmlReplay { serviceResult.testID = testID; serviceResult.fullURL = fullURL; serviceResult.auth = authForTest; + serviceResult.adminAuth = authForCleanup; serviceResult.method = method; if (expectedCodes.size()>0){ serviceResult.expectedCodes = expectedCodes; @@ -770,24 +791,22 @@ public class XmlReplay { //=== Now spit out the HTML report file === File m = new File(controlFileName); String localName = m.getName();//don't instantiate, just use File to extract file name without directory. - String reportName = localName+'-'+testGroupID+".html"; + String reportName = localName+'-'+targetedTestGroupID+".html"; File resultFile = report.saveReport(xmlReplayBaseDir, reportsDir, reportName); if (resultFile!=null) { String toc = report.getTOC(reportName); reportsList.add(toc); } - //================================ return results; } - private static String timeString() { - java.util.Date date= new java.util.Date(); - java.sql.Timestamp ts = new java.sql.Timestamp(date.getTime()); - return ts.toString(); - } - + private static String timeString() { + java.util.Date date = new java.util.Date(); + java.sql.Timestamp ts = new java.sql.Timestamp(date.getTime()); + return ts.toString(); + } //======================== MAIN =================================================================== diff --git a/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/XmlReplayEval.java b/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/XmlReplayEval.java index fde549e78..b042a8bef 100644 --- a/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/XmlReplayEval.java +++ b/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/XmlReplayEval.java @@ -56,37 +56,46 @@ public class XmlReplayEval { * uri = eval(uri, serviceResultsMap, jexl, jc);
* RESULT: "/cspace-services/orgauthorities/43a2739c-4f40-49c8-a6d5/items/" */ - public static String eval(String inputJexlExpression, Map serviceResultsMap, Map vars, JexlEngine jexl, JexlContext jc) { - //System.out.println("\r\n---- REPLACE.init-uri: "+inputJexlExpression); - String result; - try { - jc.set("itemCSID", "${itemCSID}"); //noiseless passthru. - //System.out.println("eval :: serviceResultsMap "+serviceResultsMap.size()); - for (ServiceResult serviceResult : serviceResultsMap.values()) { - jc.set(serviceResult.testID, serviceResult); - //System.out.println("eval :: "+serviceResult.testID+"==>"+serviceResult.minimal()); - } - if (vars!=null){ - for (Map.Entry entry: vars.entrySet()) { - String value = entry.getValue(); - String key = entry.getKey(); - try { - value = parse(value, jexl, jc); - vars.put(key, value); //replace template value with actual value. - } catch (Exception e){ - value = "ERROR: "+e; - } - jc.set(key, value); - } - } - result = parse(inputJexlExpression, jexl, jc); - } catch (Throwable t) { - System.err.println("ERROR: " + t); - result = "ERROR"; - } - //System.out.println("---- REPLACE.uri: "+result+"\r\n"); - return result; - } + public static String eval(String inputJexlExpression, Map serviceResultsMap, + Map vars, JexlEngine jexl, JexlContext jc) { + // System.out.println("\r\n---- REPLACE.init-uri: + // "+inputJexlExpression); + String result; + try { + jc.set("itemCSID", "${itemCSID}"); // noiseless passthru. + // System.out.println("eval :: serviceResultsMap + // "+serviceResultsMap.size()); + + if (serviceResultsMap != null) { + for (ServiceResult serviceResult : serviceResultsMap.values()) { + jc.set(serviceResult.testID, serviceResult); + // System.out.println("eval :: + // "+serviceResult.testID+"==>"+serviceResult.minimal()); + } + } + + if (vars != null) { + for (Map.Entry entry : vars.entrySet()) { + String value = entry.getValue(); + String key = entry.getKey(); + try { + value = parse(value, jexl, jc); + vars.put(key, value); // replace template value with + // actual value. + } catch (Exception e) { + value = "ERROR: " + e; + } + jc.set(key, value); + } + } + result = parse(inputJexlExpression, jexl, jc); + } catch (Throwable t) { + System.err.println("ERROR: " + t); + result = "ERROR"; + } + // System.out.println("---- REPLACE.uri: "+result+"\r\n"); + return result; + } private static String parse(String in, JexlEngine jexl, JexlContext jc) { StringBuffer result = new StringBuffer(); diff --git a/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/XmlReplayTransport.java b/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/XmlReplayTransport.java index 31dd26038..04ebb6857 100644 --- a/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/XmlReplayTransport.java +++ b/services/IntegrationTests/src/main/java/org/collectionspace/services/IntegrationTests/xmlreplay/XmlReplayTransport.java @@ -181,23 +181,32 @@ public class XmlReplayTransport { */ /** Use this overload for NON-multipart messages, that is, regular POSTs. */ - public static ServiceResult doPOST_PUTFromXML(String fileName, - Map vars, - String protoHostPort, - String uri, - String method, - String contentType, - XmlReplayEval evalStruct, - String authForTest, - String fromTestID) - throws Exception { - byte[] b = FileUtils.readFileToByteArray(new File(fileName)); - String xmlString = new String(b); - String contentRaw = xmlString; - xmlString = evalStruct.eval(xmlString, evalStruct.serviceResultsMap, vars, evalStruct.jexl, evalStruct.jc); - String urlString = protoHostPort+uri; - return doPOST_PUT(urlString, xmlString, contentRaw, BOUNDARY, method, contentType, authForTest, fromTestID); //method is POST or PUT. - } + public static ServiceResult doPOST_PUTFromXML( + String fileName, + Map vars, + String protoHostPort, + String uri, + String method, + String contentType, + XmlReplayEval evalStruct, + String authForTest, + String fromTestID) throws Exception { + byte[] b = FileUtils.readFileToByteArray(new File(fileName)); + String xmlString = new String(b); + String contentRaw = xmlString; + // + // Add the test ID so we can substitute instances of "${testID}" in the + // payload with the test ID. + // + String testId = fromTestID.split("\\.")[1]; // Get the unqualified (without the group ID part) test name + vars.put("Test.ID", testId); + + xmlString = evalStruct.eval(xmlString, evalStruct.serviceResultsMap, vars, evalStruct.jexl, evalStruct.jc); + String urlString = protoHostPort + uri; + + return doPOST_PUT(urlString, xmlString, contentRaw, BOUNDARY, method, contentType, authForTest, fromTestID); // method is POST or PUT. + } //HACK for speed testing in doPOST_PUT. // Result: XmlReplay takes 9ms to process one test @@ -208,18 +217,21 @@ public class XmlReplayTransport { //if (true) return result; //END-HACK - public static ServiceResult doPOST_PUT(String urlString, - String content, - String contentRaw, - String boundary, - String method, - String contentType, - String authForTest, - String fromTestID) throws Exception { + private static ServiceResult doPOST_PUT( + String urlString, + String content, + String contentRaw, + String boundary, + String method, + String contentType, + String authForTest, + String fromTestID) throws Exception { ServiceResult result = new ServiceResult(); result.method = method; + String deleteURL = ""; String location = ""; + try { URL url = new URL(urlString); HttpURLConnection conn; @@ -273,56 +285,83 @@ public class XmlReplayTransport { } catch (Throwable t2){ result.error = "ERROR in XmlReplayTransport: "+t2; } + return result; } - public static ServiceResult doPOST_PUT_PostMethod(String urlString, String content, Map contentRaw, - String boundary, String method, String contentType, - String authForTest, String fromTestID) throws Exception { - ServiceResult result = new ServiceResult(); - result.method = method; - String deleteURL = ""; - String location = ""; - try { - HttpClient client = new HttpClient(); - PostMethod postMethod = new PostMethod(urlString); - postMethod.setRequestHeader("Accept", "multipart/mixed"); - postMethod.addRequestHeader("Accept", "application/xml"); - postMethod.setRequestHeader("Authorization", formatAuth(authForTest)); - postMethod.setRequestHeader("X-XmlReplay-fromTestID", fromTestID); - //this method takes an array of params. Not sure what they expect us to do with a raw post: - // postMethod.setRequestBody(); - int statusCode1 = 0; - String res = ""; - try { - statusCode1 = client.executeMethod(postMethod); - result.responseCode = statusCode1; - //System.out.println("statusCode: "+statusCode1+" statusLine ==>" + postMethod.getStatusLine()); - result.responseMessage = postMethod.getStatusText(); - res = postMethod.getResponseBodyAsString(); - Header[] headers = postMethod.getResponseHeaders("Location"); - if (headers.length>0) { - System.out.println("headers[0]: "+headers[0]); - String locationZero = headers[0].getValue(); - if (locationZero != null){ - String[] segments = locationZero.split("/"); - location = segments[segments.length - 1]; - deleteURL = Tools.glue(urlString, "/", location); - } - } - postMethod.releaseConnection(); - } catch (Throwable t){ - result.error = t.toString(); - } - result.result = res; - result.location = location; - result.deleteURL = deleteURL; - result.CSID = location; - } catch (Throwable t2){ - result.error = "ERROR in XmlReplayTransport: "+t2; - } - return result; - } + @Deprecated + /** + * This method is not used and is a good candidate for removal. + * + * @param urlString + * @param content + * @param contentRaw + * @param boundary + * @param method + * @param contentType + * @param authForTest + * @param fromTestID + * @return + * @throws Exception + */ + private static ServiceResult doPOST_PUT_PostMethod( + String urlString, + String content, + Map contentRaw, + String boundary, + String method, + String contentType, + String authForTest, + String fromTestID) + throws Exception { + ServiceResult result = new ServiceResult(); + result.method = method; + String deleteURL = ""; + String location = ""; + + try { + HttpClient client = new HttpClient(); + PostMethod postMethod = new PostMethod(urlString); + postMethod.setRequestHeader("Accept", "multipart/mixed"); + postMethod.addRequestHeader("Accept", "application/xml"); + postMethod.setRequestHeader("Authorization", formatAuth(authForTest)); + postMethod.setRequestHeader("X-XmlReplay-fromTestID", fromTestID); + // this method takes an array of params. Not sure what they expect + // us to do with a raw post: + // postMethod.setRequestBody(); + int statusCode1 = 0; + String res = ""; + try { + statusCode1 = client.executeMethod(postMethod); + result.responseCode = statusCode1; + // System.out.println("statusCode: "+statusCode1+" statusLine + // ==>" + postMethod.getStatusLine()); + result.responseMessage = postMethod.getStatusText(); + res = postMethod.getResponseBodyAsString(); + Header[] headers = postMethod.getResponseHeaders("Location"); + if (headers.length > 0) { + System.out.println("headers[0]: " + headers[0]); + String locationZero = headers[0].getValue(); + if (locationZero != null) { + String[] segments = locationZero.split("/"); + location = segments[segments.length - 1]; + deleteURL = Tools.glue(urlString, "/", location); + } + } + postMethod.releaseConnection(); + } catch (Throwable t) { + result.error = t.toString(); + } + result.result = res; + result.location = location; + result.deleteURL = deleteURL; + result.CSID = location; + } catch (Throwable t2) { + result.error = "ERROR in XmlReplayTransport: " + t2; + } + + return result; + } private static void readStream(HttpURLConnection conn, ServiceResult result) throws Throwable { BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/DeleteVocabJustItems/responses/DeleteVocabJustItems.res.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/DeleteVocabJustItems/responses/DeleteVocabJustItems.res.xml new file mode 100644 index 000000000..6a843d402 --- /dev/null +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/DeleteVocabJustItems/responses/DeleteVocabJustItems.res.xml @@ -0,0 +1,25 @@ + + + 6 + createEmptyVocab100 + ${csid} + createEmptyVocab100 Vocabulary + + This is a test vocabulary for test createEmptyVocab100 + + createEmptyVocab100 + + urn:cspace:testsci.collectionspace.org:vocabularies:name(createEmptyVocab100)'createEmptyVocab100 Vocabulary' + + enum + + + 0 + 2500 + 0 + 0 + + csid|uri|refName|updatedAt|workflowState|rev|sourcePage|sas|proposed|deprecated|termStatus|description|source|order|displayName|shortIdentifier + + + \ No newline at end of file diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/DeleteVocabWithItems/objectExit.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/DeleteVocabWithItems/objectExit.xml new file mode 100644 index 000000000..94e148365 --- /dev/null +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/DeleteVocabWithItems/objectExit.xml @@ -0,0 +1,13 @@ + + + + ${exitNumber} + + + ${itemRefName} + + \ No newline at end of file diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ReplaceVocabItems/replaceWithItems-vocab.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ReplaceVocabItems/replaceWithItems-vocab.xml new file mode 100644 index 000000000..8e4ff9463 --- /dev/null +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ReplaceVocabItems/replaceWithItems-vocab.xml @@ -0,0 +1,26 @@ + + + + Updated createReplaceVocabItems Vocabulary + createReplaceVocabItems + This is an updated test vocabulary created in the createReplaceVocabItems XMLReplay test group + Some updated mythical book posted with item terms in createReplaceVocabItems test group + + + + 1 + Replacement item A + replacementitema + + + 2 + Replacement item B + replacementitemb + + + 3 + Replacement item C + replacementitemc + + + diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ReplaceVocabItems/responses/ReplaceVocabItems.res.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ReplaceVocabItems/responses/ReplaceVocabItems.res.xml new file mode 100644 index 000000000..c6f84fdec --- /dev/null +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ReplaceVocabItems/responses/ReplaceVocabItems.res.xml @@ -0,0 +1,25 @@ + + + 6 + createReplaceVocabItems + ${csid} + createReplaceVocabItems Vocabulary + + This is a test vocabulary for test createReplaceVocabItems + + createReplaceVocabItems + + urn:cspace:testsci.collectionspace.org:vocabularies:name(createReplaceVocabItems)'createReplaceVocabItems Vocabulary' + + enum + + + 0 + 2500 + 0 + 0 + + csid|uri|refName|updatedAt|workflowState|rev|sourcePage|sas|proposed|deprecated|termStatus|description|source|order|displayName|shortIdentifier + + + \ No newline at end of file diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/res/showVocab.res.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/responses/showVocab.res.xml similarity index 100% rename from services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/res/showVocab.res.xml rename to services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/responses/showVocab.res.xml diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/res/showVocabWithItems.res.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/responses/showVocabWithItems.res.xml similarity index 100% rename from services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/res/showVocabWithItems.res.xml rename to services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/responses/showVocabWithItems.res.xml diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/res/showVocabWithItemsLastPage.res.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/responses/showVocabWithItemsLastPage.res.xml similarity index 100% rename from services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/res/showVocabWithItemsLastPage.res.xml rename to services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/responses/showVocabWithItemsLastPage.res.xml diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/res/showVocabWithItemsPaged.res.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/responses/showVocabWithItemsPaged.res.xml similarity index 100% rename from services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/res/showVocabWithItemsPaged.res.xml rename to services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/responses/showVocabWithItemsPaged.res.xml diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/showItems-item-template.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/showItems-item-template.xml index 2d61b5003..6176fe8d1 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/showItems-item-template.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/ShowItems/showItems-item-template.xml @@ -4,7 +4,6 @@ xmlns:ns2="http://collectionspace.org/services/jaxb"> ${displayName} ${itemID} - urn:cspace:org.collectionspace.demo:vocabulary:name(TestOrderVocab):item:name(${itemID})'${displayName}' ${order} This is a test vocabulary item Some mythical book diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/res/GetVocabularyItems.res.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/responses/GetVocabularyItems.res.xml similarity index 100% rename from services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/res/GetVocabularyItems.res.xml rename to services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/responses/GetVocabularyItems.res.xml diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/vocab-Item-template.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/vocab-Item-template.xml new file mode 100644 index 000000000..a82a53497 --- /dev/null +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/vocab-Item-template.xml @@ -0,0 +1,11 @@ + + + + ${Test.ID} + ${Test.ID} + ${order} + This is a test vocabulary item for test ${Test.ID} + Some mythical book for test ${Test.ID} + The page should match the order:${order} + + diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/vocab-Template.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/vocab-Template.xml new file mode 100644 index 000000000..8f65a9547 --- /dev/null +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/vocab-Template.xml @@ -0,0 +1,13 @@ + + + + + ${Test.ID} Vocabulary + ${Test.ID} + enum + This is a test vocabulary for test ${Test.ID} + ${Test.ID} + + diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/vocabulary.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/vocabulary.xml index b91cce90f..f9bb9190e 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/vocabulary.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/vocabulary/vocabulary.xml @@ -3,9 +3,239 @@ YWRtaW5AY29yZS5jb2xsZWN0aW9uc3BhY2Uub3JnOkFkbWluaXN0cmF0b3I= - YWRtaW5AY29sbGVjdGlvbnNwYWNlLm9yZzpBZG1pbmlzdHJhdG9y + YWRtaW5AdGVzdHNjaS5jb2xsZWN0aW9uc3BhY2Uub3JnOkFkbWluaXN0cmF0b3I= + + + + POST + /cspace-services/vocabularies/ + vocabulary/vocab-Template.xml + + + POST + /cspace-services/vocabularies/${createReplaceVocabItems.CSID}/items/ + vocabulary/vocab-Item-template.xml + + 1 + + + + POST + /cspace-services/vocabularies/${createReplaceVocabItems.CSID}/items/ + vocabulary/vocab-Item-template.xml + + 2 + + + + POST + /cspace-services/vocabularies/${createReplaceVocabItems.CSID}/items/ + vocabulary/vocab-Item-template.xml + + 3 + + + + PUT + /cspace-services/vocabularies/${createReplaceVocabItems.CSID} + vocabulary/ReplaceVocabItems/ReplaceVocabItems.res.xml + + + + + + + POST + /cspace-services/vocabularies/ + vocabulary/vocab-Template.xml + + + POST + /cspace-services/vocabularies/${createEmptyVocab100.CSID}/items/ + vocabulary/vocab-Item-template.xml + + 1 + + + + POST + /cspace-services/vocabularies/${createEmptyVocab100.CSID}/items/ + vocabulary/vocab-Item-template.xml + + 2 + + + + POST + /cspace-services/vocabularies/${createEmptyVocab100.CSID}/items/ + vocabulary/vocab-Item-template.xml + + 3 + + + + DELETE + /cspace-services/vocabularies/${createEmptyVocab100.CSID}/items + + + GET + /cspace-services/vocabularies/${createEmptyVocab100.CSID} + + + GET + /cspace-services/vocabularies/${createEmptyVocab100.CSID}?showItems=true + + + ${createEmptyVocab100.CSID} + + + + vocabulary/DeleteVocabJustItems/responses/DeleteVocabJustItems.res.xml + + + + + + + + POST + /cspace-services/vocabularies/ + vocabulary/vocab-Template.xml + + + POST + /cspace-services/vocabularies/${createEmptyVocab.CSID}/items/ + vocabulary/vocab-Item-template.xml + + 1 + + + + POST + /cspace-services/vocabularies/${createEmptyVocab.CSID}/items/ + vocabulary/vocab-Item-template.xml + + 2 + + + + POST + /cspace-services/vocabularies/${createEmptyVocab.CSID}/items/ + vocabulary/vocab-Item-template.xml + + 3 + + + + POST + /cspace-services/objectexit + vocabulary/DeleteVocabWithItems/objectExit.xml + + 2 + urn:cspace:testsci.collectionspace.org:vocabularies:name(createEmptyVocab):item:name(createItem2)'createItem2' + + + + 409 + DELETE + /cspace-services/vocabularies/${createEmptyVocab.CSID} + + + DELETE + /cspace-services/objectexit/${createObjectExit.CSID} + + + DELETE + /cspace-services/vocabularies/${createEmptyVocab.CSID} + + + 404 + GET + /cspace-services/vocabularies/${createEmptyVocab.CSID} + + + + + + + POST + /cspace-services/vocabularies/ + vocabulary/vocab-Template.xml + + + POST + /cspace-services/vocabularies/${createEmptyVocab.CSID}/items/ + vocabulary/vocab-Item-template.xml + + 1 + + + + POST + /cspace-services/vocabularies/${createEmptyVocab.CSID}/items/ + vocabulary/vocab-Item-template.xml + + 2 + + + + POST + /cspace-services/vocabularies/${createEmptyVocab.CSID}/items/ + vocabulary/vocab-Item-template.xml + + 3 + + + + DELETE + /cspace-services/vocabularies/${createEmptyVocab.CSID} + + + 404 + GET + /cspace-services/vocabularies/${createEmptyVocab.CSID} + + + @@ -27,7 +257,7 @@ /cspace-services/vocabularies/ vocabulary/ShowItems/showItems-vocab.xml - + POST /cspace-services/vocabularies/${createShowItemsVocab.CSID}/items/ vocabulary/ShowItems/showItems-item-template.xml @@ -37,7 +267,7 @@ ShowsItems item ${itemID} - + POST /cspace-services/vocabularies/${createShowItemsVocab.CSID}/items/ vocabulary/ShowItems/showItems-item-template.xml @@ -47,7 +277,7 @@ ShowsItems item ${itemID} - + POST /cspace-services/vocabularies/${createShowItemsVocab.CSID}/items/ vocabulary/ShowItems/showItems-item-template.xml @@ -66,7 +296,7 @@ - vocabulary/ShowItems/res/showVocab.res.xml + vocabulary/ShowItems/responses/showVocab.res.xml @@ -83,7 +313,7 @@ ${createItem3.CSID} - vocabulary/ShowItems/res/showVocabWithItems.res.xml + vocabulary/ShowItems/responses/showVocabWithItems.res.xml @@ -100,7 +330,7 @@ ${createItem3.CSID} - vocabulary/ShowItems/res/showVocabWithItemsPaged.res.xml + vocabulary/ShowItems/responses/showVocabWithItemsPaged.res.xml @@ -117,26 +347,24 @@ ${createItem3.CSID} - vocabulary/ShowItems/res/showVocabWithLastPage.res.xml + vocabulary/ShowItems/responses/showVocabWithLastPage.res.xml - + - - + POST /cspace-services/vocabularies/ vocabulary/1-vocab.xml - + POST /cspace-services/vocabularies/${Vocabulary1.CSID}/items/ vocabulary/2-item.xml @@ -146,7 +374,7 @@ TestOrder item ${itemID} order ${order} - + POST /cspace-services/vocabularies/${Vocabulary1.CSID}/items/ vocabulary/2-item.xml @@ -156,7 +384,7 @@ TestOrder item ${itemID} order ${order} - + POST /cspace-services/vocabularies/${Vocabulary1.CSID}/items/ vocabulary/2-item.xml @@ -167,17 +395,17 @@ - + GET /cspace-services/vocabularies/ - + GET /cspace-services/vocabularies/${Vocabulary1.CSID}/items/ - vocabulary/res/GetVocabularyItems.res.xml + vocabulary/responses/GetVocabularyItems.res.xml ${Item3DupeOrder.displayName} @@ -185,12 +413,12 @@ - + GET /cspace-services/vocabularies/${Vocabulary1.CSID}/items/${Item1.CSID} - + GET /cspace-services/vocabularies/${Vocabulary1.CSID}/items/${Item1.CSID}/refObjs diff --git a/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/AuthorityResource.java b/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/AuthorityResource.java index 883b4504d..cd910aae5 100644 --- a/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/AuthorityResource.java +++ b/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/AuthorityResource.java @@ -76,12 +76,14 @@ import org.collectionspace.services.common.document.DocumentReferenceException; import org.collectionspace.services.common.document.DocumentWrapper; import org.collectionspace.services.common.document.Hierarchy; import org.collectionspace.services.common.query.QueryManager; +import org.collectionspace.services.common.repository.RepositoryClient; import org.collectionspace.services.common.vocabulary.nuxeo.AuthorityDocumentModelHandler; import org.collectionspace.services.common.vocabulary.nuxeo.AuthorityItemDocumentModelHandler; import org.collectionspace.services.common.workflow.service.nuxeo.WorkflowDocumentModelHandler; import org.collectionspace.services.config.ClientType; import org.collectionspace.services.config.service.ServiceBindingType; import org.collectionspace.services.jaxb.AbstractCommonList; +import org.collectionspace.services.jaxb.AbstractCommonList.ListItem; import org.collectionspace.services.lifecycle.TransitionDef; import org.collectionspace.services.nuxeo.client.java.DocumentModelHandler; import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface; @@ -98,6 +100,7 @@ import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.DocumentModelList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.w3c.dom.Element; /** * The Class AuthorityResource. @@ -563,6 +566,8 @@ public abstract class AuthorityResource @PUT @Path("{csid}") public byte[] updateAuthority( + @Context ResourceMap resourceMap, + @Context UriInfo uriInfo, @PathParam("csid") String specifier, String xmlPayload) { PoxPayloadOut result = null; @@ -586,6 +591,46 @@ public abstract class AuthorityResource return result.getBytes(); } + /** + * Delete all the items in an authority list. + * + * @param specifier + * @param uriInfo + * @return + */ + @DELETE + @Path("{csid}/items") + public Response deleteAuthorityItemList(@PathParam("csid") String specifier, + @Context UriInfo uriInfo) { + uriInfo = new UriInfoWrapper(uriInfo); + + try { + ServiceContext ctx = createServiceContext(uriInfo); + RepositoryClient repoClient = this.getRepositoryClient(ctx); + + CoreSessionInterface repoSession = repoClient.getRepositorySession(ctx); + try { + DocumentHandler handler = createDocumentHandler(ctx); + // + // Delete all the items one by one + // + AbstractCommonList itemsList = this.getAuthorityItemList(ctx, specifier, uriInfo); + for (ListItem item : itemsList.getListItem()) { + deleteAuthorityItem(ctx, specifier, getCsid(item), AuthorityServiceUtils.UPDATE_REV); + } + } catch (Throwable t) { + repoSession.setTransactionRollbackOnly(); + throw t; + } finally { + repoClient.releaseRepositorySession(ctx, repoSession); + } + + return Response.status(HttpResponseCodes.SC_OK).build(); + } catch (Exception e) { + throw bigReThrow(e, ServiceMessages.DELETE_FAILED, specifier); + } + } + /** * Delete authority * @@ -607,30 +652,64 @@ public abstract class AuthorityResource try { ServiceContext ctx = createServiceContext(uriInfo); - DocumentHandler handler = createDocumentHandler(ctx); - Specifier spec = Specifier.getSpecifier(specifier, "getAuthority", "GET"); - if (spec.form == SpecifierForm.CSID) { - if (logger.isDebugEnabled()) { - logger.debug("deleteAuthority with csid=" + spec.value); - } - ensureCSID(spec.value, ServiceMessages.DELETE_FAILED, "Authority.csid"); - getRepositoryClient(ctx).delete(ctx, spec.value, handler); - } else { - if (logger.isDebugEnabled()) { - logger.debug("deleteAuthority with specifier=" + spec.value); - } - String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, spec.value); - getRepositoryClient(ctx).deleteWithWhereClause(ctx, whereClause, handler); - } + RepositoryClient repoClient = this.getRepositoryClient(ctx); + CoreSessionInterface repoSession = repoClient.getRepositorySession(ctx); + try { + DocumentHandler handler = createDocumentHandler(ctx); + // + // First try to delete all the items + // + AbstractCommonList itemsList = this.getAuthorityItemList(ctx, specifier, uriInfo); + for (ListItem item : itemsList.getListItem()) { + deleteAuthorityItem(ctx, specifier, getCsid(item), AuthorityServiceUtils.UPDATE_REV); + } + + // + // Lastly, delete the parent/container + // + if (spec.form == SpecifierForm.CSID) { + if (logger.isDebugEnabled()) { + logger.debug("deleteAuthority with csid=" + spec.value); + } + ensureCSID(spec.value, ServiceMessages.DELETE_FAILED, "Authority.csid"); + getRepositoryClient(ctx).delete(ctx, spec.value, handler); + } else { + if (logger.isDebugEnabled()) { + logger.debug("deleteAuthority with specifier=" + spec.value); + } + String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, spec.value); + getRepositoryClient(ctx).deleteWithWhereClause(ctx, whereClause, handler); + } + } catch (Throwable t) { + repoSession.setTransactionRollbackOnly(); + throw t; + } finally { + repoClient.releaseRepositorySession(ctx, repoSession); + } + return Response.status(HttpResponseCodes.SC_OK).build(); } catch (Exception e) { throw bigReThrow(e, ServiceMessages.DELETE_FAILED, specifier); } } - /** + private String getCsid(ListItem item) { + String result = null; + + for (Element ele : item.getAny()) { + String elementName = ele.getTagName().toLowerCase(); + if (elementName.equals("csid")) { + result = ele.getTextContent(); + break; + } + } + + return result; + } + + /** * * @param ctx * @param parentspecifier - ID of the container. Can be URN or CSID form @@ -662,6 +741,49 @@ public abstract class AuthorityResource return result; } + + public PoxPayloadOut updateAuthorityItem( + ServiceContext itemServiceCtx, // Ok to be null. Will be null on PUT calls, but not on sync calls + ResourceMap resourceMap, + UriInfo uriInfo, + String parentspecifier, + String itemspecifier, + PoxPayloadIn theUpdate, + boolean shouldUpdateRevNumber, + Boolean isProposed, + Boolean isSASItem + ) throws Exception { + PoxPayloadOut result = null; + + CsidAndShortIdentifier csidAndShortId = lookupParentCSIDAndShortIdentifer(itemServiceCtx, parentspecifier, "updateAuthorityItem(parent)", "UPDATE_ITEM", null); + String parentcsid = csidAndShortId.CSID; + String parentShortId = csidAndShortId.shortIdentifier; + // + // If the itemServiceCtx context is not null, use it. Otherwise, create a new context + // + ServiceContext ctx = itemServiceCtx; + if (ctx == null) { + ctx = createServiceContext(getItemServiceName(), theUpdate, resourceMap, uriInfo); + } else { + ctx.setInput(theUpdate); // the update payload + } + + String itemcsid = lookupItemCSID(ctx, itemspecifier, parentcsid, "updateAuthorityItem(item)", "UPDATE_ITEM"); //use itemServiceCtx if it is not null + + // We omit the parentShortId, only needed when doing a create... + AuthorityItemDocumentModelHandler handler = (AuthorityItemDocumentModelHandler)createItemDocumentHandler(ctx, parentcsid, parentShortId); + handler.setShouldUpdateRevNumber(shouldUpdateRevNumber); + if (isProposed != null) { + handler.setIsProposed(isProposed); + } + if (isSASItem != null) { + handler.setIsSASItem(isSASItem); + } + getRepositoryClient(ctx).update(ctx, itemcsid, handler); + result = ctx.getOutput(); + + return result; + } /** * Called with an existing context. @@ -1282,48 +1404,7 @@ public abstract class AuthorityResource return result.getBytes(); } - public PoxPayloadOut updateAuthorityItem( - ServiceContext itemServiceCtx, // Ok to be null. Will be null on PUT calls, but not on sync calls - ResourceMap resourceMap, - UriInfo uriInfo, - String parentspecifier, - String itemspecifier, - PoxPayloadIn theUpdate, - boolean shouldUpdateRevNumber, - Boolean isProposed, - Boolean isSASItem - ) throws Exception { - PoxPayloadOut result = null; - - CsidAndShortIdentifier csidAndShortId = lookupParentCSIDAndShortIdentifer(itemServiceCtx, parentspecifier, "updateAuthorityItem(parent)", "UPDATE_ITEM", null); - String parentcsid = csidAndShortId.CSID; - String parentShortId = csidAndShortId.shortIdentifier; - // - // If the itemServiceCtx context is not null, use it. Otherwise, create a new context - // - ServiceContext ctx = itemServiceCtx; - if (ctx == null) { - ctx = createServiceContext(getItemServiceName(), theUpdate, resourceMap, uriInfo); - } else { - ctx.setInput(theUpdate); // the update payload - } - - String itemcsid = lookupItemCSID(ctx, itemspecifier, parentcsid, "updateAuthorityItem(item)", "UPDATE_ITEM"); //use itemServiceCtx if it is not null - // We omit the parentShortId, only needed when doing a create... - AuthorityItemDocumentModelHandler handler = (AuthorityItemDocumentModelHandler)createItemDocumentHandler(ctx, parentcsid, parentShortId); - handler.setShouldUpdateRevNumber(shouldUpdateRevNumber); - if (isProposed != null) { - handler.setIsProposed(isProposed); - } - if (isSASItem != null) { - handler.setIsSASItem(isSASItem); - } - getRepositoryClient(ctx).update(ctx, itemcsid, handler); - result = ctx.getOutput(); - - return result; - } /** * Delete authorityItem. @@ -1383,8 +1464,10 @@ public abstract class AuthorityResource try { parentcsid = lookupParentCSID(ctx, parentIdentifier, "deleteAuthorityItem(parent)", "DELETE_ITEM", null); } catch (DocumentNotFoundException de) { - logger.warn(String.format("Could not find parent with ID='%s' when trying to delete item ID='%s'", - parentIdentifier, itemIdentifier)); + String msg = String.format("Could not find parent with ID='%s' when trying to delete item ID='%s'", + parentIdentifier, itemIdentifier); + logger.warn(msg); + throw de; } String itemCsid = lookupItemCSID(ctx, itemIdentifier, parentcsid, "deleteAuthorityItem(item)", "DELETE_ITEM"); //use itemServiceCtx if it is not null diff --git a/services/common/src/main/java/org/collectionspace/services/common/document/DocumentReferenceException.java b/services/common/src/main/java/org/collectionspace/services/common/document/DocumentReferenceException.java index 2affc2a4d..aee838820 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/document/DocumentReferenceException.java +++ b/services/common/src/main/java/org/collectionspace/services/common/document/DocumentReferenceException.java @@ -5,7 +5,7 @@ public class DocumentReferenceException extends DocumentException { * */ private static final long serialVersionUID = 1L; - final public static int HTTP_CODE = 500; + final public static int HTTP_CODE = 409; // Conflict response. There may be a dependency issue. For example, trying to delete a term from an authority that is being referenced in another record. /** * Creates a new instance of DocumentNotFoundException without detail message. diff --git a/services/vocabulary/service/src/main/java/org/collectionspace/services/vocabulary/VocabularyResource.java b/services/vocabulary/service/src/main/java/org/collectionspace/services/vocabulary/VocabularyResource.java index 034535d64..caa828e7f 100644 --- a/services/vocabulary/service/src/main/java/org/collectionspace/services/vocabulary/VocabularyResource.java +++ b/services/vocabulary/service/src/main/java/org/collectionspace/services/vocabulary/VocabularyResource.java @@ -43,6 +43,9 @@ import org.collectionspace.services.common.document.JaxbUtils; import org.collectionspace.services.common.repository.RepositoryClient; import org.collectionspace.services.common.vocabulary.AuthorityResource; import org.collectionspace.services.common.vocabulary.AuthorityServiceUtils; +import org.collectionspace.services.common.vocabulary.RefNameServiceUtils; +import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.Specifier; +import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.SpecifierForm; import org.collectionspace.services.jaxb.AbstractCommonList; import org.collectionspace.services.jaxb.AbstractCommonList.ListItem; import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface; @@ -57,6 +60,7 @@ import org.w3c.dom.Element; import javax.ws.rs.GET; import javax.ws.rs.POST; +import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.core.Context; @@ -70,6 +74,10 @@ import javax.ws.rs.core.UriInfo; public class VocabularyResource extends AuthorityResource { + private enum Method { + POST, PUT; + } + private final static String vocabularyServiceName = VocabularyClient.SERVICE_PATH_COMPONENT; private final static String VOCABULARIES_COMMON = "vocabularies_common"; @@ -84,8 +92,8 @@ public class VocabularyResource extends VOCABULARIES_COMMON, VOCABULARYITEMS_COMMON); } - @Override @POST + @Override public Response createAuthority( @Context ResourceMap resourceMap, @Context UriInfo uriInfo, @@ -107,7 +115,7 @@ public class VocabularyResource extends try { DocumentHandler handler = createDocumentHandler(ctx); String csid = repoClient.create(ctx, handler); - handleItemsPayload(repoSession, csid, resourceMap, uriInfo, input); + handleItemsPayload(Method.POST, repoSession, csid, resourceMap, uriInfo, input); UriBuilder path = UriBuilder.fromResource(resourceClass); path.path("" + csid); Response response = Response.created(path.build()).build(); @@ -123,24 +131,139 @@ public class VocabularyResource extends } } } + + @PUT + @Path("{csid}") + @Override + public byte[] updateAuthority( + @Context ResourceMap resourceMap, + @Context UriInfo uriInfo, + @PathParam("csid") String specifier, + String xmlPayload) { + PoxPayloadOut result = null; + try { + PoxPayloadIn theUpdate = new PoxPayloadIn(xmlPayload); + Specifier spec = Specifier.getSpecifier(specifier, "updateAuthority", "UPDATE"); + ServiceContext ctx = createServiceContext(theUpdate); + RepositoryClient repoClient = this.getRepositoryClient(ctx); + + CoreSessionInterface repoSession = repoClient.getRepositorySession(ctx); + try { + DocumentHandler handler = createDocumentHandler(ctx); + String csid; + if (spec.form == SpecifierForm.CSID) { + csid = spec.value; + } else { + String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, spec.value); + csid = getRepositoryClient(ctx).findDocCSID(null, ctx, whereClause); + } + getRepositoryClient(ctx).update(ctx, csid, handler); + handleItemsPayload(Method.PUT, repoSession, csid, resourceMap, uriInfo, theUpdate); + result = ctx.getOutput(); + } catch (Throwable t) { + repoSession.setTransactionRollbackOnly(); + throw t; + } finally { + repoClient.releaseRepositorySession(ctx, repoSession); + } + } catch (Exception e) { + throw bigReThrow(e, ServiceMessages.UPDATE_FAILED); + } + return result.getBytes(); + } - private void handleItemsPayload(CoreSessionInterface repoSession, + private boolean handleItemsPayload( + Method method, + CoreSessionInterface repoSession, String parentIdentifier, ResourceMap resourceMap, UriInfo uriInfo, PoxPayloadIn input) throws Exception { + boolean result = true; + PayloadInputPart abstractCommonListPart = input.getPart(PoxPayload.ABSTRACT_COMMON_LIST_ROOT_ELEMENT_LABEL); if (abstractCommonListPart != null) { AbstractCommonList itemsList = (AbstractCommonList) abstractCommonListPart.getBody(); for (ListItem item : itemsList.getListItem()) { + String errMsg = null; + boolean success = true; + Response response = null; + PoxPayloadOut payloadOut = null; PoxPayloadIn itemXmlPayload = getItemXmlPayload(item); - Response res = this.createAuthorityItem(repoSession, resourceMap, uriInfo, parentIdentifier, itemXmlPayload); + switch (method) { + case POST: + response = this.createAuthorityItem(repoSession, resourceMap, uriInfo, parentIdentifier, itemXmlPayload); + if (response.getStatus() != Response.Status.CREATED.getStatusCode()) { + success = false; + errMsg = String.format("Could not create the term list payload of vocabuary '%s'.", parentIdentifier); + } + break; + case PUT: + String itemSpecifier = getSpecifier(item); + if (itemSpecifier != null) { + payloadOut = updateAuthorityItem(repoSession, resourceMap, uriInfo, parentIdentifier, itemSpecifier, itemXmlPayload); + if (payloadOut == null) { + success = false; + errMsg = String.format("Could not update the term list payload of vocabuary '%s'.", parentIdentifier); + } + } else { + success = false; + errMsg = String.format("Could not update the term list payload of vocabuary '%s' because one of the item is missing a CSID or short identifier value.", + parentIdentifier); + } + break; + } + // + // Throw an exception as soon as we have problems with any item + // + if (success == false) { + throw new DocumentException(errMsg); + } } } - } + return result; + } /** + * We'll return null if we can create a specifier from the list item. + * + * @param item + * @return + */ + private String getSpecifier(ListItem item) { + String result = null; + + String csid = null; + for (Element ele : item.getAny()) { + String fieldName = ele.getTagName(); + String fieldValue = ele.getTextContent(); + if (fieldName.equalsIgnoreCase("csid")) { + result = csid = fieldValue; + break; + } + } + + if (csid == null) { + String shortId = null; + for (Element ele : item.getAny()) { + String fieldName = ele.getTagName(); + String fieldValue = ele.getTextContent(); + if (fieldName.equalsIgnoreCase("shortIdentifier")) { + shortId = fieldValue; + break; + } + } + + if (shortId != null) { + result = Specifier.createShortIdURNValue(shortId); + } + } + + return result; + } + + /** * This is very brittle. If the class VocabularyitemsCommon changed with new fields we'd have to * update this method. * @@ -178,7 +301,12 @@ public class VocabularyResource extends case "description": vocabularyItem.setDescription(fieldValue); + break; + case "csid": + vocabularyItem.setCsid(fieldValue); + break; + default: throw new DocumentException(String.format("Unknown field '%s' in vocabulary item payload.", fieldName)); @@ -207,7 +335,26 @@ public class VocabularyResource extends return result; } - + + private PoxPayloadOut updateAuthorityItem( + CoreSessionInterface repoSession, + ResourceMap resourceMap, + UriInfo uriInfo, + String parentSpecifier, // Either a CSID or a URN form -e.g., a8ad38ec-1d7d-4bf2-bd31 or urn:cspace:name(bugsbunny) + String itemSpecifier, // Either a CSID or a URN form. + PoxPayloadIn theUpdate) throws Exception { + PoxPayloadOut result = null; + + ServiceContext ctx = createServiceContext(getItemServiceName(), theUpdate, resourceMap, uriInfo); + ctx.setCurrentRepositorySession(repoSession); + + result = updateAuthorityItem(ctx, resourceMap, uriInfo, parentSpecifier, itemSpecifier, theUpdate, + AuthorityServiceUtils.UPDATE_REV, // passing TRUE so rev num increases, passing + AuthorityServiceUtils.NO_CHANGE, // don't change the state of the "proposed" field -we could be performing a sync or just a plain update + AuthorityServiceUtils.NO_CHANGE); // don't change the state of the "sas" field -we could be performing a sync or just a plain update + + return result; + } @GET @Path("{csid}") -- 2.47.3