From 0a1ed8ed4684db03654836a766b5cecd79c99d86 Mon Sep 17 00:00:00 2001 From: remillet Date: Fri, 17 Mar 2017 12:19:22 -0700 Subject: [PATCH] CSPACE-6909: Adding botanical garden contributions from UCB for official botgarden profile. --- .../botgarden/build.properties | 1 + .../botgarden/build.xml | 104 ++ .../nuxeo-platform-listener/botgarden/pom.xml | 72 + .../botgarden/CreateVersionListener.java | 50 + .../botgarden/DeleteDeadLocationListener.java | 47 + .../botgarden/UpdateAccessCodeListener.java | 351 +++++ .../botgarden/UpdateDeadFlagListener.java | 103 ++ .../botgarden/UpdateLocationListener.java | 78 + .../botgarden/UpdateRareFlagListener.java | 201 +++ .../botgarden/UpdateStyledNameListener.java | 93 ++ .../src/main/resources/META-INF/MANIFEST.MF | 16 + .../resources/OSGI-INF/core-types-contrib.xml | 4 + .../OSGI-INF/default-life-cycle-contrib.xml | 4 + .../OSGI-INF/deployment-fragment.xml | 10 + .../resources/OSGI-INF/ecm-types-contrib.xml | 83 ++ .../resources/OSGI-INF/layouts-contrib.xml | 4 + .../nuxeo/nuxeo-platform-listener/build.xml | 6 + .../naturalhistory/build.properties | 1 + .../naturalhistory/build.xml | 104 ++ .../naturalhistory/pom.xml | 62 + .../UpdateFormattedDisplayNameListener.java | 113 ++ .../src/main/resources/META-INF/MANIFEST.MF | 16 + .../resources/OSGI-INF/core-types-contrib.xml | 4 + .../OSGI-INF/default-life-cycle-contrib.xml | 4 + .../OSGI-INF/deployment-fragment.xml | 10 + .../resources/OSGI-INF/ecm-types-contrib.xml | 5 + .../resources/OSGI-INF/layouts-contrib.xml | 4 + .../nuxeo/nuxeo-platform-listener/pom.xml | 2 + .../listener/UpdateImageDerivatives.java | 9 +- .../listener/UpdateRelationsOnDelete.java | 8 +- .../src/main/resources/schemas/core-types.xsd | 29 + services/JaxRsServiceProvider/pom.xml | 15 + .../jaxrs/CSpaceResteasyBootstrap.java | 3 +- .../CollectionSpaceJaxRsApplication.java | 8 +- services/authentication/client/pom.xml | 5 + .../test/resources/META-INF/persistence.xml | 3 +- .../common/vocabulary/AuthorityResource.java | 2 +- .../test/resources/META-INF/persistence.xml | 4 +- services/batch/build.xml | 6 +- services/batch/pom.xml | 2 +- services/batch/service/pom.xml | 52 +- .../batch/AbstractBatchInvocable.java | 46 +- .../services/batch/BatchInvocable.java | 8 +- .../services/batch/BatchResource.java | 45 +- .../batch/nuxeo/AbstractBatchJob.java | 678 +++++++++ .../nuxeo/BatchDocumentModelHandler.java | 101 +- .../ClearLocationLabelRequestBatchJob.java | 120 ++ .../ClearPotTagLabelRequestBatchJob.java | 112 ++ .../ClearVoucherLabelRequestBatchJob.java | 113 ++ .../nuxeo/CreateAndLinkLoanOutBatchJob.java | 36 +- .../batch/nuxeo/CreateVoucherBatchJob.java | 288 ++++ .../batch/nuxeo/FormatTaxonBatchJob.java | 145 ++ .../nuxeo/FormatVoucherNameBatchJob.java | 245 +++ .../nuxeo/MergeAuthorityItemsBatchJob.java | 636 ++++++++ .../batch/nuxeo/ReindexFullTextBatchJob.java | 694 +++++++++ .../batch/nuxeo/UpdateAccessCodeBatchJob.java | 473 ++++++ .../batch/nuxeo/UpdateDeadFlagBatchJob.java | 233 +++ .../batch/nuxeo/UpdateRareFlagBatchJob.java | 347 +++++ services/claim/3rdparty/build.xml | 129 ++ .../nuxeo-platform-cs-claim/build.xml | 161 ++ .../3rdparty/nuxeo-platform-cs-claim/pom.xml | 48 + .../resources/OSGI-INF/layouts-contrib.xml | 47 + .../main/resources/schemas/claims_common.xsd | 67 + services/claim/3rdparty/pom.xml | 32 + services/claim/build.xml | 124 ++ services/claim/client/pom.xml | 97 ++ .../services/client/ClaimClient.java | 55 + .../services/client/ClaimProxy.java | 72 + .../client/test/ClaimAuthRefsTest.java | 349 +++++ .../client/test/ClaimServiceTest.java | 240 +++ .../src/test/resources/log4j.properties | 23 + services/claim/jaxb/pom.xml | 35 + .../services/ClaimJAXBSchema.java | 10 + .../services/ClaimListItemJAXBSchema.java | 7 + .../jaxb/src/main/resources/claims-common.xsd | 72 + services/claim/pom.xml | 24 + services/claim/service/pom.xml | 106 ++ services/claim/service/profiles.xml | 4 + .../services/claim/ClaimResource.java | 64 + .../services/claim/nuxeo/ClaimConstants.java | 35 + .../nuxeo/ClaimDocumentModelHandler.java | 39 + .../claim/nuxeo/ClaimValidatorHandler.java | 25 + .../services/test/ClaimServiceTest.java | 10 + .../service/src/test/resources/log4j.xml | 45 + .../client/AbstractServiceClientImpl.java | 14 +- .../client/CollectionSpaceClient.java | 21 + .../services/client/IRelationsManager.java | 5 + .../test/AbstractPoxServiceTestImpl.java | 8 +- .../client/test/AbstractServiceTestImpl.java | 98 +- .../services/client/test/BaseServiceTest.java | 8 + .../test/CollectionObjectAuthRefsTest.java | 61 +- .../CollectionObjectBotGardenConstants.java | 66 + .../nuxeo/CollectionObjectConstants.java | 4 + services/common-api/pom.xml | 21 +- .../services/common/api/TaxonFormatter.java | 153 ++ .../AbstractCollectionSpaceResourceImpl.java | 3 +- .../services/common/ResourceMapHolder.java | 5 +- .../services/common/invocable/Invocable.java | 7 +- .../relation/nuxeo/RelationConstants.java | 20 + .../nuxeo/util/ReindexFulltextRoot.java | 3 +- .../db/postgresql/load_id_generators.sql | 45 + .../AuthorityResourceWithContacts.java | 10 +- .../client/test/IntakeAuthRefsTest.java | 35 +- .../client/test/IntakeServiceTest.java | 247 +--- services/loanout/service/pom.xml | 6 +- .../nuxeo/LoanoutBotGardenConstants.java | 17 + .../nuxeo/MovementBotGardenConstants.java | 29 + .../movement/nuxeo/MovementConstants.java | 17 + .../place/nuxeo/PlaceBotGardenConstants.java | 5 + .../services/place/nuxeo/PlaceConstants.java | 12 + services/pom.xml | 6 +- .../org.eclipse.jdt.core.javabuilder.launch | 7 + ...org.maven.ide.eclipse.maven2Builder.launch | 7 + services/pottag/3rdparty/build.xml | 131 ++ .../nuxeo-platform-cs-pottag/build.xml | 161 ++ .../3rdparty/nuxeo-platform-cs-pottag/pom.xml | 56 + .../resources/OSGI-INF/layouts-contrib.xml | 118 ++ .../main/resources/schemas/pottags_common.xsd | 31 + services/pottag/3rdparty/pom.xml | 32 + services/pottag/build.xml | 124 ++ .../services/client/PottagClient.class | Bin 0 -> 2055 bytes .../services/client/PottagProxy.class | Bin 0 -> 233 bytes .../client/test/PottagAuthRefsTest.class | Bin 0 -> 11791 bytes .../client/test/PottagServiceTest.class | Bin 0 -> 13795 bytes services/pottag/client/pom.xml | 94 ++ .../services/client/PottagClient.java | 58 + .../services/client/PottagProxy.java | 34 + .../client/test/PottagAuthRefsTest.java | 328 ++++ .../client/test/PottagServiceTest.java | 177 +++ .../src/test/resources/log4j.properties | 23 + .../services/PottagJAXBSchema.class | Bin 0 -> 442 bytes .../services/PottagListItemJAXBSchema.class | Bin 0 -> 399 bytes services/pottag/jaxb/pom.xml | 35 + .../services/PottagJAXBSchema.java | 15 + .../services/PottagListItemJAXBSchema.java | 9 + .../src/main/resources/pottags-common.xsd | 49 + services/pottag/pom.xml | 26 + services/pottag/service/pom.xml | 106 ++ services/pottag/service/profiles.xml | 4 + .../services/pottag/PottagResource.java | 63 + .../pottag/nuxeo/PottagConstants.java | 42 + .../nuxeo/PottagDocumentModelHandler.java | 36 + .../pottag/nuxeo/PottagValidatorHandler.java | 18 + .../services/test/PottagServiceTest.java | 13 + .../service/src/test/resources/log4j.xml | 45 + services/propagation/3rdparty/build.xml | 131 ++ .../nuxeo-platform-cs-propagation/build.xml | 161 ++ .../nuxeo-platform-cs-propagation/pom.xml | 56 + .../resources/OSGI-INF/layouts-contrib.xml | 117 ++ .../resources/schemas/propagations_common.xsd | 113 ++ services/propagation/3rdparty/pom.xml | 32 + services/propagation/build.xml | 124 ++ services/propagation/client/.gitignore | 1 + services/propagation/client/pom.xml | 92 ++ .../services/client/PropagationClient.java | 56 + .../services/client/PropagationProxy.java | 34 + .../client/test/PropagationAuthRefsTest.java | 353 +++++ .../client/test/PropagationServiceTest.java | 262 ++++ .../src/test/resources/log4j.properties | 23 + .../jaxb/bin/META-INF/sun-jaxb.episode | 34 + .../services/PropagationJAXBSchema.class | Bin 0 -> 429 bytes .../PropagationListItemJAXBSchema.class | Bin 0 -> 441 bytes .../services/propagation/ObjectFactory.class | Bin 0 -> 1791 bytes .../propagation/PropActivityGroup.class | Bin 0 -> 7148 bytes .../propagation/PropActivityGroupList.class | Bin 0 -> 3282 bytes .../propagation/PropagationsCommon.class | Bin 0 -> 10621 bytes .../services/propagation/ScarStratGroup.class | Bin 0 -> 4091 bytes .../propagation/ScarStratGroupList.class | Bin 0 -> 3258 bytes .../propagation/StructuredDateGroup.class | Bin 0 -> 13057 bytes .../services/propagation/package-info.class | Bin 0 -> 293 bytes services/propagation/jaxb/pom.xml | 34 + .../services/PropagationJAXBSchema.java | 15 + .../PropagationListItemJAXBSchema.java | 9 + .../main/resources/propagations-common.xsd | 129 ++ services/propagation/pom.xml | 27 + services/propagation/service/.gitignore | 1 + services/propagation/service/pom.xml | 106 ++ services/propagation/service/profiles.xml | 4 + .../propagation/PropagationResource.java | 63 + .../nuxeo/PropagationConstants.java | 35 + .../PropagationDocumentModelHandler.java | 36 + .../nuxeo/PropagationValidatorHandler.java | 18 + .../services/test/PropagationServiceTest.java | 13 + .../service/src/test/resources/log4j.xml | 45 + .../nuxeo/ReportDocumentModelHandler.java | 17 +- services/security/client/pom.xml | 5 + .../test/resources/META-INF/persistence.xml | 3 +- services/structureddate/README.txt | 7 + services/structureddate/pom.xml | 121 ++ .../structureddate/antlr/StructuredDate.g4 | 164 ++ .../services/structureddate/Certainty.java | 20 + .../services/structureddate/Date.java | 181 +++ .../services/structureddate/DateUtils.java | 1315 +++++++++++++++++ .../structureddate/DeferredCenturyDate.java | 12 + .../DeferredCenturyEndDate.java | 30 + .../DeferredCenturyStartDate.java | 30 + .../services/structureddate/DeferredDate.java | 16 + .../structureddate/DeferredDecadeDate.java | 12 + .../structureddate/DeferredDecadeEndDate.java | 30 + .../DeferredDecadeStartDate.java | 30 + .../DeferredHalfCenturyDate.java | 18 + .../DeferredHalfCenturyEndDate.java | 30 + .../DeferredHalfCenturyStartDate.java | 30 + .../DeferredMillenniumDate.java | 12 + .../DeferredMillenniumEndDate.java | 29 + .../DeferredMillenniumStartDate.java | 29 + .../structureddate/DeferredMonthEndDate.java | 22 + .../DeferredPartialCenturyDate.java | 18 + .../DeferredPartialCenturyEndDate.java | 30 + .../DeferredPartialCenturyStartDate.java | 29 + .../DeferredPartialDecadeDate.java | 18 + .../DeferredPartialDecadeEndDate.java | 29 + .../DeferredPartialDecadeStartDate.java | 29 + .../DeferredQuarterCenturyDate.java | 18 + .../DeferredQuarterCenturyEndDate.java | 30 + .../DeferredQuarterCenturyStartDate.java | 30 + .../services/structureddate/Era.java | 22 + .../structureddate/InvalidDateException.java | 14 + .../services/structureddate/ParseDates.java | 112 ++ .../services/structureddate/Part.java | 7 + .../structureddate/QualifierType.java | 17 + .../structureddate/QualifierUnit.java | 17 + .../structureddate/StructuredDate.java | 241 +++ .../StructuredDateEvaluator.java | 5 + .../StructuredDateFormatException.java | 21 + .../antlr/ANTLRStructuredDateEvaluator.java | 1116 ++++++++++++++ .../StructuredDateEvaluatorTest.java | 118 ++ .../structureddate/StructuredDateTest.java | 20 + .../src/test/resources/log4j.properties | 21 + .../src/test/resources/test-dates.yaml | 1151 +++++++++++++++ .../nuxeo/TaxonBotGardenConstants.java | 18 + .../taxonomy/nuxeo/TaxonConstants.java | 21 + 232 files changed, 17681 insertions(+), 464 deletions(-) create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/botgarden/build.properties create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/botgarden/build.xml create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/botgarden/pom.xml create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/CreateVersionListener.java create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/DeleteDeadLocationListener.java create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateAccessCodeListener.java create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateDeadFlagListener.java create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateLocationListener.java create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateRareFlagListener.java create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateStyledNameListener.java create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/META-INF/MANIFEST.MF create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/core-types-contrib.xml create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/default-life-cycle-contrib.xml create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/deployment-fragment.xml create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/ecm-types-contrib.xml create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/layouts-contrib.xml create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/build.properties create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/build.xml create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/pom.xml create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/java/org/collectionspace/services/listener/ucb/UpdateFormattedDisplayNameListener.java create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/META-INF/MANIFEST.MF create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/core-types-contrib.xml create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/default-life-cycle-contrib.xml create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/deployment-fragment.xml create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/ecm-types-contrib.xml create mode 100644 3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/layouts-contrib.xml create mode 100644 3rdparty/nuxeo/nuxeo-platform-thumbnail/src/main/resources/schemas/core-types.xsd create mode 100644 services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/AbstractBatchJob.java create mode 100644 services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/ClearLocationLabelRequestBatchJob.java create mode 100644 services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/ClearPotTagLabelRequestBatchJob.java create mode 100644 services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/ClearVoucherLabelRequestBatchJob.java create mode 100644 services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/CreateVoucherBatchJob.java create mode 100644 services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/FormatTaxonBatchJob.java create mode 100644 services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/FormatVoucherNameBatchJob.java create mode 100644 services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/MergeAuthorityItemsBatchJob.java create mode 100644 services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/ReindexFullTextBatchJob.java create mode 100644 services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/UpdateAccessCodeBatchJob.java create mode 100644 services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/UpdateDeadFlagBatchJob.java create mode 100644 services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/UpdateRareFlagBatchJob.java create mode 100644 services/claim/3rdparty/build.xml create mode 100644 services/claim/3rdparty/nuxeo-platform-cs-claim/build.xml create mode 100644 services/claim/3rdparty/nuxeo-platform-cs-claim/pom.xml create mode 100644 services/claim/3rdparty/nuxeo-platform-cs-claim/src/main/resources/OSGI-INF/layouts-contrib.xml create mode 100644 services/claim/3rdparty/nuxeo-platform-cs-claim/src/main/resources/schemas/claims_common.xsd create mode 100644 services/claim/3rdparty/pom.xml create mode 100644 services/claim/build.xml create mode 100644 services/claim/client/pom.xml create mode 100644 services/claim/client/src/main/java/org/collectionspace/services/client/ClaimClient.java create mode 100644 services/claim/client/src/main/java/org/collectionspace/services/client/ClaimProxy.java create mode 100644 services/claim/client/src/test/java/org/collectionspace/services/client/test/ClaimAuthRefsTest.java create mode 100644 services/claim/client/src/test/java/org/collectionspace/services/client/test/ClaimServiceTest.java create mode 100644 services/claim/client/src/test/resources/log4j.properties create mode 100644 services/claim/jaxb/pom.xml create mode 100644 services/claim/jaxb/src/main/java/org/collectionspace/services/ClaimJAXBSchema.java create mode 100644 services/claim/jaxb/src/main/java/org/collectionspace/services/ClaimListItemJAXBSchema.java create mode 100644 services/claim/jaxb/src/main/resources/claims-common.xsd create mode 100644 services/claim/pom.xml create mode 100644 services/claim/service/pom.xml create mode 100644 services/claim/service/profiles.xml create mode 100644 services/claim/service/src/main/java/org/collectionspace/services/claim/ClaimResource.java create mode 100644 services/claim/service/src/main/java/org/collectionspace/services/claim/nuxeo/ClaimConstants.java create mode 100644 services/claim/service/src/main/java/org/collectionspace/services/claim/nuxeo/ClaimDocumentModelHandler.java create mode 100644 services/claim/service/src/main/java/org/collectionspace/services/claim/nuxeo/ClaimValidatorHandler.java create mode 100644 services/claim/service/src/test/java/org/collectionspace/services/test/ClaimServiceTest.java create mode 100644 services/claim/service/src/test/resources/log4j.xml create mode 100644 services/collectionobject/service/src/main/java/org/collectionspace/services/collectionobject/nuxeo/CollectionObjectBotGardenConstants.java create mode 100644 services/common-api/src/main/java/org/collectionspace/services/common/api/TaxonFormatter.java create mode 100644 services/loanout/service/src/main/java/org/collectionspace/services/loanout/nuxeo/LoanoutBotGardenConstants.java create mode 100644 services/movement/service/src/main/java/org/collectionspace/services/movement/nuxeo/MovementBotGardenConstants.java create mode 100644 services/place/service/src/main/java/org/collectionspace/services/place/nuxeo/PlaceBotGardenConstants.java create mode 100644 services/pottag/3rdparty/.externalToolBuilders/org.eclipse.jdt.core.javabuilder.launch create mode 100644 services/pottag/3rdparty/.externalToolBuilders/org.maven.ide.eclipse.maven2Builder.launch create mode 100644 services/pottag/3rdparty/build.xml create mode 100644 services/pottag/3rdparty/nuxeo-platform-cs-pottag/build.xml create mode 100644 services/pottag/3rdparty/nuxeo-platform-cs-pottag/pom.xml create mode 100644 services/pottag/3rdparty/nuxeo-platform-cs-pottag/src/main/resources/OSGI-INF/layouts-contrib.xml create mode 100644 services/pottag/3rdparty/nuxeo-platform-cs-pottag/src/main/resources/schemas/pottags_common.xsd create mode 100644 services/pottag/3rdparty/pom.xml create mode 100644 services/pottag/build.xml create mode 100644 services/pottag/client/bin/org/collectionspace/services/client/PottagClient.class create mode 100644 services/pottag/client/bin/org/collectionspace/services/client/PottagProxy.class create mode 100644 services/pottag/client/bin/org/collectionspace/services/client/test/PottagAuthRefsTest.class create mode 100644 services/pottag/client/bin/org/collectionspace/services/client/test/PottagServiceTest.class create mode 100644 services/pottag/client/pom.xml create mode 100644 services/pottag/client/src/main/java/org/collectionspace/services/client/PottagClient.java create mode 100644 services/pottag/client/src/main/java/org/collectionspace/services/client/PottagProxy.java create mode 100644 services/pottag/client/src/test/java/org/collectionspace/services/client/test/PottagAuthRefsTest.java create mode 100644 services/pottag/client/src/test/java/org/collectionspace/services/client/test/PottagServiceTest.java create mode 100644 services/pottag/client/src/test/resources/log4j.properties create mode 100644 services/pottag/jaxb/bin/org/collectionspace/services/PottagJAXBSchema.class create mode 100644 services/pottag/jaxb/bin/org/collectionspace/services/PottagListItemJAXBSchema.class create mode 100644 services/pottag/jaxb/pom.xml create mode 100644 services/pottag/jaxb/src/main/java/org/collectionspace/services/PottagJAXBSchema.java create mode 100644 services/pottag/jaxb/src/main/java/org/collectionspace/services/PottagListItemJAXBSchema.java create mode 100644 services/pottag/jaxb/src/main/resources/pottags-common.xsd create mode 100644 services/pottag/pom.xml create mode 100644 services/pottag/service/pom.xml create mode 100644 services/pottag/service/profiles.xml create mode 100644 services/pottag/service/src/main/java/org/collectionspace/services/pottag/PottagResource.java create mode 100644 services/pottag/service/src/main/java/org/collectionspace/services/pottag/nuxeo/PottagConstants.java create mode 100644 services/pottag/service/src/main/java/org/collectionspace/services/pottag/nuxeo/PottagDocumentModelHandler.java create mode 100644 services/pottag/service/src/main/java/org/collectionspace/services/pottag/nuxeo/PottagValidatorHandler.java create mode 100644 services/pottag/service/src/test/java/org/collectionspace/services/test/PottagServiceTest.java create mode 100644 services/pottag/service/src/test/resources/log4j.xml create mode 100644 services/propagation/3rdparty/build.xml create mode 100644 services/propagation/3rdparty/nuxeo-platform-cs-propagation/build.xml create mode 100644 services/propagation/3rdparty/nuxeo-platform-cs-propagation/pom.xml create mode 100644 services/propagation/3rdparty/nuxeo-platform-cs-propagation/src/main/resources/OSGI-INF/layouts-contrib.xml create mode 100644 services/propagation/3rdparty/nuxeo-platform-cs-propagation/src/main/resources/schemas/propagations_common.xsd create mode 100644 services/propagation/3rdparty/pom.xml create mode 100644 services/propagation/build.xml create mode 100644 services/propagation/client/.gitignore create mode 100644 services/propagation/client/pom.xml create mode 100644 services/propagation/client/src/main/java/org/collectionspace/services/client/PropagationClient.java create mode 100644 services/propagation/client/src/main/java/org/collectionspace/services/client/PropagationProxy.java create mode 100644 services/propagation/client/src/test/java/org/collectionspace/services/client/test/PropagationAuthRefsTest.java create mode 100644 services/propagation/client/src/test/java/org/collectionspace/services/client/test/PropagationServiceTest.java create mode 100644 services/propagation/client/src/test/resources/log4j.properties create mode 100644 services/propagation/jaxb/bin/META-INF/sun-jaxb.episode create mode 100644 services/propagation/jaxb/bin/org/collectionspace/services/PropagationJAXBSchema.class create mode 100644 services/propagation/jaxb/bin/org/collectionspace/services/PropagationListItemJAXBSchema.class create mode 100644 services/propagation/jaxb/bin/org/collectionspace/services/propagation/ObjectFactory.class create mode 100644 services/propagation/jaxb/bin/org/collectionspace/services/propagation/PropActivityGroup.class create mode 100644 services/propagation/jaxb/bin/org/collectionspace/services/propagation/PropActivityGroupList.class create mode 100644 services/propagation/jaxb/bin/org/collectionspace/services/propagation/PropagationsCommon.class create mode 100644 services/propagation/jaxb/bin/org/collectionspace/services/propagation/ScarStratGroup.class create mode 100644 services/propagation/jaxb/bin/org/collectionspace/services/propagation/ScarStratGroupList.class create mode 100644 services/propagation/jaxb/bin/org/collectionspace/services/propagation/StructuredDateGroup.class create mode 100644 services/propagation/jaxb/bin/org/collectionspace/services/propagation/package-info.class create mode 100644 services/propagation/jaxb/pom.xml create mode 100644 services/propagation/jaxb/src/main/java/org/collectionspace/services/PropagationJAXBSchema.java create mode 100644 services/propagation/jaxb/src/main/java/org/collectionspace/services/PropagationListItemJAXBSchema.java create mode 100644 services/propagation/jaxb/src/main/resources/propagations-common.xsd create mode 100644 services/propagation/pom.xml create mode 100644 services/propagation/service/.gitignore create mode 100644 services/propagation/service/pom.xml create mode 100644 services/propagation/service/profiles.xml create mode 100644 services/propagation/service/src/main/java/org/collectionspace/services/propagation/PropagationResource.java create mode 100644 services/propagation/service/src/main/java/org/collectionspace/services/propagation/nuxeo/PropagationConstants.java create mode 100644 services/propagation/service/src/main/java/org/collectionspace/services/propagation/nuxeo/PropagationDocumentModelHandler.java create mode 100644 services/propagation/service/src/main/java/org/collectionspace/services/propagation/nuxeo/PropagationValidatorHandler.java create mode 100644 services/propagation/service/src/test/java/org/collectionspace/services/test/PropagationServiceTest.java create mode 100644 services/propagation/service/src/test/resources/log4j.xml create mode 100644 services/structureddate/README.txt create mode 100644 services/structureddate/pom.xml create mode 100644 services/structureddate/src/main/antlr4/org/collectionspace/services/structureddate/antlr/StructuredDate.g4 create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/Certainty.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/Date.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DateUtils.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredCenturyDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredCenturyEndDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredCenturyStartDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredDecadeDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredDecadeEndDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredDecadeStartDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredHalfCenturyDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredHalfCenturyEndDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredHalfCenturyStartDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredMillenniumDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredMillenniumEndDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredMillenniumStartDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredMonthEndDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialCenturyDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialCenturyEndDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialCenturyStartDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialDecadeDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialDecadeEndDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialDecadeStartDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredQuarterCenturyDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredQuarterCenturyEndDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredQuarterCenturyStartDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/Era.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/InvalidDateException.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/ParseDates.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/Part.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/QualifierType.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/QualifierUnit.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/StructuredDate.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/StructuredDateEvaluator.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/StructuredDateFormatException.java create mode 100644 services/structureddate/src/main/java/org/collectionspace/services/structureddate/antlr/ANTLRStructuredDateEvaluator.java create mode 100644 services/structureddate/src/test/java/org/collectionspace/services/structureddate/StructuredDateEvaluatorTest.java create mode 100644 services/structureddate/src/test/java/org/collectionspace/services/structureddate/StructuredDateTest.java create mode 100644 services/structureddate/src/test/resources/log4j.properties create mode 100644 services/structureddate/src/test/resources/test-dates.yaml create mode 100644 services/taxonomy/service/src/main/java/org/collectionspace/services/taxonomy/nuxeo/TaxonBotGardenConstants.java diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/build.properties b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/build.properties new file mode 100644 index 000000000..20f34ad3e --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/build.properties @@ -0,0 +1 @@ +listener.module.name=botgarden \ No newline at end of file diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/build.xml b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/build.xml new file mode 100644 index 000000000..cc31cc061 --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/build.xml @@ -0,0 +1,104 @@ + + + CollectionSpace Nuxeo listener component type + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/pom.xml b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/pom.xml new file mode 100644 index 000000000..8dbe4737b --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + org.collectionspace.services.3rdparty.nuxeo.listener + org.collectionspace.services + 4.5-SNAPSHOT + + + UTF-8 + + org.collectionspace.services.listener.botgarden + org.collectionspace.services.listener.botgarden + http://maven.apache.org + + + + + org.nuxeo.ecm.core + nuxeo-core-storage-sql + ${nuxeo.core.version} + + + org.nuxeo.ecm.platform + nuxeo-platform-filemanager-api + ${nuxeo.platform.version} + + + org.collectionspace.services + org.collectionspace.services.collectionobject.service + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.movement.service + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.batch.service + ${project.version} + + + + + + + + src/main/resources + true + + + + + org.apache.maven.plugins + maven-jar-plugin + + + src/main/resources/META-INF/MANIFEST.MF + + ${eclipseVersion} + 2 + + + + + + + + diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/CreateVersionListener.java b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/CreateVersionListener.java new file mode 100644 index 000000000..b380ae4e2 --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/CreateVersionListener.java @@ -0,0 +1,50 @@ +package org.collectionspace.services.listener.botgarden; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.collectionspace.services.client.workflow.WorkflowClient; +import org.collectionspace.services.movement.nuxeo.MovementConstants; +import org.collectionspace.services.nuxeo.listener.AbstractCSEventListenerImpl; +import org.nuxeo.ecm.core.api.DocumentModel; +import org.nuxeo.ecm.core.api.DocumentRef; +import org.nuxeo.ecm.core.api.VersioningOption; +import org.nuxeo.ecm.core.event.Event; +import org.nuxeo.ecm.core.event.EventContext; +import org.nuxeo.ecm.core.event.impl.DocumentEventContext; + +public class CreateVersionListener extends AbstractCSEventListenerImpl { + public static final String SKIP_PROPERTY = "CreateVersionListener.SKIP"; + + final Log logger = LogFactory.getLog(CreateVersionListener.class); + + public void handleEvent(Event event) { + EventContext ec = event.getContext(); + + if (ec instanceof DocumentEventContext) { + DocumentEventContext context = (DocumentEventContext) ec; + + if (ec.hasProperty(SKIP_PROPERTY) && ((Boolean) ec.getProperty(SKIP_PROPERTY))) { + logger.debug("Skipping create version"); + } + else { + DocumentModel doc = context.getSourceDocument(); + + logger.debug("docType=" + doc.getType()); + + if (doc.getType().startsWith(MovementConstants.NUXEO_DOCTYPE) && + !doc.isVersion() && + !doc.isProxy() && + !doc.getCurrentLifeCycleState().equals(WorkflowClient.WORKFLOWSTATE_DELETED)) { + // Version the document + DocumentRef versionRef = doc.checkIn(VersioningOption.MINOR, null); + DocumentModel versionDoc = context.getCoreSession().getDocument(versionRef); + + logger.debug("created version: id=" + versionDoc.getId() + " csid=" + versionDoc.getName()); + + // Check out the document, so it can be modified + doc.checkOut(); + } + } + } + } +} \ No newline at end of file diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/DeleteDeadLocationListener.java b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/DeleteDeadLocationListener.java new file mode 100644 index 000000000..e8d14949b --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/DeleteDeadLocationListener.java @@ -0,0 +1,47 @@ +package org.collectionspace.services.listener.botgarden; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.collectionspace.services.client.workflow.WorkflowClient; +import org.collectionspace.services.movement.nuxeo.MovementBotGardenConstants; +import org.collectionspace.services.movement.nuxeo.MovementConstants; +import org.collectionspace.services.nuxeo.listener.AbstractCSEventListenerImpl; +import org.nuxeo.ecm.core.api.CoreSession; +import org.nuxeo.ecm.core.api.DocumentModel; +import org.nuxeo.ecm.core.event.Event; +import org.nuxeo.ecm.core.event.EventContext; +import org.nuxeo.ecm.core.event.impl.DocumentEventContext; + +public class DeleteDeadLocationListener extends AbstractCSEventListenerImpl { + final Log logger = LogFactory.getLog(DeleteDeadLocationListener.class); + + /* + * Delete dead locations. + */ + public void handleEvent(Event event) { + EventContext ec = event.getContext(); + + if (ec instanceof DocumentEventContext) { + DocumentEventContext context = (DocumentEventContext) ec; + DocumentModel doc = context.getSourceDocument(); + + if (doc.getType().startsWith(MovementConstants.NUXEO_DOCTYPE) && + !doc.isVersion() && + !doc.isProxy() && + !doc.getCurrentLifeCycleState().equals(WorkflowClient.WORKFLOWSTATE_DELETED)) { + String actionCode = (String) doc.getProperty(MovementBotGardenConstants.ACTION_CODE_SCHEMA_NAME, + MovementBotGardenConstants.ACTION_CODE_FIELD_NAME); + + logger.debug("actionCode=" + actionCode); + + if (actionCode != null && actionCode.equals(MovementBotGardenConstants.DEAD_ACTION_CODE)) { + CoreSession session = context.getCoreSession(); + + if (session.getAllowedStateTransitions(doc.getRef()).contains(WorkflowClient.WORKFLOWTRANSITION_DELETE)) { + session.followTransition(doc.getRef(), WorkflowClient.WORKFLOWTRANSITION_DELETE); + } + } + } + } + } +} \ No newline at end of file diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateAccessCodeListener.java b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateAccessCodeListener.java new file mode 100644 index 000000000..36beffc0a --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateAccessCodeListener.java @@ -0,0 +1,351 @@ +package org.collectionspace.services.listener.botgarden; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.collectionspace.services.batch.nuxeo.UpdateAccessCodeBatchJob; +import org.collectionspace.services.client.workflow.WorkflowClient; +import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectBotGardenConstants; +import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectConstants; +import org.collectionspace.services.common.ResourceMap; +import org.collectionspace.services.common.invocable.InvocationResults; +import org.collectionspace.services.common.relation.nuxeo.RelationConstants; +import org.collectionspace.services.nuxeo.listener.AbstractCSEventListenerImpl; +import org.collectionspace.services.taxonomy.nuxeo.TaxonBotGardenConstants; +import org.collectionspace.services.taxonomy.nuxeo.TaxonConstants; +import org.jboss.resteasy.spi.ResteasyProviderFactory; +import org.nuxeo.ecm.core.api.DocumentModel; +import org.nuxeo.ecm.core.api.event.CoreEventConstants; +import org.nuxeo.ecm.core.api.event.DocumentEventTypes; +import org.nuxeo.ecm.core.event.Event; +import org.nuxeo.ecm.core.event.EventContext; +import org.nuxeo.ecm.core.event.impl.DocumentEventContext; + +/** + * A listener that updates the access code on taxon records when collectionobjects + * or taxon records are created or modified. + * + * @see org.collectionspace.services.batch.nuxeo.UpdateAccessCodeBatchJob + * @author ray + * + */ +public class UpdateAccessCodeListener extends AbstractCSEventListenerImpl { + final Log logger = LogFactory.getLog(UpdateAccessCodeListener.class); + + public static final String PREVIOUS_DEAD_FLAG_PROPERTY_NAME = "UpdateAccessCodeListener.previousDeadFlag"; + public static final String PREVIOUS_TAXON_NAMES_PROPERTY_NAME = "UpdateAccessCodeListener.previousTaxonNames"; + public static final String PREVIOUS_ACCESS_CODE_PROPERTY_NAME = "UpdateAccessCodeListener.previousAccessCode"; + public static final String DELETED_RELATION_PARENT_CSID_PROPERTY_NAME = "UpdateAccessCodeListener.deletedRelationParentCsid"; + + private static final String[] TAXON_PATH_ELEMENTS = CollectionObjectBotGardenConstants.TAXON_FIELD_NAME.split("/"); + private static final String TAXONOMIC_IDENT_GROUP_LIST_FIELD_NAME = TAXON_PATH_ELEMENTS[0]; + private static final String TAXON_FIELD_NAME = TAXON_PATH_ELEMENTS[2]; + + public void handleEvent(Event event) { + EventContext ec = event.getContext(); + + if (ec instanceof DocumentEventContext) { + DocumentEventContext context = (DocumentEventContext) ec; + DocumentModel doc = context.getSourceDocument(); + + logger.debug("docType=" + doc.getType()); + + if (doc.getType().startsWith(CollectionObjectConstants.NUXEO_DOCTYPE) && + !doc.isVersion() && + !doc.isProxy() && + !doc.getCurrentLifeCycleState().equals(WorkflowClient.WORKFLOWSTATE_DELETED)) { + + if (event.getName().equals(DocumentEventTypes.BEFORE_DOC_UPDATE)) { + // Stash the previous dead flag and taxonomic ident values, so they can be retrieved in the documentModified handler. + + DocumentModel previousDoc = (DocumentModel) context.getProperty(CoreEventConstants.PREVIOUS_DOCUMENT_MODEL); + + String previousDeadFlag = (String) previousDoc.getProperty(CollectionObjectBotGardenConstants.DEAD_FLAG_SCHEMA_NAME, + CollectionObjectBotGardenConstants.DEAD_FLAG_FIELD_NAME); + context.setProperty(PREVIOUS_DEAD_FLAG_PROPERTY_NAME, previousDeadFlag); + + List previousTaxonNames = getTaxonNames(previousDoc); + context.setProperty(PREVIOUS_TAXON_NAMES_PROPERTY_NAME, previousTaxonNames.toArray(new String[previousTaxonNames.size()])); + } + else { + boolean deadFlagChanged = false; + Set deletedTaxonNames = null; + Set addedTaxonNames = null; + + if (event.getName().equals(DocumentEventTypes.DOCUMENT_UPDATED)) { + // As an optimization, check if the dead flag of the collectionobject has + // changed, or if the taxonomic identification has changed. If so, we need to + // update the access codes of referenced taxon records. + + String previousDeadFlag = (String) context.getProperty(PREVIOUS_DEAD_FLAG_PROPERTY_NAME); + String currentDeadFlag = (String) doc.getProperty(CollectionObjectBotGardenConstants.DEAD_FLAG_SCHEMA_NAME, + CollectionObjectBotGardenConstants.DEAD_FLAG_FIELD_NAME); + + if (previousDeadFlag == null) { + previousDeadFlag = ""; + } + + if (currentDeadFlag == null) { + currentDeadFlag = ""; + } + + if (previousDeadFlag.equals(currentDeadFlag)) { + logger.debug("dead flag not changed: previousDeadFlag=" + previousDeadFlag + " currentDeadFlag=" + currentDeadFlag); + } + else { + logger.debug("dead flag changed: previousDeadFlag=" + previousDeadFlag + " currentDeadFlag=" + currentDeadFlag); + deadFlagChanged = true; + } + + List previousTaxonNames = Arrays.asList((String[]) context.getProperty(PREVIOUS_TAXON_NAMES_PROPERTY_NAME)); + List currentTaxonNames = getTaxonNames(doc); + + deletedTaxonNames = findDeletedTaxonNames(previousTaxonNames, currentTaxonNames); + logger.debug("found deleted taxon names: " + StringUtils.join(deletedTaxonNames, ", ")); + + addedTaxonNames = findAddedTaxonNames(previousTaxonNames, currentTaxonNames); + logger.debug("found added taxon names: " + StringUtils.join(addedTaxonNames, ", ")); + } + else if (event.getName().equals(DocumentEventTypes.DOCUMENT_CREATED)) { + deadFlagChanged = true; + } + + UpdateAccessCodeBatchJob updater = createUpdater(); + + if (deadFlagChanged) { + String collectionObjectCsid = doc.getName(); + + try { + // Pass false for the second parameter to updateReferencedAccessCodes, so that it doesn't + // propagate changes up the taxon hierarchy. Propagation is taken care of by this + // event handler: As taxon records are modified, this handler executes, and updates the + // parent. + + InvocationResults results = updater.updateReferencedAccessCodes(collectionObjectCsid, false); + + logger.debug("updateReferencedAccessCodes complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote()); + } + catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + else { + // If the dead flag didn't change, we still need to recalculate the access codes of + // any taxonomic idents that were added. + + if (addedTaxonNames != null) { + for (String addedTaxonName : addedTaxonNames) { + logger.debug("updating added taxon: " + addedTaxonName); + + try { + InvocationResults results = updater.updateAccessCode(addedTaxonName, false); + + logger.debug("updateAccessCode complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote()); + } + catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + } + } + + if (deletedTaxonNames != null) { + // If any taxonomic idents were removed from the collectionobject, they need to have their + // access codes recalculated. + + for (String deletedTaxonName : deletedTaxonNames) { + logger.debug("updating deleted taxon: " + deletedTaxonName); + + try { + InvocationResults results = updater.updateAccessCode(deletedTaxonName, false); + + logger.debug("updateAccessCode complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote()); + } + catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + } + } + } + else if (doc.getType().startsWith(TaxonConstants.NUXEO_DOCTYPE) && + !doc.isVersion() && + !doc.isProxy() && + !doc.getCurrentLifeCycleState().equals(WorkflowClient.WORKFLOWSTATE_DELETED)) { + + if (event.getName().equals(DocumentEventTypes.BEFORE_DOC_UPDATE)) { + // Stash the previous access code value, so it can be retrieved in the documentModified handler. + + DocumentModel previousDoc = (DocumentModel) context.getProperty(CoreEventConstants.PREVIOUS_DOCUMENT_MODEL); + String previousAccessCode = (String) previousDoc.getProperty(TaxonBotGardenConstants.ACCESS_CODE_SCHEMA_NAME, TaxonBotGardenConstants.ACCESS_CODE_FIELD_NAME); + + context.setProperty(PREVIOUS_ACCESS_CODE_PROPERTY_NAME, previousAccessCode); + } + else { + boolean updateRequired = false; + + if (event.getName().equals(DocumentEventTypes.DOCUMENT_UPDATED)) { + // As an optimization, check if the access code of the taxon has + // changed. We only need to update the access code of the parent taxon + // record if it has. + + String previousAccessCode = (String) context.getProperty(PREVIOUS_ACCESS_CODE_PROPERTY_NAME); + String currentAccessCode = (String) doc.getProperty(TaxonBotGardenConstants.ACCESS_CODE_SCHEMA_NAME, TaxonBotGardenConstants.ACCESS_CODE_FIELD_NAME); + + if (previousAccessCode == null) { + previousAccessCode = ""; + } + + if (currentAccessCode == null) { + currentAccessCode = ""; + } + + if (previousAccessCode.equals(currentAccessCode)) { + logger.debug("update not required: previousAccessCode=" + previousAccessCode + " currentAccessCode=" + currentAccessCode); + } + else { + logger.debug("update required: previousAccessCode=" + previousAccessCode + " currentAccessCode=" + currentAccessCode); + updateRequired = true; + } + } + else if (event.getName().equals(DocumentEventTypes.DOCUMENT_CREATED)) { + updateRequired = true; + } + + if (updateRequired) { + String taxonCsid = doc.getName(); + + try { + // Pass false for the second parameter to updateReferencedAccessCodes, so that it doesn't + // propagate changes up the taxon hierarchy. Propagation is taken care of by this + // event handler: As taxon records are modified, this handler executes, and updates the + // parent. + + InvocationResults results = createUpdater().updateParentAccessCode(taxonCsid, false); + + logger.debug("updateParentAccessCode complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote()); + } + catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + } + } + else if (doc.getType().equals(RelationConstants.NUXEO_DOCTYPE) && + !doc.isVersion() && + !doc.isProxy()) { + + if (event.getName().equals(DocumentEventTypes.DOCUMENT_CREATED)) { + String subjectDocType = (String) doc.getProperty(RelationConstants.SUBJECT_DOCTYPE_SCHEMA_NAME, RelationConstants.SUBJECT_DOCTYPE_FIELD_NAME); + String objectDocType = (String) doc.getProperty(RelationConstants.OBJECT_DOCTYPE_SCHEMA_NAME, RelationConstants.OBJECT_DOCTYPE_FIELD_NAME);; + String relationType = (String) doc.getProperty(RelationConstants.TYPE_SCHEMA_NAME, RelationConstants.TYPE_FIELD_NAME); + + logger.debug("subjectDocType=" + subjectDocType + " objectDocType=" + objectDocType + " relationType=" + relationType); + + if (subjectDocType.equals(TaxonConstants.NUXEO_DOCTYPE) && objectDocType.equals(TaxonConstants.NUXEO_DOCTYPE) && relationType.equals(RelationConstants.BROADER_TYPE)) { + String parentTaxonCsid = (String) doc.getProperty(RelationConstants.OBJECT_CSID_SCHEMA_NAME, RelationConstants.OBJECT_CSID_FIELD_NAME); + logger.debug("child added, updating parent taxon: parentTaxonCsid=" + parentTaxonCsid); + + try { + InvocationResults results = createUpdater().updateAccessCode(parentTaxonCsid, false); + + logger.debug("updateAccessCode complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote()); + } + catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + } + else if (event.getName().equals(DocumentEventTypes.ABOUT_TO_REMOVE)) { + String subjectDocType = (String) doc.getProperty(RelationConstants.SUBJECT_DOCTYPE_SCHEMA_NAME, RelationConstants.SUBJECT_DOCTYPE_FIELD_NAME); + String objectDocType = (String) doc.getProperty(RelationConstants.OBJECT_DOCTYPE_SCHEMA_NAME, RelationConstants.OBJECT_DOCTYPE_FIELD_NAME);; + String relationType = (String) doc.getProperty(RelationConstants.TYPE_SCHEMA_NAME, RelationConstants.TYPE_FIELD_NAME); + + logger.debug("subjectDocType=" + subjectDocType + " objectDocType=" + objectDocType + " relationType=" + relationType); + + if (subjectDocType.equals(TaxonConstants.NUXEO_DOCTYPE) && objectDocType.equals(TaxonConstants.NUXEO_DOCTYPE) && relationType.equals(RelationConstants.BROADER_TYPE)) { + String parentTaxonCsid = (String) doc.getProperty(RelationConstants.OBJECT_CSID_SCHEMA_NAME, RelationConstants.OBJECT_CSID_FIELD_NAME); + + // Stash the parent taxon csid, so it can be retrieved in the documentRemoved handler. + logger.debug("about to delete taxon hierarchy relation: parentTaxonCsid=" + parentTaxonCsid); + context.setProperty(DELETED_RELATION_PARENT_CSID_PROPERTY_NAME, parentTaxonCsid); + } + } + else if (event.getName().equals(DocumentEventTypes.DOCUMENT_REMOVED)) { + String parentTaxonCsid = (String) context.getProperty(DELETED_RELATION_PARENT_CSID_PROPERTY_NAME); + + if (StringUtils.isNotEmpty(parentTaxonCsid)) { + logger.debug("child removed, updating parent taxon: parentTaxonCsid=" + parentTaxonCsid); + + try { + InvocationResults results = createUpdater().updateAccessCode(parentTaxonCsid, false); + + logger.debug("updateAccessCode complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote()); + } + catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + } + } + } + } + + private List getTaxonNames(DocumentModel doc) { + List> taxonomicIdentGroupList = (List>) doc.getProperty(CollectionObjectBotGardenConstants.TAXON_SCHEMA_NAME, + TAXONOMIC_IDENT_GROUP_LIST_FIELD_NAME); + List taxonNames = new ArrayList(); + + for (Map taxonomicIdentGroup : taxonomicIdentGroupList) { + String taxonName = (String) taxonomicIdentGroup.get(TAXON_FIELD_NAME); + + if (StringUtils.isNotEmpty(taxonName)) { + taxonNames.add(taxonName); + } + } + + return taxonNames; + } + + private Set findDeletedTaxonNames(List previousTaxonNames, List currentTaxonNames) { + Set currentTaxonNameSet = new HashSet(currentTaxonNames); + Set deletedTaxonNameSet = new HashSet(); + + for (String previousTaxonName : previousTaxonNames) { + if (!currentTaxonNameSet.contains(previousTaxonName)) { + deletedTaxonNameSet.add(previousTaxonName); + } + } + + return deletedTaxonNameSet; + } + + private Set findAddedTaxonNames(List previousTaxonNames, List currentTaxonNames) { + Set previousTaxonNameSet = new HashSet(previousTaxonNames); + Set addedTaxonNameSet = new HashSet(); + + for (String currentTaxonName : currentTaxonNames) { + if (!previousTaxonNameSet.contains(currentTaxonName)) { + addedTaxonNameSet.add(currentTaxonName); + } + } + + return addedTaxonNameSet; + } + + private UpdateAccessCodeBatchJob createUpdater() { + ResourceMap resourceMap = ResteasyProviderFactory.getContextData(ResourceMap.class); + + UpdateAccessCodeBatchJob updater = new UpdateAccessCodeBatchJob(); + updater.setResourceMap(resourceMap); + + return updater; + } +} \ No newline at end of file diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateDeadFlagListener.java b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateDeadFlagListener.java new file mode 100644 index 000000000..751ab8e93 --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateDeadFlagListener.java @@ -0,0 +1,103 @@ +package org.collectionspace.services.listener.botgarden; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.collectionspace.services.batch.nuxeo.UpdateDeadFlagBatchJob; +import org.collectionspace.services.client.workflow.WorkflowClient; +import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectConstants; +import org.collectionspace.services.common.ResourceMap; +import org.collectionspace.services.common.invocable.InvocationResults; +import org.collectionspace.services.common.relation.nuxeo.RelationConstants; +import org.collectionspace.services.movement.nuxeo.MovementBotGardenConstants; +import org.collectionspace.services.movement.nuxeo.MovementConstants; +import org.collectionspace.services.nuxeo.listener.AbstractCSEventListenerImpl; +import org.jboss.resteasy.spi.ResteasyProviderFactory; +import org.nuxeo.ecm.core.api.DocumentModel; +import org.nuxeo.ecm.core.api.event.DocumentEventTypes; +import org.nuxeo.ecm.core.event.Event; +import org.nuxeo.ecm.core.event.EventContext; +import org.nuxeo.ecm.core.event.impl.DocumentEventContext; + +public class UpdateDeadFlagListener extends AbstractCSEventListenerImpl { + final Log logger = LogFactory.getLog(UpdateDeadFlagListener.class); + + /* + * Set the dead flag and dead date on collectionobjects related to a new or modified movement record. + */ + public void handleEvent(Event event) { + EventContext ec = event.getContext(); + + if (ec instanceof DocumentEventContext) { + DocumentEventContext context = (DocumentEventContext) ec; + DocumentModel doc = context.getSourceDocument(); + + logger.debug("docType=" + doc.getType()); + + if (event.getName().equals(DocumentEventTypes.DOCUMENT_CREATED)) { + /* + * Handle the case where a new movement is created with action code revive, and then related + * to a collectionobject. The movement won't have any relations at the time it's created, + * so we need to capture the creation of the relation. + */ + if (doc.getType().equals(RelationConstants.NUXEO_DOCTYPE) && + !doc.isVersion() && + !doc.isProxy()) { + String subjectDocType = (String) doc.getProperty(RelationConstants.SUBJECT_DOCTYPE_SCHEMA_NAME, RelationConstants.SUBJECT_DOCTYPE_FIELD_NAME); + String objectDocType = (String) doc.getProperty(RelationConstants.OBJECT_DOCTYPE_SCHEMA_NAME, RelationConstants.OBJECT_DOCTYPE_FIELD_NAME);; + + logger.debug("subjectDocType=" + subjectDocType + " objectDocType=" + objectDocType); + + if (subjectDocType.equals(MovementConstants.NUXEO_DOCTYPE) && objectDocType.equals(CollectionObjectConstants.NUXEO_DOCTYPE)) { + String movementCsid = (String) doc.getProperty(RelationConstants.SUBJECT_CSID_SCHEMA_NAME, RelationConstants.SUBJECT_CSID_FIELD_NAME); + String collectionObjectCsid = (String) doc.getProperty(RelationConstants.OBJECT_CSID_SCHEMA_NAME, RelationConstants.OBJECT_CSID_FIELD_NAME); + + try { + InvocationResults results = createUpdater().updateDeadFlag(collectionObjectCsid, movementCsid); + + logger.debug("updateDeadFlag complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote()); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + } + } + else { + /* + * Handle document modification. If the modified document was a movement record, and + * its action code is dead or revived, update the dead flag. We don't actually have to + * check the action code here, since it will be checked inside UpdateDeadFlagBatchJob.updateRelatedDeadFlags, + * but it is an optimization. + */ + if (doc.getType().startsWith(MovementConstants.NUXEO_DOCTYPE) && + !doc.isVersion() && + !doc.isProxy() && + !doc.getCurrentLifeCycleState().equals(WorkflowClient.WORKFLOWSTATE_DELETED)) { + String actionCode = (String) doc.getProperty(MovementBotGardenConstants.ACTION_CODE_SCHEMA_NAME, MovementBotGardenConstants.ACTION_CODE_FIELD_NAME); + + logger.debug("actionCode=" + actionCode); + + if (actionCode != null && (actionCode.equals(MovementBotGardenConstants.DEAD_ACTION_CODE) || actionCode.equals(MovementBotGardenConstants.REVIVED_ACTION_CODE))) { + String movementCsid = doc.getName(); + + try { + InvocationResults results = createUpdater().updateRelatedDeadFlags(movementCsid); + + logger.debug("updateRelatedDeadFlags complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote()); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + } + } + } + } + + private UpdateDeadFlagBatchJob createUpdater() { + ResourceMap resourceMap = ResteasyProviderFactory.getContextData(ResourceMap.class); + + UpdateDeadFlagBatchJob updater = new UpdateDeadFlagBatchJob(); + updater.setResourceMap(resourceMap); + + return updater; + } +} \ No newline at end of file diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateLocationListener.java b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateLocationListener.java new file mode 100644 index 000000000..c07711e89 --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateLocationListener.java @@ -0,0 +1,78 @@ +package org.collectionspace.services.listener.botgarden; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.collectionspace.services.client.workflow.WorkflowClient; +import org.collectionspace.services.movement.nuxeo.MovementBotGardenConstants; +import org.collectionspace.services.movement.nuxeo.MovementConstants; +import org.collectionspace.services.nuxeo.listener.AbstractCSEventListenerImpl; +import org.nuxeo.ecm.core.api.DocumentModel; +import org.nuxeo.ecm.core.api.event.CoreEventConstants; +import org.nuxeo.ecm.core.api.event.DocumentEventTypes; +import org.nuxeo.ecm.core.event.Event; +import org.nuxeo.ecm.core.event.EventContext; +import org.nuxeo.ecm.core.event.impl.DocumentEventContext; + +public class UpdateLocationListener extends AbstractCSEventListenerImpl { + final Log logger = LogFactory.getLog(UpdateLocationListener.class); + + /* + * Set the currentLocation and previousLocation fields in a Current Location record + * to appropriate values. + * + *
    + *
  • If the plant is dead, set currentLocation to none
  • + *
  • Set the previousLocation field to the previous value of the currentLocation field
  • + * + */ + public void handleEvent(Event event) { + EventContext ec = event.getContext(); + + if (ec instanceof DocumentEventContext) { + DocumentEventContext context = (DocumentEventContext) ec; + DocumentModel doc = context.getSourceDocument(); + + if (doc.getType().startsWith(MovementConstants.NUXEO_DOCTYPE) && + !doc.isVersion() && + !doc.isProxy() && + !doc.getCurrentLifeCycleState().equals(WorkflowClient.WORKFLOWSTATE_DELETED)) { + String actionCode = (String) doc.getProperty(MovementBotGardenConstants.ACTION_CODE_SCHEMA_NAME, + MovementBotGardenConstants.ACTION_CODE_FIELD_NAME); + + logger.debug("actionCode=" + actionCode); + + if (event.getName().equals(DocumentEventTypes.DOCUMENT_CREATED)) { + /* + * Special case for a document that is created with an action code of dead. + * In this case, we'll set the currentLocation to none, and the previousLocation to + * the current value of currentLocation, since there isn't a previous value. To do + * this, we can simply save the document, which will cause the beforeDocumentModification + * event to fire, taking us into the other branch of this code, with the current document + * becoming the previous document. + */ + if (actionCode != null && actionCode.equals(MovementBotGardenConstants.DEAD_ACTION_CODE)) { + context.getCoreSession().saveDocument(doc); + + /* + * The saveDocument call will have caused the document to be versioned via documentModified, + * so we can skip the versioning that would normally happen on documentCreated. + */ + ec.setProperty(CreateVersionListener.SKIP_PROPERTY, true); + } + } + else { + if (actionCode != null && actionCode.equals(MovementBotGardenConstants.DEAD_ACTION_CODE)) { + doc.setProperty(MovementConstants.CURRENT_LOCATION_SCHEMA_NAME, MovementConstants.CURRENT_LOCATION_FIELD_NAME, MovementConstants.NONE_LOCATION); + } + + DocumentModel previousDoc = (DocumentModel) context.getProperty(CoreEventConstants.PREVIOUS_DOCUMENT_MODEL); + String previousLocation = (String) previousDoc.getProperty(MovementConstants.CURRENT_LOCATION_SCHEMA_NAME, MovementConstants.CURRENT_LOCATION_FIELD_NAME); + + logger.debug("previousLocation=" + previousLocation); + + doc.setProperty(MovementConstants.PREVIOUS_LOCATION_SCHEMA_NAME, MovementConstants.PREVIOUS_LOCATION_FIELD_NAME, previousLocation); + } + } + } + } +} \ No newline at end of file diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateRareFlagListener.java b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateRareFlagListener.java new file mode 100644 index 000000000..3c7129ac8 --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateRareFlagListener.java @@ -0,0 +1,201 @@ +package org.collectionspace.services.listener.botgarden; + +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.collectionspace.services.batch.nuxeo.UpdateRareFlagBatchJob; +import org.collectionspace.services.client.PoxPayloadIn; +import org.collectionspace.services.client.PoxPayloadOut; +import org.collectionspace.services.client.workflow.WorkflowClient; +import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectBotGardenConstants; +import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectConstants; +import org.collectionspace.services.common.ResourceMap; +import org.collectionspace.services.common.invocable.InvocationResults; +import org.collectionspace.services.nuxeo.listener.AbstractCSEventListenerImpl; +import org.collectionspace.services.taxonomy.nuxeo.TaxonBotGardenConstants; +import org.collectionspace.services.taxonomy.nuxeo.TaxonConstants; +import org.jboss.resteasy.spi.ResteasyProviderFactory; +import org.nuxeo.ecm.core.api.DocumentModel; +import org.nuxeo.ecm.core.api.event.CoreEventConstants; +import org.nuxeo.ecm.core.api.event.DocumentEventTypes; +import org.nuxeo.ecm.core.event.Event; +import org.nuxeo.ecm.core.event.EventContext; +import org.nuxeo.ecm.core.event.impl.DocumentEventContext; + +/** + * A listener that updates the rare flag on collectionobjects when collectionobjects + * are created or modified, and when taxon records are modified. + * + * @see org.collectionspace.services.batch.nuxeo.UpdateRareFlagBatchJob + * @author ray + * + */ +public class UpdateRareFlagListener extends AbstractCSEventListenerImpl { + final Log logger = LogFactory.getLog(UpdateRareFlagListener.class); + + public static final String PREVIOUS_TAXON_PROPERTY_NAME = "UpdateRareFlagListener.previousTaxon"; + public static final String PREVIOUS_HAS_RARE_CONSERVATION_CATEGORY_PROPERTY_NAME = "UpdateRareFlagListener.previousHasRareConservationCategory"; + + private static final String[] CONSERVATION_CATEGORY_PATH_ELEMENTS = TaxonBotGardenConstants.CONSERVATION_CATEGORY_FIELD_NAME.split("/"); + private static final String PLANT_ATTRIBUTES_GROUP_LIST_FIELD_NAME = CONSERVATION_CATEGORY_PATH_ELEMENTS[0]; + private static final String CONSERVATION_CATEGORY_FIELD_NAME = CONSERVATION_CATEGORY_PATH_ELEMENTS[2]; + + public void handleEvent(Event event) { + EventContext ec = event.getContext(); + + if (ec instanceof DocumentEventContext) { + DocumentEventContext context = (DocumentEventContext) ec; + DocumentModel doc = context.getSourceDocument(); + + logger.debug("docType=" + doc.getType()); + + if (doc.getType().startsWith(CollectionObjectConstants.NUXEO_DOCTYPE) && + !doc.isVersion() && + !doc.isProxy() && + !doc.getCurrentLifeCycleState().equals(WorkflowClient.WORKFLOWSTATE_DELETED)) { + + if (event.getName().equals(DocumentEventTypes.BEFORE_DOC_UPDATE)) { + // Stash the previous primary taxonomic ident, so it can be retrieved in the documentModified handler. + + DocumentModel previousDoc = (DocumentModel) context.getProperty(CoreEventConstants.PREVIOUS_DOCUMENT_MODEL); + String previousTaxon = (String) previousDoc.getProperty(CollectionObjectBotGardenConstants.TAXON_SCHEMA_NAME, + CollectionObjectBotGardenConstants.PRIMARY_TAXON_FIELD_NAME); + + context.setProperty(PREVIOUS_TAXON_PROPERTY_NAME, previousTaxon); + } + else { + boolean updateRequired = false; + + if (event.getName().equals(DocumentEventTypes.DOCUMENT_UPDATED)) { + // A collectionobject was modified. As an optimization, check if the primary taxonomic determination + // of the collectionobject has changed. We only need to update the rare flag if it has. + + String previousTaxon = (String) context.getProperty(PREVIOUS_TAXON_PROPERTY_NAME); + String currentTaxon = (String) doc.getProperty(CollectionObjectBotGardenConstants.TAXON_SCHEMA_NAME, + CollectionObjectBotGardenConstants.PRIMARY_TAXON_FIELD_NAME); + + if (previousTaxon == null) { + previousTaxon = ""; + } + + if (currentTaxon == null) { + currentTaxon = ""; + } + + if (previousTaxon.equals(currentTaxon)) { + logger.debug("update not required: previousTaxon=" + previousTaxon + " currentTaxon=" + currentTaxon); + } + else { + logger.debug("update required: previousTaxon=" + previousTaxon + " currentTaxon=" + currentTaxon); + updateRequired = true; + } + } + else if (event.getName().equals(DocumentEventTypes.DOCUMENT_CREATED)) { + // A collectionobject was created. Always update the rare flag. + + updateRequired = true; + } + + if (updateRequired) { + String collectionObjectCsid = doc.getName(); + + try { + InvocationResults results = createUpdater().updateRareFlag(collectionObjectCsid); + + logger.debug("updateRareFlag complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote()); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + } + } + else if (doc.getType().startsWith(TaxonConstants.NUXEO_DOCTYPE) && + !doc.isVersion() && + !doc.isProxy() && + !doc.getCurrentLifeCycleState().equals(WorkflowClient.WORKFLOWSTATE_DELETED)) { + + if (event.getName().equals(DocumentEventTypes.BEFORE_DOC_UPDATE)) { + // Stash whether there was previously a non-empty conservation category, so it can be retrieved in the documentModified handler. + + DocumentModel previousDoc = (DocumentModel) context.getProperty(CoreEventConstants.PREVIOUS_DOCUMENT_MODEL); + boolean previousHasRareConservationCategory = hasRareConservationCategory(previousDoc); + + context.setProperty(PREVIOUS_HAS_RARE_CONSERVATION_CATEGORY_PROPERTY_NAME, new Boolean(previousHasRareConservationCategory)); + } + else { + boolean updateRequired = false; + + if (event.getName().equals(DocumentEventTypes.DOCUMENT_UPDATED)) { + // A taxon record was modified. As an optimization, check if there is now a rare + // conservation category when there wasn't before, or vice versa. We only need to update + // the rare flags of referencing collectionobjects if there was a change. + + boolean previousHasRareConservationCategory = (Boolean) context.getProperty(PREVIOUS_HAS_RARE_CONSERVATION_CATEGORY_PROPERTY_NAME); + boolean currentHasRareConservationCategory = hasRareConservationCategory(doc); + + if (previousHasRareConservationCategory == currentHasRareConservationCategory) { + logger.debug("update not required: previousHasRareConservationCategory=" + previousHasRareConservationCategory + + " currentHasRareConservationCategory=" + currentHasRareConservationCategory); + } + else { + logger.debug("update required: previousHasRareConservationCategory=" + previousHasRareConservationCategory + + " currentHasRareConservationCategory=" + currentHasRareConservationCategory); + updateRequired = true; + } + } + + if (updateRequired) { + String taxonCsid = doc.getName(); + + try { + InvocationResults results = createUpdater().updateReferencingRareFlags(taxonCsid); + + logger.debug("updateReferencingRareFlags complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote()); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + } + } + } + } + + private boolean hasRareConservationCategory(DocumentModel doc) { + List> plantAttributesGroupList = (List>) doc.getProperty(TaxonBotGardenConstants.CONSERVATION_CATEGORY_SCHEMA_NAME, + PLANT_ATTRIBUTES_GROUP_LIST_FIELD_NAME); + boolean hasRareConservationCategory = false; + + // UCBG-369: Changing this so that it only checks the primary conservation category. + + if (plantAttributesGroupList.size() > 0) { + Map plantAttributesGroup = plantAttributesGroupList.get(0); + String conservationCategory = (String) plantAttributesGroup.get(CONSERVATION_CATEGORY_FIELD_NAME); + + if (UpdateRareFlagBatchJob.isRare(conservationCategory)) { + hasRareConservationCategory = true; + } + } + +// for (Map plantAttributesGroup : plantAttributesGroupList) { +// String conservationCategory = (String) plantAttributesGroup.get(CONSERVATION_CATEGORY_FIELD_NAME); +// +// if (UpdateRareFlagBatchJob.isRare(conservationCategory)) { +// hasRareConservationCategory = true; +// break; +// } +// } + + return hasRareConservationCategory; + } + + private UpdateRareFlagBatchJob createUpdater() { + ResourceMap resourceMap = ResteasyProviderFactory.getContextData(ResourceMap.class); + + UpdateRareFlagBatchJob updater = new UpdateRareFlagBatchJob(); + updater.setResourceMap(resourceMap); + + return updater; + } +} \ No newline at end of file diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateStyledNameListener.java b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateStyledNameListener.java new file mode 100644 index 000000000..7208e89ad --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/java/org/collectionspace/services/listener/botgarden/UpdateStyledNameListener.java @@ -0,0 +1,93 @@ +package org.collectionspace.services.listener.botgarden; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jboss.resteasy.spi.ResteasyProviderFactory; + +import org.collectionspace.services.batch.nuxeo.FormatVoucherNameBatchJob; +import org.collectionspace.services.client.PoxPayloadIn; +import org.collectionspace.services.client.PoxPayloadOut; +import org.collectionspace.services.client.workflow.WorkflowClient; +import org.collectionspace.services.common.ResourceMap; +import org.collectionspace.services.loanout.nuxeo.LoanoutBotGardenConstants; +import org.collectionspace.services.loanout.nuxeo.LoanoutConstants; +import org.collectionspace.services.nuxeo.listener.AbstractCSEventListenerImpl; +import org.nuxeo.ecm.core.api.DocumentModel; +import org.nuxeo.ecm.core.api.event.CoreEventConstants; +import org.nuxeo.ecm.core.api.event.DocumentEventTypes; +import org.nuxeo.ecm.core.event.Event; +import org.nuxeo.ecm.core.event.EventContext; +import org.nuxeo.ecm.core.event.impl.DocumentEventContext; + +public class UpdateStyledNameListener extends AbstractCSEventListenerImpl { + public static final String RUN_AFTER_MODIFIED_PROPERTY = "UpdateStyledNameListener.RUN_AFTER_MODIFIED"; + + final Log logger = LogFactory.getLog(UpdateStyledNameListener.class); + + public void handleEvent(Event event) { + EventContext ec = event.getContext(); + + if (ec instanceof DocumentEventContext) { + DocumentEventContext context = (DocumentEventContext) ec; + DocumentModel doc = context.getSourceDocument(); + + logger.debug("docType=" + doc.getType()); + + if (doc.getType().startsWith(LoanoutConstants.NUXEO_DOCTYPE) && + !doc.isVersion() && + !doc.isProxy() && + !doc.getCurrentLifeCycleState().equals(WorkflowClient.WORKFLOWSTATE_DELETED)) { + + if (event.getName().equals(DocumentEventTypes.BEFORE_DOC_UPDATE)) { + DocumentModel previousDoc = (DocumentModel) context.getProperty(CoreEventConstants.PREVIOUS_DOCUMENT_MODEL); + + String previousLabelRequested = (String) previousDoc.getProperty(LoanoutBotGardenConstants.LABEL_REQUESTED_SCHEMA_NAME, + LoanoutBotGardenConstants.LABEL_REQUESTED_FIELD_NAME); + String labelRequested = (String) doc.getProperty(LoanoutBotGardenConstants.LABEL_REQUESTED_SCHEMA_NAME, + LoanoutBotGardenConstants.LABEL_REQUESTED_FIELD_NAME); + + logger.debug("previousLabelRequested=" + previousLabelRequested + " labelRequested=" + labelRequested); + + if ((previousLabelRequested == null || previousLabelRequested.equals(LoanoutBotGardenConstants.LABEL_REQUESTED_NO_VALUE)) && + labelRequested.equals(LoanoutBotGardenConstants.LABEL_REQUESTED_YES_VALUE)) { + // The label request is changing from no to yes, so we should update the styled name. + ec.setProperty(RUN_AFTER_MODIFIED_PROPERTY, true); + } + } + else { + boolean doUpdate = false; + + if (event.getName().equals(DocumentEventTypes.DOCUMENT_CREATED)) { + String labelRequested = (String) doc.getProperty(LoanoutBotGardenConstants.LABEL_REQUESTED_SCHEMA_NAME, + LoanoutBotGardenConstants.LABEL_REQUESTED_FIELD_NAME); + + doUpdate = (labelRequested != null && labelRequested.equals(LoanoutBotGardenConstants.LABEL_REQUESTED_YES_VALUE)); + } else { + doUpdate = ec.hasProperty(RUN_AFTER_MODIFIED_PROPERTY) && ((Boolean) ec.getProperty(RUN_AFTER_MODIFIED_PROPERTY)); + } + + if (doUpdate) { + logger.debug("Updating styled name"); + + String voucherCsid = doc.getName(); + + try { + createFormatter().formatVoucherName(voucherCsid); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + } + } + } + } + + private FormatVoucherNameBatchJob createFormatter() { + ResourceMap resourceMap = ResteasyProviderFactory.getContextData(ResourceMap.class); + + FormatVoucherNameBatchJob formatter = new FormatVoucherNameBatchJob(); + formatter.setResourceMap(resourceMap); + + return formatter; + } +} \ No newline at end of file diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/META-INF/MANIFEST.MF b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 000000000..cd667192f --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,16 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 1 +Bundle-Name: org.collectionspace.services.listener.botgarden +Bundle-SymbolicName: org.collectionspace.services.listener.botgarden;singleton:=true +Bundle-Version: 1.0.0 +Bundle-Localization: plugin +Bundle-Vendor: Nuxeo +Require-Bundle: org.nuxeo.runtime, + org.nuxeo.ecm.core.api, + org.nuxeo.ecm.core, + org.nuxeo.ecm.webapp.core +Provide-Package: org.collectionspace.services.listener.botgarden +Nuxeo-Component: OSGI-INF/core-types-contrib.xml, + OSGI-INF/default-life-cycle-contrib.xml, + OSGI-INF/ecm-types-contrib.xml, + OSGI-INF/layouts-contrib.xml diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/core-types-contrib.xml b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/core-types-contrib.xml new file mode 100644 index 000000000..7a5c9109b --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/core-types-contrib.xml @@ -0,0 +1,4 @@ + + + + diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/default-life-cycle-contrib.xml b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/default-life-cycle-contrib.xml new file mode 100644 index 000000000..8b23b831c --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/default-life-cycle-contrib.xml @@ -0,0 +1,4 @@ + + + + diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/deployment-fragment.xml b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/deployment-fragment.xml new file mode 100644 index 000000000..270abbd5f --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/deployment-fragment.xml @@ -0,0 +1,10 @@ + + + + + + ${bundle.fileName} + + + + diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/ecm-types-contrib.xml b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/ecm-types-contrib.xml new file mode 100644 index 000000000..0d5e94211 --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/ecm-types-contrib.xml @@ -0,0 +1,83 @@ + + + + + documentCreated + beforeDocumentModification + + + + + + documentCreated + documentModified + + + + + + beforeDocumentModification + documentCreated + + + + + + beforeDocumentModification + documentModified + documentCreated + + + + + + + + beforeDocumentModification + documentModified + documentCreated + aboutToRemove + documentRemoved + + + + + + + documentCreated + documentModified + + + + + + + + + diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/layouts-contrib.xml b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/layouts-contrib.xml new file mode 100644 index 000000000..b3d26f8d2 --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/botgarden/src/main/resources/OSGI-INF/layouts-contrib.xml @@ -0,0 +1,4 @@ + + + + diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/build.xml b/3rdparty/nuxeo/nuxeo-platform-listener/build.xml index a03be1e14..f5d1eb238 100644 --- a/3rdparty/nuxeo/nuxeo-platform-listener/build.xml +++ b/3rdparty/nuxeo/nuxeo-platform-listener/build.xml @@ -118,6 +118,8 @@ + + + + + + diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/build.properties b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/build.properties new file mode 100644 index 000000000..31fd5accd --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/build.properties @@ -0,0 +1 @@ +listener.module.name=naturalhistory \ No newline at end of file diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/build.xml b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/build.xml new file mode 100644 index 000000000..873edca8e --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/build.xml @@ -0,0 +1,104 @@ + + + CollectionSpace Nuxeo listener component type + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/pom.xml b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/pom.xml new file mode 100644 index 000000000..335672a75 --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + org.collectionspace.services.3rdparty.nuxeo.listener + org.collectionspace.services + 4.5-SNAPSHOT + + + UTF-8 + + org.collectionspace.services.listener.naturalhistory + org.collectionspace.services.listener.naturalhistory + http://maven.apache.org + + + + + org.nuxeo.ecm.core + nuxeo-core-storage-sql + ${nuxeo.core.version} + + + org.nuxeo.ecm.platform + nuxeo-platform-filemanager-api + ${nuxeo.platform.version} + + + org.collectionspace.services + org.collectionspace.services.taxonomy.service + ${project.version} + + + + + + + + src/main/resources + true + + + + + org.apache.maven.plugins + maven-jar-plugin + + + src/main/resources/META-INF/MANIFEST.MF + + ${eclipseVersion} + 2 + + + + + + + + diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/java/org/collectionspace/services/listener/ucb/UpdateFormattedDisplayNameListener.java b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/java/org/collectionspace/services/listener/ucb/UpdateFormattedDisplayNameListener.java new file mode 100644 index 000000000..5663176af --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/java/org/collectionspace/services/listener/ucb/UpdateFormattedDisplayNameListener.java @@ -0,0 +1,113 @@ +package org.collectionspace.services.listener.ucb; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.collectionspace.services.client.workflow.WorkflowClient; +import org.collectionspace.services.common.api.RefName; +import org.collectionspace.services.common.api.TaxonFormatter; +import org.collectionspace.services.nuxeo.listener.AbstractCSEventListenerImpl; +import org.collectionspace.services.taxonomy.nuxeo.TaxonBotGardenConstants; +import org.collectionspace.services.taxonomy.nuxeo.TaxonConstants; +import org.nuxeo.ecm.core.api.DocumentModel; +import org.nuxeo.ecm.core.api.event.CoreEventConstants; +import org.nuxeo.ecm.core.api.event.DocumentEventTypes; +import org.nuxeo.ecm.core.event.Event; +import org.nuxeo.ecm.core.event.EventContext; +import org.nuxeo.ecm.core.event.impl.DocumentEventContext; + +public class UpdateFormattedDisplayNameListener extends AbstractCSEventListenerImpl { + public static final String RUN_AFTER_MODIFIED_PROPERTY = "UpdateFormattedDisplayNameListener.RUN_AFTER_MODIFIED"; + + private static final String[] DISPLAY_NAME_PATH_ELEMENTS = TaxonConstants.DISPLAY_NAME_FIELD_NAME.split("/"); + private static final String TERM_GROUP_LIST_FIELD_NAME = DISPLAY_NAME_PATH_ELEMENTS[0]; + private static final String DISPLAY_NAME_FIELD_NAME = DISPLAY_NAME_PATH_ELEMENTS[2]; + + private static final String[] FORMATTED_DISPLAY_NAME_PATH_ELEMENTS = TaxonConstants.FORMATTED_DISPLAY_NAME_FIELD_NAME.split("/"); + private static final String FORMATTED_DISPLAY_NAME_FIELD_NAME = FORMATTED_DISPLAY_NAME_PATH_ELEMENTS[2]; + + final Log logger = LogFactory.getLog(UpdateFormattedDisplayNameListener.class); + + @Override + public void handleEvent(Event event) { + EventContext ec = event.getContext(); + + if (ec instanceof DocumentEventContext) { + DocumentEventContext context = (DocumentEventContext) ec; + DocumentModel doc = context.getSourceDocument(); + + logger.debug("docType=" + doc.getType()); + + if (doc.getType().startsWith(TaxonConstants.NUXEO_DOCTYPE) && + !doc.isVersion() && + !doc.isProxy() && + !doc.getCurrentLifeCycleState().equals(WorkflowClient.WORKFLOWSTATE_DELETED)) { + + String refName = (String) doc.getProperty(TaxonConstants.REFNAME_SCHEMA_NAME, TaxonConstants.REFNAME_FIELD_NAME); + RefName.AuthorityItem item = RefName.AuthorityItem.parse(refName); + String parentShortId = item.getParentShortIdentifier(); + + logger.debug("parentShortId=" + parentShortId); + + if (!parentShortId.equals(TaxonBotGardenConstants.COMMON_VOCABULARY_SHORTID)) { + if (event.getName().equals(DocumentEventTypes.DOCUMENT_CREATED)) { + // Save the document, to get the BEFORE_DOC_UPDATE branch to run. + doc.getCoreSession().saveDocument(doc); + } + else if (event.getName().equals(DocumentEventTypes.BEFORE_DOC_UPDATE)) { + DocumentModel previousDoc = (DocumentModel) context.getProperty(CoreEventConstants.PREVIOUS_DOCUMENT_MODEL); + + updateFormattedDisplayNames(doc, previousDoc); + } + } + } + } + } + + private void updateFormattedDisplayNames(DocumentModel doc, DocumentModel previousDoc) { + //Set previousDisplayNames = getDisplayNames(previousDoc); + TaxonFormatter formatter = new TaxonFormatter(); + List> termGroupList = (List>) doc.getProperty(TaxonConstants.DISPLAY_NAME_SCHEMA_NAME, TERM_GROUP_LIST_FIELD_NAME); + + for (Map termGroup : termGroupList) { + String displayName = (String) termGroup.get(DISPLAY_NAME_FIELD_NAME); + String formattedDisplayName = (String) termGroup.get(FORMATTED_DISPLAY_NAME_FIELD_NAME); + + if (StringUtils.isBlank(formattedDisplayName)) { + formattedDisplayName = ""; + + if (StringUtils.isNotBlank(displayName)) { + formattedDisplayName = formatter.format(displayName); + } + + termGroup.put(FORMATTED_DISPLAY_NAME_FIELD_NAME, formattedDisplayName); + } + } + + Map updateMap = new HashMap(); + updateMap.put(TERM_GROUP_LIST_FIELD_NAME, termGroupList); + + doc.setProperties(TaxonConstants.DISPLAY_NAME_SCHEMA_NAME, updateMap); + } + + /* + private Set getDisplayNames(DocumentModel doc) throws ClientException { + Set displayNames = new HashSet(); + List> termGroupList = (List>) doc.getProperty(TaxonConstants.DISPLAY_NAME_SCHEMA_NAME, TERM_GROUP_LIST_FIELD_NAME); + + for (Map termGroup : termGroupList) { + String displayName = (String) termGroup.get(DISPLAY_NAME_FIELD_NAME); + + if (displayName != null) { + displayNames.add(displayName); + } + } + + return displayNames; + } + */ +} \ No newline at end of file diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/META-INF/MANIFEST.MF b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 000000000..a0befd6a1 --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,16 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 1 +Bundle-Name: org.collectionspace.services.listener.naturalhistory +Bundle-SymbolicName: org.collectionspace.services.listener.naturalhistory;singleton:=true +Bundle-Version: 1.0.0 +Bundle-Localization: plugin +Bundle-Vendor: Nuxeo +Require-Bundle: org.nuxeo.runtime, + org.nuxeo.ecm.core.api, + org.nuxeo.ecm.core, + org.nuxeo.ecm.webapp.core +Provide-Package: org.collectionspace.services.listener.naturalhistory +Nuxeo-Component: OSGI-INF/core-types-contrib.xml, + OSGI-INF/default-life-cycle-contrib.xml, + OSGI-INF/ecm-types-contrib.xml, + OSGI-INF/layouts-contrib.xml diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/core-types-contrib.xml b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/core-types-contrib.xml new file mode 100644 index 000000000..664d33cb7 --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/core-types-contrib.xml @@ -0,0 +1,4 @@ + + + + diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/default-life-cycle-contrib.xml b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/default-life-cycle-contrib.xml new file mode 100644 index 000000000..d83bdbf3d --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/default-life-cycle-contrib.xml @@ -0,0 +1,4 @@ + + + + diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/deployment-fragment.xml b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/deployment-fragment.xml new file mode 100644 index 000000000..270abbd5f --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/deployment-fragment.xml @@ -0,0 +1,10 @@ + + + + + + ${bundle.fileName} + + + + diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/ecm-types-contrib.xml b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/ecm-types-contrib.xml new file mode 100644 index 000000000..1539e4fc0 --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/ecm-types-contrib.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/layouts-contrib.xml b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/layouts-contrib.xml new file mode 100644 index 000000000..68326c45d --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-listener/naturalhistory/src/main/resources/OSGI-INF/layouts-contrib.xml @@ -0,0 +1,4 @@ + + + + diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/pom.xml b/3rdparty/nuxeo/nuxeo-platform-listener/pom.xml index a056d6019..000a35d4f 100644 --- a/3rdparty/nuxeo/nuxeo-platform-listener/pom.xml +++ b/3rdparty/nuxeo/nuxeo-platform-listener/pom.xml @@ -16,6 +16,8 @@ updateobjectlocationonmove updaterelationsondelete updateimagederivatives + naturalhistory + botgarden diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/updateimagederivatives/src/main/java/org/collectionspace/services/listener/UpdateImageDerivatives.java b/3rdparty/nuxeo/nuxeo-platform-listener/updateimagederivatives/src/main/java/org/collectionspace/services/listener/UpdateImageDerivatives.java index b73f7cc29..240362866 100644 --- a/3rdparty/nuxeo/nuxeo-platform-listener/updateimagederivatives/src/main/java/org/collectionspace/services/listener/UpdateImageDerivatives.java +++ b/3rdparty/nuxeo/nuxeo-platform-listener/updateimagederivatives/src/main/java/org/collectionspace/services/listener/UpdateImageDerivatives.java @@ -7,21 +7,20 @@ import org.apache.commons.logging.LogFactory; import org.collectionspace.services.common.api.CommonAPI; import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface; import org.collectionspace.services.nuxeo.client.java.CoreSessionWrapper; +import org.collectionspace.services.nuxeo.listener.AbstractCSEventListenerImpl; import org.collectionspace.services.nuxeo.util.NuxeoUtils; import org.nuxeo.ecm.core.api.Blob; -import org.nuxeo.ecm.core.api.ClientException; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.blobholder.BlobHolder; import org.nuxeo.ecm.core.api.blobholder.DocumentBlobHolder; //import org.nuxeo.ecm.core.api.event.DocumentEventTypes; import org.nuxeo.ecm.core.event.Event; import org.nuxeo.ecm.core.event.EventContext; -import org.nuxeo.ecm.core.event.EventListener; import org.nuxeo.ecm.core.event.impl.DocumentEventContext; //import org.nuxeo.ecm.platform.picture.api.ImagingDocumentConstants; import org.nuxeo.ecm.platform.picture.api.ImagingDocumentConstants; -public class UpdateImageDerivatives implements EventListener { +public class UpdateImageDerivatives extends AbstractCSEventListenerImpl { // All Nuxeo sessions that get passed around to CollectionSpace code need to // be wrapped inside of a CoreSessionWrapper. For example: @@ -33,7 +32,7 @@ public class UpdateImageDerivatives implements EventListener { private final static Log logger = LogFactory.getLog(UpdateImageDerivatives.class); @Override - public void handleEvent(Event event) throws ClientException { + public void handleEvent(Event event) { if (logger.isTraceEnabled()) { logger.trace(String.format("Entering handleEvent in '%s'...", getClass().getName())); } @@ -72,7 +71,7 @@ public class UpdateImageDerivatives implements EventListener { private void purgeOriginalImage(DocumentModel docModel, CoreSessionInterface nuxeoSession) { // // Empty the document model's "content" property -this does not delete the actual file/blob it - // just disassociates the blob content (aka, the orginal image) from the document. + // just disassociates the blob content (aka, the original image) from the document. // docModel.setPropertyValue("file:content", (Serializable) null); diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/updaterelationsondelete/src/main/java/org/collectionspace/services/listener/UpdateRelationsOnDelete.java b/3rdparty/nuxeo/nuxeo-platform-listener/updaterelationsondelete/src/main/java/org/collectionspace/services/listener/UpdateRelationsOnDelete.java index 302be9020..2e1477d5e 100644 --- a/3rdparty/nuxeo/nuxeo-platform-listener/updaterelationsondelete/src/main/java/org/collectionspace/services/listener/UpdateRelationsOnDelete.java +++ b/3rdparty/nuxeo/nuxeo-platform-listener/updaterelationsondelete/src/main/java/org/collectionspace/services/listener/UpdateRelationsOnDelete.java @@ -9,7 +9,7 @@ import org.apache.commons.logging.LogFactory; import org.collectionspace.services.client.workflow.WorkflowClient; import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface; import org.collectionspace.services.nuxeo.client.java.CoreSessionWrapper; -import org.nuxeo.ecm.core.api.ClientException; +import org.collectionspace.services.nuxeo.listener.AbstractCSEventListenerImpl; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.DocumentModelList; import org.nuxeo.ecm.core.api.impl.LifeCycleFilter; @@ -18,7 +18,7 @@ import org.nuxeo.ecm.core.event.EventContext; import org.nuxeo.ecm.core.event.EventListener; import org.nuxeo.ecm.core.event.impl.DocumentEventContext; -public class UpdateRelationsOnDelete implements EventListener { +public class UpdateRelationsOnDelete extends AbstractCSEventListenerImpl { // FIXME: We might experiment here with using log4j instead of Apache Commons Logging; // am using the latter to follow Ray's pattern for now @@ -30,7 +30,7 @@ public class UpdateRelationsOnDelete implements EventListener { final static String RELATIONS_COMMON_OBJECT_CSID_FIELD = "relations_common:objectCsid"; @Override - public void handleEvent(Event event) throws ClientException { + public void handleEvent(Event event) { logger.trace("In handleEvent in UpdateRelationsOnDelete ..."); EventContext eventContext = event.getContext(); @@ -82,7 +82,7 @@ public class UpdateRelationsOnDelete implements EventListener { DocumentModelList matchingDocuments; try { matchingDocuments = session.query(queryString.toString(), workflowStateFilter); - } catch (ClientException ce) { + } catch (Exception ce) { logger.warn("Error attempting to retrieve relation records where " + "record of type '" + docModel.getType() + "' with CSID " + csid + " is the subject or object of any relation: " + ce.getMessage()); diff --git a/3rdparty/nuxeo/nuxeo-platform-thumbnail/src/main/resources/schemas/core-types.xsd b/3rdparty/nuxeo/nuxeo-platform-thumbnail/src/main/resources/schemas/core-types.xsd new file mode 100644 index 000000000..0d4a0b167 --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-thumbnail/src/main/resources/schemas/core-types.xsd @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/JaxRsServiceProvider/pom.xml b/services/JaxRsServiceProvider/pom.xml index b8876d88f..7b7beda3b 100644 --- a/services/JaxRsServiceProvider/pom.xml +++ b/services/JaxRsServiceProvider/pom.xml @@ -223,6 +223,11 @@ org.collectionspace.services.loanout.service ${project.version} + + org.collectionspace.services + org.collectionspace.services.claim.service + ${project.version} + org.collectionspace.services org.collectionspace.services.exhibition.service @@ -298,6 +303,16 @@ org.collectionspace.services.movement.service ${project.version} + + org.collectionspace.services + org.collectionspace.services.propagation.service + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.pottag.service + ${project.version} + org.collectionspace.services org.collectionspace.services.report.service diff --git a/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/jaxrs/CSpaceResteasyBootstrap.java b/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/jaxrs/CSpaceResteasyBootstrap.java index 6d32ab644..3a9f2b540 100644 --- a/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/jaxrs/CSpaceResteasyBootstrap.java +++ b/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/jaxrs/CSpaceResteasyBootstrap.java @@ -17,8 +17,7 @@ public class CSpaceResteasyBootstrap extends ResteasyBootstrap { // System.out.println(String.format("%tc [INFO] Starting up the CollectionSpace Services' JAX-RS application.", new Date())); super.contextInitialized(event); - CollectionSpaceJaxRsApplication app = - (CollectionSpaceJaxRsApplication)deployment.getApplication(); + CollectionSpaceJaxRsApplication app = (CollectionSpaceJaxRsApplication)deployment.getApplication(); Dispatcher disp = deployment.getDispatcher(); disp.getDefaultContextObjects().put(ResourceMap.class, app.getResourceMap()); System.out.println(String.format("%tc [INFO] CollectionSpace Services' JAX-RS application started.", new Date())); diff --git a/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/jaxrs/CollectionSpaceJaxRsApplication.java b/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/jaxrs/CollectionSpaceJaxRsApplication.java index d1a5cd6d2..d7f34063c 100644 --- a/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/jaxrs/CollectionSpaceJaxRsApplication.java +++ b/services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/jaxrs/CollectionSpaceJaxRsApplication.java @@ -45,6 +45,8 @@ import org.collectionspace.services.material.MaterialAuthorityResource; import org.collectionspace.services.concept.ConceptAuthorityResource; import org.collectionspace.services.taxonomy.TaxonomyAuthorityResource; import org.collectionspace.services.movement.MovementResource; +import org.collectionspace.services.propagation.PropagationResource; +import org.collectionspace.services.pottag.PottagResource; import org.collectionspace.services.report.ReportResource; import org.collectionspace.services.acquisition.AcquisitionResource; import org.collectionspace.services.dimension.DimensionResource; @@ -54,6 +56,7 @@ import org.collectionspace.services.vocabulary.VocabularyResource; import org.collectionspace.services.organization.OrgAuthorityResource; import org.collectionspace.services.person.PersonAuthorityResource; import org.collectionspace.services.citation.CitationAuthorityResource; +import org.collectionspace.services.claim.ClaimResource; import org.collectionspace.services.exhibition.ExhibitionResource; import org.collectionspace.services.conditioncheck.ConditioncheckResource; import org.collectionspace.services.conservation.ConservationResource; @@ -134,6 +137,9 @@ public class CollectionSpaceJaxRsApplication extends Application addResourceToMapAndSingletons(new MediaResource()); addResourceToMapAndSingletons(new BlobResource()); addResourceToMapAndSingletons(new MovementResource()); + addResourceToMapAndSingletons(new PropagationResource()); + addResourceToMapAndSingletons(new PottagResource()); + addResourceToMapAndSingletons(new ClaimResource()); addResourceToMapAndSingletons(new ReportResource()); addResourceToMapAndSingletons(new PublicItemResource()); @@ -162,7 +168,7 @@ public class CollectionSpaceJaxRsApplication extends Application } @Override - public ResourceMap getResourceMap() { + public ResourceMap getResourceMap() { return resourceMap; } diff --git a/services/authentication/client/pom.xml b/services/authentication/client/pom.xml index d477d849b..6d68a5fb4 100644 --- a/services/authentication/client/pom.xml +++ b/services/authentication/client/pom.xml @@ -31,6 +31,11 @@ org.collectionspace.services.authentication.jaxb ${project.version} + + org.collectionspace.services + org.collectionspace.services.authorization.jaxb + ${project.version} + org.collectionspace.services org.collectionspace.services.client diff --git a/services/authentication/client/src/test/resources/META-INF/persistence.xml b/services/authentication/client/src/test/resources/META-INF/persistence.xml index 61ab30d52..9577e7319 100644 --- a/services/authentication/client/src/test/resources/META-INF/persistence.xml +++ b/services/authentication/client/src/test/resources/META-INF/persistence.xml @@ -3,8 +3,7 @@ http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:orm="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> org.collectionspace.services.authentication.User - org.collectionspace.services.authentication.Role - org.collectionspace.services.authentication.UserRole + org.collectionspace.services.authorization.Role 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 41d49dfb4..58ddcc3bf 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 @@ -1428,7 +1428,7 @@ public abstract class AuthorityResource * @param tenantId * @return */ - protected String getItemDocType(String tenantId) { + public String getItemDocType(String tenantId) { return getDocType(tenantId, getItemServiceName()); } diff --git a/services/authorization/pstore/src/test/resources/META-INF/persistence.xml b/services/authorization/pstore/src/test/resources/META-INF/persistence.xml index 8fcff4b37..52fe747e8 100644 --- a/services/authorization/pstore/src/test/resources/META-INF/persistence.xml +++ b/services/authorization/pstore/src/test/resources/META-INF/persistence.xml @@ -2,8 +2,8 @@ - org.collectionspace.services.authorization.Permission - org.collectionspace.services.authorization.PermissionAction + org.collectionspace.services.authorization.perms.Permission + org.collectionspace.services.authorization.perms.PermissionAction org.collectionspace.services.authorization.PermissionRoleRel org.collectionspace.services.authorization.Role org.collectionspace.services.authorization.AccountRoleRel diff --git a/services/batch/build.xml b/services/batch/build.xml index 876b0f8a2..6984ff89b 100644 --- a/services/batch/build.xml +++ b/services/batch/build.xml @@ -106,9 +106,11 @@ - + + + jaxb service - 3rdparty + client diff --git a/services/batch/service/pom.xml b/services/batch/service/pom.xml index 5f95b8820..79051f344 100644 --- a/services/batch/service/pom.xml +++ b/services/batch/service/pom.xml @@ -37,6 +37,11 @@ org.collectionspace.services.batch.jaxb ${project.version} + + org.collectionspace.services + org.collectionspace.services.authority.service + ${project.version} + org.collectionspace.services org.collectionspace.services.collectionobject.client @@ -47,11 +52,21 @@ org.collectionspace.services.collectionobject.jaxb ${project.version} + + org.collectionspace.services + org.collectionspace.services.collectionobject.service + ${project.version} + org.collectionspace.services org.collectionspace.services.loanout.client ${project.version} + + org.collectionspace.services + org.collectionspace.services.loanout.service + ${project.version} + org.collectionspace.services org.collectionspace.services.movement.client @@ -62,11 +77,46 @@ org.collectionspace.services.movement.service ${project.version} + + org.collectionspace.services + org.collectionspace.services.place.client + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.place.service + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.pottag.client + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.pottag.service + ${project.version} + org.collectionspace.services org.collectionspace.services.relation.client ${project.version} - + + + org.collectionspace.services + org.collectionspace.services.relation.service + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.taxonomy.client + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.taxonomy.service + ${project.version} + junit diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/AbstractBatchInvocable.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/AbstractBatchInvocable.java index d5f0b7491..d612fc149 100644 --- a/services/batch/service/src/main/java/org/collectionspace/services/batch/AbstractBatchInvocable.java +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/AbstractBatchInvocable.java @@ -3,10 +3,14 @@ package org.collectionspace.services.batch; import java.util.Collections; import java.util.List; import javax.ws.rs.core.Response; + +import org.collectionspace.services.client.PoxPayloadIn; +import org.collectionspace.services.client.PoxPayloadOut; import org.collectionspace.services.common.ResourceMap; import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.invocable.InvocationContext; import org.collectionspace.services.common.invocable.InvocationResults; +import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,9 +38,11 @@ public abstract class AbstractBatchInvocable implements BatchInvocable { protected final String CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT = "Could not find required CSID values in the invocation context for this batch job."; private List invocationModes; - private ResourceMap resourceMap; + private ResourceMap resourceMap; private InvocationContext invocationCtx; - private ServiceContext ctx; + private ServiceContext ctx; + private String tenantId; + private int completionStatus; private InvocationResults results; @@ -65,24 +71,52 @@ public abstract class AbstractBatchInvocable implements BatchInvocable { this.invocationModes = invocationModes; } - public ResourceMap getResourceMap() { + public ResourceMap getResourceMap() { return resourceMap; } @Override - public void setResourceMap(ResourceMap resourceMap) { + public void setResourceMap(ResourceMap resourceMap) { this.resourceMap = resourceMap; } @Override - public void setServiceContext(ServiceContext context) { + public void setServiceContext(ServiceContext context) { this.ctx = context; } @Override - public ServiceContext getServiceContext() { + public ServiceContext getServiceContext() { return ctx; } + + @Override + public CoreSessionInterface getRepoSession() { + CoreSessionInterface result = null; + + if (ctx != null) { + result = (CoreSessionInterface) ctx.getCurrentRepositorySession(); + } else { + logger.error(String.format("Batch job '%s' invoked with a null/empty service context.", + this.getClass().getName())); + } + + return result; + } + + @Override + public String getTenantId() { + String result = null; + + if (ctx != null) { + result = ctx.getTenantId(); + } else { + logger.error(String.format("Batch job '%s' invoked with a null/empty service context.", + this.getClass().getName())); + } + + return result; + } @Override public InvocationContext getInvocationContext() { diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/BatchInvocable.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/BatchInvocable.java index 9e9f5cca3..2ddc5a12a 100644 --- a/services/batch/service/src/main/java/org/collectionspace/services/batch/BatchInvocable.java +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/BatchInvocable.java @@ -1,7 +1,10 @@ package org.collectionspace.services.batch; +import org.collectionspace.services.client.PoxPayloadIn; +import org.collectionspace.services.client.PoxPayloadOut; import org.collectionspace.services.common.ResourceMap; import org.collectionspace.services.common.invocable.Invocable; +import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface; public interface BatchInvocable extends Invocable { @@ -9,6 +12,9 @@ public interface BatchInvocable extends Invocable { * Sets the invocation context for the batch job. Called before run(). * @param context an instance of InvocationContext. */ - public void setResourceMap(ResourceMap resourceMap); + public void setResourceMap(ResourceMap resourceMap); + public CoreSessionInterface getRepoSession(); + + public String getTenantId(); } diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/BatchResource.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/BatchResource.java index 41322dbf6..5a5dd2d49 100644 --- a/services/batch/service/src/main/java/org/collectionspace/services/batch/BatchResource.java +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/BatchResource.java @@ -39,6 +39,7 @@ import org.collectionspace.services.common.document.DocumentHandler; import org.collectionspace.services.common.invocable.Invocable; import org.collectionspace.services.common.invocable.InvocationContext; import org.collectionspace.services.common.invocable.InvocationResults; +import org.collectionspace.services.common.query.QueryManager; import org.collectionspace.services.jaxb.AbstractCommonList; import javax.ws.rs.Consumes; @@ -49,6 +50,7 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; +import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.UriInfo; @Path(BatchClient.SERVICE_PATH) @@ -79,6 +81,46 @@ public class BatchResource extends NuxeoBasedResource { } } + // FIXME: Resource classes should not be invoking handlers directly. This resource method should follow the conventions used by + // other resource methods and use the getRepositoryClient() methods. + @Override + protected AbstractCommonList getCommonList(UriInfo ui) { + try { + ServiceContext ctx = createServiceContext(ui); + MultivaluedMap queryParams = ctx.getQueryParams(); + DocumentHandler handler = createDocumentHandler(ctx); + String docType = queryParams.getFirst(IQueryManager.SEARCH_TYPE_DOCTYPE); + String mode = queryParams.getFirst(IQueryManager.SEARCH_TYPE_INVOCATION_MODE); + String whereClause = null; + DocumentFilter documentFilter = null; + String common_part = ctx.getCommonPartLabel(); + + if (docType != null && !docType.isEmpty()) { + whereClause = QueryManager.createWhereClauseForInvocableByDocType( + common_part, docType); + documentFilter = handler.getDocumentFilter(); + documentFilter.appendWhereClause(whereClause, IQueryManager.SEARCH_QUALIFIER_AND); + } + + if (mode != null && !mode.isEmpty()) { + whereClause = QueryManager.createWhereClauseForInvocableByMode( + common_part, mode); + documentFilter = handler.getDocumentFilter(); + documentFilter.appendWhereClause(whereClause, IQueryManager.SEARCH_QUALIFIER_AND); + } + + if (whereClause !=null && logger.isDebugEnabled()) { + logger.debug("The WHERE clause is: " + documentFilter.getWhereClause()); + } + + getRepositoryClient(ctx).getFiltered(ctx, handler); + AbstractCommonList list = (AbstractCommonList) handler.getCommonPartList(); + return list; + } catch (Exception e) { + throw bigReThrow(e, ServiceMessages.LIST_FAILED); + } + } + /** * Gets the authorityItem list for the specified authority * If partialPerm is specified, keywords will be ignored. @@ -159,9 +201,6 @@ public class BatchResource extends NuxeoBasedResource { return ptClause; } - - - @POST @Path("{csid}") public InvocationResults invokeBatchJob( diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/AbstractBatchJob.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/AbstractBatchJob.java new file mode 100644 index 000000000..e37ce40a8 --- /dev/null +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/AbstractBatchJob.java @@ -0,0 +1,678 @@ +package org.collectionspace.services.batch.nuxeo; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.PathSegment; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.lang.StringUtils; +import org.collectionspace.services.batch.AbstractBatchInvocable; +import org.collectionspace.services.client.CollectionObjectClient; +import org.collectionspace.services.client.CollectionSpaceClient; +import org.collectionspace.services.client.CollectionSpaceClientUtils; +import org.collectionspace.services.client.IRelationsManager; +import org.collectionspace.services.client.MovementClient; +import org.collectionspace.services.client.PayloadOutputPart; +import org.collectionspace.services.client.PlaceAuthorityClient; +import org.collectionspace.services.client.PoxPayloadOut; +import org.collectionspace.services.client.RelationClient; +import org.collectionspace.services.client.TaxonomyAuthorityClient; +import org.collectionspace.services.client.workflow.WorkflowClient; +import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectConstants; +import org.collectionspace.services.common.NuxeoBasedResource; +import org.collectionspace.services.common.ServiceMain; +import org.collectionspace.services.common.UriTemplateRegistry; +import org.collectionspace.services.common.api.RefName; +import org.collectionspace.services.common.authorityref.AuthorityRefDocList; +import org.collectionspace.services.common.context.ServiceBindingUtils; +import org.collectionspace.services.common.relation.RelationResource; +import org.collectionspace.services.common.vocabulary.AuthorityResource; +import org.collectionspace.services.jaxb.AbstractCommonList; +import org.collectionspace.services.movement.nuxeo.MovementConstants; +import org.collectionspace.services.relation.RelationsCommonList; +import org.collectionspace.services.relation.RelationsCommonList.RelationListItem; + +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.dom4j.Node; + +import org.jboss.resteasy.specimpl.PathSegmentImpl; +//import org.jboss.resteasy.specimpl.UriInfoImpl; +import org.collectionspace.services.batch.UriInfoImpl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AbstractBatchJob extends AbstractBatchInvocable { + public final int CREATED_STATUS = Response.Status.CREATED.getStatusCode(); + public final int BAD_REQUEST_STATUS = Response.Status.BAD_REQUEST.getStatusCode(); + public final int INT_ERROR_STATUS = Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(); + + final Logger logger = LoggerFactory.getLogger(AbstractBatchJob.class); + + private Map authorityServiceNamesByDocType; + + public abstract void run(); + + protected String getFieldXml(Map fields, String fieldName) { + return getFieldXml(fieldName, fields.get(fieldName)); + } + + protected String getFieldXml(Map fields) { + StringBuffer xmlBuffer = new StringBuffer(); + + for (String fieldName : fields.keySet()) { + xmlBuffer.append(getFieldXml(fields, fieldName)); + } + + return xmlBuffer.toString(); + } + + protected String getFieldXml(String fieldName, String fieldValue) { + String xml = "<" + fieldName + ">" + (fieldValue == null ? "" : StringEscapeUtils.escapeXml(fieldValue)) + ""; + + return xml; + } + + protected String createRelation(String subjectCsid, String subjectDocType, String objectCsid, String objectDocType, String relationshipType) throws ResourceException { + String relationCsid = null; + + String createRelationPayload = + "" + + "" + + "" + + "" + StringEscapeUtils.escapeXml(subjectCsid) + "" + + "" + StringEscapeUtils.escapeXml(subjectDocType) + "" + + "" + StringEscapeUtils.escapeXml(objectCsid) + "" + + "" + StringEscapeUtils.escapeXml(objectDocType) + "" + + "" + StringEscapeUtils.escapeXml(relationshipType) + "" + + "" + + ""; + + NuxeoBasedResource resource = (NuxeoBasedResource) getResourceMap().get(RelationClient.SERVICE_NAME); + Response response = resource.create(getResourceMap(), null, createRelationPayload); + + if (response.getStatus() == CREATED_STATUS) { + relationCsid = CollectionSpaceClientUtils.extractId(response); + } + else { + throw new ResourceException(response, "Error creating relation"); + } + + return relationCsid; + } + + /** + * Return related records, based on the supplied search criteria. Soft-deleted relations + * are filtered from the list, but soft-deleted subject/object records are not. + * + * @param subjectCsid The csid of the subject record. If null or empty, match any subject. + * @param subjectDocType The document type of the subject record. If null or empty, match any subject type. + * @param predicate The predicate of the relation. If null or empty, match any predicate. + * @param objectCsid The csid of the object record. If null or empty, match any object. + * @param objectDocType The document type of the object record. If null or empty, match any object type. + * @return + * @throws URISyntaxException + */ + protected List findRelated(String subjectCsid, String subjectDocType, String predicate, String objectCsid, String objectDocType) throws URISyntaxException { + RelationResource relationResource = (RelationResource) getResourceMap().get(RelationClient.SERVICE_NAME); + RelationsCommonList relationList = relationResource.getList(createRelationSearchUriInfo(subjectCsid, subjectDocType, predicate, objectCsid, objectDocType)); + + return relationList.getRelationListItem(); + } + + protected List findRelatedObjects(String subjectCsid, String subjectDocType, String predicate, String objectCsid, String objectDocType) throws URISyntaxException { + List csids = new ArrayList(); + + for (RelationsCommonList.RelationListItem item : findRelated(subjectCsid, subjectDocType, predicate, objectCsid, objectDocType)) { + csids.add(item.getObjectCsid()); + } + + return csids; + } + + protected List findRelatedSubjects(String subjectCsid, String subjectDocType, String predicate, String objectCsid, String objectDocType) throws URISyntaxException { + List csids = new ArrayList(); + + for (RelationsCommonList.RelationListItem item : findRelated(subjectCsid, subjectDocType, predicate, objectCsid, objectDocType)) { + csids.add(item.getSubjectCsid()); + } + + return csids; + } + + protected List findRelatedCollectionObjects(String subjectCsid) throws URISyntaxException { + return findRelatedObjects(subjectCsid, null, "affects", null, CollectionObjectConstants.NUXEO_DOCTYPE); + } + + protected List findRelatedMovements(String subjectCsid) throws URISyntaxException { + return findRelatedObjects(subjectCsid, null, "affects", null, MovementConstants.NUXEO_DOCTYPE); + } + + protected String findBroader(String subjectCsid) throws URISyntaxException { + List relatedObjects = findRelatedObjects(subjectCsid, null, "hasBroader", null, null); + + // There should be only one broader object. + String broader = relatedObjects.size() > 0 ? relatedObjects.get(0) : null; + + return broader; + } + + protected List findNarrower(String subjectCsid) throws URISyntaxException { + return findRelatedSubjects(null, null, "hasBroader", subjectCsid, null); + } + + /** + * Returns the movement record related to the specified record, if there is only one. + * Returns null if there are zero or more than one related movement records. + * + * @param subjectCsid The csid of the record + * @return + * @throws URISyntaxException + * @throws DocumentException + */ + protected String findSingleRelatedMovement(String subjectCsid) throws URISyntaxException, DocumentException { + String foundMovementCsid = null; + List movementCsids = findRelatedMovements(subjectCsid); + + for (String movementCsid : movementCsids) { + PoxPayloadOut movementPayload = findMovementByCsid(movementCsid); + String movementWorkflowState = getFieldValue(movementPayload, CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA, CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE); + + if (!movementWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED)) { + if (foundMovementCsid != null) { + return null; + } + + foundMovementCsid = movementCsid; + } + } + + return foundMovementCsid; + } + + protected PoxPayloadOut findByUri(String uri) throws URISyntaxException, DocumentException { + PoxPayloadOut payload = null; + String[] uriParts = uri.split("/"); + + if (uriParts.length == 3) { + String serviceName = uriParts[1]; + String csid = uriParts[2]; + + payload = findByCsid(serviceName, csid); + } + else if (uriParts.length == 5) { + String serviceName = uriParts[1]; + String vocabularyCsid = uriParts[2]; + String items = uriParts[3]; + String csid = uriParts[4]; + + if (items.equals("items")) { + payload = findAuthorityItemByCsid(serviceName, vocabularyCsid, csid); + } + } + + return payload; + } + + protected PoxPayloadOut findByCsid(String serviceName, String csid) throws URISyntaxException, DocumentException { + NuxeoBasedResource resource = (NuxeoBasedResource) getResourceMap().get(serviceName); + byte[] outputBytes = (byte[]) resource.get(null, createUriInfo(), csid).getEntity(); + + PoxPayloadOut payload = new PoxPayloadOut(outputBytes); + + return payload; + } + + protected PoxPayloadOut findCollectionObjectByCsid(String csid) throws URISyntaxException, DocumentException { + return findByCsid(CollectionObjectClient.SERVICE_NAME, csid); + } + + protected PoxPayloadOut findMovementByCsid(String csid) throws URISyntaxException, DocumentException { + return findByCsid(MovementClient.SERVICE_NAME, csid); + } + + protected List findAll(String serviceName, int pageSize, int pageNum) throws URISyntaxException, DocumentException { + return findAll(serviceName, pageSize, pageNum, null); + } + + protected List findAll(String serviceName, int pageSize, int pageNum, String sortBy) throws URISyntaxException, DocumentException { + NuxeoBasedResource resource = (NuxeoBasedResource) getResourceMap().get(serviceName); + + return findAll(resource, pageSize, pageNum, null); + } + + protected List findAll(NuxeoBasedResource resource, int pageSize, int pageNum, String sortBy) throws URISyntaxException, DocumentException { + AbstractCommonList list = resource.getList(createPagedListUriInfo(resource.getServiceName(), pageNum, pageSize, sortBy)); + List csids = new ArrayList(); + + if (list instanceof RelationsCommonList) { + for (RelationListItem item : ((RelationsCommonList) list).getRelationListItem()) { + csids.add(item.getCsid()); + } + } + else { + for (AbstractCommonList.ListItem item : list.getListItem()) { + for (org.w3c.dom.Element element : item.getAny()) { + + if (element.getTagName().equals("csid")) { + csids.add(element.getTextContent()); + break; + } + } + } + } + + return csids; + } + + protected List findAllCollectionObjects(int pageSize, int pageNum) throws URISyntaxException, DocumentException { + return findAll(CollectionObjectClient.SERVICE_NAME, pageSize, pageNum); + } + + protected List getVocabularyCsids(String serviceName) throws URISyntaxException { + AuthorityResource resource = (AuthorityResource) getResourceMap().get(serviceName); + + return getVocabularyCsids(resource); + } + + protected List getVocabularyCsids(AuthorityResource resource) throws URISyntaxException { + AbstractCommonList vocabularyList = resource.getAuthorityList(createDeleteFilterUriInfo()); + List csids = new ArrayList(); + + for (AbstractCommonList.ListItem item : vocabularyList.getListItem()) { + for (org.w3c.dom.Element element : item.getAny()) { + if (element.getTagName().equals("csid")) { + csids.add(element.getTextContent()); + break; + } + } + } + + return csids; + } + + protected List findAllAuthorityItems(String serviceName, String vocabularyCsid, int pageSize, int pageNum) throws URISyntaxException, DocumentException { + return findAllAuthorityItems(serviceName, vocabularyCsid, pageSize, pageNum, null); + } + + protected List findAllAuthorityItems(String serviceName, String vocabularyCsid, int pageSize, int pageNum, String sortBy) throws URISyntaxException, DocumentException { + AuthorityResource resource = (AuthorityResource) getResourceMap().get(serviceName); + + return findAllAuthorityItems(resource, vocabularyCsid, pageSize, pageNum, sortBy); + } + + protected List findAllAuthorityItems(AuthorityResource resource, String vocabularyCsid, int pageSize, int pageNum, String sortBy) throws URISyntaxException, DocumentException { + AbstractCommonList list = resource.getAuthorityItemList(vocabularyCsid, createPagedListUriInfo(resource.getServiceName(), pageNum, pageSize, sortBy)); + List csids = new ArrayList(); + + for (AbstractCommonList.ListItem item : list.getListItem()) { + for (org.w3c.dom.Element element : item.getAny()) { + + if (element.getTagName().equals("csid")) { + csids.add(element.getTextContent()); + break; + } + } + } + + return csids; + } + + protected PoxPayloadOut findAuthorityItemByCsid(String serviceName, String csid) throws URISyntaxException, DocumentException { + List vocabularyCsids = getVocabularyCsids(serviceName); + PoxPayloadOut itemPayload = null; + + for (String vocabularyCsid : vocabularyCsids) { + logger.debug("vocabularyCsid=" + vocabularyCsid); + + // FIXME: This throws DocumentNotFoundException, so will never go to the next vocabulary + itemPayload = findAuthorityItemByCsid(serviceName, vocabularyCsid, csid); + + if (itemPayload != null) { + break; + } + } + + return itemPayload; + } + + protected PoxPayloadOut findAuthorityItemByCsid(String serviceName, String vocabularyCsid, String csid) throws URISyntaxException, DocumentException { + AuthorityResource resource = (AuthorityResource) getResourceMap().get(serviceName); + byte[] response = resource.getAuthorityItem(null, createDeleteFilterUriInfo(), getResourceMap(), vocabularyCsid, csid); + + PoxPayloadOut payload = new PoxPayloadOut(response); + + return payload; + } + + protected String getAuthorityServiceNameForDocType(String authorityDocType) { + if (authorityServiceNamesByDocType == null) { + authorityServiceNamesByDocType = new HashMap(); + + for (String serviceName : getResourceMap().keySet()) { + AuthorityResource resource = (AuthorityResource) getResourceMap().get(serviceName); + + if (resource instanceof AuthorityResource) { + AuthorityResource authorityResource = (AuthorityResource) resource; + String docType = authorityResource.getItemDocType(getTenantId()); + + authorityServiceNamesByDocType.put(docType, serviceName); + } + } + } + + return authorityServiceNamesByDocType.get(authorityDocType); + } + + protected PoxPayloadOut findTaxonByCsid(String csid) throws URISyntaxException, DocumentException { + return findAuthorityItemByCsid(TaxonomyAuthorityClient.SERVICE_NAME, csid); + } + + protected PoxPayloadOut findAuthorityItemByShortId(String serviceName, String vocabularyShortId, String itemShortId) throws URISyntaxException, DocumentException { + AuthorityResource resource = (AuthorityResource) getResourceMap().get(serviceName); + byte[] response = resource.getAuthorityItem(null, createDeleteFilterUriInfo(), getResourceMap(), "urn:cspace:name(" + vocabularyShortId + ")", "urn:cspace:name(" + itemShortId + ")"); + + PoxPayloadOut payload = new PoxPayloadOut(response); + + return payload; + } + + protected PoxPayloadOut findAuthorityItemByRefName(String serviceName, String refName) throws URISyntaxException, DocumentException { + RefName.AuthorityItem item = RefName.AuthorityItem.parse(refName); + + String vocabularyShortId = item.getParentShortIdentifier(); + String itemShortId = item.getShortIdentifier(); + + return findAuthorityItemByShortId(serviceName, vocabularyShortId, itemShortId); + } + + protected PoxPayloadOut findPlaceByRefName(String refName) throws URISyntaxException, DocumentException { + return findAuthorityItemByRefName(PlaceAuthorityClient.SERVICE_NAME, refName); + } + + protected PoxPayloadOut findTaxonByRefName(String refName) throws URISyntaxException, DocumentException { + return findAuthorityItemByRefName(TaxonomyAuthorityClient.SERVICE_NAME, refName); + } + + protected List findReferencingFields(String serviceName, String parentCsid, String csid, String type, int pageNum, int pageSize) throws URISyntaxException { + logger.debug("findReferencingFields serviceName=" + serviceName + " parentCsid=" + parentCsid + " csid=" + csid + " type=" + type); + + AuthorityResource resource = (AuthorityResource) getResourceMap().get(serviceName); + UriTemplateRegistry uriTemplateRegistry = ServiceMain.getInstance().getUriTemplateRegistry(); + + // The pageNum and pageSize params don't work right for the refobj request. + // More items than the pageSize might be returned, and the next page may + // contain repeats of items already returned on the previous page. Any + // code that uses this function should be aware of this. + + AuthorityRefDocList refDocList = resource.getReferencingObjects(parentCsid, csid, uriTemplateRegistry, createRefSearchFilterUriInfo(type, pageNum, pageSize)); + + return refDocList.getAuthorityRefDocItem(); + } + + /** + * Finds records that reference a given authority item. Soft-deleted records are not included. + * + * @param serviceName The name of the authority service, e.g. "personauthorities" + * @param parentCsid The csid of the authority instance (aka vocabulary). This may be a guid or a urn, + * e.g. "7a4981c4-77b7-433b-8086", "urn:cspace:name(person)" + * @param csid The csid of the authority item. + * @param type The meta-type of the referencing record, e.g. "object", "procedure", "authority". + * The possible values are any of the ServiceBindingUtils.SERVICE_TYPE_* constants. + * Only referencing records that are of the specified type are returned. + * If null, all referencing records of type "object", "procedure", and "authority" + * are returned. + * @param sourceField The name of the source field in the referencing record, + * e.g. "collectionobjects_common:fieldCollector". + * Only records that reference the given item in the specified field are returned. + * If null, returns records that reference the item in any field. + * @return A List containing the csids of referencing records. + * @throws URISyntaxException + */ + protected List findReferencingObjects(String serviceName, String parentCsid, String csid, String type, String sourceField) throws URISyntaxException { + logger.debug("findReferencingObjects serviceName=" + serviceName + " parentCsid=" + parentCsid + " csid=" + csid + " type=" + type + " sourceField=" + sourceField); + + List items = findReferencingFields(serviceName, parentCsid, csid, type, 0, 0); + List csids = new ArrayList(); + + for (AuthorityRefDocList.AuthorityRefDocItem item : items) { + /* + * If a multivalue field contains a reference to the object multiple times, the referencing object + * seems to get returned multiple times in the list, but only the first has a non-null workflow state. + * A bug? Handle this by discarding list items with a null workflow state. + */ + + if (item.getWorkflowState() != null && !item.getWorkflowState().equals(WorkflowClient.WORKFLOWSTATE_DELETED) && (sourceField == null || item.getSourceField().equals(sourceField))) { + csids.add(item.getDocId()); + } + } + + return csids; + } + + protected List findReferencingObjects(String serviceName, String csid, String type, String sourceField) throws URISyntaxException, DocumentException { + logger.debug("findReferencingObjects serviceName=" + serviceName + " csid=" + csid + " type=" + type + " sourceField=" + sourceField); + + List vocabularyCsids = getVocabularyCsids(serviceName); + String parentCsid = null; + + if (vocabularyCsids.size() == 1) { + parentCsid = vocabularyCsids.get(0); + } + else { + for (String vocabularyCsid : vocabularyCsids) { + PoxPayloadOut itemPayload = findAuthorityItemByCsid(serviceName, vocabularyCsid, csid); + + if (itemPayload != null) { + parentCsid = vocabularyCsid; + break; + } + } + } + + return findReferencingObjects(serviceName, parentCsid, csid, type, sourceField); + } + + protected List findReferencingCollectionObjects(String serviceName, String csid, String sourceField) throws URISyntaxException, DocumentException { + return findReferencingObjects(serviceName, csid, ServiceBindingUtils.SERVICE_TYPE_OBJECT, sourceField); + } + + protected List findReferencingCollectionObjects(String serviceName, String vocabularyShortId, String csid, String sourceField) throws URISyntaxException, DocumentException { + return findReferencingObjects(serviceName, "urn:cspace:name(" + vocabularyShortId + ")", csid, ServiceBindingUtils.SERVICE_TYPE_OBJECT, sourceField); + } + + /** + * Create a stub UriInfo + * + * @throws URISyntaxException + */ + protected UriInfo createUriInfo() throws URISyntaxException { + return createUriInfo(""); + } + + protected UriInfo createUriInfo(String queryString) throws URISyntaxException { + return createUriInfo(queryString, Collections. emptyList()); + } + + protected UriInfo createUriInfo(String queryString, List pathSegments) throws URISyntaxException { + queryString = escapeQueryString(queryString); + + URI absolutePath = new URI(""); + URI baseUri = new URI(""); + + return new UriInfoImpl(absolutePath, baseUri, "", queryString, pathSegments); + } + + protected UriInfo createDeleteFilterUriInfo() throws URISyntaxException { + return createUriInfo("wf_deleted=false&pgSz=0"); + } + + protected UriInfo createKeywordSearchUriInfo(String schemaName, String fieldName, String value) throws URISyntaxException { + return createUriInfo("kw=&as=( (" +schemaName + ":" + fieldName + " ILIKE \"" + value + "\") )&wf_deleted=false&pgSz=0"); + } + + protected UriInfo createRelationSearchUriInfo(String subjectCsid, String subjectDocType, String predicate, String objectCsid, String objectDocType) throws URISyntaxException { + List queryParams = new ArrayList(6); + + if (StringUtils.isNotEmpty(subjectCsid)) { + queryParams.add(IRelationsManager.SUBJECT_QP + "=" + subjectCsid); + } + + if (StringUtils.isNotEmpty(subjectDocType)) { + queryParams.add(IRelationsManager.SUBJECT_TYPE_QP + "=" + subjectDocType); + } + + if (StringUtils.isNotEmpty(predicate)) { + queryParams.add(IRelationsManager.PREDICATE_QP + "=" + predicate); + } + + if (StringUtils.isNotEmpty(objectCsid)) { + queryParams.add(IRelationsManager.OBJECT_QP + "=" + objectCsid); + } + + if (StringUtils.isNotEmpty(objectDocType)) { + queryParams.add(IRelationsManager.OBJECT_TYPE_QP + "=" + objectDocType); + } + + queryParams.add("wf_deleted=false"); + queryParams.add("pgSz=0"); + + return createUriInfo(StringUtils.join(queryParams, "&")); + } + + protected UriInfo createRefSearchFilterUriInfo(String type) throws URISyntaxException { + return createRefSearchFilterUriInfo(type, 1, 0); + } + + protected UriInfo createRefSearchFilterUriInfo(String type, int pageNum, int pageSize) throws URISyntaxException { + String queryString = "wf_deleted=false&pgSz=" + pageSize + "&pgNum=" + pageNum; + + if (type != null) { + queryString = "type=" + type + "&" + queryString; + } + + return createUriInfo(queryString); + } + + protected UriInfo createPagedListUriInfo(String serviceName, int pageNum, int pageSize) throws URISyntaxException { + return createPagedListUriInfo(serviceName, pageNum, pageSize, null); + } + + protected UriInfo createPagedListUriInfo(String serviceName, int pageNum, int pageSize, String sortBy) throws URISyntaxException { + List pathSegments = new ArrayList(1); + pathSegments.add(new PathSegmentImpl(serviceName, false)); + + return createUriInfo("pgSz=" + pageSize + "&pgNum=" + pageNum + (sortBy != null ? "&sortBy=" + sortBy : "") + "&wf_deleted=false", pathSegments); + } + + protected String escapeQueryString(String queryString) throws URISyntaxException { + URI uri = new URI(null, null, null, queryString, null); + + return uri.getRawQuery(); + } + + /** + * Get a field value from a PoxPayloadOut, given a part name and xpath expression. + */ + protected String getFieldValue(PoxPayloadOut payload, String partLabel, String fieldPath) { + String value = null; + PayloadOutputPart part = payload.getPart(partLabel); + + if (part != null) { + Element element = part.asElement(); + Node node = element.selectSingleNode(fieldPath); + + if (node != null) { + value = node.getText(); + } + } + + return value; + } + + protected String getFieldValue(PoxPayloadOut payload, String fieldPath) { + String value = null; + + for (PayloadOutputPart part : payload.getParts()) { + Element element = part.asElement(); + Node node = element.selectSingleNode(fieldPath); + + if (node != null) { + value = node.getText(); + break; + } + } + + return value; + } + + protected boolean getBooleanFieldValue(PoxPayloadOut payload, String partLabel, String fieldPath) { + String value = getFieldValue(payload, partLabel, fieldPath); + + return (value != null && value.equals("true")); + } + + protected List getFieldValues(PoxPayloadOut payload, String partLabel, String fieldPath) { + List values = new ArrayList(); + PayloadOutputPart part = payload.getPart(partLabel); + + if (part != null) { + Element element = part.asElement(); + List nodes = element.selectNodes(fieldPath); + + if (nodes != null) { + for (Node node : nodes) { + values.add(node.getText()); + } + } + } + + return values; + } + + protected String getDisplayNameFromRefName(String refName) { + RefName.AuthorityItem item = RefName.AuthorityItem.parse(refName); + + return (item == null ? refName : item.displayName); + } + + protected String getCsid(PoxPayloadOut payload) { + String uri = getFieldValue(payload, CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA, CollectionSpaceClient.COLLECTIONSPACE_CORE_URI); + String[] elements = StringUtils.split(uri, '/'); + String csid = elements[elements.length - 1]; + + return csid; + } + + protected String getRefName(PoxPayloadOut payload) { + return getFieldValue(payload, CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA, CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME); + } + + protected class ResourceException extends Exception { + private static final long serialVersionUID = 1L; + + private Response response; + + public ResourceException(Response response, String message) { + super(message); + this.setResponse(response); + } + + public Response getResponse() { + return response; + } + + public void setResponse(Response response) { + this.response = response; + } + } +} diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/BatchDocumentModelHandler.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/BatchDocumentModelHandler.java index 5509ebf0c..4529bcf69 100644 --- a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/BatchDocumentModelHandler.java +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/BatchDocumentModelHandler.java @@ -58,10 +58,7 @@ public class BatchDocumentModelHandler extends NuxeoDocumentModelHandler ctx, String csid, - ResourceMap resourceMap, InvocationContext invocationCtx) throws Exception { - - CoreSessionInterface repoSession = null; - boolean releaseRepoSession = false; + ResourceMap resourceMap, InvocationContext invocationCtx) throws Exception { String invocationMode = invocationCtx.getMode(); String modeProperty = null; @@ -79,8 +76,11 @@ public class BatchDocumentModelHandler extends NuxeoDocumentModelHandler c = tccl.loadClass(className); + // enable validation assertions + tccl.setClassAssertionStatus(className, true); + if (!BatchInvocable.class.isAssignableFrom(c)) { + throw new RuntimeException("BatchResource: Class: " + className + " does not implement BatchInvocable!"); + } + + BatchInvocable batchInstance = (BatchInvocable) c.newInstance(); + List modes = batchInstance.getSupportedInvocationModes(); + if (!modes.contains(invocationMode)) { + throw new BadRequestException("BatchResource: Invoked with unsupported context mode: " + invocationMode); + } + + batchInstance.setInvocationContext(invocationCtx); + batchInstance.setServiceContext(ctx); + + if (resourceMap != null) { + batchInstance.setResourceMap(resourceMap); + } else { + resourceMap = ResteasyProviderFactory.getContextData(ResourceMap.class); + if (resourceMap != null) { + batchInstance.setResourceMap(resourceMap); + } else { + logger.warn("BatchResource.invoke did not get a resourceMapHolder in Context!"); + } + } + + batchInstance.run(); + int status = batchInstance.getCompletionStatus(); + if (status == Invocable.STATUS_ERROR) { + InvocationError error = batchInstance.getErrorInfo(); + if (error.getResponseCode() == BAD_REQUEST_STATUS) { + throw new BadRequestException("BatchResouce: batchProcess encountered error: " + + batchInstance.getErrorInfo()); + } else { + throw new RuntimeException("BatchResouce: batchProcess encountered error: " + + batchInstance.getErrorInfo()); + + } + } + + InvocationResults results = batchInstance.getResults(); + return results; } catch (PropertyException pe) { if (logger.isDebugEnabled()) { logger.debug("Property exception getting batch values: ", pe); @@ -125,50 +171,5 @@ public class BatchDocumentModelHandler extends NuxeoDocumentModelHandler c = tccl.loadClass(className); - // enable validation assertions - tccl.setClassAssertionStatus(className, true); - if (!BatchInvocable.class.isAssignableFrom(c)) { - throw new RuntimeException("BatchResource: Class: " + className + " does not implement BatchInvocable!"); - } - - BatchInvocable batchInstance = (BatchInvocable) c.newInstance(); - List modes = batchInstance.getSupportedInvocationModes(); - if (!modes.contains(invocationMode)) { - throw new BadRequestException("BatchResource: Invoked with unsupported context mode: " + invocationMode); - } - - batchInstance.setInvocationContext(invocationCtx); - batchInstance.setServiceContext(ctx); - - if (resourceMap != null) { - batchInstance.setResourceMap(resourceMap); - } else { - resourceMap = ResteasyProviderFactory.getContextData(ResourceMap.class); - if (resourceMap != null) { - batchInstance.setResourceMap(resourceMap); - } else { - logger.warn("BatchResource.invoke did not get a resourceMapHolder in Context!"); - } - } - - batchInstance.run(); - int status = batchInstance.getCompletionStatus(); - if (status == Invocable.STATUS_ERROR) { - InvocationError error = batchInstance.getErrorInfo(); - if (error.getResponseCode() == BAD_REQUEST_STATUS) { - throw new BadRequestException("BatchResouce: batchProcess encountered error: " - + batchInstance.getErrorInfo()); - } else { - throw new RuntimeException("BatchResouce: batchProcess encountered error: " - + batchInstance.getErrorInfo()); - - } - } - - InvocationResults results = batchInstance.getResults(); - return results; } } diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/ClearLocationLabelRequestBatchJob.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/ClearLocationLabelRequestBatchJob.java new file mode 100644 index 000000000..8be238954 --- /dev/null +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/ClearLocationLabelRequestBatchJob.java @@ -0,0 +1,120 @@ +package org.collectionspace.services.batch.nuxeo; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.ws.rs.core.UriInfo; + +import org.collectionspace.services.client.MovementClient; +import org.collectionspace.services.common.NuxeoBasedResource; +import org.collectionspace.services.common.invocable.InvocationResults; +import org.collectionspace.services.jaxb.AbstractCommonList; +import org.collectionspace.services.movement.MovementResource; +import org.collectionspace.services.movement.nuxeo.MovementBotGardenConstants; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ClearLocationLabelRequestBatchJob extends AbstractBatchJob { + final Logger logger = LoggerFactory.getLogger(ClearLocationLabelRequestBatchJob.class); + + public ClearLocationLabelRequestBatchJob() { + setSupportedInvocationModes(Arrays.asList(INVOCATION_MODE_SINGLE, INVOCATION_MODE_LIST, INVOCATION_MODE_NO_CONTEXT)); + } + + public void run() { + setCompletionStatus(STATUS_MIN_PROGRESS); + + try { + /* + * For now, treat any mode as if it were no context. + */ + + setResults(clearLabelRequests()); + setCompletionStatus(STATUS_COMPLETE); + } + catch(Exception e) { + setCompletionStatus(STATUS_ERROR); + setErrorInfo(new InvocationError(INT_ERROR_STATUS, e.getMessage())); + } + } + + public InvocationResults clearLabelRequests() throws URISyntaxException { + List movementCsids = findLabelRequests(); + InvocationResults results = null; + + if (movementCsids.size() > 0) { + results = clearLabelRequests(movementCsids); + } + else { + results = new InvocationResults(); + results.setUserNote("No label requests found"); + } + + return results; + } + + public InvocationResults clearLabelRequests(String movementCsid) throws URISyntaxException { + return clearLabelRequests(Arrays.asList(movementCsid)); + } + + public InvocationResults clearLabelRequests(List movementCsids) throws URISyntaxException { + InvocationResults results = new InvocationResults(); + long numAffected = 0; + + for (String movementCsid : movementCsids) { + clearLabelRequest(movementCsid); + numAffected = numAffected + 1; + } + + results.setNumAffected(numAffected); + results.setUserNote("Removed " + numAffected + " label " + (numAffected == 1 ? "request" : "requests")); + + return results; + } + + private void clearLabelRequest(String movementCsid) throws URISyntaxException { + logger.debug("clear label request: movementCsid=" + movementCsid); + + final String updatePayload = + "" + + "" + + "" + + getFieldXml("reasonForMove", MovementBotGardenConstants.OTHER_ACTION_CODE) + + "" + + "" + + getFieldXml("labelRequested", MovementBotGardenConstants.LABEL_REQUESTED_NO_VALUE) + + getFieldXml("labelSize", "") + + getFieldXml("labelStandType", "") + + getFieldXml("labelCount", "") + + "" + + ""; + + NuxeoBasedResource resource = (NuxeoBasedResource) getResourceMap().get(MovementClient.SERVICE_NAME); + resource.update(getResourceMap(), createUriInfo(), movementCsid, updatePayload); + } + + private List findLabelRequests() throws URISyntaxException { + List csids = new ArrayList(); + MovementResource movementResource = (MovementResource) getResourceMap().get(MovementClient.SERVICE_NAME); + AbstractCommonList movementList = movementResource.getList(createLabelRequestSearchUriInfo()); + + for (AbstractCommonList.ListItem item : movementList.getListItem()) { + for (org.w3c.dom.Element element : item.getAny()) { + if (element.getTagName().equals("csid")) { + csids.add(element.getTextContent()); + break; + } + } + } + + return csids; + } + + private UriInfo createLabelRequestSearchUriInfo() throws URISyntaxException { + return createKeywordSearchUriInfo(MovementBotGardenConstants.LABEL_REQUESTED_SCHEMA_NAME, MovementBotGardenConstants.LABEL_REQUESTED_FIELD_NAME, + MovementBotGardenConstants.LABEL_REQUESTED_YES_VALUE); + } +} \ No newline at end of file diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/ClearPotTagLabelRequestBatchJob.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/ClearPotTagLabelRequestBatchJob.java new file mode 100644 index 000000000..41821d3b3 --- /dev/null +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/ClearPotTagLabelRequestBatchJob.java @@ -0,0 +1,112 @@ +package org.collectionspace.services.batch.nuxeo; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.ws.rs.core.UriInfo; + +import org.collectionspace.services.client.PottagClient; +import org.collectionspace.services.common.NuxeoBasedResource; +import org.collectionspace.services.common.invocable.InvocationResults; +import org.collectionspace.services.jaxb.AbstractCommonList; +import org.collectionspace.services.pottag.PottagResource; +import org.collectionspace.services.pottag.nuxeo.PottagConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ClearPotTagLabelRequestBatchJob extends AbstractBatchJob { + final Logger logger = LoggerFactory.getLogger(ClearPotTagLabelRequestBatchJob.class); + + public ClearPotTagLabelRequestBatchJob() { + setSupportedInvocationModes(Arrays.asList(INVOCATION_MODE_SINGLE, INVOCATION_MODE_LIST, INVOCATION_MODE_NO_CONTEXT)); + } + + public void run() { + setCompletionStatus(STATUS_MIN_PROGRESS); + + try { + /* + * For now, treat any mode as if it were no context. + */ + + setResults(clearLabelRequests()); + setCompletionStatus(STATUS_COMPLETE); + } + catch(Exception e) { + setCompletionStatus(STATUS_ERROR); + setErrorInfo(new InvocationError(INT_ERROR_STATUS, e.getMessage())); + } + } + + public InvocationResults clearLabelRequests() throws URISyntaxException { + List potTagCsids = findLabelRequests(); + InvocationResults results = null; + + if (potTagCsids.size() > 0) { + results = clearLabelRequests(potTagCsids); + } + else { + results = new InvocationResults(); + results.setUserNote("No label requests found"); + } + + return results; + } + + public InvocationResults clearLabelRequests(String potTagCsid) throws URISyntaxException { + return clearLabelRequests(Arrays.asList(potTagCsid)); + } + + public InvocationResults clearLabelRequests(List potTagCsids) throws URISyntaxException { + InvocationResults results = new InvocationResults(); + long numAffected = 0; + + for (String potTagCsid : potTagCsids) { + clearLabelRequest(potTagCsid); + numAffected = numAffected + 1; + } + + results.setNumAffected(numAffected); + results.setUserNote("Removed " + numAffected + " label " + (numAffected == 1 ? "request" : "requests")); + + return results; + } + + private void clearLabelRequest(String potTagCsid) throws URISyntaxException { + logger.debug("clear label request: potTagCsid=" + potTagCsid); + + final String updatePayload = + "" + + "" + + "" + + getFieldXml(PottagConstants.LABEL_REQUESTED_FIELD_NAME, PottagConstants.LABEL_REQUESTED_NO_VALUE) + + "" + + ""; + + NuxeoBasedResource resource = (NuxeoBasedResource) getResourceMap().get(PottagClient.SERVICE_NAME); + resource.update(getResourceMap(), createUriInfo(), potTagCsid, updatePayload); + } + + private List findLabelRequests() throws URISyntaxException { + List csids = new ArrayList(); + PottagResource potTagResource = (PottagResource) getResourceMap().get(PottagClient.SERVICE_NAME); + AbstractCommonList potTagList = potTagResource.getList(createLabelRequestSearchUriInfo()); + + for (AbstractCommonList.ListItem item : potTagList.getListItem()) { + for (org.w3c.dom.Element element : item.getAny()) { + if (element.getTagName().equals("csid")) { + csids.add(element.getTextContent()); + break; + } + } + } + + return csids; + } + + private UriInfo createLabelRequestSearchUriInfo() throws URISyntaxException { + return createKeywordSearchUriInfo(PottagConstants.LABEL_REQUESTED_SCHEMA_NAME, PottagConstants.LABEL_REQUESTED_FIELD_NAME, PottagConstants.LABEL_REQUESTED_YES_VALUE); + } +} \ No newline at end of file diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/ClearVoucherLabelRequestBatchJob.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/ClearVoucherLabelRequestBatchJob.java new file mode 100644 index 000000000..98b8a4040 --- /dev/null +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/ClearVoucherLabelRequestBatchJob.java @@ -0,0 +1,113 @@ +package org.collectionspace.services.batch.nuxeo; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.ws.rs.core.UriInfo; + +import org.collectionspace.services.client.LoanoutClient; +import org.collectionspace.services.common.NuxeoBasedResource; +import org.collectionspace.services.common.invocable.InvocationResults; +import org.collectionspace.services.jaxb.AbstractCommonList; +import org.collectionspace.services.loanout.LoanoutResource; +import org.collectionspace.services.loanout.nuxeo.LoanoutBotGardenConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ClearVoucherLabelRequestBatchJob extends AbstractBatchJob { + final Logger logger = LoggerFactory.getLogger(ClearVoucherLabelRequestBatchJob.class); + + public ClearVoucherLabelRequestBatchJob() { + this.setSupportedInvocationModes(Arrays.asList(INVOCATION_MODE_SINGLE, INVOCATION_MODE_LIST, INVOCATION_MODE_NO_CONTEXT)); + } + + public void run() { + setCompletionStatus(STATUS_MIN_PROGRESS); + + try { + /* + * For now, treat any mode as if it were no context. + */ + + setResults(clearLabelRequests()); + setCompletionStatus(STATUS_COMPLETE); + } + catch(Exception e) { + setCompletionStatus(STATUS_ERROR); + setErrorInfo(new InvocationError(INT_ERROR_STATUS, e.getMessage())); + } + } + + public InvocationResults clearLabelRequests() throws URISyntaxException { + List loanoutCsids = findLabelRequests(); + InvocationResults results = null; + + if (loanoutCsids.size() > 0) { + results = clearLabelRequests(loanoutCsids); + } + else { + results = new InvocationResults(); + results.setUserNote("No label requests found"); + } + + return results; + } + + public InvocationResults clearLabelRequests(String loanoutCsid) throws URISyntaxException { + return clearLabelRequests(Arrays.asList(loanoutCsid)); + } + + public InvocationResults clearLabelRequests(List loanoutCsids) throws URISyntaxException { + InvocationResults results = new InvocationResults(); + long numAffected = 0; + + for (String loanoutCsid : loanoutCsids) { + clearLabelRequest(loanoutCsid); + numAffected = numAffected + 1; + } + + results.setNumAffected(numAffected); + results.setUserNote("Removed " + numAffected + " label " + (numAffected == 1 ? "request" : "requests")); + + return results; + } + + private void clearLabelRequest(String loanoutCsid) throws URISyntaxException { + logger.debug("clear label request: loanoutCsid=" + loanoutCsid); + + final String updatePayload = + "" + + "" + + "" + + getFieldXml("labelRequested", LoanoutBotGardenConstants.LABEL_REQUESTED_NO_VALUE) + + "" + + ""; + + NuxeoBasedResource resource = (NuxeoBasedResource) getResourceMap().get(LoanoutClient.SERVICE_NAME); + resource.update(getResourceMap(), createUriInfo(), loanoutCsid, updatePayload); + } + + private List findLabelRequests() throws URISyntaxException { + List csids = new ArrayList(); + LoanoutResource loanoutResource = (LoanoutResource) getResourceMap().get(LoanoutClient.SERVICE_NAME); + AbstractCommonList loanoutList = loanoutResource.getList(createLabelRequestSearchUriInfo()); + + for (AbstractCommonList.ListItem item : loanoutList.getListItem()) { + for (org.w3c.dom.Element element : item.getAny()) { + if (element.getTagName().equals("csid")) { + csids.add(element.getTextContent()); + break; + } + } + } + + return csids; + } + + private UriInfo createLabelRequestSearchUriInfo() throws URISyntaxException { + return createKeywordSearchUriInfo(LoanoutBotGardenConstants.LABEL_REQUESTED_SCHEMA_NAME, LoanoutBotGardenConstants.LABEL_REQUESTED_FIELD_NAME, + LoanoutBotGardenConstants.LABEL_REQUESTED_YES_VALUE); + } +} \ No newline at end of file diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/CreateAndLinkLoanOutBatchJob.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/CreateAndLinkLoanOutBatchJob.java index 1781753f1..b6053b06a 100644 --- a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/CreateAndLinkLoanOutBatchJob.java +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/CreateAndLinkLoanOutBatchJob.java @@ -5,28 +5,24 @@ import java.util.List; import javax.ws.rs.core.Response; -import org.collectionspace.services.batch.BatchInvocable; +import org.collectionspace.services.batch.AbstractBatchInvocable; import org.collectionspace.services.client.CollectionSpaceClientUtils; import org.collectionspace.services.common.NuxeoBasedResource; -import org.collectionspace.services.common.ResourceMap; import org.collectionspace.services.common.api.GregorianCalendarDateTimeUtils; -import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.invocable.InvocationContext; import org.collectionspace.services.common.invocable.InvocationResults; import org.collectionspace.services.client.LoanoutClient; import org.collectionspace.services.client.RelationClient; -public class CreateAndLinkLoanOutBatchJob implements BatchInvocable { +public class CreateAndLinkLoanOutBatchJob extends AbstractBatchInvocable { private static ArrayList invocationModes = null; private InvocationContext invocationCtx; - private ServiceContext ctx; private int completionStatus; - private ResourceMap resourceMap; private InvocationResults results; private InvocationError errorInfo; private final String RELATION_TYPE = "affects"; - private final String LOAN_DOCTYPE = "LoanOut"; + private final String LOAN_DOCTYPE = "LoanOut"; private final String RELATION_PREDICATE_DISP = "affects"; protected final int CREATED_STATUS = Response.Status.CREATED.getStatusCode(); protected final int BAD_REQUEST_STATUS = Response.Status.BAD_REQUEST.getStatusCode(); @@ -36,7 +32,6 @@ public class CreateAndLinkLoanOutBatchJob implements BatchInvocable { CreateAndLinkLoanOutBatchJob.setupClassStatics(); invocationCtx = null; completionStatus = STATUS_UNSTARTED; - resourceMap = null; results = new InvocationResults(); errorInfo = null; } @@ -56,16 +51,7 @@ public class CreateAndLinkLoanOutBatchJob implements BatchInvocable { return CreateAndLinkLoanOutBatchJob.invocationModes; } - @Override - public void setServiceContext(ServiceContext context) { - this.ctx = context; - } - @Override - public ServiceContext getServiceContext() { - return ctx; - } - @Override public InvocationContext getInvocationContext() { return invocationCtx; @@ -80,14 +66,6 @@ public class CreateAndLinkLoanOutBatchJob implements BatchInvocable { this.invocationCtx = context; } - /** - * Sets the invocation context for the batch job. Called before run(). - * @param invocationCtx an instance of InvocationContext. - */ - public void setResourceMap(ResourceMap resourceMap) { - this.resourceMap = resourceMap; - } - /** * The main work logic of the batch job. Will be called after setContext. */ @@ -151,8 +129,8 @@ public class CreateAndLinkLoanOutBatchJob implements BatchInvocable { // First, create the Loanout // We fetch the resource class by service name - NuxeoBasedResource resource = (NuxeoBasedResource) resourceMap.get( LoanoutClient.SERVICE_NAME); - Response response = resource.create(resourceMap, null, loanoutPayload); + NuxeoBasedResource resource = (NuxeoBasedResource) getResourceMap().get(LoanoutClient.SERVICE_NAME); + Response response = resource.create(getResourceMap(), null, loanoutPayload); if(response.getStatus() != CREATED_STATUS) { completionStatus = STATUS_ERROR; errorInfo = new InvocationError(INT_ERROR_STATUS, @@ -177,8 +155,8 @@ public class CreateAndLinkLoanOutBatchJob implements BatchInvocable { + ""+RELATION_TYPE+"" + ""+RELATION_PREDICATE_DISP+"" + ""; - NuxeoBasedResource resource = (NuxeoBasedResource) resourceMap.get(RelationClient.SERVICE_NAME); - Response response = resource.create(resourceMap, null, relationPayload); + NuxeoBasedResource resource = (NuxeoBasedResource) getResourceMap().get(RelationClient.SERVICE_NAME); + Response response = resource.create(getResourceMap(), null, relationPayload); if(response.getStatus() != CREATED_STATUS) { completionStatus = STATUS_ERROR; errorInfo = new InvocationError(INT_ERROR_STATUS, diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/CreateVoucherBatchJob.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/CreateVoucherBatchJob.java new file mode 100644 index 000000000..b6c7af9da --- /dev/null +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/CreateVoucherBatchJob.java @@ -0,0 +1,288 @@ +package org.collectionspace.services.batch.nuxeo; + +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; + +import org.apache.commons.lang.StringUtils; +import org.collectionspace.services.client.CollectionSpaceClientUtils; +import org.collectionspace.services.client.LoanoutClient; +import org.collectionspace.services.client.PoxPayloadOut; +import org.collectionspace.services.client.workflow.WorkflowClient; +import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectBotGardenConstants; +import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectConstants; +import org.collectionspace.services.common.NuxeoBasedResource; +import org.collectionspace.services.common.invocable.InvocationResults; +import org.collectionspace.services.common.relation.nuxeo.RelationConstants; +import org.collectionspace.services.loanout.nuxeo.LoanoutBotGardenConstants; +import org.collectionspace.services.loanout.nuxeo.LoanoutConstants; +import org.collectionspace.services.movement.nuxeo.MovementConstants; +import org.collectionspace.services.place.nuxeo.PlaceBotGardenConstants; +import org.collectionspace.services.place.nuxeo.PlaceConstants; +import org.dom4j.DocumentException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CreateVoucherBatchJob extends AbstractBatchJob { + final Logger logger = LoggerFactory.getLogger(CreateVoucherBatchJob.class); + + public CreateVoucherBatchJob() { + setSupportedInvocationModes(Arrays.asList(INVOCATION_MODE_SINGLE)); + } + + public void run() { + setCompletionStatus(STATUS_MIN_PROGRESS); + + try { + String mode = getInvocationContext().getMode(); + + if (!mode.equalsIgnoreCase(INVOCATION_MODE_SINGLE)) { + throw new Exception("Unsupported invocation mode: " + mode); + } + + String csid = getInvocationContext().getSingleCSID(); + + if (StringUtils.isEmpty(csid)) { + throw new Exception("Missing context csid"); + } + + String docType = getInvocationContext().getDocType(); + + if (docType.equals(CollectionObjectConstants.NUXEO_DOCTYPE)) { + setResults(createVoucherFromCataloging(csid)); + } + else if (docType.equals(MovementConstants.NUXEO_DOCTYPE)) { + setResults(createVoucherFromCurrentLocation(csid)); + } + else { + throw new Exception("Unsupported docType: " + docType); + } + + setCompletionStatus(STATUS_COMPLETE); + } + catch(Exception e) { + setCompletionStatus(STATUS_ERROR); + setErrorInfo(new InvocationError(INT_ERROR_STATUS, e.getMessage())); + } + } + + public InvocationResults createVoucherFromCataloging(String collectionObjectCsid) throws ResourceException, URISyntaxException, DocumentException { + return createVoucherFromCataloging(collectionObjectCsid, null); + } + + public InvocationResults createVoucherFromCataloging(String collectionObjectCsid, String movementCsid) throws ResourceException, URISyntaxException, DocumentException { + InvocationResults results = new InvocationResults(); + + PoxPayloadOut collectionObjectPayload = findCollectionObjectByCsid(collectionObjectCsid); + String collectionObjectWorkflowState = getFieldValue(collectionObjectPayload, CollectionObjectBotGardenConstants.WORKFLOW_STATE_SCHEMA_NAME, + CollectionObjectBotGardenConstants.WORKFLOW_STATE_FIELD_NAME); + + if (collectionObjectWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED)) { + logger.debug("skipping deleted collectionobject: collectionObjectCsid=" + collectionObjectCsid); + + results.setNumAffected(0); + results.setUserNote("skipped deleted record"); + } + else { + Map botGardenFields = new HashMap(); + + if (movementCsid == null) { + movementCsid = findSingleRelatedMovement(collectionObjectCsid); + } + + if (movementCsid != null) { + PoxPayloadOut movementPayload = findMovementByCsid(movementCsid); + + if (movementPayload != null) { + botGardenFields.put("gardenLocation", getFieldValue(movementPayload, MovementConstants.CURRENT_LOCATION_SCHEMA_NAME, MovementConstants.CURRENT_LOCATION_FIELD_NAME)); + } + } + + botGardenFields.put("fieldCollectionNote", getFieldCollectionNote(collectionObjectPayload)); + botGardenFields.put("annotation", getAnnotation(collectionObjectPayload)); + botGardenFields.put("labelRequested", LoanoutBotGardenConstants.LABEL_REQUESTED_NO_VALUE); + + Map naturalHistoryFields = new HashMap(); + naturalHistoryFields.put("numLent", "1"); + + String voucherCsid = createVoucher(botGardenFields, naturalHistoryFields); + logger.debug("voucher created: voucherCsid=" + voucherCsid); + + String forwardRelationCsid = createRelation(voucherCsid, LoanoutConstants.NUXEO_DOCTYPE, collectionObjectCsid, CollectionObjectConstants.NUXEO_DOCTYPE, RelationConstants.AFFECTS_TYPE); + String backwardRelationCsid = createRelation(collectionObjectCsid, CollectionObjectConstants.NUXEO_DOCTYPE, voucherCsid, LoanoutConstants.NUXEO_DOCTYPE, RelationConstants.AFFECTS_TYPE); + logger.debug("relations created: forwardRelationCsid=" + forwardRelationCsid + " backwardRelationCsid=" + backwardRelationCsid); + + results.setNumAffected(1); + results.setPrimaryURICreated("loanout.html?csid=" + voucherCsid); + results.setUserNote("Voucher created"); + } + + return results; + } + + private String getFieldCollectionNote(PoxPayloadOut collectionObjectPayload) throws URISyntaxException, DocumentException { + String placeNote = ""; + String reverseFieldCollectionPlace = getReverseFieldCollectionPlace(collectionObjectPayload); + + if (StringUtils.isNotBlank(reverseFieldCollectionPlace)) { + placeNote = reverseFieldCollectionPlace; + } + else { + String taxonomicRange = this.getFieldValue(collectionObjectPayload, CollectionObjectBotGardenConstants.TAXONOMIC_RANGE_SCHEMA_NAME, + CollectionObjectBotGardenConstants.TAXONOMIC_RANGE_FIELD_NAME); + + if (StringUtils.isNotBlank(taxonomicRange)) { + placeNote = "Geographic range " + taxonomicRange; + } + } + + String comment = this.getFieldValue(collectionObjectPayload, CollectionObjectBotGardenConstants.COMMENT_SCHEMA_NAME, + CollectionObjectBotGardenConstants.COMMENT_FIELD_NAME); + String collectionNote = ""; + + if (StringUtils.isNotBlank(placeNote) && StringUtils.isNotBlank(comment)) { + collectionNote = placeNote + ": " + comment; + } + else if (StringUtils.isNotBlank(placeNote)) { + collectionNote = placeNote; + } + else { + collectionNote = comment; + } + + return collectionNote; + } + + private String getReverseFieldCollectionPlace(PoxPayloadOut collectionObjectPayload) throws URISyntaxException, DocumentException { + String reverseDisplayName = null; + String fieldCollectionPlaceRefName = getFieldValue(collectionObjectPayload, CollectionObjectBotGardenConstants.FIELD_COLLECTION_PLACE_SCHEMA_NAME, + CollectionObjectBotGardenConstants.FIELD_COLLECTION_PLACE_FIELD_NAME); + + if (StringUtils.isNotBlank(fieldCollectionPlaceRefName)) { + PoxPayloadOut placePayload = null; + + try { + placePayload = findPlaceByRefName(fieldCollectionPlaceRefName); + } + catch (WebApplicationException e) { + logger.error("Error finding place: refName=" + fieldCollectionPlaceRefName, e); + } + + if (placePayload != null) { + List termTypes = getFieldValues(placePayload, PlaceConstants.TERM_TYPE_SCHEMA_NAME, PlaceConstants.TERM_TYPE_FIELD_NAME); + List displayNames = getFieldValues(placePayload, PlaceConstants.DISPLAY_NAME_SCHEMA_NAME, PlaceConstants.DISPLAY_NAME_FIELD_NAME); + + int index = termTypes.indexOf(PlaceBotGardenConstants.REVERSE_TERM_TYPE); + + if (index < 0) { + // There's no reverse term. Just use the primary. + + if (displayNames.size() > 0) { + reverseDisplayName = displayNames.get(0); + } + } + else { + reverseDisplayName = displayNames.get(index); + } + } + } + + if (reverseDisplayName == null) { + reverseDisplayName = ""; + } + + return reverseDisplayName; + } + + private String getAnnotation(PoxPayloadOut collectionObjectPayload) { + String annotation = ""; + String determinationKind = getFieldValue(collectionObjectPayload, CollectionObjectBotGardenConstants.DETERMINATION_KIND_SCHEMA_NAME, + CollectionObjectBotGardenConstants.DETERMINATION_KIND_FIELD_NAME); + + if (determinationKind.equals(CollectionObjectBotGardenConstants.DETERMINATION_KIND_DETERMINATION_VALUE)) { + String determinationBy = getDisplayNameFromRefName(getFieldValue(collectionObjectPayload, CollectionObjectBotGardenConstants.DETERMINATION_BY_SCHEMA_NAME, + CollectionObjectBotGardenConstants.DETERMINATION_BY_FIELD_NAME)); + + if (StringUtils.isNotBlank(determinationBy)) { + annotation += "det. by " + determinationBy; + + String determinationInstitution = getDisplayNameFromRefName(getFieldValue(collectionObjectPayload, CollectionObjectBotGardenConstants.DETERMINATION_INSTITUTION_SCHEMA_NAME, + CollectionObjectBotGardenConstants.DETERMINATION_INSTITUTION_FIELD_NAME)); + String determinationDate = getFieldValue(collectionObjectPayload, CollectionObjectBotGardenConstants.DETERMINATION_DATE_SCHEMA_NAME, + CollectionObjectBotGardenConstants.DETERMINATION_DATE_FIELD_NAME); + + if (StringUtils.isNotBlank(determinationInstitution)) { + annotation += ", " + determinationInstitution; + } + + if (StringUtils.isNotBlank(determinationDate)) { + annotation += ", " + determinationDate; + } + } + } + + return annotation; + } + + public InvocationResults createVoucherFromCurrentLocation(String movementCsid) throws ResourceException, URISyntaxException, DocumentException { + long numAffected = 0; + String primaryUriCreated = null; + + List collectionObjectCsids = findRelatedCollectionObjects(movementCsid); + + for (String collectionObjectCsid : collectionObjectCsids) { + InvocationResults innerResults = createVoucherFromCataloging(collectionObjectCsid, movementCsid); + + numAffected = numAffected + innerResults.getNumAffected(); + + if (primaryUriCreated == null) { + primaryUriCreated = innerResults.getPrimaryURICreated(); + } + } + + InvocationResults results = new InvocationResults(); + results.setNumAffected(numAffected); + results.setPrimaryURICreated(primaryUriCreated); + + if (collectionObjectCsids.size() == 0) { + results.setUserNote("No related cataloging record found"); + } + else { + results.setUserNote("Voucher created for " + numAffected + " cataloging " + (numAffected == 1 ? "record" : "records")); + } + + return results; + } + + private String createVoucher(Map botGardenFields, Map naturalHistoryFields) throws ResourceException { + String voucherCsid = null; + + String createVoucherPayload = + "" + + "" + + "" + + getFieldXml(botGardenFields) + + "" + + "" + + getFieldXml(naturalHistoryFields) + + "" + + ""; + + NuxeoBasedResource resource = (NuxeoBasedResource) getResourceMap().get(LoanoutClient.SERVICE_NAME); + Response response = resource.create(getResourceMap(), null, createVoucherPayload); + + if (response.getStatus() == CREATED_STATUS) { + voucherCsid = CollectionSpaceClientUtils.extractId(response); + } + else { + throw new ResourceException(response, "Error creating voucher"); + } + + return voucherCsid; + } +} diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/FormatTaxonBatchJob.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/FormatTaxonBatchJob.java new file mode 100644 index 000000000..f79694092 --- /dev/null +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/FormatTaxonBatchJob.java @@ -0,0 +1,145 @@ +package org.collectionspace.services.batch.nuxeo; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.lang.StringUtils; +import org.collectionspace.services.client.PayloadOutputPart; +import org.collectionspace.services.client.PoxPayloadOut; +import org.collectionspace.services.client.TaxonomyAuthorityClient; +import org.collectionspace.services.common.api.TaxonFormatter; +import org.collectionspace.services.common.invocable.InvocationResults; +import org.collectionspace.services.common.vocabulary.AuthorityResource; +import org.collectionspace.services.taxonomy.nuxeo.TaxonConstants; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.dom4j.Node; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class FormatTaxonBatchJob extends AbstractBatchJob { + final Logger logger = LoggerFactory.getLogger(FormatTaxonBatchJob.class); + + private TaxonFormatter taxonFormatter; + + public FormatTaxonBatchJob() { + setSupportedInvocationModes(Arrays.asList(INVOCATION_MODE_SINGLE, INVOCATION_MODE_LIST, INVOCATION_MODE_NO_CONTEXT)); + this.taxonFormatter = new TaxonFormatter(); + } + + public void run() { + setCompletionStatus(STATUS_MIN_PROGRESS); + + try { + String mode = getInvocationContext().getMode(); + + if (mode.equalsIgnoreCase(INVOCATION_MODE_SINGLE)) { + String csid = getInvocationContext().getSingleCSID(); + + if (StringUtils.isEmpty(csid)) { + throw new Exception("Missing context csid"); + } + + setResults(formatTaxon(csid)); + } + else if (mode.equalsIgnoreCase(INVOCATION_MODE_LIST)) { + setResults(formatTaxons(getInvocationContext().getListCSIDs().getCsid())); + } + else if (mode.equalsIgnoreCase(INVOCATION_MODE_NO_CONTEXT)) { + setResults(formatAllTaxons()); + } + else { + throw new Exception("Unsupported invocation mode: " + mode); + } + + setCompletionStatus(STATUS_COMPLETE); + } + catch(Exception e) { + setCompletionStatus(STATUS_ERROR); + setErrorInfo(new InvocationError(INT_ERROR_STATUS, e.getMessage())); + } + } + + public InvocationResults formatAllTaxons() throws URISyntaxException, DocumentException { + return formatTaxons(findAllTaxonRecords()); + } + + public InvocationResults formatTaxon(String taxonCsid) throws URISyntaxException, DocumentException { + return formatTaxons(Arrays.asList(taxonCsid)); + } + + public InvocationResults formatTaxons(List taxonCsids) throws URISyntaxException, DocumentException { + InvocationResults results = new InvocationResults(); + int numAffected = 0; + + for (String taxonCsid : taxonCsids) { + formatDisplayNames(taxonCsid); + + numAffected = numAffected + 1; + } + + results.setNumAffected(numAffected); + results.setUserNote("Updated " + numAffected + " taxonomy " + (numAffected == 1 ? "record" : "records")); + + return results; + } + + private List formatDisplayNames(String taxonCsid) throws URISyntaxException, DocumentException { + List formattedDisplayNames = new ArrayList(); + + PoxPayloadOut taxonPayload = findTaxonByCsid(taxonCsid); + String inAuthority = getFieldValue(taxonPayload, TaxonConstants.IN_AUTHORITY_SCHEMA_NAME, TaxonConstants.IN_AUTHORITY_FIELD_NAME); + + String[] displayNamePathElements = TaxonConstants.DISPLAY_NAME_FIELD_NAME.split("/"); + String termGroupListFieldName = displayNamePathElements[0]; + String termGroupFieldName = displayNamePathElements[1]; + String displayNameFieldName = displayNamePathElements[2]; + + String[] formattedDisplayNamePathElements = TaxonConstants.FORMATTED_DISPLAY_NAME_FIELD_NAME.split("/"); + String formattedDisplayNameFieldName = formattedDisplayNamePathElements[2]; + + PayloadOutputPart part = taxonPayload.getPart(TaxonConstants.DISPLAY_NAME_SCHEMA_NAME); + + if (part != null) { + Element element = part.asElement(); + Node termGroupListNode = element.selectSingleNode(termGroupListFieldName); + List termGroupElements = termGroupListNode.selectNodes(termGroupFieldName); + + for (Element termGroupElement : termGroupElements) { + Node displayNameNode = termGroupElement.selectSingleNode(displayNameFieldName); + String displayName = (displayNameNode == null) ? "" : displayNameNode.getText(); + String formattedDisplayName = taxonFormatter.format(displayName); + + Element formattedDisplayNameElement = (Element) termGroupElement.selectSingleNode(formattedDisplayNameFieldName); + + if (formattedDisplayNameElement == null) { + formattedDisplayNameElement = termGroupElement.addElement(formattedDisplayNameFieldName); + } + + formattedDisplayNameElement.setText(formattedDisplayName); + formattedDisplayNames.add(formattedDisplayName); + } + + String updatePayload = + "" + + "" + + "" + + termGroupListNode.asXML() + + "" + + ""; + + AuthorityResource resource = (AuthorityResource) getResourceMap().get(TaxonomyAuthorityClient.SERVICE_NAME); + resource.updateAuthorityItem(getResourceMap(), createUriInfo(), inAuthority, taxonCsid, updatePayload); + } + + return formattedDisplayNames; + } + + private List findAllTaxonRecords() { + // TODO + return Collections.emptyList(); + } +} diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/FormatVoucherNameBatchJob.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/FormatVoucherNameBatchJob.java new file mode 100644 index 000000000..afab83b22 --- /dev/null +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/FormatVoucherNameBatchJob.java @@ -0,0 +1,245 @@ +package org.collectionspace.services.batch.nuxeo; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.ws.rs.core.UriInfo; + +import org.apache.commons.lang.StringUtils; +import org.collectionspace.services.client.LoanoutClient; +import org.collectionspace.services.client.PoxPayloadOut; +import org.collectionspace.services.client.workflow.WorkflowClient; +import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectBotGardenConstants; +import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectConstants; +import org.collectionspace.services.common.NuxeoBasedResource; +import org.collectionspace.services.common.api.TaxonFormatter; +import org.collectionspace.services.common.invocable.InvocationResults; +import org.collectionspace.services.jaxb.AbstractCommonList; +import org.collectionspace.services.loanout.LoanoutResource; +import org.collectionspace.services.loanout.nuxeo.LoanoutBotGardenConstants; + +import org.dom4j.DocumentException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class FormatVoucherNameBatchJob extends AbstractBatchJob { + public static final String HYBRID_SEPARATOR = " x "; + + final Logger logger = LoggerFactory.getLogger(FormatVoucherNameBatchJob.class); + + private TaxonFormatter taxonFormatter; + + public FormatVoucherNameBatchJob() { + setSupportedInvocationModes(Arrays.asList(INVOCATION_MODE_SINGLE, INVOCATION_MODE_LIST, INVOCATION_MODE_NO_CONTEXT)); + this.taxonFormatter = new TaxonFormatter(); + } + + public void run() { + setCompletionStatus(STATUS_MIN_PROGRESS); + + try { + String mode = getInvocationContext().getMode(); + + if (mode.equalsIgnoreCase(INVOCATION_MODE_SINGLE)) { + String csid = getInvocationContext().getSingleCSID(); + + if (StringUtils.isEmpty(csid)) { + throw new Exception("Missing context csid"); + } + + setResults(formatVoucherName(csid)); + } + else if (mode.equalsIgnoreCase(INVOCATION_MODE_LIST)) { + setResults(formatVoucherNames(getInvocationContext().getListCSIDs().getCsid())); + } + else if (mode.equalsIgnoreCase(INVOCATION_MODE_NO_CONTEXT)) { + setResults(formatQueuedVoucherNames()); + } + else { + throw new Exception("Unsupported invocation mode: " + mode); + } + + setCompletionStatus(STATUS_COMPLETE); + } + catch(Exception e) { + setCompletionStatus(STATUS_ERROR); + setErrorInfo(new InvocationError(INT_ERROR_STATUS, e.getMessage())); + } + } + + public InvocationResults formatQueuedVoucherNames() throws URISyntaxException, DocumentException { + return formatVoucherNames(findLabelRequests()); + } + + public InvocationResults formatVoucherName(String voucherCsid) throws URISyntaxException, DocumentException { + return formatVoucherNames(Arrays.asList(voucherCsid)); + } + + public InvocationResults formatVoucherNames(List voucherCsids) throws URISyntaxException, DocumentException { + InvocationResults results = new InvocationResults(); + int numAffected = 0; + List formattedNames = new ArrayList(); + + for (String voucherCsid : voucherCsids) { + VoucherName name = getVoucherName(voucherCsid); + String formattedName = formatVoucherName(name); + + logger.debug("formattedName=" + formattedName); + + setStyledName(voucherCsid, formattedName); + + formattedNames.add(formattedName); + numAffected = numAffected + 1; + } + + results.setNumAffected(numAffected); + results.setUserNote("Updated " + numAffected + " " + (numAffected == 1 ? "voucher" : "vouchers") + (numAffected == 1 ? ": " + formattedNames.get(0) : "")); + + return results; + } + + private List findLabelRequests() throws URISyntaxException { + List csids = new ArrayList(); + LoanoutResource loanoutResource = (LoanoutResource) getResourceMap().get(LoanoutClient.SERVICE_NAME); + AbstractCommonList loanoutList = loanoutResource.getList(createLabelRequestSearchUriInfo()); + + for (AbstractCommonList.ListItem item : loanoutList.getListItem()) { + for (org.w3c.dom.Element element : item.getAny()) { + if (element.getTagName().equals("csid")) { + csids.add(element.getTextContent()); + break; + } + } + } + + return csids; + } + + public VoucherName getVoucherName(String voucherCsid) throws URISyntaxException, DocumentException { + VoucherName name = null; + List collectionObjectCsids = findRelatedCollectionObjects(voucherCsid); + PoxPayloadOut collectionObjectPayload = null; + + for (String candidateCsid : collectionObjectCsids) { + PoxPayloadOut candidatePayload = findCollectionObjectByCsid(candidateCsid); + String workflowState = getFieldValue(candidatePayload, CollectionObjectConstants.WORKFLOW_STATE_SCHEMA_NAME, CollectionObjectConstants.WORKFLOW_STATE_FIELD_NAME); + + if (!workflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED)) { + collectionObjectPayload = candidatePayload; + } + } + + if (collectionObjectPayload != null) { + name = new VoucherName(); + + name.setName(getDisplayNameFromRefName(getFieldValue(collectionObjectPayload, CollectionObjectBotGardenConstants.TAXON_SCHEMA_NAME, + CollectionObjectBotGardenConstants.TAXON_FIELD_NAME))); + name.setHybrid(getBooleanFieldValue(collectionObjectPayload, CollectionObjectBotGardenConstants.HYBRID_FLAG_SCHEMA_NAME, + CollectionObjectBotGardenConstants.HYBRID_FLAG_FIELD_NAME)); + + if (name.isHybrid()) { + List hybridParents = this.getFieldValues(collectionObjectPayload, CollectionObjectBotGardenConstants.HYBRID_PARENT_SCHEMA_NAME, + CollectionObjectBotGardenConstants.HYBRID_PARENT_FIELD_NAME); + List hybridQualifiers = this.getFieldValues(collectionObjectPayload, CollectionObjectBotGardenConstants.HYBRID_QUALIFIER_SCHEMA_NAME, + CollectionObjectBotGardenConstants.HYBRID_QUALIFIER_FIELD_NAME); + + int femaleIndex = hybridQualifiers.indexOf(CollectionObjectBotGardenConstants.HYBRID_QUALIFIER_FEMALE_VALUE); + int maleIndex = hybridQualifiers.indexOf(CollectionObjectBotGardenConstants.HYBRID_QUALIFIER_MALE_VALUE); + + if (femaleIndex >= 0) { + name.setFemaleParentName(getDisplayNameFromRefName(hybridParents.get(femaleIndex))); + } + + if (maleIndex >= 0) { + name.setMaleParentName(getDisplayNameFromRefName(hybridParents.get(maleIndex))); + } + } + } + + return name; + } + + public String formatVoucherName(VoucherName name) { + String formattedName = ""; + + if (name != null) { + if (name.isHybrid()) { + if (name.getFemaleParentName() != null) { + formattedName += taxonFormatter.format(name.getFemaleParentName()); + } + + formattedName += HYBRID_SEPARATOR; + + if (name.getMaleParentName() != null) { + formattedName += taxonFormatter.format(name.getMaleParentName()); + } + } + else { + if (name.getName() != null) { + formattedName = taxonFormatter.format(name.getName()); + } + } + } + + return formattedName; + } + + private void setStyledName(String loanoutCsid, String styledName) throws URISyntaxException { + final String updatePayload = + "" + + "" + + "" + + getFieldXml("styledName", styledName) + + "" + + ""; + + NuxeoBasedResource resource = (NuxeoBasedResource) getResourceMap().get(LoanoutClient.SERVICE_NAME); + resource.update(getResourceMap(), createUriInfo(), loanoutCsid, updatePayload); + } + + private UriInfo createLabelRequestSearchUriInfo() throws URISyntaxException { + return createKeywordSearchUriInfo(LoanoutBotGardenConstants.LABEL_REQUESTED_SCHEMA_NAME, LoanoutBotGardenConstants.LABEL_REQUESTED_FIELD_NAME, + LoanoutBotGardenConstants.LABEL_REQUESTED_YES_VALUE); + } + + public class VoucherName { + private boolean isHybrid = false; + private String name; + private String femaleParentName; + private String maleParentName; + + public boolean isHybrid() { + return isHybrid; + } + + public void setHybrid(boolean isHybrid) { + this.isHybrid = isHybrid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getFemaleParentName() { + return femaleParentName; + } + + public void setFemaleParentName(String femaleParentName) { + this.femaleParentName = femaleParentName; + } + + public String getMaleParentName() { + return maleParentName; + } + + public void setMaleParentName(String maleParentName) { + this.maleParentName = maleParentName; + } + } +} diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/MergeAuthorityItemsBatchJob.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/MergeAuthorityItemsBatchJob.java new file mode 100644 index 000000000..cc23a47c5 --- /dev/null +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/MergeAuthorityItemsBatchJob.java @@ -0,0 +1,636 @@ +package org.collectionspace.services.batch.nuxeo; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.collectionspace.services.client.PayloadOutputPart; +import org.collectionspace.services.client.PoxPayloadOut; +import org.collectionspace.services.client.RelationClient; +import org.collectionspace.services.client.workflow.WorkflowClient; +import org.collectionspace.services.common.NuxeoBasedResource; +import org.collectionspace.services.common.api.RefNameUtils; +import org.collectionspace.services.common.api.RefNameUtils.AuthorityTermInfo; +import org.collectionspace.services.common.authorityref.AuthorityRefDocList; +import org.collectionspace.services.common.invocable.InvocationContext.Params.Param; +import org.collectionspace.services.common.invocable.InvocationResults; +import org.collectionspace.services.common.relation.RelationResource; +import org.collectionspace.services.common.vocabulary.AuthorityResource; +import org.collectionspace.services.relation.RelationsCommonList; +import org.collectionspace.services.relation.RelationsCommonList.RelationListItem; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; +import org.dom4j.Node; +import org.nuxeo.common.utils.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A batch job that merges authority items. The single and list contexts are + * supported. + * + * The merge target is a record into which one or more source records will be + * merged. A merge source is a record that will be merged into the target, as + * follows: Each term in a source record is added to the target as a non- + * preferred term, if that term does not already exist in the target. If a term + * in the source already exists in the target, each non-blank term field is + * copied to the target, if that field is empty in the target. If the field is + * non-empty in the target, and differs from the source field, a warning is + * emitted and no action is taken. If a source is successfully merged into the + * target, all references to the source are transferred to the target, and the + * source record is soft-deleted. + * + * The context (singleCSID or listCSIDs of the batch invocation payload + * specifies the source record(s). + * + * The following parameters are allowed: + * + * targetCSID: The csid of the target record. Only one target may be supplied. + * + * @author ray + */ +public class MergeAuthorityItemsBatchJob extends AbstractBatchJob { + final Logger logger = LoggerFactory.getLogger(MergeAuthorityItemsBatchJob.class); + + public MergeAuthorityItemsBatchJob() { + setSupportedInvocationModes(Arrays.asList(INVOCATION_MODE_SINGLE, INVOCATION_MODE_LIST)); + } + + @Override + public void run() { + setCompletionStatus(STATUS_MIN_PROGRESS); + + try { + String docType = null; + String targetCsid = null; + List sourceCsids = new ArrayList(); + + for (Param param : this.getParams()) { + String key = param.getKey(); + + // I don't want this batch job to appear in the UI, since it won't run successfully without parameters. + // That means it can't be registered with any docType. But if the invocation payload contains a docType, + // it will be checked against the null registered docType, and will fail. So docType should be passed as a + // parameter instead. + + if (key.equals("docType")) { + docType = param.getValue(); + } + else if (key.equals("targetCSID")) { + targetCsid = param.getValue(); + } + else if (key.equals("sourceCSID")) { + sourceCsids.add(param.getValue()); + } + } + + if (docType == null || docType.equals("")) { + throw new Exception("a docType must be supplied"); + } + + if (targetCsid == null || targetCsid.equals("")) { + throw new Exception("a target csid parameter (targetCSID) must be supplied"); + } + + if (sourceCsids.size() == 0) { + throw new Exception("a source csid must be supplied"); + } + + InvocationResults results = merge(docType, targetCsid, sourceCsids); + + setResults(results); + setCompletionStatus(STATUS_COMPLETE); + } + catch (Exception e) { + setCompletionStatus(STATUS_ERROR); + setErrorInfo(new InvocationError(INT_ERROR_STATUS, e.getMessage())); + } + } + + public InvocationResults merge(String docType, String targetCsid, String sourceCsid) throws URISyntaxException, DocumentException { + return merge(docType, targetCsid, Arrays.asList(sourceCsid)); + } + + public InvocationResults merge(String docType, String targetCsid, List sourceCsids) throws URISyntaxException, DocumentException { + logger.debug("Merging docType=" + docType + " targetCsid=" + targetCsid + " sourceCsids=" + StringUtils.join(sourceCsids, ",")); + + String serviceName = getAuthorityServiceNameForDocType(docType); + + PoxPayloadOut targetItemPayload = findAuthorityItemByCsid(serviceName, targetCsid); + List sourceItemPayloads = new ArrayList(); + + for (String sourceCsid : sourceCsids) { + sourceItemPayloads.add(findAuthorityItemByCsid(serviceName, sourceCsid)); + } + + return merge(docType, targetItemPayload, sourceItemPayloads); + } + + private InvocationResults merge(String docType, PoxPayloadOut targetItemPayload, List sourceItemPayloads) throws URISyntaxException, DocumentException { + int numAffected = 0; + List userNotes = new ArrayList(); + + Element targetTermGroupListElement = getTermGroupListElement(targetItemPayload); + Element mergedTermGroupListElement = targetTermGroupListElement.createCopy(); + + String targetCsid = getCsid(targetItemPayload); + String targetRefName = getRefName(targetItemPayload); + String inAuthority = getFieldValue(targetItemPayload, "inAuthority"); + + logger.debug("Merging term groups"); + + for (PoxPayloadOut sourceItemPayload : sourceItemPayloads) { + String sourceCsid = getCsid(sourceItemPayload); + Element sourceTermGroupListElement = getTermGroupListElement(sourceItemPayload); + + logger.debug("Merging term groups from source " + sourceCsid + " into target " + targetCsid); + + try { + mergeTermGroupLists(mergedTermGroupListElement, sourceTermGroupListElement); + } + catch(RuntimeException e) { + throw new RuntimeException("Error merging source record " + sourceCsid + " into target record " + targetCsid + ": " + e.getMessage(), e); + } + } + + logger.debug("Updating target: docType=" + docType + " inAuthority=" + inAuthority + " targetCsid=" + targetCsid); + + updateAuthorityItem(docType, inAuthority, targetCsid, getUpdatePayload(targetTermGroupListElement, mergedTermGroupListElement)); + + userNotes.add("The target record with CSID " + targetCsid + " (" + targetRefName + ") was updated."); + numAffected++; + + String serviceName = getAuthorityServiceNameForDocType(docType); + + logger.debug("Updating references"); + + for (PoxPayloadOut sourceItemPayload : sourceItemPayloads) { + String sourceCsid = getCsid(sourceItemPayload); + String sourceRefName = getRefName(sourceItemPayload); + + InvocationResults results = updateReferences(serviceName, inAuthority, sourceCsid, sourceRefName, targetRefName); + + userNotes.add(results.getUserNote()); + numAffected += results.getNumAffected(); + } + + logger.debug("Deleting source items"); + + for (PoxPayloadOut sourceItemPayload : sourceItemPayloads) { + String sourceCsid = getCsid(sourceItemPayload); + String sourceRefName = getRefName(sourceItemPayload); + + InvocationResults results = deleteAuthorityItem(docType, getFieldValue(sourceItemPayload, "inAuthority"), sourceCsid); + + userNotes.add(results.getUserNote()); + numAffected += results.getNumAffected(); + } + + InvocationResults results = new InvocationResults(); + results.setNumAffected(numAffected); + results.setUserNote(StringUtils.join(userNotes, "\n")); + + return results; + } + + private InvocationResults updateReferences(String serviceName, String inAuthority, String sourceCsid, String sourceRefName, String targetRefName) throws URISyntaxException, DocumentException { + logger.debug("Updating references: serviceName=" + serviceName + " inAuthority=" + inAuthority + " sourceCsid=" + sourceCsid + " sourceRefName=" + sourceRefName + " targetRefName=" + targetRefName); + + int pageNum = 0; + int pageSize = 100; + List items; + + int loopCount = 0; + int numUpdated = 0; + + logger.debug("Looping with pageSize=" + pageSize); + + do { + loopCount++; + + // The pageNum/pageSize parameters don't work properly for refobj requests! + // It should be safe to repeatedly fetch page 0 for a large-ish page size, + // and update that page, until no references are left. + + items = findReferencingFields(serviceName, inAuthority, sourceCsid, null, pageNum, pageSize); + Map referencingRecordsByCsid = new LinkedHashMap(); + + logger.debug("Loop " + loopCount + ": " + items.size() + " items found"); + + for (AuthorityRefDocList.AuthorityRefDocItem item : items) { + // If a record contains a reference to the record multiple times, multiple items are returned, + // but only the first has a non-null workflow state. A bug? + + String itemCsid = item.getDocId(); + ReferencingRecord record = referencingRecordsByCsid.get(itemCsid); + + if (record == null) { + if (item.getWorkflowState() != null && !item.getWorkflowState().equals(WorkflowClient.WORKFLOWSTATE_DELETED)) { + record = new ReferencingRecord(item.getUri()); + referencingRecordsByCsid.put(itemCsid, record); + } + } + + if (record != null) { + String[] sourceFieldElements = item.getSourceField().split(":"); + String partName = sourceFieldElements[0]; + String fieldName = sourceFieldElements[1]; + + Map> fields = record.getFields(); + Set fieldsInPart = fields.get(partName); + + if (fieldsInPart == null) { + fieldsInPart = new HashSet(); + fields.put(partName, fieldsInPart); + } + + fieldsInPart.add(fieldName); + } + } + + List referencingRecords = new ArrayList(referencingRecordsByCsid.values()); + + logger.debug("Loop " + loopCount + ": updating " + referencingRecords.size() + " records"); + + for (ReferencingRecord record : referencingRecords) { + InvocationResults results = updateReferencingRecord(record, sourceRefName, targetRefName); + numUpdated += results.getNumAffected(); + } + } + while (items.size() > 0); + + InvocationResults results = new InvocationResults(); + results.setNumAffected(numUpdated); + results.setUserNote(numUpdated > 0 ? + numUpdated + " records that referenced the source record with CSID " + sourceCsid + " were updated." : + "No records referenced the source record with CSID " + sourceCsid + "."); + + return results; + } + + private InvocationResults updateReferencingRecord(ReferencingRecord record, String fromRefName, String toRefName) throws URISyntaxException, DocumentException { + String fromRefNameStem = RefNameUtils.stripAuthorityTermDisplayName(fromRefName); + // String toRefNameStem = RefNameUtils.stripAuthorityTermDisplayName(toRefName); + + logger.debug("Updating references: record.uri=" + record.getUri() + " fromRefName=" + fromRefName + " toRefName=" + toRefName); + + Map> fields = record.getFields(); + + PoxPayloadOut recordPayload = findByUri(record.getUri()); + Document recordDocument = recordPayload.getDOMDocument(); + Document newDocument = (Document) recordDocument.clone(); + Element rootElement = newDocument.getRootElement(); + + for (Element partElement : (List) rootElement.elements()) { + String partName = partElement.getName(); + + if (fields.containsKey(partName)) { + for (String fieldName : fields.get(partName)) { + List nodes = partElement.selectNodes("descendant::" + fieldName); + + for (Node node : nodes) { + String text = node.getText(); + String refNameStem = null; + + try { + refNameStem = RefNameUtils.stripAuthorityTermDisplayName(text); + } + catch(IllegalArgumentException e) {} + + if (refNameStem != null && refNameStem.equals(fromRefNameStem)) { + AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(text); + // String newRefName = toRefNameStem + "'" + termInfo.displayName + "'"; + String newRefName = toRefName; + + node.setText(newRefName); + } + } + } + } + else { + rootElement.remove(partElement); + } + } + + String payload = newDocument.asXML(); + + return updateUri(record.getUri(), payload); + } + + private InvocationResults updateUri(String uri, String payload) throws URISyntaxException { + String[] uriParts = uri.split("/"); + + if (uriParts.length == 3) { + String serviceName = uriParts[1]; + String csid = uriParts[2]; + + NuxeoBasedResource resource = (NuxeoBasedResource) getResourceMap().get(serviceName); + + resource.update(getResourceMap(), createUriInfo(), csid, payload); + } + else if (uriParts.length == 5) { + String serviceName = uriParts[1]; + String vocabularyCsid = uriParts[2]; + String items = uriParts[3]; + String csid = uriParts[4]; + + if (items.equals("items")) { + AuthorityResource resource = (AuthorityResource) getResourceMap().get(serviceName); + + resource.updateAuthorityItem(getResourceMap(), createUriInfo(), vocabularyCsid, csid, payload); + } + } + else { + throw new IllegalArgumentException("Invalid uri " + uri); + } + + logger.debug("Updated referencing record " + uri); + + InvocationResults results = new InvocationResults(); + results.setNumAffected(1); + results.setUserNote("Updated referencing record " + uri); + + return results; + } + + private void updateAuthorityItem(String docType, String inAuthority, String csid, String payload) throws URISyntaxException { + String serviceName = getAuthorityServiceNameForDocType(docType); + AuthorityResource resource = (AuthorityResource) getResourceMap().get(serviceName); + + resource.updateAuthorityItem(getResourceMap(), createUriInfo(), inAuthority, csid, payload); + } + + private InvocationResults deleteAuthorityItem(String docType, String inAuthority, String csid) throws URISyntaxException { + int numAffected = 0; + List userNotes = new ArrayList(); + + // If the item is the broader context of any items, warn and do nothing. + + List narrowerItemCsids = findNarrower(csid); + + if (narrowerItemCsids.size() > 0) { + logger.debug("Item " + csid + " has narrower items -- not deleting"); + + userNotes.add("The source record with CSID " + csid + " was not deleted because it has narrower context items."); + } + else { + // If the item has a broader context, delete the relation. + + List relationCsids = new ArrayList(); + + for (RelationsCommonList.RelationListItem item : findRelated(csid, null, "hasBroader", null, null)) { + relationCsids.add(item.getCsid()); + } + + if (relationCsids.size() > 0) { + RelationResource relationResource = (RelationResource) getResourceMap().get(RelationClient.SERVICE_NAME); + + for (String relationCsid : relationCsids) { + logger.debug("Deleting hasBroader relation " + relationCsid); + + relationResource.delete(relationCsid); + + userNotes.add("The broader relation with CSID " + relationCsid + " was deleted."); + numAffected++; + } + } + + String serviceName = getAuthorityServiceNameForDocType(docType); + AuthorityResource resource = (AuthorityResource) getResourceMap().get(serviceName); + + logger.debug("Soft deleting: docType=" + docType + " inAuthority=" + inAuthority + " csid=" + csid); + + resource.updateItemWorkflowWithTransition(null, inAuthority, csid, "delete"); + + userNotes.add("The source record with CSID " + csid + " was soft deleted."); + numAffected++; + } + + InvocationResults results = new InvocationResults(); + results.setNumAffected(numAffected); + results.setUserNote(StringUtils.join(userNotes, "\n")); + + return results; + } + + /** + * @param Returns a map of the term groups in term group list, keyed by display name. + * If multiple groups have the same display name, an exception is thrown. + * @return The term groups. + */ + private Map getTermGroups(Element termGroupListElement) { + Map termGroups = new LinkedHashMap(); + Iterator childIterator = termGroupListElement.elementIterator(); + + while (childIterator.hasNext()) { + Element termGroupElement = childIterator.next(); + String displayName = getDisplayName(termGroupElement); + + if (termGroups.containsKey(displayName)) { + // Two term groups in the same item have identical display names. + + throw new RuntimeException("multiple terms have display name \"" + displayName + "\""); + } + else { + termGroups.put(displayName, termGroupElement); + } + } + + return termGroups; + } + + private String getDisplayName(Element termGroupElement) { + Node displayNameNode = termGroupElement.selectSingleNode("termDisplayName"); + String displayName = (displayNameNode == null) ? "" : displayNameNode.getText(); + + return displayName; + } + + private Element getTermGroupListElement(PoxPayloadOut itemPayload) { + Element termGroupListElement = null; + Element commonPartElement = findCommonPartElement(itemPayload); + + if (commonPartElement != null) { + termGroupListElement = findTermGroupListElement(commonPartElement); + } + + return termGroupListElement; + } + + private Element findCommonPartElement(PoxPayloadOut itemPayload) { + Element commonPartElement = null; + + for (PayloadOutputPart candidatePart : itemPayload.getParts()) { + Element candidatePartElement = candidatePart.asElement(); + + if (candidatePartElement.getName().endsWith("_common")) { + commonPartElement = candidatePartElement; + break; + } + } + + return commonPartElement; + } + + private Element findTermGroupListElement(Element contextElement) { + Element termGroupListElement = null; + Iterator childIterator = contextElement.elementIterator(); + + while (childIterator.hasNext()) { + Element candidateElement = childIterator.next(); + + if (candidateElement.getName().endsWith("TermGroupList")) { + termGroupListElement = candidateElement; + break; + } + } + + return termGroupListElement; + } + + private void mergeTermGroupLists(Element targetTermGroupListElement, Element sourceTermGroupListElement) { + Map sourceTermGroups; + + try { + sourceTermGroups = getTermGroups(sourceTermGroupListElement); + } + catch(RuntimeException e) { + throw new RuntimeException("a problem was found in the source record: " + e.getMessage(), e); + } + + for (Element targetTermGroupElement : (List) targetTermGroupListElement.elements()) { + String displayName = getDisplayName(targetTermGroupElement); + + if (sourceTermGroups.containsKey(displayName)) { + logger.debug("Merging in existing term \"" + displayName + "\""); + + try { + mergeTermGroups(targetTermGroupElement, sourceTermGroups.get(displayName)); + } + catch(RuntimeException e) { + throw new RuntimeException("could not merge term groups with display name \"" + displayName + "\": " + e.getMessage(), e); + } + + sourceTermGroups.remove(displayName); + } + } + + for (Element sourceTermGroupElement : sourceTermGroups.values()) { + logger.debug("Adding new term \"" + getDisplayName(sourceTermGroupElement) + "\""); + + targetTermGroupListElement.add(sourceTermGroupElement.createCopy()); + } + } + + private void mergeTermGroups(Element targetTermGroupElement, Element sourceTermGroupElement) { + // This function assumes there are no nested repeating groups. + + for (Element sourceChildElement : (List) sourceTermGroupElement.elements()) { + String sourceValue = sourceChildElement.getText(); + + if (sourceValue == null) { + sourceValue = ""; + } + + if (sourceValue.length() > 0) { + String name = sourceChildElement.getName(); + Element targetChildElement = targetTermGroupElement.element(name); + + if (targetChildElement == null) { + targetTermGroupElement.add(sourceChildElement.createCopy()); + } + else { + String targetValue = targetChildElement.getText(); + + if (targetValue == null) { + targetValue = ""; + } + + if (!targetValue.equals(sourceValue)) { + if (targetValue.length() > 0) { + throw new RuntimeException("merge conflict in field " + name + ": source value \"" + sourceValue + "\" differs from target value \"" + targetValue +"\""); + } + + targetTermGroupElement.remove(targetChildElement); + targetTermGroupElement.add(sourceChildElement.createCopy()); + } + } + } + } + } + + private String getUpdatePayload(Element originalTermGroupListElement, Element updatedTermGroupListElement) { + List parents = new ArrayList(); + + for (Element e = originalTermGroupListElement; e != null; e = e.getParent()) { + parents.add(e); + } + + Collections.reverse(parents); + + // Remove the original termGroupList element + parents.remove(parents.size() - 1); + + // Remove the root + Element rootElement = parents.remove(0); + + // Copy the root to a new document + Document document = DocumentHelper.createDocument(copyElement(rootElement)); + Element current = document.getRootElement(); + + // Copy the remaining parents + for (Element parent : parents) { + Element parentCopy = copyElement(parent); + + current.add(parentCopy); + current = parentCopy; + } + + // Add the updated termGroupList element + + current.add(updatedTermGroupListElement); + + String payload = document.asXML(); + + return payload; + } + + private Element copyElement(Element element) { + Element copy = DocumentHelper.createElement(element.getQName()); + copy.appendAttributes(element); + + return copy; + } + + private class ReferencingRecord { + private String uri; + private Map> fields; + + public ReferencingRecord(String uri) { + this.uri = uri; + this.fields = new HashMap>(); + } + + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } + + public Map> getFields() { + return fields; + } + } +} \ No newline at end of file diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/ReindexFullTextBatchJob.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/ReindexFullTextBatchJob.java new file mode 100644 index 000000000..d4350e085 --- /dev/null +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/ReindexFullTextBatchJob.java @@ -0,0 +1,694 @@ +/* + * This file contains code from Florent Guillame's nuxeo-reindex-fulltext module. + * + */ + +package org.collectionspace.services.batch.nuxeo; + +import java.io.File; +import java.io.Serializable; +import java.lang.reflect.Field; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.collectionspace.services.client.PoxPayloadIn; +import org.collectionspace.services.client.PoxPayloadOut; +import org.collectionspace.services.common.CollectionSpaceResource; +import org.collectionspace.services.common.NuxeoBasedResource; +import org.collectionspace.services.common.StoredValuesUriTemplate; +import org.collectionspace.services.common.UriTemplateFactory; +import org.collectionspace.services.common.UriTemplateRegistryKey; +import org.collectionspace.services.common.invocable.InvocationContext.ListCSIDs; +import org.collectionspace.services.common.invocable.InvocationContext.Params.Param; +import org.collectionspace.services.common.invocable.InvocationResults; +import org.collectionspace.services.common.vocabulary.AuthorityResource; +import org.collectionspace.services.nuxeo.util.ReindexFulltextRoot.ReindexInfo; +import org.nuxeo.ecm.core.api.AbstractSession; +import org.nuxeo.ecm.core.api.ClientException; +import org.nuxeo.ecm.core.api.CoreSession; +import org.nuxeo.ecm.core.api.IterableQueryResult; +import org.nuxeo.ecm.core.api.NuxeoException; +import org.nuxeo.ecm.core.api.NuxeoPrincipal; +import org.nuxeo.ecm.core.event.EventService; +import org.nuxeo.ecm.core.query.QueryFilter; +import org.nuxeo.ecm.core.query.sql.NXQL; +import org.nuxeo.ecm.core.storage.FulltextConfiguration; +import org.nuxeo.ecm.core.storage.sql.Model; +import org.nuxeo.ecm.core.storage.sql.Node; +import org.nuxeo.ecm.core.storage.sql.Session; +import org.nuxeo.ecm.core.storage.sql.SimpleProperty; +import org.nuxeo.ecm.core.storage.sql.coremodel.SQLFulltextExtractorWork; +import org.nuxeo.ecm.core.storage.sql.coremodel.SQLSession; +import org.nuxeo.ecm.core.work.api.Work; +import org.nuxeo.ecm.core.work.api.WorkManager; +import org.nuxeo.ecm.core.work.api.WorkManager.Scheduling; +import org.nuxeo.runtime.api.Framework; +import org.nuxeo.runtime.transaction.TransactionHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ReindexFullTextBatchJob extends AbstractBatchJob { + final Logger log = LoggerFactory.getLogger(ReindexFullTextBatchJob.class); + + public static final String DC_TITLE = "dc:title"; + public static final int DEFAULT_BATCH_SIZE = 1000; + public static final int DEFAULT_START_BATCH = 0; + public static final int DEFAULT_END_BATCH = 0; + public static final int DEFAULT_BATCH_PAUSE = 0; + public static final String BATCH_STOP_FILE = "stopBatch"; + public static final String DOCTYPE_STOP_FILE = "stopDocType"; + + private int batchSize = DEFAULT_BATCH_SIZE; + private int batchPause = DEFAULT_BATCH_PAUSE; + private int startBatch = DEFAULT_START_BATCH; + private int endBatch = DEFAULT_END_BATCH; + private int numAffected = 0; + + private String stopFileDirectory; + + private CoreSession coreSession; + private Session session = null; + protected FulltextConfiguration fulltextConfiguration; + + private Map resourcesByDocType; + + public ReindexFullTextBatchJob() { + setSupportedInvocationModes(Arrays.asList(INVOCATION_MODE_NO_CONTEXT, INVOCATION_MODE_SINGLE, INVOCATION_MODE_LIST)); + + stopFileDirectory = System.getProperty("java.io.tmpdir") + File.separator + ReindexFullTextBatchJob.class.getName(); + + log.debug("stop file directory is " + stopFileDirectory); + } + + @Override + public void run() { + setCompletionStatus(STATUS_MIN_PROGRESS); + + numAffected = 0; + + // This is needed so that resource calls (which start transactions) + // will work. Otherwise, a javax.transaction.NotSupportedException + // ("Nested transactions are not supported") is thrown. + + boolean isTransactionActive = TransactionHelper.isTransactionActive(); + + if (isTransactionActive) { + TransactionHelper.commitOrRollbackTransaction(); + } + + try { + coreSession = getRepoSession().getCoreSession(); + + if (requestIsForInvocationModeSingle()) { + String csid = getInvocationContext().getSingleCSID(); + + if (csid == null) { + throw new Exception("No singleCSID was supplied in invocation context."); + } + + String docType = getInvocationContext().getDocType(); + + if (StringUtils.isEmpty(docType)) { + throw new Exception("No docType was supplied in invocation context."); + } + + log.debug("Reindexing " + docType + " record with csid: " + csid); + + reindexDocument(docType, csid); + } + else if (requestIsForInvocationModeList()) { + ListCSIDs list = getInvocationContext().getListCSIDs(); + List csids = list.getCsid(); + + if (csids == null || csids.size() == 0) { + throw new Exception("no listCSIDs were supplied"); + } + + String docType = getInvocationContext().getDocType(); + + if (StringUtils.isEmpty(docType)) { + throw new Exception("No docType was supplied in invocation context."); + } + + log.debug("Reindexing " + csids.size() + " " + docType + " records with csids: " + csids.get(0) + ", ..."); + + if (log.isTraceEnabled()) { + log.trace(StringUtils.join(csids, ", ")); + } + + reindexDocuments(docType, csids); + } + else if (requestIsForInvocationModeNoContext()) { + Set docTypes = new LinkedHashSet(); + String docType; + + docType = getInvocationContext().getDocType(); + + if (StringUtils.isNotEmpty(docType)) { + docTypes.add(docType); + } + + // Read batch size, start and end batches, pause, and additional doctypes from params. + + for (Param param : this.getParams()) { + if (param.getKey().equals("batchSize")) { + batchSize = Integer.parseInt(param.getValue()); + } + else if (param.getKey().equals("startBatch")) { + startBatch = Integer.parseInt(param.getValue()); + } + else if (param.getKey().equals("endBatch")) { + endBatch = Integer.parseInt(param.getValue()); + } + else if (param.getKey().equals("batchPause")) { + batchPause = Integer.parseInt(param.getValue()); + } + else if (param.getKey().equals("docType")) { + docType = param.getValue(); + + if (StringUtils.isNotEmpty(docType)) { + docTypes.add(docType); + } + } + } + + initResourceMap(); + reindexDocuments(docTypes); + } + + log.debug("reindexing complete"); + + InvocationResults results = new InvocationResults(); + results.setNumAffected(numAffected); + results.setUserNote("reindexed " + numAffected + " records"); + + setResults(results); + setCompletionStatus(STATUS_COMPLETE); + } + catch(StoppedException e) { + log.debug("reindexing terminated by stop file"); + + InvocationResults results = new InvocationResults(); + results.setNumAffected(numAffected); + results.setUserNote("reindexing terminated by stop file"); + + setResults(results); + setCompletionStatus(STATUS_COMPLETE); + } + catch(Exception e) { + setErrorResult(e.getMessage()); + } + finally { + // This is needed so that when the session is released after this + // batch job exits (in BatchDocumentModelHandler), there isn't an exception. + // Otherwise, a "Session invoked in a container without a transaction active" + // error is thrown from RepositoryJavaClientImpl.releaseRepositorySession. + + if (isTransactionActive) { + TransactionHelper.startTransaction(); + } + } + } + + private void initResourceMap() { + resourcesByDocType = new HashMap(); + + for (CollectionSpaceResource resource : getResourceMap().values()) { + Map entries = resource.getUriRegistryEntries(); + + for (UriTemplateRegistryKey key : entries.keySet()) { + String docType = key.getDocType(); + String tenantId = key.getTenantId(); + + if (getTenantId().equals(tenantId)) { + if (resourcesByDocType.containsKey(docType)) { + log.warn("multiple resources found for docType " + docType); + + NuxeoBasedResource currentResource = resourcesByDocType.get(docType); + NuxeoBasedResource candidateResource = (NuxeoBasedResource) resource; + + // Favor the resource that isn't an AuthorityResource. This + // is really just to deal with Contacts, which are handled + // by ContactResource, PersonAuthorityResource, and + // OrgAuthorityResource. We want to use ContactResource. + + if (!(candidateResource instanceof AuthorityResource) && (currentResource instanceof AuthorityResource)) { + resourcesByDocType.put(docType, candidateResource); + } + + log.warn("using " + resourcesByDocType.get(docType)); + } + else { + resourcesByDocType.put(docType, (NuxeoBasedResource) resource); + } + } + } + } + } + + private void reindexDocuments(Set docTypes) throws Exception { + if (docTypes == null) { + docTypes = new LinkedHashSet(); + } + + // If no types are specified, do them all. + + if (docTypes.size() == 0) { + docTypes.addAll(getAllDocTypes()); + } + + for (String docType : docTypes) { + reindexDocuments(docType); + } + } + + private List getAllDocTypes() { + List docTypes = new ArrayList(resourcesByDocType.keySet()); + Collections.sort(docTypes); + + log.debug("Call to getAllDocTypes() method found: " + StringUtils.join(docTypes, ", ")); + + return docTypes; + } + + private void reindexDocuments(String docType) throws Exception { + // Check for a stop file before reindexing the docType. + + if (batchStopFileExists() || docTypeStopFileExists()) { + throw new StoppedException(); + } + + log.debug("reindexing docType " + docType); + + NuxeoBasedResource resource = resourcesByDocType.get(docType); + + if (resource == null) { + log.warn("No service resource found for docType " + docType); + } + + boolean isAuthorityItem = false; + + if (resource instanceof AuthorityResource) { + UriTemplateRegistryKey key = new UriTemplateRegistryKey(getTenantId(), docType); + StoredValuesUriTemplate uriTemplate = resource.getUriRegistryEntries().get(key); + + log.debug("uriTemplateType=" + uriTemplate.getUriTemplateType()); + + if (uriTemplate.getUriTemplateType() == UriTemplateFactory.ITEM) { + isAuthorityItem = true; + } + } + + int pageSize = batchSize; + + // The supplied start and end batch numbers start with 1, but the page number starts with 0. + int startPage = (startBatch > 0) ? startBatch - 1 : 0; + int endPage = (endBatch > 0) ? endBatch - 1 : Integer.MAX_VALUE; + + if (isAuthorityItem) { + List vocabularyCsids = getVocabularyCsids((AuthorityResource) resource); + + for (String vocabularyCsid : vocabularyCsids) { + int pageNum = startPage; + List csids = null; + + log.debug("Reindexing vocabulary of " + docType + " with csid " + vocabularyCsid); + + do { + // Check for a stop file before reindexing the batch. + + if (batchStopFileExists()) { + throw new StoppedException(); + } + + csids = findAllAuthorityItems((AuthorityResource) resource, vocabularyCsid, pageSize, pageNum, "collectionspace_core:createdAt, ecm:name"); + + if (csids.size() > 0) { + log.debug("reindexing vocabulary of " + docType +" with csid " + vocabularyCsid + ", batch " + (pageNum + 1) + ": " + csids.size() + " records starting with " + csids.get(0)); + + // Pause for the configured amount of time. + + if (batchPause > 0) { + log.trace("pausing " + batchPause + " ms"); + + Thread.sleep(batchPause); + } + + reindexDocuments(docType, csids); + } + + pageNum++; + } + while(csids.size() == pageSize && pageNum <= endPage); + } + } else { + int pageNum = startPage; + List csids = null; + + do { + // Check for a stop file before reindexing the batch. + + if (batchStopFileExists()) { + throw new StoppedException(); + } + + csids = findAll(resource, pageSize, pageNum, "collectionspace_core:createdAt, ecm:name"); + + if (csids.size() > 0) { + log.debug("reindexing " + docType +" batch " + (pageNum + 1) + ": " + csids.size() + " records starting with " + csids.get(0)); + + // Pause for the configured amount of time. + + if (batchPause > 0) { + log.trace("pausing " + batchPause + " ms"); + + Thread.sleep(batchPause); + } + + reindexDocuments(docType, csids); + } + + pageNum++; + } + while(csids.size() == pageSize && pageNum <= endPage); + } + } + + private void reindexDocument(String docType, String csid) throws Exception { + reindexDocuments(docType, Arrays.asList(csid)); + } + + private void reindexDocuments(String docType, List csids) throws Exception { + // Convert the csids to structs of nuxeo id and type, as expected + // by doBatch. + + if (csids == null || csids.size() == 0) { + return; + } + + getLowLevelSession(); + List infos = new ArrayList(); + + String query = "SELECT ecm:uuid, ecm:primaryType FROM Document " + + "WHERE ecm:name IN (" + StringUtils.join(quoteList(csids), ',') + ") " + + "AND ecm:primaryType LIKE '" + docType + "%' " + + "AND ecm:isCheckedInVersion = 0 AND ecm:isProxy = 0"; + IterableQueryResult result = session.queryAndFetch(query, NXQL.NXQL, QueryFilter.EMPTY); + + try { + for (Map map : result) { + String id = (String) map.get(NXQL.ECM_UUID); + String type = (String) map.get(NXQL.ECM_PRIMARYTYPE); + infos.add(new ReindexInfo(id, type)); + } + } finally { + result.close(); + } + + if (csids.size() != infos.size()) { + log.warn("didn't find info for all the supplied csids: expected " + csids.size() + ", found " + infos.size()); + } + + if (log.isTraceEnabled()) { + for (ReindexInfo info : infos) { + log.trace(info.type + " " + info.id); + } + } + + doBatch(infos); + + numAffected += infos.size(); + } + + private List quoteList(List values) { + List quoted = new ArrayList(values.size()); + + for (String value : values) { + quoted.add("'" + value + "'"); + } + + return quoted; + } + + private boolean batchStopFileExists() { + return (stopFileDirectory != null && new File(stopFileDirectory + File.separator + BATCH_STOP_FILE).isFile()); + } + + private boolean docTypeStopFileExists() { + return (stopFileDirectory != null && new File(stopFileDirectory + File.separator + DOCTYPE_STOP_FILE).isFile()); + } + + private static class StoppedException extends Exception { + private static final long serialVersionUID = 8813189331855935939L; + + public StoppedException() { + + } + } + + /* + * The code below this comment is copied from the nuxeo-reindex-fulltext + * module. The original copyright is below. + */ + + /* + * (C) Copyright 2012 Nuxeo SA (http://nuxeo.com/) and contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public License + * (LGPL) version 2.1 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * Contributors: + * Florent Guillaume + */ + + /** + * Launches a fulltext reindexing of the database. + * + * @param batchSize the batch size, defaults to 100 + * @param batch if present, the batch number to process instead of all + * batches; starts at 1 + * @return when done, ok + the total number of docs + */ + public String reindexFulltext(int batchSize, int batch, String query) throws Exception { + Principal principal = coreSession.getPrincipal(); + if (!(principal instanceof NuxeoPrincipal)) { + return "unauthorized"; + } + NuxeoPrincipal nuxeoPrincipal = (NuxeoPrincipal) principal; + if (!nuxeoPrincipal.isAdministrator()) { + return "unauthorized"; + } + + log("Reindexing starting"); + if (batchSize <= 0) { + batchSize = DEFAULT_BATCH_SIZE; + } + + // + // A default query that gets ALL the documents + // + if (query == null) { + query = "SELECT ecm:uuid, ecm:primaryType FROM Document" + + " WHERE ecm:isProxy = 0" + + " AND ecm:currentLifeCycleState <> 'deleted'" + + " ORDER BY ecm:uuid"; + } + + List infos = getInfos(query); + int size = infos.size(); + int numBatches = (size + batchSize - 1) / batchSize; + if (batch < 0 || batch > numBatches) { + batch = 0; // all + } + batch--; + + log("Reindexing of %s documents, batch size: %s, number of batches: %s", + size, batchSize, numBatches); + if (batch >= 0) { + log("Reindexing limited to batch: %s", batch + 1); + } + + // + // Commit and close the transaction that was started by our standard request lifecycle. + // + boolean tx = TransactionHelper.isTransactionActive(); + if (tx) { + TransactionHelper.commitOrRollbackTransaction(); + } + + int n = 0; + int errs = 0; + for (int i = 0; i < numBatches; i++) { + if (batch >= 0 && batch != i) { + continue; + } + int pos = i * batchSize; + int end = pos + batchSize; + if (end > size) { + end = size; + } + List batchInfos = infos.subList(pos, end); + log("Reindexing batch %s/%s, first id: %s", i + 1, numBatches, + batchInfos.get(0).id); + try { + doBatch(batchInfos); + } catch (NuxeoException e) { + log.error("Error processing batch " + i + 1, e); + errs++; + } + n += end - pos; + } + + log("Reindexing done"); + // + // Start a new transaction so our standard request lifecycle can complete. + // + if (tx) { + TransactionHelper.startTransaction(); + } + return "done: " + n + " total: " + size + " batch_errors: " + errs; + } + + protected void log(String format, Object... args) { + log.warn(String.format(format, args)); + } + + /** + * This has to be called once the transaction has been started. + */ + protected void getLowLevelSession() throws Exception { + try { + SQLSession s = (SQLSession) ((AbstractSession) coreSession).getSession(); + Field f2 = SQLSession.class.getDeclaredField("session"); + f2.setAccessible(true); + session = (Session) f2.get(s); + fulltextConfiguration = session.getModel().getFulltextConfiguration(); + } catch (ReflectiveOperationException e) { + throw new NuxeoException(e); + } + } + + protected List getInfos(String query) throws Exception { + getLowLevelSession(); + List infos = new ArrayList(); + IterableQueryResult it = session.queryAndFetch(query, NXQL.NXQL, + QueryFilter.EMPTY); + try { + for (Map map : it) { + Serializable id = map.get(NXQL.ECM_UUID); + String type = (String) map.get(NXQL.ECM_PRIMARYTYPE); + infos.add(new ReindexInfo(id, type)); + } + } finally { + it.close(); + } + return infos; + } + + protected void doBatch(List infos) throws Exception { + boolean tx; + boolean ok; + + // transaction for the sync batch + tx = TransactionHelper.startTransaction(); + + getLowLevelSession(); // for fulltextInfo + List ids = new ArrayList(infos.size()); + Set asyncIds = new HashSet(); + Model model = session.getModel(); + for (ReindexInfo info : infos) { + ids.add(info.id); + if (fulltextConfiguration.isFulltextIndexable(info.type)) { + asyncIds.add(model.idToString(info.id)); + } + } + ok = false; + try { + runSyncBatch(ids, asyncIds); + ok = true; + } finally { + if (tx) { + if (!ok) { + TransactionHelper.setTransactionRollbackOnly(); + log.error("Rolling back sync"); + } + TransactionHelper.commitOrRollbackTransaction(); + } + } + + runAsyncBatch(asyncIds); + + // wait for async completion after transaction commit + Framework.getLocalService(EventService.class).waitForAsyncCompletion(); + } + + /* + * Do this at the low-level session level because we may have to modify + * things like versions which aren't usually modifiable, and it's also good + * to bypass all listeners. + */ + protected void runSyncBatch(List ids, Set asyncIds) throws Exception { + getLowLevelSession(); + + session.getNodesByIds(ids); // batch fetch + + Map titles = new HashMap(); + for (Serializable id : ids) { + Node node = session.getNodeById(id); + if (asyncIds.contains(id)) { + node.setSimpleProperty(Model.FULLTEXT_JOBID_PROP, id); + } + SimpleProperty prop; + try { + prop = node.getSimpleProperty(DC_TITLE); + } catch (IllegalArgumentException e) { + continue; + } + String title = (String) prop.getValue(); + titles.put(id, title); + prop.setValue(title + " "); + } + session.save(); + + for (Serializable id : ids) { + Node node = session.getNodeById(id); + SimpleProperty prop; + try { + prop = node.getSimpleProperty(DC_TITLE); + } catch (IllegalArgumentException e) { + continue; + } + prop.setValue(titles.get(id)); + } + session.save(); + } + + protected void runAsyncBatch(Set asyncIds) { + if (asyncIds.isEmpty()) { + return; + } + String repositoryName = coreSession.getRepositoryName(); + WorkManager workManager = Framework.getLocalService(WorkManager.class); + for (String id : asyncIds) { + Work work = new SQLFulltextExtractorWork(repositoryName, id); + // schedule immediately, we're outside a transaction + workManager.schedule(work, Scheduling.IF_NOT_SCHEDULED, false); + } + } +} diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/UpdateAccessCodeBatchJob.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/UpdateAccessCodeBatchJob.java new file mode 100644 index 000000000..50acfd79f --- /dev/null +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/UpdateAccessCodeBatchJob.java @@ -0,0 +1,473 @@ +package org.collectionspace.services.batch.nuxeo; + +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.lang.StringUtils; +import org.collectionspace.services.client.PoxPayloadOut; +import org.collectionspace.services.client.TaxonomyAuthorityClient; +import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectBotGardenConstants; +import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectConstants; +import org.collectionspace.services.common.api.RefName; +import org.collectionspace.services.common.invocable.InvocationResults; +import org.collectionspace.services.common.vocabulary.AuthorityResource; +import org.collectionspace.services.taxonomy.nuxeo.TaxonBotGardenConstants; +import org.collectionspace.services.taxonomy.nuxeo.TaxonConstants; +import org.dom4j.DocumentException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A batch job that sets the access code on taxonomy records. The single CSID context is supported. + * + * If the document is a taxon record, the access codes of the taxon record and all of its descendant + * (narrower context) records are updated. + * + * If the document is a collectionobject, the access codes of all taxon records referenced by the + * collectionobject's taxonomic identification are updated, and propagated up the taxon + * hierarchy to the ancestors (broader contexts) of each taxon record. + * + * @author ray + * + */ +public class UpdateAccessCodeBatchJob extends AbstractBatchJob { + final Logger logger = LoggerFactory.getLogger(UpdateAccessCodeBatchJob.class); + + private final String[] TAXON_FIELD_NAME_PARTS = CollectionObjectBotGardenConstants.TAXON_FIELD_NAME.split("\\/"); + private final String TAXON_FIELD_NAME_WITHOUT_PATH = TAXON_FIELD_NAME_PARTS[TAXON_FIELD_NAME_PARTS.length - 1]; + + public UpdateAccessCodeBatchJob() { + this.setSupportedInvocationModes(Arrays.asList(INVOCATION_MODE_SINGLE)); + } + + @Override + public void run() { + setCompletionStatus(STATUS_MIN_PROGRESS); + + try { + if (this.requestIsForInvocationModeSingle()) { + String csid = getInvocationContext().getSingleCSID(); + + if (StringUtils.isEmpty(csid)) { + throw new Exception("Missing context csid"); + } + + String docType = getInvocationContext().getDocType(); + + if (docType.equals(TaxonConstants.NUXEO_DOCTYPE)) { + setResults(updateAccessCode(csid, true)); + //setResults(updateParentAccessCode(csid, true)); + } + else if (docType.equals(CollectionObjectConstants.NUXEO_DOCTYPE)) { + setResults(updateReferencedAccessCodes(csid, true)); + } + else { + throw new Exception("Unsupported document type: " + docType); + } + } + else { + throw new Exception("Unsupported invocation mode: " + this.getInvocationContext().getMode()); + } + + setCompletionStatus(STATUS_COMPLETE); + } + catch(Exception e) { + setCompletionStatus(STATUS_ERROR); + setErrorInfo(new InvocationError(INT_ERROR_STATUS, e.getMessage())); + } + } + + + /** + * Updates the access code of the specified taxon record. + * + * @param taxonRefNameOrCsid The refname or csid of the taxon record. + * @param deep If true, update the access codes of all descendant (narrower context) + * taxon records. On a deep update, the access codes of all descendant + * records are updated first, before calculating the access code of the parent. + * This ensures that the access codes of children are up-to-date, and can be + * used to calculate an up-to-date value for the parent. + * + * If false, only the specified taxon record is updated. The calculation + * of the access code uses the access codes of child taxon records, so + * an accurate result depends on the accuracy of the children's access codes. + * @return The results of the invocation. + * @throws URISyntaxException + * @throws DocumentException + */ + public InvocationResults updateAccessCode(String taxonRefNameOrCsid, boolean deep) throws URISyntaxException, DocumentException { + UpdateAccessCodeResults updateResults = updateAccessCode(taxonRefNameOrCsid, deep, false); + + InvocationResults results = new InvocationResults(); + results.setNumAffected(updateResults.getNumAffected()); + results.setUserNote(updateResults.isChanged() ? "access code changed to " + updateResults.getAccessCode() : "access code not changed"); + + return results; + } + + /** + * Updates the access code of the parent (broader context) of the specified taxon record. + * + * @param taxonCsid The csid of the taxon record. + * @param propagate If true, propagate the access code up the taxon hierarchy to + * all ancestors of the taxon record. The propagation stops when + * the new value of the access code is the same as the old value, + * or when a root node (a node with no broader context) is reached. + * + * If false, update only the access code of the parent. + * @return The results of the invocation. + * @throws URISyntaxException + * @throws DocumentException + */ + public InvocationResults updateParentAccessCode(String taxonCsid, boolean propagate) throws URISyntaxException, DocumentException { + PoxPayloadOut taxonPayload = findTaxonByCsid(taxonCsid); + String taxonRefName = getFieldValue(taxonPayload, TaxonConstants.REFNAME_SCHEMA_NAME, TaxonConstants.REFNAME_FIELD_NAME); + String accessCode = getFieldValue(taxonPayload, TaxonBotGardenConstants.ACCESS_CODE_SCHEMA_NAME, TaxonBotGardenConstants.ACCESS_CODE_FIELD_NAME); + + logger.debug("updating parent access code: taxonRefName=" + taxonRefName + " propagate=" + propagate + " accessCode=" + accessCode); + + UpdateAccessCodeResults updateResults = updateParentAccessCode(taxonCsid, accessCode, propagate); + + InvocationResults results = new InvocationResults(); + results.setNumAffected(updateResults.getNumAffected()); + results.setUserNote(results.getNumAffected() + " access codes changed"); + + return results; + } + + /** + * Updates the access codes of all taxon records that are referenced in the taxonomic identification + * field of the specified collectionobject. + * + * @param collectionObjectCsid The csid of the collectionobject. + * @param propagate If true, propagate the access code up the taxon hierarchy to + * the ancestors of each referenced taxon record. The propagation stops when + * the new value of the access code is the same as the old value, + * or when a root node (a node with no broader context) is reached. + * + * If false, update only the access codes of the taxon records + * that are directly referenced. + * @return The results of the invocation. + * @throws URISyntaxException + * @throws DocumentException + */ + public InvocationResults updateReferencedAccessCodes(String collectionObjectCsid, boolean propagate) throws URISyntaxException, DocumentException { + PoxPayloadOut collectionObjectPayload = findCollectionObjectByCsid(collectionObjectCsid); + + String deadFlag = getFieldValue(collectionObjectPayload, CollectionObjectBotGardenConstants.DEAD_FLAG_SCHEMA_NAME, + CollectionObjectBotGardenConstants.DEAD_FLAG_FIELD_NAME); + boolean isAlive = (deadFlag == null) || (!deadFlag.equalsIgnoreCase("true")); + + logger.debug("updating referenced access codes: collectionObjectCsid=" + collectionObjectCsid + " propagate=" + propagate + " isAlive=" + isAlive); + + List taxonRefNames = getFieldValues(collectionObjectPayload, CollectionObjectBotGardenConstants.TAXON_SCHEMA_NAME, + CollectionObjectBotGardenConstants.TAXON_FIELD_NAME); + long numAffected = 0; + + for (String taxonRefName : taxonRefNames) { + PoxPayloadOut taxonPayload = findTaxonByRefName(taxonRefName); + UpdateAccessCodeResults updateResults = updateAccessCode(taxonPayload, false, isAlive); + + if (updateResults.isChanged()) { + numAffected += updateResults.getNumAffected(); + + if (propagate) { + UpdateAccessCodeResults parentUpdateResults = updateParentAccessCode(getCsid(taxonPayload), updateResults.getAccessCode(), true); + + numAffected += parentUpdateResults.getNumAffected(); + } + } + } + + InvocationResults results = new InvocationResults(); + results.setNumAffected(numAffected); + results.setUserNote(numAffected + " access codes changed"); + + return results; + } + + /** + * Updates the access code of the specified taxon record. The access code is determined by + * examining all collectionobjects that have a taxonomic identification that matches the + * refname of the taxon record, as well as the access codes of child (narrower context) + * taxon records. If all referencing collectionobjects are dead (as determined + * by the dead flag), and all child taxon records are dead (as determined by their access + * codes), then the access code is set to Dead. If any matching collectionobjects + * are not dead, or any child taxons are not dead, and the access code is currently Dead, + * the access code is set to Unrestricted. Otherwise, the access code is not changed. + * + * @param taxonPayload The services payload of the taxon record. + * @param deep If true, update the access code of all descendant taxon records. + * On a deep update, the access codes of all descendant + * records are updated first, before calculating the access code of the parent. + * This ensures that the access codes of children are up-to-date, and can be + * used to calculate an up-to-date value for the parent. + + * If false, only the specified taxon record is updated. The calculation + * of the access code uses the access codes of child taxon records, so + * an accurate result depends on the accuracy of the children's access codes. + * @param knownAlive A hint that a child taxon of the specified taxon is known to be + * alive, or that a collectionobject of the specified taxon is known to be + * alive. This parameter allows for optimization when propagating + * access code changes up the hierarchy; if a child taxon or + * referencing collectionobject is known to be alive, and the + * current access code is Dead, then the access code can be changed + * to Unrestricted without examining any other records. + * @return The results of the update. + * @throws DocumentException + * @throws URISyntaxException + */ + public UpdateAccessCodeResults updateAccessCode(PoxPayloadOut taxonPayload, boolean deep, boolean knownAlive) throws URISyntaxException, DocumentException { + UpdateAccessCodeResults results = new UpdateAccessCodeResults(); + boolean foundAlive = knownAlive; + + String taxonCsid = getCsid(taxonPayload); + String taxonRefName = getFieldValue(taxonPayload, TaxonConstants.REFNAME_SCHEMA_NAME, TaxonConstants.REFNAME_FIELD_NAME); + String accessCode = getFieldValue(taxonPayload, TaxonBotGardenConstants.ACCESS_CODE_SCHEMA_NAME, TaxonBotGardenConstants.ACCESS_CODE_FIELD_NAME); + + logger.debug("updating access code: taxonRefName=" + taxonRefName + " deep=" + deep + " knownAlive=" + knownAlive); + + if (accessCode == null) { + accessCode = ""; + } + + List childTaxonCsids = findNarrower(taxonCsid); + + if (deep) { + long numChildrenChanged = 0; + + // Update the access code on all the children, and track whether any are alive. + + for (String childTaxonCsid : childTaxonCsids) { + UpdateAccessCodeResults childResults = updateAccessCode(childTaxonCsid, true, false); + + if (!childResults.isSoftDeleted()) { + String childAccessCode = childResults.getAccessCode(); + boolean isChildAlive = !childAccessCode.equals(TaxonBotGardenConstants.ACCESS_CODE_DEAD_VALUE); + + if (isChildAlive) { + foundAlive = true; + } + + if (childResults.isChanged()) { + numChildrenChanged++; + } + } + } + + results.setNumAffected(numChildrenChanged); + } + else { + if (!foundAlive) { + // Check if any of the children are alive. + + for (String childTaxonCsid : childTaxonCsids) { + PoxPayloadOut childTaxonPayload = findTaxonByCsid(childTaxonCsid); + + String childAccessCode = getFieldValue(childTaxonPayload, TaxonBotGardenConstants.ACCESS_CODE_SCHEMA_NAME, + TaxonBotGardenConstants.ACCESS_CODE_FIELD_NAME); + boolean isChildAlive = !childAccessCode.equals(TaxonBotGardenConstants.ACCESS_CODE_DEAD_VALUE); + + if (isChildAlive) { + foundAlive = true; + break; + } + } + } + } + + if (!foundAlive) { + // Check if any examples of this taxon are alive. + + RefName.AuthorityItem item = RefName.AuthorityItem.parse(taxonRefName); + String vocabularyShortId = item.getParentShortIdentifier(); + + List collectionObjectCsids = findReferencingCollectionObjects(TaxonomyAuthorityClient.SERVICE_NAME, vocabularyShortId, taxonCsid, + CollectionObjectBotGardenConstants.TAXON_SCHEMA_NAME + ":" + TAXON_FIELD_NAME_WITHOUT_PATH); + + for (String collectionObjectCsid : collectionObjectCsids) { + PoxPayloadOut collectionObjectPayload = findCollectionObjectByCsid(collectionObjectCsid); + + String deadFlag = getFieldValue(collectionObjectPayload, CollectionObjectBotGardenConstants.DEAD_FLAG_SCHEMA_NAME, + CollectionObjectBotGardenConstants.DEAD_FLAG_FIELD_NAME); + boolean isDead = (deadFlag != null) && (deadFlag.equalsIgnoreCase("true")); + + if (!isDead) { + foundAlive = true; + break; + } + } + } + + String newAccessCode; + + // The access code only needs to be changed if: + // + // 1. There is a living example of the taxon, but the access code is dead. + // 2. There are no living examples, but the access code is not dead. + // + // Otherwise, the access code should stay the same. In particular, if there is a + // living example, and the access code is not dead, the current value of unrestricted + // or restricted should be retained. + + if (foundAlive && (StringUtils.isEmpty(accessCode) || accessCode.equals(TaxonBotGardenConstants.ACCESS_CODE_DEAD_VALUE))) { + newAccessCode = TaxonBotGardenConstants.ACCESS_CODE_UNRESTRICTED_VALUE; + } + else if (!foundAlive) { + newAccessCode = TaxonBotGardenConstants.ACCESS_CODE_DEAD_VALUE; + } + else { + newAccessCode = accessCode; + } + + if (!newAccessCode.equals(accessCode)) { + String inAuthority = getFieldValue(taxonPayload, TaxonConstants.IN_AUTHORITY_SCHEMA_NAME, TaxonConstants.IN_AUTHORITY_FIELD_NAME); + + setAccessCode(inAuthority, taxonCsid, newAccessCode); + + results.setChanged(true); + results.setNumAffected(results.getNumAffected() + 1); + } + + results.setAccessCode(newAccessCode); + + return results; + } + + /** + * Updates the access code of the taxon record with the specified refname or csid. + * + * @param taxonRefNameOrCsid + * @param deep + * @param knownAlive + * @return + * @throws URISyntaxException + * @throws DocumentException + */ + public UpdateAccessCodeResults updateAccessCode(String taxonRefNameOrCsid, boolean deep, boolean knownAlive) throws URISyntaxException, DocumentException { + PoxPayloadOut taxonPayload; + + if (RefName.AuthorityItem.parse(taxonRefNameOrCsid) == null) { + taxonPayload = findTaxonByCsid(taxonRefNameOrCsid); + } + else { + taxonPayload = findTaxonByRefName(taxonRefNameOrCsid); + } + + return updateAccessCode(taxonPayload, deep, knownAlive); + } + + /** + * Updates the access code of the parent (broader context) of the specified taxon record, + * whose access code is assumed to be a specified value. + * + * @param taxonCsid The csid of the taxon record. + * @param accessCode The access code of the taxon record. + * @param propagate If true, propagate the access code up the taxon hierarchy to + * all ancestors of the taxon record. The propagation stops when + * the new value of the access code is the same as the old value, + * or when a root node (a node with no broader context) is reached. + * + * If false, update only the access code of the parent. + * @return The results of the update. + * @throws URISyntaxException + * @throws DocumentException + */ + public UpdateAccessCodeResults updateParentAccessCode(String taxonCsid, String accessCode, boolean propagate) throws URISyntaxException, DocumentException { + UpdateAccessCodeResults results = new UpdateAccessCodeResults(); + String parentTaxonCsid = findBroader(taxonCsid); + long numAffected = 0; + + logger.debug("updating parent access code: taxonCsid=" + taxonCsid + " accessCode=" + accessCode + " propagate=" + propagate); + + if (parentTaxonCsid != null) { + boolean isAlive = (accessCode == null) || !accessCode.equals(TaxonBotGardenConstants.ACCESS_CODE_DEAD_VALUE); + + UpdateAccessCodeResults parentUpdateResults = updateAccessCode(parentTaxonCsid, false, isAlive); + + if (parentUpdateResults.isChanged()) { + // Except for numAffected, the result fields are probably not all that useful in this situation. + // Set the changed flag to whether the immediate parent was changed, and the access code to + // the immediate parent's. + + results.setAccessCode(parentUpdateResults.getAccessCode()); + results.setChanged(true); + + numAffected += parentUpdateResults.getNumAffected(); + + if (propagate) { + UpdateAccessCodeResults grandparentUpdateResults = updateParentAccessCode(parentTaxonCsid, parentUpdateResults.getAccessCode(), true); + numAffected += grandparentUpdateResults.getNumAffected(); + } + } + } + + results.setNumAffected(numAffected); + + return results; + } + + /** + * Sets the access code of the specified taxon record to the specified value. + * + * @param authorityCsid The csid of the authority containing the taxon record. + * @param taxonCsid The csid of the taxon record. + * @param accessCode The value of the access code. + * @throws URISyntaxException + */ + private void setAccessCode(String authorityCsid, String taxonCsid, String accessCode) throws URISyntaxException { + String updatePayload = + "" + + "" + + "" + + getFieldXml(TaxonBotGardenConstants.ACCESS_CODE_FIELD_NAME, accessCode) + + "" + + ""; + + AuthorityResource resource = (AuthorityResource) getResourceMap().get(TaxonomyAuthorityClient.SERVICE_NAME); + resource.updateAuthorityItem(getResourceMap(), createUriInfo(), authorityCsid, taxonCsid, updatePayload); + } + + private class UpdateAccessCodeResults { + private boolean isSoftDeleted = false; + private boolean isChanged = false; + private String accessCode = null; + private long numAffected = 0; + + public boolean isSoftDeleted() { + return isSoftDeleted; + } + + /** + * @param isSoftDeleted + */ + public void setSoftDeleted(boolean isSoftDeleted) { + this.isSoftDeleted = isSoftDeleted; + } + + public boolean isChanged() { + return isChanged; + } + + public void setChanged(boolean isChanged) { + this.isChanged = isChanged; + } + + public String getAccessCode() { + return accessCode; + } + + public void setAccessCode(String accessCode) { + this.accessCode = accessCode; + } + + public long getNumAffected() { + return numAffected; + } + + public void setNumAffected(long numAffected) { + this.numAffected = numAffected; + } + } +} diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/UpdateDeadFlagBatchJob.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/UpdateDeadFlagBatchJob.java new file mode 100644 index 000000000..0e5397da9 --- /dev/null +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/UpdateDeadFlagBatchJob.java @@ -0,0 +1,233 @@ +package org.collectionspace.services.batch.nuxeo; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.lang.StringUtils; +import org.collectionspace.services.client.CollectionObjectClient; +import org.collectionspace.services.client.PoxPayloadOut; +import org.collectionspace.services.client.workflow.WorkflowClient; +import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectBotGardenConstants; +import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectConstants; +import org.collectionspace.services.common.NuxeoBasedResource; +import org.collectionspace.services.common.invocable.InvocationResults; +import org.collectionspace.services.movement.nuxeo.MovementBotGardenConstants; +import org.collectionspace.services.movement.nuxeo.MovementConstants; +import org.dom4j.DocumentException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class UpdateDeadFlagBatchJob extends AbstractBatchJob { + final Logger logger = LoggerFactory.getLogger(UpdateDeadFlagBatchJob.class); + + public UpdateDeadFlagBatchJob() { + this.setSupportedInvocationModes(Arrays.asList(INVOCATION_MODE_SINGLE)); + } + + public void run() { + setCompletionStatus(STATUS_MIN_PROGRESS); + + try { + String mode = getInvocationContext().getMode(); + + if (!mode.equalsIgnoreCase(INVOCATION_MODE_SINGLE)) { + throw new Exception("Unsupported invocation mode: " + mode); + } + + String movementCsid = getInvocationContext().getSingleCSID(); + + if (StringUtils.isEmpty(movementCsid)) { + throw new Exception("Missing context csid"); + } + + setResults(updateRelatedDeadFlags(movementCsid)); + setCompletionStatus(STATUS_COMPLETE); + } + catch(Exception e) { + setCompletionStatus(STATUS_ERROR); + setErrorInfo(new InvocationError(INT_ERROR_STATUS, e.getMessage())); + } + } + + /** + * Update the dead flag for all collectionobjects related to the given movement record, + * based on the assumption that the action code of the specified movement record has just changed. + * + * @param movementCsid the csid of the movement that was updated + * @return + * @throws URISyntaxException + * @throws DocumentException + */ + public InvocationResults updateRelatedDeadFlags(String movementCsid) throws URISyntaxException, DocumentException { + InvocationResults results = new InvocationResults(); + long numAffected = 0; + List userNotes = new ArrayList(); + + PoxPayloadOut payload = findMovementByCsid(movementCsid); + + String actionCode = getFieldValue(payload, MovementBotGardenConstants.ACTION_CODE_SCHEMA_NAME, MovementBotGardenConstants.ACTION_CODE_FIELD_NAME); + logger.debug("actionCode=" + actionCode); + + if (actionCode.equals(MovementBotGardenConstants.DEAD_ACTION_CODE) || actionCode.equals(MovementBotGardenConstants.REVIVED_ACTION_CODE)) { + String actionDate = getFieldValue(payload, MovementBotGardenConstants.ACTION_DATE_SCHEMA_NAME, + MovementBotGardenConstants.ACTION_DATE_FIELD_NAME); + logger.debug("actionDate=" + actionDate); + + List collectionObjectCsids = findRelatedCollectionObjects(movementCsid); + + for (String collectionObjectCsid : collectionObjectCsids) { + logger.debug("found related collectionobject: " + collectionObjectCsid); + + InvocationResults collectionObjectResults = updateDeadFlag(collectionObjectCsid, movementCsid, actionCode, actionDate); + + if (collectionObjectResults.getNumAffected() > 0) { + numAffected = numAffected + collectionObjectResults.getNumAffected(); + userNotes.add(collectionObjectResults.getUserNote()); + } + } + } + + if (numAffected > 0) { + results.setNumAffected(numAffected); + results.setUserNote(StringUtils.join(userNotes, ", ")); + } + + return results; + } + + /** + * Update the dead flag for the given collectionobject, based on the assumption that the action code + * of the specified movement record has just changed, and that the movement record is related to + * the collectionobject. + * + * @param collectionObjectCsid the csid of the collectionobject to update + * @param updatedMovementCsid the csid of the related movement that was updated + * @return + * @throws URISyntaxException + * @throws DocumentException + */ + public InvocationResults updateDeadFlag(String collectionObjectCsid, String updatedMovementCsid) throws URISyntaxException, DocumentException { + InvocationResults results = new InvocationResults(); + PoxPayloadOut payload = findMovementByCsid(updatedMovementCsid); + + String actionCode = getFieldValue(payload, MovementBotGardenConstants.ACTION_CODE_SCHEMA_NAME, + MovementBotGardenConstants.ACTION_CODE_FIELD_NAME); + logger.debug("actionCode=" + actionCode); + + if (actionCode.equals(MovementBotGardenConstants.DEAD_ACTION_CODE) || actionCode.equals(MovementBotGardenConstants.REVIVED_ACTION_CODE)) { + String actionDate = getFieldValue(payload, MovementBotGardenConstants.ACTION_DATE_SCHEMA_NAME, + MovementBotGardenConstants.ACTION_DATE_FIELD_NAME); + logger.debug("actionDate=" + actionDate); + + results = updateDeadFlag(collectionObjectCsid, updatedMovementCsid, actionCode, actionDate); + } + + return results; + } + + /** + * Update the dead flag for the given collectionobject, based on the assumption that the action code + * of the specified movement record has just changed, and that the movement record is related to + * the collectionobject. + * + * @param collectionObjectCsid the csid of the collectionobject to update + * @param updatedMovementCsid the csid of the related movement that was updated + * @param actionCode the action code of the movement + * @param actionDate the action date of the movement + * @return + * @throws URISyntaxException + * @throws DocumentException + */ + private InvocationResults updateDeadFlag(String collectionObjectCsid, String updatedMovementCsid, String actionCode, String actionDate) throws URISyntaxException, DocumentException { + InvocationResults results = new InvocationResults(); + PoxPayloadOut payload = findCollectionObjectByCsid(collectionObjectCsid); + + String workflowState = getFieldValue(payload, CollectionObjectConstants.WORKFLOW_STATE_SCHEMA_NAME, CollectionObjectConstants.WORKFLOW_STATE_FIELD_NAME); + + if (workflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED)) { + logger.debug("skipping deleted collectionobject: " + collectionObjectCsid); + } + else { + String deadFlag = getFieldValue(payload, CollectionObjectBotGardenConstants.DEAD_FLAG_SCHEMA_NAME, + CollectionObjectBotGardenConstants.DEAD_FLAG_FIELD_NAME); + boolean isDead = (deadFlag != null) && (deadFlag.equalsIgnoreCase("true")); + + logger.debug("updating dead flag: collectionObjectCsid=" + collectionObjectCsid + " actionCode=" + actionCode + " isDead=" + isDead); + + if (actionCode.equals(MovementBotGardenConstants.REVIVED_ACTION_CODE)) { + if (isDead) { + /* + * The object is dead, but a location was revived. Unset the dead flag and date on the object. + */ + setDeadFlag(collectionObjectCsid, false, null); + + results.setNumAffected(1); + results.setUserNote(collectionObjectCsid + " set to alive"); + } + } + else if (actionCode.equals(MovementBotGardenConstants.DEAD_ACTION_CODE)) { + if (!isDead) { + /* + * The object is not dead, but a location was marked dead. If there are no remaining live locations, + * set the dead flag and date on the object. Any movement record that is not deleted represents + * a live location, with one exception: the movement record that was just marked dead may not have + * been deleted yet, but it should not count as a live location. + */ + List movementCsids = findRelatedMovements(collectionObjectCsid); + boolean liveLocationExists = false; + + for (String movementCsid : movementCsids) { + logger.debug("found related movement: movementCsid=" + movementCsid); + + if (!movementCsid.equals(updatedMovementCsid)) { + PoxPayloadOut movementPayload = findMovementByCsid(movementCsid); + String movementWorkflowState = getFieldValue(movementPayload, MovementConstants.WORKFLOW_STATE_SCHEMA_NAME, MovementConstants.WORKFLOW_STATE_FIELD_NAME); + + if (!movementWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED)) { + logger.debug("found live location: movementCsid=" + movementCsid); + + liveLocationExists = true; + break; + } + } + } + + if (!liveLocationExists) { + setDeadFlag(collectionObjectCsid, true, actionDate); + + results.setNumAffected(1); + results.setUserNote(collectionObjectCsid + " set to dead"); + } + } + } + } + + return results; + } + + /** + * Update the dead flag and dead date of the specified collectionobject. + * + * @param collectionObjectCsid the csid of the collectionobject to update + * @param deadFlag the new value of the dead flag field + * @param deadDate the new value of the dead date field + * @throws URISyntaxException + */ + private void setDeadFlag(String collectionObjectCsid, boolean deadFlag, String deadDate) throws URISyntaxException { + String updatePayload = + "" + + "" + + "" + + getFieldXml("deadFlag", (deadFlag ? "true" : "false")) + + getFieldXml("deadDate", deadDate) + + "" + + "" + + "" + + ""; + + NuxeoBasedResource resource = (NuxeoBasedResource) getResourceMap().get(CollectionObjectClient.SERVICE_NAME); + resource.update(getResourceMap(), createUriInfo(), collectionObjectCsid, updatePayload); + } +} diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/UpdateRareFlagBatchJob.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/UpdateRareFlagBatchJob.java new file mode 100644 index 000000000..ef437856c --- /dev/null +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/UpdateRareFlagBatchJob.java @@ -0,0 +1,347 @@ +package org.collectionspace.services.batch.nuxeo; + +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.ws.rs.WebApplicationException; + +import org.apache.commons.lang.StringUtils; +import org.collectionspace.services.client.CollectionObjectClient; +import org.collectionspace.services.client.PoxPayloadOut; +import org.collectionspace.services.client.TaxonomyAuthorityClient; +import org.collectionspace.services.client.workflow.WorkflowClient; +import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectBotGardenConstants; +import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectConstants; +import org.collectionspace.services.common.NuxeoBasedResource; +import org.collectionspace.services.common.api.RefName; +import org.collectionspace.services.common.invocable.InvocationContext.ListCSIDs; +import org.collectionspace.services.common.invocable.InvocationResults; +import org.collectionspace.services.taxonomy.nuxeo.TaxonBotGardenConstants; +import org.collectionspace.services.taxonomy.nuxeo.TaxonConstants; +import org.dom4j.DocumentException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class UpdateRareFlagBatchJob extends AbstractBatchJob { + final Logger logger = LoggerFactory.getLogger(UpdateRareFlagBatchJob.class); + + // All conservation categories are considered rare, except for ones that start with the following prefixes. + public static final List NON_RARE_CONSERVATION_CATEGORY_PREFIXES = Arrays.asList("none", "DD ", "LC ", "LR (lc) "); + + private static final String[] TAXON_FIELD_NAME_PARTS = CollectionObjectBotGardenConstants.TAXON_FIELD_NAME.split("\\/"); + private static final String TAXON_FIELD_NAME_WITHOUT_PATH = TAXON_FIELD_NAME_PARTS[TAXON_FIELD_NAME_PARTS.length - 1]; + + public UpdateRareFlagBatchJob() { + this.setSupportedInvocationModes(Arrays.asList(INVOCATION_MODE_SINGLE, INVOCATION_MODE_LIST, INVOCATION_MODE_NO_CONTEXT)); + } + + @Override + public void run() { + setCompletionStatus(STATUS_MIN_PROGRESS); + + try { + String mode = getInvocationContext().getMode(); + + if (mode.equals(INVOCATION_MODE_SINGLE)) { + /* + * In a single document context, the single csid must specify a collectionobject or a + * taxonomy record. If it's a collectionobject, the rare flag for the specified + * collectionobject will be updated. If it's a taxonomy record, the rare flag will be + * updated for each collectionobject with a primary determination that refers to the + * specified taxonomy record. + */ + + String csid = getInvocationContext().getSingleCSID(); + + if (StringUtils.isEmpty(csid)) { + throw new Exception("Missing context csid"); + } + + String docType = getInvocationContext().getDocType(); + + if (docType.equals(CollectionObjectConstants.NUXEO_DOCTYPE)) { + setResults(updateRareFlag(csid)); + } + else if (docType.equals(TaxonConstants.NUXEO_DOCTYPE)) { + setResults(updateReferencingRareFlags(csid)); + } + else { + throw new Exception("Unsupported document type: " + docType); + } + } + else if (mode.equals(INVOCATION_MODE_LIST)) { + /* + * In a list context, the csids must specify collectionobjects. The rare flag for + * each collectionobject will be updated. + */ + ListCSIDs csids = getInvocationContext().getListCSIDs(); + + setResults(updateRareFlags(csids.getCsid())); + } + else if (mode.equals(INVOCATION_MODE_NO_CONTEXT)) { + /* + * If there is no context, the rare flag will be updated for all (non-deleted) + * collectionobjects. + */ + + setResults(updateAllRareFlags()); + } + else { + throw new Exception("Unsupported invocation mode: " + mode); + } + + setCompletionStatus(STATUS_COMPLETE); + } + catch(Exception e) { + setCompletionStatus(STATUS_ERROR); + setErrorInfo(new InvocationError(INT_ERROR_STATUS, e.getMessage())); + } + } + + /** + * Updates the rare flags of collectionobjects that refer to the specified taxon record. + * A collectionobject is considered to refer to the taxon record if the refname of its + * primary taxonomic identification is the refname of the taxon record. + * + * @param taxonCsid The csid of the taxon record + * @return + * @throws URISyntaxException + * @throws DocumentException + */ + public InvocationResults updateReferencingRareFlags(String taxonCsid) throws URISyntaxException, DocumentException { + PoxPayloadOut taxonPayload = findTaxonByCsid(taxonCsid); + String taxonRefName = getFieldValue(taxonPayload, TaxonConstants.REFNAME_SCHEMA_NAME, TaxonConstants.REFNAME_FIELD_NAME); + + RefName.AuthorityItem item = RefName.AuthorityItem.parse(taxonRefName); + String vocabularyShortId = item.getParentShortIdentifier(); + + List collectionObjectCsids = findReferencingCollectionObjects(TaxonomyAuthorityClient.SERVICE_NAME, vocabularyShortId, taxonCsid, + CollectionObjectBotGardenConstants.TAXON_SCHEMA_NAME + ":" + TAXON_FIELD_NAME_WITHOUT_PATH); + long numFound = 0; + long numAffected = 0; + + for (String collectionObjectCsid : collectionObjectCsids) { + // Filter out results where the taxon is referenced in the correct field, but isn't the primary value. + + PoxPayloadOut collectionObjectPayload = findCollectionObjectByCsid(collectionObjectCsid); + String primaryTaxonRefName = getFieldValue(collectionObjectPayload, CollectionObjectBotGardenConstants.TAXON_SCHEMA_NAME, + CollectionObjectBotGardenConstants.TAXON_FIELD_NAME); + + if (primaryTaxonRefName.equals(taxonRefName)) { + numFound++; + + InvocationResults itemResults = updateRareFlag(collectionObjectPayload); + numAffected += itemResults.getNumAffected(); + } + } + + InvocationResults results = new InvocationResults(); + results.setNumAffected(numAffected); + results.setUserNote(numFound + " referencing cataloging " + (numFound == 1 ? "record" : "records") + " found, " + numAffected + " updated"); + + return results; + } + + /** + * Updates the rare flag of the specified collectionobject. + * + * @param collectionObjectCsid The csid of the collectionobject + * @return + * @throws URISyntaxException + * @throws DocumentException + */ + public InvocationResults updateRareFlag(String collectionObjectCsid) throws URISyntaxException, DocumentException { + PoxPayloadOut collectionObjectPayload = findCollectionObjectByCsid(collectionObjectCsid); + + return updateRareFlag(collectionObjectPayload); + } + + /** + * Updates the rare flag of the specified collectionobject. The rare flag is determined by looking at + * the taxon record that is referenced by the primary taxonomic determination of the collectionobject. + * If the taxon record has a conservation category that is considered rare in its primary plant attributes + * group, the rare flag is set to true. Otherwise, it is set to false. + * + * @param collectionObjectPayload The payload representing the collectionobject + * @return + * @throws URISyntaxException + * @throws DocumentException + */ + public InvocationResults updateRareFlag(PoxPayloadOut collectionObjectPayload) throws URISyntaxException, DocumentException { + InvocationResults results = new InvocationResults(); + + String uri = this.getFieldValue(collectionObjectPayload, CollectionObjectBotGardenConstants.URI_SCHEMA_NAME, + CollectionObjectBotGardenConstants.URI_FIELD_NAME); + String[] uriParts = uri.split("\\/"); + String collectionObjectCsid = uriParts[uriParts.length-1]; + + String workflowState = getFieldValue(collectionObjectPayload, CollectionObjectConstants.WORKFLOW_STATE_SCHEMA_NAME, + CollectionObjectConstants.WORKFLOW_STATE_FIELD_NAME); + + if (workflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED)) { + logger.debug("skipping deleted collectionobject: " + collectionObjectCsid); + } + else { + String taxonRefName = getFieldValue(collectionObjectPayload, CollectionObjectBotGardenConstants.TAXON_SCHEMA_NAME, + CollectionObjectBotGardenConstants.TAXON_FIELD_NAME); + String oldIsRare = getFieldValue(collectionObjectPayload, CollectionObjectBotGardenConstants.RARE_FLAG_SCHEMA_NAME, + CollectionObjectBotGardenConstants.RARE_FLAG_FIELD_NAME); + + if (oldIsRare == null) { + oldIsRare = ""; + } + + String newIsRare = "false"; + + if (StringUtils.isNotBlank(taxonRefName)) { + PoxPayloadOut taxonPayload = null; + + try { + taxonPayload = findTaxonByRefName(taxonRefName); + } + catch (WebApplicationException e) { + logger.error("Error finding taxon: refName=" + taxonRefName, e); + } + + if (taxonPayload != null) { + // UCBG-369: Changing this so that it only checks the primary conservation category. + + String conservationCategory = getFieldValue(taxonPayload, TaxonBotGardenConstants.CONSERVATION_CATEGORY_SCHEMA_NAME, + TaxonBotGardenConstants.CONSERVATION_CATEGORY_FIELD_NAME); + + if (isRare(conservationCategory)) { + newIsRare = "true"; + } + } + } + + if (!newIsRare.equals(oldIsRare)) { + logger.debug("setting rare flag: collectionObjectCsid=" + collectionObjectCsid + " oldIsRare=" + oldIsRare +" newIsRare=" + newIsRare); + + setRareFlag(collectionObjectCsid, newIsRare); + + results.setNumAffected(1); + results.setUserNote("rare flag set to " + newIsRare); + } + else { + logger.debug("not setting rare flag: collectionObjectCsid=" + collectionObjectCsid + " oldIsRare=" + oldIsRare +" newIsRare=" + newIsRare); + + results.setNumAffected(0); + results.setUserNote("rare flag not changed"); + } + } + + return results; + } + + public static boolean isRare(String conservationCategoryRefName) { + boolean isRare = false; + + if (StringUtils.isNotEmpty(conservationCategoryRefName)) { + // The conservation category is non-empty, so it's rare... + isRare = true; + + // ...unless it's one of the non-rare ones. + + // Check if the display name starts with a prefix that + // indicates that it isn't rare. + + RefName.AuthorityItem item = RefName.AuthorityItem.parse(conservationCategoryRefName); + String displayName = item.getDisplayName(); + + for (String prefix : NON_RARE_CONSERVATION_CATEGORY_PREFIXES) { + if (displayName.startsWith(prefix)) { + isRare = false; + break; + } + } + } + + return isRare; + } + + /** + * Updates the rare flags of the specified collectionobjects. + * + * @param collectionObjectCsids The csids of the collectionobjects + * @return + * @throws URISyntaxException + * @throws DocumentException + */ + public InvocationResults updateRareFlags(List collectionObjectCsids) throws URISyntaxException, DocumentException { + int numSubmitted = collectionObjectCsids.size(); + long numAffected = 0; + + + for (String collectionObjectCsid : collectionObjectCsids) { + InvocationResults itemResults = updateRareFlag(collectionObjectCsid); + + numAffected += itemResults.getNumAffected(); + } + + InvocationResults results = new InvocationResults(); + results.setNumAffected(numAffected); + results.setUserNote("updated " + numAffected + " of " + numSubmitted + " cataloging records"); + + return results; + } + + /** + * Updates the rare flags of all collectionobjects. + * + * @return + * @throws URISyntaxException + * @throws DocumentException + */ + public InvocationResults updateAllRareFlags() throws URISyntaxException, DocumentException { + long numFound = 0; + long numAffected = 0; + + int pageSize = 50; + int pageNum = 0; + List csids = Collections.emptyList(); + + do { + csids = findAllCollectionObjects(pageSize, pageNum); + logger.debug("pageNum=" + pageNum + " pageSize=" + pageSize + " result size=" + csids.size()); + + InvocationResults pageResults = updateRareFlags(csids); + + numAffected += pageResults.getNumAffected(); + numFound += csids.size(); + + pageNum++; + } + while (csids.size() == pageSize); + + InvocationResults results = new InvocationResults(); + results.setNumAffected(numAffected); + results.setUserNote("updated " + numAffected + " of " + numFound + " cataloging records"); + + return null; + } + + /** + * Sets the rare flag of the specified collectionobject to the specified value. + * + * @param collectionObjectCsid The csid of the collectionobject + * @param rareFlag The value of the rare flag + * @throws URISyntaxException + */ + private void setRareFlag(String collectionObjectCsid, String rareFlag) throws URISyntaxException { + String updatePayload = + "" + + "" + + "" + + getFieldXml("rare", rareFlag) + + "" + + "" + + "" + + ""; + + NuxeoBasedResource resource = (NuxeoBasedResource) getResourceMap().get(CollectionObjectClient.SERVICE_NAME); + resource.update(getResourceMap(), createUriInfo(), collectionObjectCsid, updatePayload); + } +} diff --git a/services/claim/3rdparty/build.xml b/services/claim/3rdparty/build.xml new file mode 100644 index 000000000..de90188f9 --- /dev/null +++ b/services/claim/3rdparty/build.xml @@ -0,0 +1,129 @@ + + + + claim service 3rdparty + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/claim/3rdparty/nuxeo-platform-cs-claim/build.xml b/services/claim/3rdparty/nuxeo-platform-cs-claim/build.xml new file mode 100644 index 000000000..a91010701 --- /dev/null +++ b/services/claim/3rdparty/nuxeo-platform-cs-claim/build.xml @@ -0,0 +1,161 @@ + + + + claim nuxeo document type + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/claim/3rdparty/nuxeo-platform-cs-claim/pom.xml b/services/claim/3rdparty/nuxeo-platform-cs-claim/pom.xml new file mode 100644 index 000000000..10c4e3aa7 --- /dev/null +++ b/services/claim/3rdparty/nuxeo-platform-cs-claim/pom.xml @@ -0,0 +1,48 @@ + + + + org.collectionspace.services + org.collectionspace.services.claim.3rdparty + 4.5-SNAPSHOT + + + 4.0.0 + org.collectionspace.services + org.collectionspace.services.claim.3rdparty.nuxeo + services.claim.3rdparty.nuxeo + jar + + Claim Nuxeo Document Type + + + + + + src/main/resources + true + + + ../../../../3rdparty/nuxeo/nuxeo-doctype/src/main/resources + true + + + + + org.apache.maven.plugins + maven-jar-plugin + + + target/classes/META-INF/MANIFEST.MF + + ${eclipseVersion} + 2 + + + + + + + + diff --git a/services/claim/3rdparty/nuxeo-platform-cs-claim/src/main/resources/OSGI-INF/layouts-contrib.xml b/services/claim/3rdparty/nuxeo-platform-cs-claim/src/main/resources/OSGI-INF/layouts-contrib.xml new file mode 100644 index 000000000..85fb7f605 --- /dev/null +++ b/services/claim/3rdparty/nuxeo-platform-cs-claim/src/main/resources/OSGI-INF/layouts-contrib.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + claimNumber + + + + + + + true + + claimNumber + + + dataInputText + + + + + diff --git a/services/claim/3rdparty/nuxeo-platform-cs-claim/src/main/resources/schemas/claims_common.xsd b/services/claim/3rdparty/nuxeo-platform-cs-claim/src/main/resources/schemas/claims_common.xsd new file mode 100644 index 000000000..631197dd0 --- /dev/null +++ b/services/claim/3rdparty/nuxeo-platform-cs-claim/src/main/resources/schemas/claims_common.xsd @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/services/claim/3rdparty/pom.xml b/services/claim/3rdparty/pom.xml new file mode 100644 index 000000000..09f159aa2 --- /dev/null +++ b/services/claim/3rdparty/pom.xml @@ -0,0 +1,32 @@ + + + + org.collectionspace.services.claim + org.collectionspace.services + 4.5-SNAPSHOT + + + 4.0.0 + org.collectionspace.services.claim.3rdparty + services.claim.3rdparty + pom + + + 3rd party build for claim service + + + + claim + Claim + claims_common + Tenant + + + + + + diff --git a/services/claim/build.xml b/services/claim/build.xml new file mode 100644 index 000000000..8fe8c2e59 --- /dev/null +++ b/services/claim/build.xml @@ -0,0 +1,124 @@ + + + + claim service + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/claim/client/pom.xml b/services/claim/client/pom.xml new file mode 100644 index 000000000..53f2e2747 --- /dev/null +++ b/services/claim/client/pom.xml @@ -0,0 +1,97 @@ + + + + org.collectionspace.services + org.collectionspace.services.claim + 4.5-SNAPSHOT + + + 4.0.0 + org.collectionspace.services + org.collectionspace.services.claim.client + services.claim.client + + + + + org.slf4j + slf4j-api + provided + + + org.slf4j + slf4j-log4j12 + provided + + + + org.collectionspace.services + org.collectionspace.services.authority.jaxb + true + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.jaxb + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.common + true + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.client + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.claim.jaxb + ${project.version} + + + + org.collectionspace.services + org.collectionspace.services.person.client + ${project.version} + + + + org.testng + testng + 5.6 + + + org.jboss.resteasy + resteasy-jaxrs + + + + tjws + webserver + + + + + org.jboss.resteasy + resteasy-jaxb-provider + + + org.jboss.resteasy + resteasy-multipart-provider + + + commons-httpclient + commons-httpclient + 3.1 + + + + + collectionspace-services-claim-client + + diff --git a/services/claim/client/src/main/java/org/collectionspace/services/client/ClaimClient.java b/services/claim/client/src/main/java/org/collectionspace/services/client/ClaimClient.java new file mode 100644 index 000000000..45b91f22f --- /dev/null +++ b/services/claim/client/src/main/java/org/collectionspace/services/client/ClaimClient.java @@ -0,0 +1,55 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + * + * http://www.collectionspace.org + * http://wiki.collectionspace.org + * + * Copyright © 2009 Regents of the University of California + * + * 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.services.client; + +import org.collectionspace.services.claim.ClaimsCommon; + +/** + * ClaimClient.java + * + * $LastChangedRevision: 5284 $ + * $LastChangedDate: 2011-07-22 12:44:36 -0700 (Fri, 22 Jul 2011) $ + * + */ +public class ClaimClient extends AbstractCommonListPoxServiceClientImpl { + public static final String SERVICE_NAME = "claims"; + public static final String SERVICE_PATH_COMPONENT = SERVICE_NAME; + public static final String SERVICE_PATH = "/" + SERVICE_PATH_COMPONENT; + public static final String SERVICE_PATH_PROXY = SERVICE_PATH + "/"; + public static final String SERVICE_PAYLOAD_NAME = SERVICE_NAME; + + public ClaimClient() throws Exception { + super(); + // TODO Auto-generated constructor stub + } + + @Override + public String getServiceName() { + return SERVICE_NAME; + } + + @Override + public String getServicePathComponent() { + return SERVICE_PATH_COMPONENT; + } + + @Override + public Class getProxyClass() { + return ClaimProxy.class; + } +} diff --git a/services/claim/client/src/main/java/org/collectionspace/services/client/ClaimProxy.java b/services/claim/client/src/main/java/org/collectionspace/services/client/ClaimProxy.java new file mode 100644 index 000000000..f513bf8f4 --- /dev/null +++ b/services/claim/client/src/main/java/org/collectionspace/services/client/ClaimProxy.java @@ -0,0 +1,72 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + * + * http://www.collectionspace.org + * http://wiki.collectionspace.org + * + * Copyright © 2009 Regents of the University of California + * + * 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 + * + * 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.collectionspace.services.client; + +import org.jboss.resteasy.client.ClientResponse; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; + +import org.collectionspace.services.client.workflow.WorkflowClient; +import org.collectionspace.services.jaxb.AbstractCommonList; + +/** + * ClaimProxy.java + * + * $LastChangedRevision: 5284 $ + * $LastChangedDate: 2011-07-22 12:44:36 -0700 (Fri, 22 Jul 2011) $ + */ +@Path("/claims/") +@Produces("application/xml") +@Consumes("application/xml") +public interface ClaimProxy extends CollectionSpaceCommonListPoxProxy { + + // Sorted list + @GET + @Produces({"application/xml"}) + ClientResponse readListSortedBy( + @QueryParam(IClientQueryParams.ORDER_BY_PARAM) String sortFieldName); + + @Override + @GET + @Produces({"application/xml"}) + ClientResponse readIncludeDeleted( + @QueryParam(WorkflowClient.WORKFLOW_QUERY_NONDELETED) String includeDeleted); + + @Override + @GET + @Produces({"application/xml"}) + ClientResponse keywordSearchIncludeDeleted( + @QueryParam(IQueryManager.SEARCH_TYPE_KEYWORDS_KW) String keywords, + @QueryParam(WorkflowClient.WORKFLOW_QUERY_NONDELETED) String includeDeleted); + + @GET + @Produces({"application/xml"}) + ClientResponse keywordSearchSortedBy( + @QueryParam(IQueryManager.SEARCH_TYPE_KEYWORDS_KW) String keywords, + @QueryParam(IClientQueryParams.ORDER_BY_PARAM) String sortFieldName); +} diff --git a/services/claim/client/src/test/java/org/collectionspace/services/client/test/ClaimAuthRefsTest.java b/services/claim/client/src/test/java/org/collectionspace/services/client/test/ClaimAuthRefsTest.java new file mode 100644 index 000000000..b132ad1fe --- /dev/null +++ b/services/claim/client/src/test/java/org/collectionspace/services/client/test/ClaimAuthRefsTest.java @@ -0,0 +1,349 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + * + * http://www.collectionspace.org + * http://wiki.collectionspace.org + * + * Copyright © 2009 Regents of the University of California + * + * 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 + * + * 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.collectionspace.services.client.test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.collectionspace.services.PersonJAXBSchema; +import org.collectionspace.services.client.CollectionSpaceClient; +import org.collectionspace.services.client.ClaimClient; +import org.collectionspace.services.client.PersonAuthorityClient; +import org.collectionspace.services.client.PersonAuthorityClientUtils; +import org.collectionspace.services.client.PayloadOutputPart; +import org.collectionspace.services.client.PoxPayloadIn; +import org.collectionspace.services.client.PoxPayloadOut; +import org.collectionspace.services.common.authorityref.AuthorityRefList; +import org.collectionspace.services.jaxb.AbstractCommonList; +import org.collectionspace.services.claim.ClaimsCommon; +import org.collectionspace.services.claim.ClaimClaimantGroupList; +import org.collectionspace.services.claim.ClaimClaimantGroup; +import org.collectionspace.services.person.PersonTermGroup; + +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.Test; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * ClaimAuthRefsTest, carries out Authority References tests against a + * deployed and running Claim Service. + * + * $LastChangedRevision: 4159 $ + * $LastChangedDate: 2011-02-15 16:11:08 -0800 (Tue, 15 Feb 2011) $ + */ +public class ClaimAuthRefsTest extends BaseServiceTest { + + private final String CLASS_NAME = ClaimAuthRefsTest.class.getName(); + private final Logger logger = LoggerFactory.getLogger(CLASS_NAME); + + // Instance variables specific to this test. + final String PERSON_AUTHORITY_NAME = "TestPersonAuth"; + private String knownResourceId = null; + private List claimIdsCreated = new ArrayList(); + private List personIdsCreated = new ArrayList(); + private String personAuthCSID = null; + private String claimFilerRefName = null; + private String claimOnBehalfOfRefName = null; + + /* (non-Javadoc) + * @see org.collectionspace.services.client.test.BaseServiceTest#getClientInstance() + */ + @Override + protected CollectionSpaceClient getClientInstance() { + throw new UnsupportedOperationException(); //method not supported (or needed) in this test class + } + + /* (non-Javadoc) + * @see org.collectionspace.services.client.test.BaseServiceTest#getAbstractCommonList(org.jboss.resteasy.client.ClientResponse) + */ + @Override + protected AbstractCommonList getCommonList( + Response response) { + throw new UnsupportedOperationException(); //method not supported (or needed) in this test class + } + + // --------------------------------------------------------------- + // CRUD tests : CREATE tests + // --------------------------------------------------------------- + // Success outcomes + @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class) + public void createWithAuthRefs(String testName) throws Exception { + testSetup(STATUS_CREATED, ServiceRequestType.CREATE); + + // Submit the request to the service and store the response. + String identifier = createIdentifier(); + + // Create all the person refs and entities + createPersonRefs(); + + // Create a new Claim resource. + // + // One or more fields in this resource will be PersonAuthority + // references, and will refer to Person resources by their refNames. + ClaimClient claimClient = new ClaimClient(); + PoxPayloadOut claminInstance = createClaimInstance("claimNumber-" + identifier, + claimFilerRefName, + claimOnBehalfOfRefName); + Response res = claimClient.create(claminInstance); + try { + int statusCode = res.getStatus(); + if (logger.isDebugEnabled()) { + logger.debug(testName + ": status = " + statusCode); + } + Assert.assertTrue(testRequestType.isValidStatusCode(statusCode), invalidStatusCodeMessage(testRequestType, statusCode)); + Assert.assertEquals(statusCode, testExpectedStatusCode); + + String csid = extractId(res); + if (knownResourceId == null) { + knownResourceId = csid; + } + // Store the IDs from every resource created by tests, + // so they can be deleted after tests have been run. + claimIdsCreated.add(csid); + } finally { + res.close(); + } + } + + protected void createPersonRefs() throws Exception{ + // Create a temporary PersonAuthority resource, and its corresponding + // refName by which it can be identified. + PersonAuthorityClient personAuthClient = new PersonAuthorityClient(); + PoxPayloadOut multipart = PersonAuthorityClientUtils.createPersonAuthorityInstance( + PERSON_AUTHORITY_NAME, PERSON_AUTHORITY_NAME, personAuthClient.getCommonPartName()); + Response res = personAuthClient.create(multipart); + try { + int statusCode = res.getStatus(); + + Assert.assertTrue(testRequestType.isValidStatusCode(statusCode), + invalidStatusCodeMessage(testRequestType, statusCode)); + Assert.assertEquals(statusCode, STATUS_CREATED); + personAuthCSID = extractId(res); + } finally { + res.close(); + } + String authRefName = PersonAuthorityClientUtils.getAuthorityRefName(personAuthCSID, personAuthClient); + + // Create temporary Person resources, and their corresponding refNames + // by which they can be identified. + String csid = createPerson("Carrie", "ClaimFiler", "carrieClaimFiler", authRefName); + personIdsCreated.add(csid); + claimFilerRefName = PersonAuthorityClientUtils.getPersonRefName(personAuthCSID, csid, personAuthClient); + + csid = createPerson("Ben", "BehalfOf", "benBehalfOf", authRefName); + personIdsCreated.add(csid); + claimOnBehalfOfRefName = PersonAuthorityClientUtils.getPersonRefName(personAuthCSID, csid, personAuthClient); + } + + protected String createPerson(String firstName, String surName, String shortId, String authRefName ) throws Exception { + PersonAuthorityClient personAuthClient = new PersonAuthorityClient(); + Map personInfo = new HashMap(); + personInfo.put(PersonJAXBSchema.FORE_NAME, firstName); + personInfo.put(PersonJAXBSchema.SUR_NAME, surName); + personInfo.put(PersonJAXBSchema.SHORT_IDENTIFIER, shortId); + List personTerms = new ArrayList(); + PersonTermGroup term = new PersonTermGroup(); + String termName = firstName + " " + surName; + term.setTermDisplayName(termName); + term.setTermName(termName); + personTerms.add(term); + PoxPayloadOut multipart = + PersonAuthorityClientUtils.createPersonInstance(personAuthCSID, + authRefName, personInfo, personTerms, personAuthClient.getItemCommonPartName()); + Response res = personAuthClient.createItem(personAuthCSID, multipart); + int statusCode = res.getStatus(); + + Assert.assertTrue(testRequestType.isValidStatusCode(statusCode), + invalidStatusCodeMessage(testRequestType, statusCode)); + Assert.assertEquals(statusCode, STATUS_CREATED); + return extractId(res); + } + + // Success outcomes + @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class, + dependsOnMethods = {"createWithAuthRefs"}) + public void readAndCheckAuthRefs(String testName) throws Exception { + // Perform setup. + testSetup(STATUS_OK, ServiceRequestType.READ); + + // Submit the request to the service and store the response. + ClaimClient claimClient = new ClaimClient(); + Response res = claimClient.read(knownResourceId); + try { + assertStatusCode(res, testName); + PoxPayloadIn input = new PoxPayloadIn(res.readEntity(String.class)); + ClaimsCommon claimCommon = (ClaimsCommon) extractPart(input, claimClient.getCommonPartName(), ClaimsCommon.class); + Assert.assertNotNull(claimCommon); + + if(logger.isDebugEnabled()){ + logger.debug(objectAsXmlString(claimCommon, ClaimsCommon.class)); + } + // Check a couple of fields + Assert.assertEquals(claimCommon.getClaimClaimantGroupList().getClaimClaimantGroup().get(0).getFiledBy(), claimFilerRefName); + Assert.assertEquals(claimCommon.getClaimClaimantGroupList().getClaimClaimantGroup().get(0).getFiledOnBehalfOf(), claimOnBehalfOfRefName); + } finally { + if (res != null) { + res.close(); + } + } + + // Get the auth refs and check them + res = claimClient.getAuthorityRefs(knownResourceId); + AuthorityRefList list = null; + try { + assertStatusCode(res, testName); + list = res.readEntity(AuthorityRefList.class); + Assert.assertNotNull(list); + } finally { + if (res != null) { + res.close(); + } + } + + int expectedAuthRefs = personIdsCreated.size(); + List items = list.getAuthorityRefItem(); + int numAuthRefsFound = items.size(); + if (logger.isDebugEnabled()) { + logger.debug("Expected " + expectedAuthRefs + " authority references, found " + numAuthRefsFound); + } + + // Optionally output additional data about list members for debugging. + boolean iterateThroughList = true; + if (iterateThroughList && logger.isDebugEnabled()) { + int i = 0; + for (AuthorityRefList.AuthorityRefItem item : items) { + logger.debug(testName + ": list-item[" + i + "] Field:" + + item.getSourceField() + "= " + + item.getAuthDisplayName() + + item.getItemDisplayName()); + logger.debug(testName + ": list-item[" + i + "] refName=" + + item.getRefName()); + logger.debug(testName + ": list-item[" + i + "] URI=" + + item.getUri()); + i++; + } + } + + Assert.assertEquals(numAuthRefsFound, expectedAuthRefs, + "Did not find all expected authority references! " + "Expected " + expectedAuthRefs + ", found " + numAuthRefsFound); + } + + + // --------------------------------------------------------------- + // Cleanup of resources created during testing + // --------------------------------------------------------------- + + /** + * Deletes all resources created by tests, after all tests have been run. + * + * This cleanup method will always be run, even if one or more tests fail. + * For this reason, it attempts to remove all resources created + * at any point during testing, even if some of those resources + * may be expected to be deleted by certain tests. + * @throws Exception + */ + @AfterClass(alwaysRun=true) + public void cleanUp() throws Exception { + String noTest = System.getProperty("noTestCleanup"); + if (Boolean.TRUE.toString().equalsIgnoreCase(noTest)) { + if (logger.isDebugEnabled()) { + logger.debug("Skipping Cleanup phase ..."); + } + return; + } + if (logger.isDebugEnabled()) { + logger.debug("Cleaning up temporary resources created for testing ..."); + } + + // + // Delete Claim resource(s). + ClaimClient claimClient = new ClaimClient(); + for (String resourceId : claimIdsCreated) { + // Note: Any non-success responses are ignored and not reported. + claimClient.delete(resourceId).close(); + } + + // + // Delete Person resource(s) (before PersonAuthority resources). + PersonAuthorityClient personAuthClient = new PersonAuthorityClient(); + for (String resourceId : personIdsCreated) { + // Note: Any non-success responses are ignored and not reported. + personAuthClient.deleteItem(personAuthCSID, resourceId).close(); + } + if (personAuthCSID != null) { + personAuthClient.delete(personAuthCSID).close(); + } + } + + // --------------------------------------------------------------- + // Utility methods used by tests above + // --------------------------------------------------------------- + + @Override + protected String getServiceName() { + return ClaimClient.SERVICE_NAME; + } + + @Override + public String getServicePathComponent() { + return ClaimClient.SERVICE_PATH_COMPONENT; + } + + private PoxPayloadOut createClaimInstance(String claimNumber, + String claimFiler, + String claimFiledOnBehalfOf) throws Exception { + ClaimsCommon claimCommon = new ClaimsCommon(); + claimCommon.setClaimNumber(claimNumber); + ClaimClaimantGroupList claimClaimantGroupList = new ClaimClaimantGroupList(); + ClaimClaimantGroup claimClaimantGroup = new ClaimClaimantGroup(); + claimClaimantGroup.setFiledBy(claimFiler); + claimClaimantGroup.setFiledOnBehalfOf(claimFiledOnBehalfOf); + claimClaimantGroupList.getClaimClaimantGroup().add(claimClaimantGroup); + claimCommon.setClaimClaimantGroupList(claimClaimantGroupList); + + PoxPayloadOut multipart = new PoxPayloadOut(this.getServicePathComponent()); + PayloadOutputPart commonPart = + multipart.addPart(claimCommon, MediaType.APPLICATION_XML_TYPE); + commonPart.setLabel(new ClaimClient().getCommonPartName()); + + if(logger.isDebugEnabled()){ + logger.debug("to be created, claim common"); + logger.debug(objectAsXmlString(claimCommon, ClaimsCommon.class)); + } + + return multipart; + } + + @Override + protected CollectionSpaceClient getClientInstance(String clientPropertiesFilename) throws Exception { + throw new UnsupportedOperationException(); //method not supported (or needed) in this test class + } +} diff --git a/services/claim/client/src/test/java/org/collectionspace/services/client/test/ClaimServiceTest.java b/services/claim/client/src/test/java/org/collectionspace/services/client/test/ClaimServiceTest.java new file mode 100644 index 000000000..d1f999fb6 --- /dev/null +++ b/services/claim/client/src/test/java/org/collectionspace/services/client/test/ClaimServiceTest.java @@ -0,0 +1,240 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + * + * http://www.collectionspace.org + * http://wiki.collectionspace.org + * + * Copyright © 2009 Regents of the University of California + * + * 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 + * + * 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.collectionspace.services.client.test; + +import java.util.List; +import javax.ws.rs.core.MediaType; +import org.collectionspace.services.common.api.GregorianCalendarDateTimeUtils; +import org.collectionspace.services.client.CollectionSpaceClient; +import org.collectionspace.services.client.ClaimClient; +import org.collectionspace.services.jaxb.AbstractCommonList; +import org.collectionspace.services.claim.ClaimsCommon; +import org.collectionspace.services.claim.ResponsibleDepartmentsList; +import org.collectionspace.services.claim.ClaimClaimantGroupList; +import org.collectionspace.services.claim.ClaimClaimantGroup; +import org.collectionspace.services.claim.ClaimReceivedGroupList; +import org.collectionspace.services.claim.ClaimReceivedGroup; + +import org.collectionspace.services.client.PayloadOutputPart; +import org.collectionspace.services.client.PoxPayloadOut; +import org.testng.Assert; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * ClaimServiceTest, carries out tests against a + * deployed and running Claim Service. + * + * $LastChangedRevision: 5952 $ + * $LastChangedDate: 2011-11-14 23:26:36 -0800 (Mon, 14 Nov 2011) $ + */ +public class ClaimServiceTest extends AbstractPoxServiceTestImpl { + + /** The logger. */ + private final String CLASS_NAME = ClaimServiceTest.class.getName(); + private final Logger logger = LoggerFactory.getLogger(CLASS_NAME); + + // Instance variables specific to this test. + private final static String CURRENT_DATE_UTC = + GregorianCalendarDateTimeUtils.timestampUTC(); + + /* (non-Javadoc) + * @see org.collectionspace.services.client.test.BaseServiceTest#getClientInstance() + */ + @Override + protected CollectionSpaceClient getClientInstance() throws Exception { + return new ClaimClient(); + } + + // --------------------------------------------------------------- + // Utility methods used by tests above + // --------------------------------------------------------------- + + @Override + protected String getServiceName() { + return ClaimClient.SERVICE_NAME; + } + + /* (non-Javadoc) + * @see org.collectionspace.services.client.test.BaseServiceTest#getServicePathComponent() + */ + @Override + public String getServicePathComponent() { + return ClaimClient.SERVICE_PATH_COMPONENT; + } + + /** + * Creates the claim instance. + * + * @param identifier the identifier + * @return the multipart output + * @throws Exception + */ + private PoxPayloadOut createClaimInstance(String identifier) throws Exception { + return createInstance("claimNumber-" + identifier); + } + + /** + * Creates an instance of a Claim record for testing. + * + * @param claimNumber A claim number. + * @return Multipart output suitable for use as a payload + * in a create or update request. + * @throws Exception + */ + @Override + protected PoxPayloadOut createInstance(String claimNumber) throws Exception { + ClaimsCommon claimCommon = new ClaimsCommon(); + + ResponsibleDepartmentsList responsibleDepartmentsList = new ResponsibleDepartmentsList(); + List responsibleDepartments = responsibleDepartmentsList.getResponsibleDepartment(); + String identifier = createIdentifier(); + responsibleDepartments.add("First Responsible Department-" + identifier); + responsibleDepartments.add("Second Responsible Department-" + identifier); + + ClaimClaimantGroupList claimClaimantGroupList = new ClaimClaimantGroupList(); + ClaimClaimantGroup claimClaimantGroup = new ClaimClaimantGroup(); + claimClaimantGroup.setFiledBy("urn:cspace:core.collectionspace.org:personauthorities:name(TestPersonAuth):item:name(carrieClaimFiler)'Carrie ClaimFiler'"); + claimClaimantGroup.setFiledOnBehalfOf("urn:cspace:core.collectionspace.org:personauthorities:name(TestPersonAuth):item:name(benBehalfOf)'Ben BehalfOf'"); + claimClaimantGroup.setClaimantNote(getUTF8DataFragment()); + claimClaimantGroupList.getClaimClaimantGroup().add(claimClaimantGroup); + + ClaimReceivedGroupList claimReceivedGroupList = new ClaimReceivedGroupList(); + ClaimReceivedGroup claimReceivedGroup = new ClaimReceivedGroup(); + claimReceivedGroup.setClaimReceivedDate(CURRENT_DATE_UTC); + claimReceivedGroup.setClaimReceivedNote(getUTF8DataFragment()); + claimReceivedGroupList.getClaimReceivedGroup().add(claimReceivedGroup); + + claimCommon.setResponsibleDepartments(responsibleDepartmentsList); + claimCommon.setClaimClaimantGroupList(claimClaimantGroupList); + claimCommon.setClaimReceivedGroupList(claimReceivedGroupList); + claimCommon.setClaimNumber(claimNumber); + + PoxPayloadOut multipart = new PoxPayloadOut(this.getServicePathComponent()); + PayloadOutputPart commonPart = multipart.addPart(claimCommon, MediaType.APPLICATION_XML_TYPE); + commonPart.setLabel(new ClaimClient().getCommonPartName()); + + if(logger.isDebugEnabled()){ + logger.debug("to be created, claim common"); + logger.debug(objectAsXmlString(claimCommon, ClaimsCommon.class)); + } + + return multipart; + } + + /* + * For convenience and terseness, this test method is the base of the test execution dependency chain. Other test methods may + * refer to this method in their @Test annotation declarations. + */ + @Override + public void CRUDTests(String testName) { + // Needed for TestNG dependency chain. + } + + @Override + protected PoxPayloadOut createInstance(String commonPartName, String identifier) throws Exception { + PoxPayloadOut result = createClaimInstance(identifier); + return result; + } + + @Override + protected ClaimsCommon updateInstance(ClaimsCommon claimCommon) { + // Update its content. + claimCommon.setClaimNumber(""); // Test deletion of existing string value + + String claimNote = claimCommon.getClaimClaimantGroupList().getClaimClaimantGroup().get(0).getClaimantNote(); + claimCommon.getClaimClaimantGroupList().getClaimClaimantGroup().get(0).setClaimantNote("updated claim note-" + claimNote); + + claimCommon.getResponsibleDepartments().getResponsibleDepartment().remove(0); // Test removing a value from a list + + String currentTimestamp = GregorianCalendarDateTimeUtils.timestampUTC(); + claimCommon.getClaimReceivedGroupList().getClaimReceivedGroup().get(0).setClaimReceivedDate(currentTimestamp); + claimCommon.getClaimReceivedGroupList().getClaimReceivedGroup().get(0).setClaimReceivedNote(""); + + return claimCommon; + } + + @Override + protected void compareReadInstances(ClaimsCommon original, ClaimsCommon fromRead) throws Exception { + // Check selected fields. + + // Check the values of one or more date/time fields. + String receivedDate = fromRead.getClaimReceivedGroupList().getClaimReceivedGroup().get(0).getClaimReceivedDate(); + + if (logger.isDebugEnabled()) { + logger.debug("receivedDate=" + receivedDate); + logger.debug("TIMESTAMP_UTC=" + CURRENT_DATE_UTC); + } + Assert.assertTrue(receivedDate.equals(CURRENT_DATE_UTC)); + + // Check the values of fields containing Unicode UTF-8 (non-Latin-1) characters. + String claimNote = fromRead.getClaimClaimantGroupList().getClaimClaimantGroup().get(0).getClaimantNote(); + + if(logger.isDebugEnabled()){ + logger.debug("UTF-8 data sent=" + getUTF8DataFragment() + "\n" + + "UTF-8 data received=" + claimNote); + } + Assert.assertEquals(claimNote, getUTF8DataFragment(), + "UTF-8 data retrieved '" + claimNote + + "' does not match expected data '" + getUTF8DataFragment()); + } + + @Override + protected void compareUpdatedInstances(ClaimsCommon claimCommon, ClaimsCommon updatedClaimCommon) throws Exception { + String originalClaimNote = claimCommon.getClaimClaimantGroupList().getClaimClaimantGroup().get(0).getClaimantNote(); + String updatedClaimNote = updatedClaimCommon.getClaimClaimantGroupList().getClaimClaimantGroup().get(0).getClaimantNote(); + + Assert.assertEquals(updatedClaimNote, originalClaimNote, + "Data in updated object did not match submitted data."); + + List updatedResponsibleDepartments = updatedClaimCommon.getResponsibleDepartments().getResponsibleDepartment(); + Assert.assertEquals(1, + updatedResponsibleDepartments.size(), + "Data in updated object did not match submitted data."); + + Assert.assertEquals(updatedResponsibleDepartments.get(0), + claimCommon.getResponsibleDepartments().getResponsibleDepartment().get(0), + "Data in updated object did not match submitted data."); + + Assert.assertNotSame(claimCommon.getClaimReceivedGroupList().getClaimReceivedGroup().get(0).getClaimReceivedDate(), + updatedClaimCommon.getClaimReceivedGroupList().getClaimReceivedGroup().get(0).getClaimReceivedDate(), + "Data in updated object did not match submitted data."); + + if(logger.isDebugEnabled()){ + logger.debug("UTF-8 data sent=" + originalClaimNote + "\n" + + "UTF-8 data received=" + updatedClaimNote); + } + Assert.assertTrue(updatedClaimNote.contains(getUTF8DataFragment()), + "UTF-8 data retrieved '" + updatedClaimNote + + "' does not contain expected data '" + getUTF8DataFragment()); + Assert.assertEquals(updatedClaimNote, + originalClaimNote, + "Data in updated object did not match submitted data."); +} + + @Override + protected CollectionSpaceClient getClientInstance(String clientPropertiesFilename) throws Exception { + // TODO Auto-generated method stub + return new ClaimClient(); + } +} diff --git a/services/claim/client/src/test/resources/log4j.properties b/services/claim/client/src/test/resources/log4j.properties new file mode 100644 index 000000000..148a3e865 --- /dev/null +++ b/services/claim/client/src/test/resources/log4j.properties @@ -0,0 +1,23 @@ +log4j.rootLogger=debug, stdout, R + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + +# Pattern to output the caller's file name and line number. +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%t] [%c:%L] %m%n + +log4j.appender.R=org.apache.log4j.RollingFileAppender +log4j.appender.R.File=target/test-client.log + +log4j.appender.R.MaxFileSize=100KB +# Keep one backup file +log4j.appender.R.MaxBackupIndex=1 + +log4j.appender.R.layout=org.apache.log4j.PatternLayout +log4j.appender.R.layout.ConversionPattern=%d %-5p [%t] [%c:%L] %m%n + +#packages +log4j.logger.org.collectionspace=DEBUG +log4j.logger.org.apache=INFO +log4j.logger.httpclient=INFO +log4j.logger.org.jboss.resteasy=INFO diff --git a/services/claim/jaxb/pom.xml b/services/claim/jaxb/pom.xml new file mode 100644 index 000000000..9fd52065d --- /dev/null +++ b/services/claim/jaxb/pom.xml @@ -0,0 +1,35 @@ + + + + org.collectionspace.services.claim + org.collectionspace.services + 4.5-SNAPSHOT + + + 4.0.0 + org.collectionspace.services + org.collectionspace.services.claim.jaxb + services.claim.jaxb + + + + org.collectionspace.services + org.collectionspace.services.jaxb + ${project.version} + + + + + collectionspace-services-claim-jaxb + install + + + org.jvnet.jaxb2.maven2 + maven-jaxb2-plugin + + + + + diff --git a/services/claim/jaxb/src/main/java/org/collectionspace/services/ClaimJAXBSchema.java b/services/claim/jaxb/src/main/java/org/collectionspace/services/ClaimJAXBSchema.java new file mode 100644 index 000000000..b2b3ef5e1 --- /dev/null +++ b/services/claim/jaxb/src/main/java/org/collectionspace/services/ClaimJAXBSchema.java @@ -0,0 +1,10 @@ +/** + * + */ +package org.collectionspace.services; + +public interface ClaimJAXBSchema { + + final static String CLAIM_NUMBER = "claimNumber"; + +} diff --git a/services/claim/jaxb/src/main/java/org/collectionspace/services/ClaimListItemJAXBSchema.java b/services/claim/jaxb/src/main/java/org/collectionspace/services/ClaimListItemJAXBSchema.java new file mode 100644 index 000000000..d67896b39 --- /dev/null +++ b/services/claim/jaxb/src/main/java/org/collectionspace/services/ClaimListItemJAXBSchema.java @@ -0,0 +1,7 @@ +package org.collectionspace.services; + +public interface ClaimListItemJAXBSchema { + final static String CLAIM_NUMBER = "claimNumber"; + final static String CSID = "csid"; + final static String URI = "url"; +} diff --git a/services/claim/jaxb/src/main/resources/claims-common.xsd b/services/claim/jaxb/src/main/resources/claims-common.xsd new file mode 100644 index 000000000..5dff7e85d --- /dev/null +++ b/services/claim/jaxb/src/main/resources/claims-common.xsd @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/claim/pom.xml b/services/claim/pom.xml new file mode 100644 index 000000000..c224758fa --- /dev/null +++ b/services/claim/pom.xml @@ -0,0 +1,24 @@ + + + + + org.collectionspace.services + org.collectionspace.services.main + 4.5-SNAPSHOT + + + 4.0.0 + org.collectionspace.services + org.collectionspace.services.claim + services.claim + pom + + + jaxb + service + 3rdparty + client + + + + diff --git a/services/claim/service/pom.xml b/services/claim/service/pom.xml new file mode 100644 index 000000000..f9cb0290e --- /dev/null +++ b/services/claim/service/pom.xml @@ -0,0 +1,106 @@ + + + + + org.collectionspace.services + org.collectionspace.services.claim + 4.5-SNAPSHOT + + + 4.0.0 + org.collectionspace.services + org.collectionspace.services.claim.service + services.claim.service + jar + + + + org.collectionspace.services + org.collectionspace.services.common + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.claim.jaxb + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.claim.client + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.collectionobject.jaxb + ${project.version} + + + + junit + junit + 4.1 + test + + + org.testng + testng + 5.6 + + + + + + javax.security + jaas + 1.0.01 + provided + + + + dom4j + dom4j + 1.6.1 + provided + + + + + + org.jboss.resteasy + resteasy-jaxrs + + + tjws + webserver + + + + + org.jboss.resteasy + resteasy-jaxb-provider + + + org.jboss.resteasy + resteasy-multipart-provider + + + + + + org.nuxeo.ecm.core + nuxeo-core-api + ${nuxeo.core.version} + + + jboss-remoting + jboss + + + + + + + collectionspace-services-claim + + + diff --git a/services/claim/service/profiles.xml b/services/claim/service/profiles.xml new file mode 100644 index 000000000..347b9df22 --- /dev/null +++ b/services/claim/service/profiles.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/services/claim/service/src/main/java/org/collectionspace/services/claim/ClaimResource.java b/services/claim/service/src/main/java/org/collectionspace/services/claim/ClaimResource.java new file mode 100644 index 000000000..f49e11901 --- /dev/null +++ b/services/claim/service/src/main/java/org/collectionspace/services/claim/ClaimResource.java @@ -0,0 +1,64 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + * + * http://www.collectionspace.org + * http://wiki.collectionspace.org + * + * Copyright © 2009 Regents of the University of California + * + * 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 + * + * 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.collectionspace.services.claim; + +import org.collectionspace.services.client.ClaimClient; +import org.collectionspace.services.common.NuxeoBasedResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +/** + * ClaimResource.java + * + * Handles requests to the Claim service, orchestrates the retrieval + * of relevant resources, and returns responses to the client. + */ +@Path(ClaimClient.SERVICE_PATH) +@Consumes("application/xml") +@Produces("application/xml") +public class ClaimResource extends NuxeoBasedResource { + + final Logger logger = LoggerFactory.getLogger(ClaimResource.class); + + @Override + protected String getVersionString() { + final String lastChangeRevision = "$LastChangedRevision: 4763 $"; + return lastChangeRevision; + } + + @Override + public String getServiceName() { + return ClaimClient.SERVICE_NAME; + } + + @Override + public Class getCommonPartClass() { + return ClaimsCommon.class; + } + +} diff --git a/services/claim/service/src/main/java/org/collectionspace/services/claim/nuxeo/ClaimConstants.java b/services/claim/service/src/main/java/org/collectionspace/services/claim/nuxeo/ClaimConstants.java new file mode 100644 index 000000000..e517d5c02 --- /dev/null +++ b/services/claim/service/src/main/java/org/collectionspace/services/claim/nuxeo/ClaimConstants.java @@ -0,0 +1,35 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + + * http://www.collectionspace.org + * http://wiki.collectionspace.org + + * Copyright 2009 University of California at Berkeley + + * 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 + + * 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.collectionspace.services.claim.nuxeo; + +/** + * ClaimConstants specifies constants for the Claim service + * + */ +public class ClaimConstants { + + public final static String NUXEO_DOCTYPE = "Claim"; + public final static String NUXEO_SCHEMA_NAME = "claim"; + public final static String NUXEO_DC_TITLE = "CollectionSpace-Claim"; +} diff --git a/services/claim/service/src/main/java/org/collectionspace/services/claim/nuxeo/ClaimDocumentModelHandler.java b/services/claim/service/src/main/java/org/collectionspace/services/claim/nuxeo/ClaimDocumentModelHandler.java new file mode 100644 index 000000000..c482b59a4 --- /dev/null +++ b/services/claim/service/src/main/java/org/collectionspace/services/claim/nuxeo/ClaimDocumentModelHandler.java @@ -0,0 +1,39 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + + * http://www.collectionspace.org + * http://wiki.collectionspace.org + + * Copyright 2009 University of California at Berkeley + + * 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 + + * 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.collectionspace.services.claim.nuxeo; + +import org.collectionspace.services.claim.ClaimsCommon; +import org.collectionspace.services.nuxeo.client.java.NuxeoDocumentModelHandler; + +/** + * The Class ClaimDocumentModelHandler. + * + * $LastChangedRevision: 5284 $ + * $LastChangedDate: 2011-07-22 12:44:36 -0700 (Fri, 22 Jul 2011) $ + */ +public class ClaimDocumentModelHandler + extends NuxeoDocumentModelHandler { + +} + diff --git a/services/claim/service/src/main/java/org/collectionspace/services/claim/nuxeo/ClaimValidatorHandler.java b/services/claim/service/src/main/java/org/collectionspace/services/claim/nuxeo/ClaimValidatorHandler.java new file mode 100644 index 000000000..39a2b4cd6 --- /dev/null +++ b/services/claim/service/src/main/java/org/collectionspace/services/claim/nuxeo/ClaimValidatorHandler.java @@ -0,0 +1,25 @@ +package org.collectionspace.services.claim.nuxeo; + +import org.collectionspace.services.common.context.ServiceContext; +import org.collectionspace.services.common.document.InvalidDocumentException; +import org.collectionspace.services.common.document.ValidatorHandler; +import org.collectionspace.services.common.document.DocumentHandler.Action; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ClaimValidatorHandler implements ValidatorHandler { + + final Logger logger = LoggerFactory.getLogger(ClaimValidatorHandler.class); + + @Override + public void validate(Action action, ServiceContext ctx) + throws InvalidDocumentException { + + if(logger.isDebugEnabled()) { + logger.debug("validate() action=" + action.name()); + } + + } + +} diff --git a/services/claim/service/src/test/java/org/collectionspace/services/test/ClaimServiceTest.java b/services/claim/service/src/test/java/org/collectionspace/services/test/ClaimServiceTest.java new file mode 100644 index 000000000..08e022091 --- /dev/null +++ b/services/claim/service/src/test/java/org/collectionspace/services/test/ClaimServiceTest.java @@ -0,0 +1,10 @@ +package org.collectionspace.services.test; + +/** + * Placeholder for server-side testing of Claim service code. + * + * @version $Revision: 2107 $ + */ +public class ClaimServiceTest { + //empty +} diff --git a/services/claim/service/src/test/resources/log4j.xml b/services/claim/service/src/test/resources/log4j.xml new file mode 100644 index 000000000..52121cb83 --- /dev/null +++ b/services/claim/service/src/test/resources/log4j.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/client/src/main/java/org/collectionspace/services/client/AbstractServiceClientImpl.java b/services/client/src/main/java/org/collectionspace/services/client/AbstractServiceClientImpl.java index c5580ee9f..e2adcb7da 100644 --- a/services/client/src/main/java/org/collectionspace/services/client/AbstractServiceClientImpl.java +++ b/services/client/src/main/java/org/collectionspace/services/client/AbstractServiceClientImpl.java @@ -29,7 +29,6 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.Properties; -import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.ClientRequestContext; import javax.ws.rs.client.ClientRequestFilter; import javax.ws.rs.core.MultivaluedMap; @@ -53,13 +52,9 @@ import org.slf4j.LoggerFactory; // FIXME: Deprecated classes that need to be updated import org.jboss.resteasy.client.ProxyFactory; import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.conn.ssl.SSLContexts; - -import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; -import javax.net.ssl.SSLContext; import javax.net.ssl.X509TrustManager; /** @@ -139,12 +134,7 @@ public abstract class AbstractServiceClientImpl extends AbstractServiceTestImpl { + private final String CLASS_NAME = AbstractPoxServiceTestImpl.class.getName(); + private final Logger logger = LoggerFactory.getLogger(CLASS_NAME); + @Override public CPT extractCommonPartValue(Response res) throws Exception { CPT result = null; @@ -126,4 +129,5 @@ public abstract class AbstractPoxServiceTestImplservices.common-api jar + + org.apache.commons + commons-lang3 + 3.1 + + + + org.gbif + name-parser + 2.0 + org.slf4j slf4j-api @@ -35,8 +46,16 @@ jaxen 1.1.6 - + + + + + gbif-all + gbif-all + http://repository.gbif.org/content/groups/gbif + + diff --git a/services/common-api/src/main/java/org/collectionspace/services/common/api/TaxonFormatter.java b/services/common-api/src/main/java/org/collectionspace/services/common/api/TaxonFormatter.java new file mode 100644 index 000000000..215ec6f70 --- /dev/null +++ b/services/common-api/src/main/java/org/collectionspace/services/common/api/TaxonFormatter.java @@ -0,0 +1,153 @@ +package org.collectionspace.services.common.api; + +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.gbif.api.model.checklistbank.ParsedName; +import org.gbif.nameparser.NameParser; +import org.gbif.nameparser.UnparsableException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TaxonFormatter { + private static final Logger logger = LoggerFactory.getLogger(TaxonFormatter.class); + private static final Pattern HYBRID_FORMULA_PATTERN = Pattern.compile("^(.*?)(\\s[×xX]\\s)(.*)$"); + private static final Pattern BROKEN_HYBRID_FORMULA_PATTERN = Pattern.compile("^×\\s*|\\s*×$"); + private static final Pattern ADJACENT_ITALIC_TAG_PATTERN = Pattern.compile("(\\s*)"); + private static final Pattern STARTS_WITH_INFRASPECIFIC_RANK_PATTERN = Pattern.compile("^\\s*(var|subsp|cv|aff)\\."); + private static final String SUBSPECIES_QUALIFIER_MARKER_REGEXP = "(section|subsection|ser\\.|sser\\.)"; + private static final Pattern SUBSPECIES_WITH_QUALIFIER_PATTERN = Pattern.compile("(\\s|^)(subsp\\.\\s+)" + SUBSPECIES_QUALIFIER_MARKER_REGEXP + "(\\s)(.*?)(\\s|$)", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); + private static final Pattern PARENTHESIZED_SUBSPECIES_WITH_QUALIFIER_PATTERN = Pattern.compile("(\\s|^)(subsp\\.\\s+)\\(" + SUBSPECIES_QUALIFIER_MARKER_REGEXP + "(.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); + private static final Pattern FAMILY_NAME_PATTERN = Pattern.compile("^[A-Z]+$"); + + private NameParser nameParser; + + public TaxonFormatter() { + this.nameParser = new NameParser(); + } + + public String format(String name) { + if (StringUtils.isBlank(name)) { + return name; + } + + if (FAMILY_NAME_PATTERN.matcher(name).matches()) { + // Per Barbara Keller, family names are never italicized. + return name; + } + + Matcher hybridMatcher = HYBRID_FORMULA_PATTERN.matcher(name); + + if (hybridMatcher.matches()) { + String parentName1 = hybridMatcher.group(1); + String separator = hybridMatcher.group(2); + String parentName2 = hybridMatcher.group(3); + + logger.info("hybrid formula: parentName1=" + parentName1 + " parentName2=" + parentName2); + + return (format(parentName1) + separator + format(parentName2)); + } + + String normalizedName = name; + + if (BROKEN_HYBRID_FORMULA_PATTERN.matcher(normalizedName).find()) { + logger.info("broken hybrid: name=" + name + " normalizedName=" + normalizedName); + + normalizedName = BROKEN_HYBRID_FORMULA_PATTERN.matcher(normalizedName).replaceAll(""); + logger.info("normalized to:" + normalizedName); + } + + if (PARENTHESIZED_SUBSPECIES_WITH_QUALIFIER_PATTERN.matcher(normalizedName).find()) { + logger.info("parenthesized qualified subspecies: name=" + name + " normalizedName=" + normalizedName); + + normalizedName = PARENTHESIZED_SUBSPECIES_WITH_QUALIFIER_PATTERN.matcher(normalizedName).replaceFirst("$1$2$3$4"); + logger.info("normalized to:" + normalizedName); + } + + Matcher subspeciesWithQualifierMatcher = SUBSPECIES_WITH_QUALIFIER_PATTERN.matcher(normalizedName); + + if (subspeciesWithQualifierMatcher.find()) { + logger.info("qualified subspecies: name=" + name + " normalizedName=" + normalizedName); + + MatchResult matchResult = subspeciesWithQualifierMatcher.toMatchResult(); + + // Remove the qualifier (e.g. section, ser., sser.). In some data from SAGE, the latin name + // following the qualifier is capitalized, which the GBIF parser won't handle, so lowercase it. + String replacement = matchResult.group(1) + matchResult.group(2) + matchResult.group(5).toLowerCase() + matchResult.group(6); + normalizedName = normalizedName.substring(0, matchResult.start()) + replacement + normalizedName.substring(matchResult.end()); + logger.info("normalized to:" + normalizedName); + } + + if (STARTS_WITH_INFRASPECIFIC_RANK_PATTERN.matcher(normalizedName).find()) { + /* + * There are some non-standard taxon names in SAGE data, where there is an infraspecific rank/epithet, but no genus/species, e.g. + * subsp. occidentalis (J.T. Howell) C.B. Wolf + * + * Since the GBIF parser can't handle this, we'll temporarily prepend an arbitrary genus and species for parsing purposes. + */ + logger.info("name starts with infraspecific rank: name=" + name + " normalizedName=" + normalizedName); + + normalizedName = "Tempgenus tempspecies " + normalizedName; + logger.info("normalized to:" + normalizedName); + } + + ParsedName parsedName = null; + + try { + parsedName = nameParser.parse(normalizedName); + } + catch (UnparsableException e) { + /* + * Some non-standard taxon names in SAGE data have a species, but no genus. Try to account for these by + * temporarily prepending an arbitrary genus. + */ + + logger.info("Unparsable name, trying with a temp genus: name=" + name + " normalizedName=" + normalizedName); + + normalizedName = "Tempgenus " + normalizedName; + + try { + parsedName = nameParser.parse(normalizedName); + } + catch (UnparsableException ex) { + logger.error("error parsing name: name=" + name + " normalizedName=" + normalizedName + " message=" + e.getMessage()); + } + } + + if (parsedName != null) { + String genusOrAbove = parsedName.getGenusOrAbove(); + String specificEpithet = parsedName.getSpecificEpithet(); + String infraSpecificEpithet = parsedName.getInfraSpecificEpithet(); + + logger.debug("parsed name: genusOrAbove=" + genusOrAbove + " specificEpithet=" + specificEpithet + " infraSpecificEpithet=" + infraSpecificEpithet); + + if (StringUtils.isNotBlank(genusOrAbove)) { + name = italicize(name, genusOrAbove); + } + + if (StringUtils.isNotBlank(specificEpithet)) { + name = italicize(name, specificEpithet); + } + + if (StringUtils.isNotBlank(infraSpecificEpithet)) { + name = italicize(name, infraSpecificEpithet); + } + + name = compressTags(name); + } + + return name; + } + + private String italicize(String string, String substring) { + return Pattern.compile("(\\s|\\(|^)(" + Pattern.quote(substring) + ")(\\s|\\)|$)", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE).matcher(string).replaceAll("$1$2$3"); + } + + private String compressTags(String html) { + html = ADJACENT_ITALIC_TAG_PATTERN.matcher(html).replaceAll("$1"); + + return html; + } +} diff --git a/services/common/src/main/java/org/collectionspace/services/common/AbstractCollectionSpaceResourceImpl.java b/services/common/src/main/java/org/collectionspace/services/common/AbstractCollectionSpaceResourceImpl.java index 1a5dde637..e6636327b 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/AbstractCollectionSpaceResourceImpl.java +++ b/services/common/src/main/java/org/collectionspace/services/common/AbstractCollectionSpaceResourceImpl.java @@ -38,8 +38,6 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.collectionspace.services.client.CollectionSpaceClient; -import org.collectionspace.services.client.PoxPayloadIn; -import org.collectionspace.services.client.PoxPayloadOut; import org.collectionspace.services.common.CSWebApplicationException; import org.collectionspace.services.common.api.Tools; import org.collectionspace.services.common.config.ServiceConfigUtils; @@ -62,6 +60,7 @@ import org.collectionspace.services.config.service.DocHandlerParams.Params.Cache import org.collectionspace.services.config.service.ServiceBindingType; import org.collectionspace.services.config.service.DocHandlerParams.Params; import org.collectionspace.services.description.ServiceDescription; + import org.jboss.resteasy.spi.HttpRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/services/common/src/main/java/org/collectionspace/services/common/ResourceMapHolder.java b/services/common/src/main/java/org/collectionspace/services/common/ResourceMapHolder.java index 950ec7c3a..4e81e5a0e 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/ResourceMapHolder.java +++ b/services/common/src/main/java/org/collectionspace/services/common/ResourceMapHolder.java @@ -1,5 +1,8 @@ package org.collectionspace.services.common; +import org.collectionspace.services.client.PoxPayloadIn; +import org.collectionspace.services.client.PoxPayloadOut; + public interface ResourceMapHolder { - public ResourceMap getResourceMap(); + public ResourceMap getResourceMap(); } diff --git a/services/common/src/main/java/org/collectionspace/services/common/invocable/Invocable.java b/services/common/src/main/java/org/collectionspace/services/common/invocable/Invocable.java index efd24294d..b3002f479 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/invocable/Invocable.java +++ b/services/common/src/main/java/org/collectionspace/services/common/invocable/Invocable.java @@ -24,6 +24,9 @@ package org.collectionspace.services.common.invocable; import org.collectionspace.services.common.invocable.InvocationContext; import java.util.List; + +import org.collectionspace.services.client.PoxPayloadIn; +import org.collectionspace.services.client.PoxPayloadOut; import org.collectionspace.services.common.api.Tools; import org.collectionspace.services.common.context.ServiceContext; @@ -113,9 +116,9 @@ public interface Invocable { /* * Save a handle to the JAX-RS related service context */ - void setServiceContext(ServiceContext context); + void setServiceContext(ServiceContext context); - ServiceContext getServiceContext(); + ServiceContext getServiceContext(); InvocationContext getInvocationContext(); } diff --git a/services/common/src/main/java/org/collectionspace/services/common/relation/nuxeo/RelationConstants.java b/services/common/src/main/java/org/collectionspace/services/common/relation/nuxeo/RelationConstants.java index 5c02e0816..798da03d2 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/relation/nuxeo/RelationConstants.java +++ b/services/common/src/main/java/org/collectionspace/services/common/relation/nuxeo/RelationConstants.java @@ -23,6 +23,8 @@ */ package org.collectionspace.services.common.relation.nuxeo; +import org.collectionspace.services.client.IRelationsManager; +import org.collectionspace.services.relation.RelationshipType; /** * RelationConstants processes Relation document @@ -35,5 +37,23 @@ public class RelationConstants { /** The Constant REL_NUXEO_SCHEMA_ROOT_ELEMENT. */ final public static String NUXEO_SCHEMA_ROOT_ELEMENT = "relationtype"; + public final static String COMMON_SCHEMA_NAME = IRelationsManager.SERVICE_COMMONPART_NAME; + + public final static String SUBJECT_CSID_SCHEMA_NAME = COMMON_SCHEMA_NAME; + public final static String SUBJECT_CSID_FIELD_NAME = IRelationsManager.SUBJECT; + + public final static String SUBJECT_DOCTYPE_SCHEMA_NAME = COMMON_SCHEMA_NAME; + public final static String SUBJECT_DOCTYPE_FIELD_NAME = IRelationsManager.SUBJECT_DOCTYPE; + + public final static String OBJECT_CSID_SCHEMA_NAME = COMMON_SCHEMA_NAME; + public final static String OBJECT_CSID_FIELD_NAME = IRelationsManager.OBJECT; + + public final static String OBJECT_DOCTYPE_SCHEMA_NAME = COMMON_SCHEMA_NAME; + public final static String OBJECT_DOCTYPE_FIELD_NAME = IRelationsManager.OBJECT_DOCTYPE; + + public final static String TYPE_SCHEMA_NAME = COMMON_SCHEMA_NAME; + public final static String TYPE_FIELD_NAME = IRelationsManager.RELATIONSHIP_TYPE; + public final static String AFFECTS_TYPE = RelationshipType.AFFECTS.toString(); + public final static String BROADER_TYPE = RelationshipType.HAS_BROADER.toString(); } diff --git a/services/common/src/main/java/org/collectionspace/services/nuxeo/util/ReindexFulltextRoot.java b/services/common/src/main/java/org/collectionspace/services/nuxeo/util/ReindexFulltextRoot.java index ceb4e66c6..2ea74bc2d 100644 --- a/services/common/src/main/java/org/collectionspace/services/nuxeo/util/ReindexFulltextRoot.java +++ b/services/common/src/main/java/org/collectionspace/services/nuxeo/util/ReindexFulltextRoot.java @@ -307,8 +307,7 @@ public class ReindexFulltextRoot { session.save(); } - protected void runAsyncBatch(Set asyncIds) - { + protected void runAsyncBatch(Set asyncIds) { if (asyncIds.isEmpty()) { return; } diff --git a/services/common/src/main/resources/db/postgresql/load_id_generators.sql b/services/common/src/main/resources/db/postgresql/load_id_generators.sql index 933436b2f..d22dc93f5 100644 --- a/services/common/src/main/resources/db/postgresql/load_id_generators.sql +++ b/services/common/src/main/resources/db/postgresql/load_id_generators.sql @@ -191,6 +191,51 @@ acquired for the archives.', FROM id_generators ); +-- CLAIM_NUMBER + +INSERT INTO id_generators + (csid, displayname, description, priority, last_generated_id, id_generator_state) + SELECT + 'a253d167-4f1a-4be3-a477-a2bd8a30cd7f', + 'Claim Number', + 'Identifies a Claim.', + '9', + '', +' + + + CL + CL + + + + + + . + . + + + 6 + 1 + -1 + + + . + . + + + 6 + 1 + -1 + + +' + WHERE 'a253d167-4f1a-4be3-a477-a2bd8a30cd7f' NOT IN + ( + SELECT csid + FROM id_generators + ); + -- CONDITIONCHECK_NUMBER INSERT INTO id_generators diff --git a/services/contact/service/src/main/java/org/collectionspace/services/contact/AuthorityResourceWithContacts.java b/services/contact/service/src/main/java/org/collectionspace/services/contact/AuthorityResourceWithContacts.java index ec136e99e..51ca42f40 100644 --- a/services/contact/service/src/main/java/org/collectionspace/services/contact/AuthorityResourceWithContacts.java +++ b/services/contact/service/src/main/java/org/collectionspace/services/contact/AuthorityResourceWithContacts.java @@ -209,14 +209,14 @@ public abstract class AuthorityResourceWithContacts try { String parentcsid = lookupParentCSID(parentspecifier, "getContact(parent)", "GET_ITEM_CONTACT", null); - ServiceContext itemCtx = createServiceContext(getItemServiceName()); + ServiceContext itemCtx = createServiceContext(getItemServiceName()); String itemcsid = lookupItemCSID(itemCtx, itemspecifier, parentcsid, "getContact(item)", "GET_ITEM_CONTACT"); // Note that we have to create the service context and document handler for the Contact service, not the main service. ServiceContext ctx = createServiceContext(getContactServiceName()); DocumentHandler handler = createContactDocumentHandler(ctx, parentcsid, itemcsid); getRepositoryClient(ctx).get(ctx, csid, handler); - result = ctx.getOutput(); + result = (PoxPayloadOut) ctx.getOutput(); } catch (Exception e) { throw bigReThrow(e, "Get failed, the requested Contact CSID:" + csid + ": or one of the specifiers for authority:" + parentspecifier @@ -251,7 +251,7 @@ public abstract class AuthorityResourceWithContacts PoxPayloadIn theUpdate = new PoxPayloadIn(xmlPayload); String parentcsid = lookupParentCSID(parentspecifier, "updateContact(authority)", "UPDATE_CONTACT", null); - ServiceContext itemCtx = createServiceContext(getItemServiceName()); + ServiceContext itemCtx = createServiceContext(getItemServiceName()); String itemcsid = lookupItemCSID(itemCtx, itemspecifier, parentcsid, "updateContact(item)", "UPDATE_CONTACT"); ServiceContext ctx = null; @@ -259,7 +259,7 @@ public abstract class AuthorityResourceWithContacts ctx = createServiceContext(getContactServiceName(), theUpdate); DocumentHandler handler = createContactDocumentHandler(ctx, parentcsid, itemcsid); getRepositoryClient(ctx).update(ctx, csid, handler); - result = ctx.getOutput(); + result = (PoxPayloadOut) ctx.getOutput(); } catch (Exception e) { throw bigReThrow(e, "Update failed, the requested Contact CSID:" + csid + ": or one of the specifiers for authority:" + parentspecifier @@ -287,7 +287,7 @@ public abstract class AuthorityResourceWithContacts try { String parentcsid = lookupParentCSID(parentspecifier, "deleteContact(authority)", "DELETE_CONTACT", null); - ServiceContext itemCtx = createServiceContext(getItemServiceName()); + ServiceContext itemCtx = createServiceContext(getItemServiceName()); String itemcsid = lookupItemCSID(itemCtx, itemspecifier, parentcsid, "deleteContact(item)", "DELETE_CONTACT"); //NOTE: itemcsid is not used below. Leaving the above call in for possible side effects??? CSPACE-3175 diff --git a/services/intake/client/src/test/java/org/collectionspace/services/client/test/IntakeAuthRefsTest.java b/services/intake/client/src/test/java/org/collectionspace/services/client/test/IntakeAuthRefsTest.java index b71e41a00..eebe5109d 100644 --- a/services/intake/client/src/test/java/org/collectionspace/services/client/test/IntakeAuthRefsTest.java +++ b/services/intake/client/src/test/java/org/collectionspace/services/client/test/IntakeAuthRefsTest.java @@ -74,9 +74,7 @@ public class IntakeAuthRefsTest extends BaseServiceTest { private String conditionCheckerOrAssessorRefName = null; private String insurerRefName = null; private String valuerRefName = null; - private final int NUM_AUTH_REFS_EXPECTED = 5; - private final static String CURRENT_DATE_UTC = - GregorianCalendarDateTimeUtils.currentDateUTC(); + private final static String CURRENT_DATE_UTC = GregorianCalendarDateTimeUtils.currentDateUTC(); @Override protected String getServiceName() { @@ -237,8 +235,7 @@ public class IntakeAuthRefsTest extends BaseServiceTest { } // Success outcomes - @Test(dataProvider="testName", - dependsOnMethods = {"createWithAuthRefs"}) + @Test(dataProvider="testName", dependsOnMethods = {"createWithAuthRefs"}) public void readAndCheckAuthRefs(String testName) throws Exception { // Perform setup. testSetup(STATUS_OK, ServiceRequestType.READ); @@ -249,8 +246,7 @@ public class IntakeAuthRefsTest extends BaseServiceTest { try { assertStatusCode(res, testName); PoxPayloadIn input = new PoxPayloadIn(res.readEntity(String.class)); - IntakesCommon intake = (IntakesCommon) extractPart(input, - intakeClient.getCommonPartName(), IntakesCommon.class); + IntakesCommon intake = (IntakesCommon) extractPart(input, intakeClient.getCommonPartName(), IntakesCommon.class); Assert.assertNotNull(intake); // Check a couple of fields Assert.assertEquals(intake.getCurrentOwner(), currentOwnerRefName); @@ -276,30 +272,28 @@ public class IntakeAuthRefsTest extends BaseServiceTest { List items = list.getAuthorityRefItem(); int numAuthRefsFound = items.size(); - if(logger.isDebugEnabled()){ - logger.debug("Expected " + NUM_AUTH_REFS_EXPECTED + - " authority references, found " + numAuthRefsFound); + if (logger.isDebugEnabled()) { + logger.debug("Expected " + personIdsCreated.size() + " authority references, found " + numAuthRefsFound); } - Assert.assertEquals(numAuthRefsFound, NUM_AUTH_REFS_EXPECTED, - "Did not find all expected authority references! " + - "Expected " + NUM_AUTH_REFS_EXPECTED + ", found " + numAuthRefsFound); // Optionally output additional data about list members for debugging. boolean iterateThroughList = true; - if(iterateThroughList && logger.isDebugEnabled()){ + if (iterateThroughList && logger.isDebugEnabled()) { int i = 0; for(AuthorityRefList.AuthorityRefItem item : items){ logger.debug(testName + ": list-item[" + i + "] Field:" + item.getSourceField() + "= " + item.getAuthDisplayName() + item.getItemDisplayName()); - logger.debug(testName + ": list-item[" + i + "] refName=" + - item.getRefName()); - logger.debug(testName + ": list-item[" + i + "] URI=" + - item.getUri()); + logger.debug(testName + ": list-item[" + i + "] refName=" + item.getRefName()); + logger.debug(testName + ": list-item[" + i + "] URI=" + item.getUri()); i++; } } + // + // Ensure we got the correct number of authRefs + Assert.assertEquals(numAuthRefsFound, personIdsCreated.size(), + "Did not find all expected authority references! " + "Expected " + personIdsCreated.size() + ", found " + numAuthRefsFound); } @@ -319,7 +313,7 @@ public class IntakeAuthRefsTest extends BaseServiceTest { @AfterClass(alwaysRun=true) public void cleanUp() throws Exception { String noTest = System.getProperty("noTestCleanup"); - if(Boolean.TRUE.toString().equalsIgnoreCase(noTest)) { + if (Boolean.TRUE.toString().equalsIgnoreCase(noTest)) { if (logger.isDebugEnabled()) { logger.debug("Skipping Cleanup phase ..."); } @@ -333,8 +327,9 @@ public class IntakeAuthRefsTest extends BaseServiceTest { for (String resourceId : intakeIdsCreated) { intakeClient.delete(resourceId).close(); } + // + // Delete all the person records then the parent resource PersonAuthorityClient personAuthClient = new PersonAuthorityClient(); - // Delete persons before PersonAuth for (String resourceId : personIdsCreated) { personAuthClient.deleteItem(personAuthCSID, resourceId).close(); } diff --git a/services/intake/client/src/test/java/org/collectionspace/services/client/test/IntakeServiceTest.java b/services/intake/client/src/test/java/org/collectionspace/services/client/test/IntakeServiceTest.java index 9bac78c9c..38a421ad3 100644 --- a/services/intake/client/src/test/java/org/collectionspace/services/client/test/IntakeServiceTest.java +++ b/services/intake/client/src/test/java/org/collectionspace/services/client/test/IntakeServiceTest.java @@ -76,129 +76,11 @@ public class IntakeServiceTest extends AbstractPoxServiceTestImplorg.collectionspace.services.loanout.jaxb ${project.version} + + org.collectionspace.services + org.collectionspace.services.loanout.client + ${project.version} + org.collectionspace.services org.collectionspace.services.collectionobject.jaxb @@ -80,7 +85,6 @@ - diff --git a/services/loanout/service/src/main/java/org/collectionspace/services/loanout/nuxeo/LoanoutBotGardenConstants.java b/services/loanout/service/src/main/java/org/collectionspace/services/loanout/nuxeo/LoanoutBotGardenConstants.java new file mode 100644 index 000000000..212f50392 --- /dev/null +++ b/services/loanout/service/src/main/java/org/collectionspace/services/loanout/nuxeo/LoanoutBotGardenConstants.java @@ -0,0 +1,17 @@ +package org.collectionspace.services.loanout.nuxeo; + +import org.collectionspace.services.client.CollectionSpaceClient; +import org.collectionspace.services.client.LoanoutClient; + +public class LoanoutBotGardenConstants { + public static final String BOTGARDEN_PROFILE_NAME = CollectionSpaceClient.BOTGARDEN_PROFILE_NAME; + public static final String BOTGARDEN_SCHEMA_NAME = LoanoutClient.SERVICE_NAME + CollectionSpaceClient.PART_LABEL_SEPARATOR + BOTGARDEN_PROFILE_NAME; + + public static final String LABEL_REQUESTED_SCHEMA_NAME = BOTGARDEN_SCHEMA_NAME; + public static final String LABEL_REQUESTED_FIELD_NAME = "labelRequested"; + public static final String LABEL_REQUESTED_YES_VALUE = "Yes"; + public static final String LABEL_REQUESTED_NO_VALUE = "No"; + + public static final String STYLED_NAME_SCHEMA_NAME = BOTGARDEN_SCHEMA_NAME; + public static final String STYLED_NAME_FIELD_NAME = "styledName"; +} diff --git a/services/movement/service/src/main/java/org/collectionspace/services/movement/nuxeo/MovementBotGardenConstants.java b/services/movement/service/src/main/java/org/collectionspace/services/movement/nuxeo/MovementBotGardenConstants.java new file mode 100644 index 000000000..0aea05b37 --- /dev/null +++ b/services/movement/service/src/main/java/org/collectionspace/services/movement/nuxeo/MovementBotGardenConstants.java @@ -0,0 +1,29 @@ +package org.collectionspace.services.movement.nuxeo; + +import org.collectionspace.services.client.MovementClient; +import org.collectionspace.services.client.CollectionSpaceClient; + +/** + * Constants related to the official Botanical Garden profile. + * @author remillet + * + */ +public class MovementBotGardenConstants { + public static final String BOTGARDEN_PROFILE_NAME = MovementClient.BOTGARDEN_PROFILE_NAME; + public static final String BOTGARDEN_SCHEMA_NAME = MovementClient.SERVICE_NAME + CollectionSpaceClient.PART_LABEL_SEPARATOR + BOTGARDEN_PROFILE_NAME; + + public static final String ACTION_CODE_SCHEMA_NAME = MovementConstants.COMMON_SCHEMA_NAME; + public static final String ACTION_CODE_FIELD_NAME = "reasonForMove"; + + public static final String ACTION_DATE_SCHEMA_NAME = MovementConstants.COMMON_SCHEMA_NAME; + public static final String ACTION_DATE_FIELD_NAME = "locationDate"; + + public static final String DEAD_ACTION_CODE = "Dead"; + public static final String REVIVED_ACTION_CODE = "Revived"; + public static final String OTHER_ACTION_CODE = "Other"; + + public static final String LABEL_REQUESTED_SCHEMA_NAME = BOTGARDEN_SCHEMA_NAME; + public static final String LABEL_REQUESTED_FIELD_NAME = "labelRequested"; + public static final String LABEL_REQUESTED_YES_VALUE = "Yes"; + public static final String LABEL_REQUESTED_NO_VALUE = "No"; +} diff --git a/services/movement/service/src/main/java/org/collectionspace/services/movement/nuxeo/MovementConstants.java b/services/movement/service/src/main/java/org/collectionspace/services/movement/nuxeo/MovementConstants.java index 84554cbb3..c49bae963 100644 --- a/services/movement/service/src/main/java/org/collectionspace/services/movement/nuxeo/MovementConstants.java +++ b/services/movement/service/src/main/java/org/collectionspace/services/movement/nuxeo/MovementConstants.java @@ -23,6 +23,9 @@ */ package org.collectionspace.services.movement.nuxeo; +import org.collectionspace.services.client.CollectionSpaceClient; +import org.collectionspace.services.client.MovementClient; + /** * MovementConstants specifies constants for the Movement service * @@ -32,4 +35,18 @@ public class MovementConstants { public final static String NUXEO_DOCTYPE = "Movement"; public final static String NUXEO_SCHEMA_NAME = "movement"; public final static String NUXEO_DC_TITLE = "CollectionSpace-Movement"; + + public final static String CORE_SCHEMA_NAME = CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA; + public static final String COMMON_SCHEMA_NAME = MovementClient.SERVICE_NAME + CollectionSpaceClient.PART_LABEL_SEPARATOR + CollectionSpaceClient.PART_COMMON_LABEL; + + public static final String CURRENT_LOCATION_SCHEMA_NAME = COMMON_SCHEMA_NAME; + public static final String CURRENT_LOCATION_FIELD_NAME = "currentLocation"; + + public static final String PREVIOUS_LOCATION_SCHEMA_NAME = COMMON_SCHEMA_NAME; + public static final String PREVIOUS_LOCATION_FIELD_NAME = "previousLocation"; + + public static final String WORKFLOW_STATE_SCHEMA_NAME = MovementConstants.CORE_SCHEMA_NAME; + public static final String WORKFLOW_STATE_FIELD_NAME = CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE; + + public static final String NONE_LOCATION = null; } diff --git a/services/place/service/src/main/java/org/collectionspace/services/place/nuxeo/PlaceBotGardenConstants.java b/services/place/service/src/main/java/org/collectionspace/services/place/nuxeo/PlaceBotGardenConstants.java new file mode 100644 index 000000000..f85b6fc3e --- /dev/null +++ b/services/place/service/src/main/java/org/collectionspace/services/place/nuxeo/PlaceBotGardenConstants.java @@ -0,0 +1,5 @@ +package org.collectionspace.services.place.nuxeo; + +public class PlaceBotGardenConstants { + public final static String REVERSE_TERM_TYPE = "Reverse Qualified Name"; +} diff --git a/services/place/service/src/main/java/org/collectionspace/services/place/nuxeo/PlaceConstants.java b/services/place/service/src/main/java/org/collectionspace/services/place/nuxeo/PlaceConstants.java index 50ee2dac8..de66fa869 100644 --- a/services/place/service/src/main/java/org/collectionspace/services/place/nuxeo/PlaceConstants.java +++ b/services/place/service/src/main/java/org/collectionspace/services/place/nuxeo/PlaceConstants.java @@ -23,6 +23,8 @@ */ package org.collectionspace.services.place.nuxeo; +import org.collectionspace.services.client.PlaceAuthorityClient; + /** * PlaceConstants processes CollectionObject document * @@ -32,4 +34,14 @@ public class PlaceConstants { public final static String NUXEO_DOCTYPE = "Place"; public final static String NUXEO_SCHEMA_NAME = "place"; public final static String NUXEO_DC_TITLE = "CollectionSpace-Place"; + + public static final String COMMON_SCHEMA_NAME = PlaceAuthorityClient.SERVICE_COMMON_PART_NAME; + public final static String COMMON_ITEM_SCHEMA_NAME = PlaceAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME; + + public final static String DISPLAY_NAME_SCHEMA_NAME = COMMON_ITEM_SCHEMA_NAME; + public final static String DISPLAY_NAME_FIELD_NAME = "placeTermGroupList/placeTermGroup/termDisplayName"; + + public final static String TERM_TYPE_SCHEMA_NAME = COMMON_ITEM_SCHEMA_NAME; + public final static String TERM_TYPE_FIELD_NAME = "placeTermGroupList/placeTermGroup/termType"; + } diff --git a/services/pom.xml b/services/pom.xml index dd558b56c..fc2300ce8 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -14,7 +14,7 @@ 0.5.0 0.6.2 - 0.12.3 + 0.13.1 2.2.11 3.0.19.Final 5.1.8 @@ -41,6 +41,7 @@ jaxb hyperjaxb common + structureddate authority authorization-mgt common-test @@ -62,11 +63,14 @@ intake loanin loanout + claim exhibition conditioncheck conservation valuationcontrol objectexit + propagation + pottag batch imports location diff --git a/services/pottag/3rdparty/.externalToolBuilders/org.eclipse.jdt.core.javabuilder.launch b/services/pottag/3rdparty/.externalToolBuilders/org.eclipse.jdt.core.javabuilder.launch new file mode 100644 index 000000000..7fe64afc0 --- /dev/null +++ b/services/pottag/3rdparty/.externalToolBuilders/org.eclipse.jdt.core.javabuilder.launch @@ -0,0 +1,7 @@ + + + + + + + diff --git a/services/pottag/3rdparty/.externalToolBuilders/org.maven.ide.eclipse.maven2Builder.launch b/services/pottag/3rdparty/.externalToolBuilders/org.maven.ide.eclipse.maven2Builder.launch new file mode 100644 index 000000000..e17afcfe6 --- /dev/null +++ b/services/pottag/3rdparty/.externalToolBuilders/org.maven.ide.eclipse.maven2Builder.launch @@ -0,0 +1,7 @@ + + + + + + + diff --git a/services/pottag/3rdparty/build.xml b/services/pottag/3rdparty/build.xml new file mode 100644 index 000000000..72135dba5 --- /dev/null +++ b/services/pottag/3rdparty/build.xml @@ -0,0 +1,131 @@ + + + + pottag service 3rdparty + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/pottag/3rdparty/nuxeo-platform-cs-pottag/build.xml b/services/pottag/3rdparty/nuxeo-platform-cs-pottag/build.xml new file mode 100644 index 000000000..e8e53b41f --- /dev/null +++ b/services/pottag/3rdparty/nuxeo-platform-cs-pottag/build.xml @@ -0,0 +1,161 @@ + + + + pottag nuxeo document type + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/pottag/3rdparty/nuxeo-platform-cs-pottag/pom.xml b/services/pottag/3rdparty/nuxeo-platform-cs-pottag/pom.xml new file mode 100644 index 000000000..27b56d843 --- /dev/null +++ b/services/pottag/3rdparty/nuxeo-platform-cs-pottag/pom.xml @@ -0,0 +1,56 @@ + + + + + org.collectionspace.services + org.collectionspace.services.pottag.3rdparty + 4.1.1 + + + 4.0.0 + org.collectionspace.services + org.collectionspace.services.pottag.3rdparty.nuxeo + services.pottag.3rdparty.nuxeo + jar + + Pottag Nuxeo Document Type + + + + pottag + Pottag + pottags_common + cs_default + + + + + + src/main/resources + true + + + ../../../../3rdparty/nuxeo/nuxeo-doctype/src/main/resources + true + + + + + org.apache.maven.plugins + maven-jar-plugin + + + target/classes/META-INF/MANIFEST.MF + + ${eclipseVersion} + 2 + + + + + + + + diff --git a/services/pottag/3rdparty/nuxeo-platform-cs-pottag/src/main/resources/OSGI-INF/layouts-contrib.xml b/services/pottag/3rdparty/nuxeo-platform-cs-pottag/src/main/resources/OSGI-INF/layouts-contrib.xml new file mode 100644 index 000000000..42a636178 --- /dev/null +++ b/services/pottag/3rdparty/nuxeo-platform-cs-pottag/src/main/resources/OSGI-INF/layouts-contrib.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + family + commonName + locale + taxonName + labelData + numberOfLabels + + + + + + + + true + + family + + + dataInputText + + + + + + + + true + + commonName + + + dataInputText + + + + + + + + true + + locale + + + dataInputText + + + + + + + + true + + taxonName + + + dataInputText + + + + + + + + true + + numberOfLabels + + + dataInputText + + + + + + + + true + + labelData + + + dataInputText + + + + + + diff --git a/services/pottag/3rdparty/nuxeo-platform-cs-pottag/src/main/resources/schemas/pottags_common.xsd b/services/pottag/3rdparty/nuxeo-platform-cs-pottag/src/main/resources/schemas/pottags_common.xsd new file mode 100644 index 000000000..10135ba1f --- /dev/null +++ b/services/pottag/3rdparty/nuxeo-platform-cs-pottag/src/main/resources/schemas/pottags_common.xsd @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + diff --git a/services/pottag/3rdparty/pom.xml b/services/pottag/3rdparty/pom.xml new file mode 100644 index 000000000..95f19083e --- /dev/null +++ b/services/pottag/3rdparty/pom.xml @@ -0,0 +1,32 @@ + + + + org.collectionspace.services.pottag + org.collectionspace.services + 4.5-SNAPSHOT + + + 4.0.0 + org.collectionspace.services.pottag.3rdparty + services.pottag.3rdparty + pom + + + 3rd party build for pottag service + + + + pottag + Pottag + pottags_common + Tenant + + + + + + diff --git a/services/pottag/build.xml b/services/pottag/build.xml new file mode 100644 index 000000000..e8b0d728c --- /dev/null +++ b/services/pottag/build.xml @@ -0,0 +1,124 @@ + + + + pottag service + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/pottag/client/bin/org/collectionspace/services/client/PottagClient.class b/services/pottag/client/bin/org/collectionspace/services/client/PottagClient.class new file mode 100644 index 0000000000000000000000000000000000000000..6cc132cd9e015b1cd0e567ff6b4c81fc7adc65af GIT binary patch literal 2055 zcmcgtTTc@~6h4EMZfm)fo8WD{VJpfa@+5?$sU?tB3W7IQJ5ky(kVTs zlvD=XB;@lOS8-q3`@Bvq>3G6sLBpu0WX_+Jcdev1hY3_j=;xD7I<~yL9f8NI+`IceyW|Jkg)x+vS_$Ww-nBH zy*Fa@0wWERt*osW@QlERi*}$>>ea}bK)ftl%Hr!H(Ct|FL0<^`iNL%3`TXWGO~5m? z>00R4=wav|8-gpQtma`9mI%z9FE2XxmhgSF{tDWT5BCAB7Joh0VFs^3L{UToU?7hF zfp2u-1il~c!Xt>sy08HeluKjeZFSC57S~FrU*O_ITqSn OCM+Tje)|x1>&0(H07+B; literal 0 HcmV?d00001 diff --git a/services/pottag/client/bin/org/collectionspace/services/client/PottagProxy.class b/services/pottag/client/bin/org/collectionspace/services/client/PottagProxy.class new file mode 100644 index 0000000000000000000000000000000000000000..40771d79846acc507ed10ce2ebcb44762ea2ebf0 GIT binary patch literal 233 zcmYjMOK!q25Pc?r0AOm{v7{5lle3hx^^QW;4d-myCh~BuP%1nh%*kkP zLaRT&58_lSc!KjOW@8?G|~P@+CpJn-|P2dea&6R;a#K+ l54Tc<)rU*Th4$;*Q<^C#6f$DG0#m^WW~greQm_<${tu(?LS6s> literal 0 HcmV?d00001 diff --git a/services/pottag/client/bin/org/collectionspace/services/client/test/PottagAuthRefsTest.class b/services/pottag/client/bin/org/collectionspace/services/client/test/PottagAuthRefsTest.class new file mode 100644 index 0000000000000000000000000000000000000000..3aca0cb04b502d302b1b2475ba5c29c82ea55c26 GIT binary patch literal 11791 zcmeHNTX);W5uTM~^Ag#X<=D=x4cx|w*rsAH?&Va98XBTyCX~pMpkk+P*%i2?AcFu4 zfU@-1f6(XthQ9Qv=OpJOFL}z3$WO`1?1Cgfa!CS|oM2MmTk|Ytz6eTtgxo5 z;j08J5m-5*$24nFtCcMs93cvUv|7wntM$FyZl1tuF=$Zr9K&jD5V&gEtrl|#B#TAy z4c{$S^OgOAn(y_yrlK?+wY+_5c3BdZ2_!l~1ed^_e%o?Rdt6sbyXDeeeorHCyPU67 zOMCTPO?zCb6tt%TEeR_Gu5*#g%yDgtPlCXW=a&7#sxa5?Iyx&fa4!A;g@&s-jCzc} z^2P6ZhM6rIu7`rAsz%GAUe{p+-W&OOb9}a2*kz?7#tj2s_g`^Q)M{ZH=d@Sb6>`@r z`A5}y{z*BnYI*#D6c*87R}e+5RVp~m`gTss*K3-Jirh3T!`mXTxR%)`u%y}zmH-7O z7ic~2IJT37pO9PM)hq|6YaX+PqTB6`VNz~%rQ_HKCTqKYRMIK!kSRvHV>_P0Ax_p` zxQb3K%l4E5ru4hd)ec>4eZwX`TjrD8lZRFPkhSToL8pTGe!H#?{JP5hS{2@W z9>9Sc*^c3zUNCSCoo}WQqUfVcPfeRPN?oth^~w}YJj1e)K<$$rZoxWVJe2cnfOfl$ z-hHMyo*m+do*bM>27gmJ8-&?m$qgS;msR~h z$0LITOz~`mD&A>_rBk^B*K?@uslp+6B$&~`k86<{JwjaDIhC|cr*<6H!aassDm9ta zpw2eNCCzBF$(usdT`xR6zU#M{cW5`17W4f5tRUjA&ek%D-4tq2#&ZCr-9=T{$INkz z22*T@2M>%Tm^Ps9+%85ruD~An9D4L+!6@Cqe1J)G6rM(GE5)#U$X5zD#Tk-RwVYO~ z*40WLWA*mL#>W(Gi?KJI>cLmoKf36R8rRwQ#^uj7=oB)WE;5!v0~K#H4d$TL3|Hw| z4c0U)jQ346jfYki))=>AI#nL*$!V|P1|rv6H_zFbp7CGV*BOj*RS*VIL)A*D|z zx?QmC&y|713dfmH~u&Ws6mh0~3(T_xA_n4_9hTFi!xKWwGm8Yoj_ z<&Yq=Elj7+wf>2dWyh^%((o?4w+!#VeI!EH#+gXY5Z)+aHL%xhA23Jbg$E~SZJnC? zy+Wk-a>+Y1kZ1j+@!`xqGtJaf1ieiU0o5;n==KO^YwqT}=>U4Ui(C0)Q8QK*0@ zZDJ&?oS*pr(gk?J`YWK>44 zgoKOgAWB9cUoIJyIJ{P@YHYaZ9b`K5Fe-TcN;es`Y8?!;#eiPH`YJ`u7xdJ{Vk`+o z0-sD@p8S$g(0zKkg}1h_nC_7>oHxUr0_lUG81xxLWDd#%?#&#Rz}*0Dh~r5@mB7Dd z0*KpD!8!Rwvqd=DUP>b+goju8+Ub74&D4xNk2o2Az zv05LpktngzO)}$eClDM-ct)TyThRDqL%J$1`#mx^^L2Op7<`?(7J(0nBez2)2#P#g z!P4Th1;yJ^o+|-IoZArB zCKF&H-!x<|T%EiA@j17J3-!Ifx*yg6_zkmq0u;1&V-)($=XtxS)z?%d7x8N;!TfAS!ckjTD@c)ky;Q>lV z@H71-y!&+Z-v8jIUklv#@jsUiu+<0npZCI6um&0YT!Np$&!q?-N)R+D!a8J8c7GPX z9LTc3M-j68N`mxEf<*8#?R}iZ10feKPWU~34ZjU6m-$-#Essv#HG^mAS;A_tJqkRd&js#%|p1{+AM}9u=oM1M8ksy68Mfxj_ cz5svgd3PQDj?VyX{Nrnyf({H?V8HVq05~lv>i_@% literal 0 HcmV?d00001 diff --git a/services/pottag/client/bin/org/collectionspace/services/client/test/PottagServiceTest.class b/services/pottag/client/bin/org/collectionspace/services/client/test/PottagServiceTest.class new file mode 100644 index 0000000000000000000000000000000000000000..4afcdc6c66f2675ea952e193c3975500c4107771 GIT binary patch literal 13795 zcmeHO&2!sC6o2amteEtJ(56tn6rhkmN%&}?w4o3jhq^dUu$|EI6=ZqiD9Dm4$!U7; z05?t?IC0~|kr`%Srrh}(IPr%tyw%#)+TK`JA~DR6Lleu{{k^wu-`D=M{p0Un?*qW= za61ht0#|IOKCj!RNp;V#Ew`!Zbl#=TuAx(RUN;SDdGj81z4RJ9KEGPsK@b9&Y+i`n5PNfXTvgXB%l6bNRXfuUG!8dou5UBuI-4=a zH)C$G>Uxf*ds(~Du&sjOdh4EHhN>_MbhhgUW#$K2fA_=IHVW{XcGtAETHb25yfqCC z7hPEdQVt7|MucJWZp!vXr+eJF?>ZLfML03Lv&K~0_mDt z7k3UfuoF6K_MVW(@{Oh`!Bst5)l~0plV-*{6N8uwnZT2!%g2X|mwK&cmdlw{4OeYhHM(V3SjM){)E;h{Z{2}y;bp45pz6thZa#aTzjOWW#V>>@hlv z71YU$M{@~7o!xSDn#UGupe;K5jQC6*U0cmGgVnh_-ejwkL=|h) z5={XZ{Ix^{_&KJ;q(n>$kpeIJrUY3hZ|&=GypBg18s4&UU*^a{I%8$gZ`N3+Thtn? zEQX~~ZlcV1l)fR#*df6lUp)2NV7;<@8Cwu-+0p8(%8y_Lp(z`hpW+pi7#fSnqHX2& zF!Y%LIwdW(9!b!=8=RuRaKG!Zc%(Qjt`;|QH}mBRJ8tKf_6@wC;Kb)>L%cb>4ra!~ z$MjdJ5P=7(mBQvRM@hUsbUnnE6wx&zx4)5mDKfgucHt}PB-#IrBMQrCHET(q5x593 zKO~uuYSg3&A9}cmL>DyV7|_$>Va;!+(ssd5BF6_p)--P$dkj43^1>RMw4gx>Kfxt6 zR)~shX?>}J#~wHS+8IrIMtzUQ=0xbqGB62eC*UMJO5n`QAW_cv>XQXL#1~tQDs?LC z+{PGnTi48u_BpQo<(Ri^AThpBK<4y-bUt$M4-YUp1Rk52El7Eys|u(D?vFs2OQ-MK zFi87ppNZ~r1MzVv1deyxbygP%JU>(DtzB1V3xo9ujWu32>K2}j9o&yx9)9+_#wWWuAKrEdl134Aj=XLMfS z_Hf>boWAgFGdtNS@ESSx^KhlQFP7U{v3bd-u@aLKer`HANf3SCgSPC5%oDE@h+N|k zHX^=|6-pjj*cn6BP z7%yIzN$Pxb-%6x*`L~?f#psAR{I4;Gc$DI7o&tChy*J!v`Wl{um_}p|1_(w4@ZR0G z2hdK;UU8F@Xgf|EDzF&Ub_tQ1XuAO}Q-P(Zwj02Rz0{zh?Bz+hRc#oZLy!XR6Sx+> z$-}5r0w)4;lQF0SP9yIWumao(kaGHfz*ph2dYhMIm{|g61D$&aSp|www{9OKY|i7N zcG1NAoYY~_2s|s}91B4QjubFjyhLCn+~Pg6hBHmLw_KCn8$SdtN%R@?f!RI{k%{Zl z#0q>w;OlU01^?*IO(0lK@QfH(T%AGX1uhv0!QN*DJ{i&60!o;<&+7Q9Ky&E2|BeZ7 z;j)X>c)gpzbiY1*>M#a6Q}%E3EE|k`p5p2XC&D=vqy!ebgeQ7}@Mm_pQc8yQNxFu0 z>`dO$@$R1Czb(Ix_s=v(-@cm$9ZBy&vM~*)&$-U`gHqE_N1yMdK+|BL7%3l}hA*)) z@bgm|8b}QH>`WSL|104}8Qxi6;n&UBtt=O&0WX>3)%pjJ)_(vV#7~ou1_ghgf^mFC zQ{v|oJcQ53d!En134i<)jyZ{c#Waf}FCrSj-2Vq5muq{CtgH>1)bO>VhLkU7df{=o81cW>LAXEe$SWe`5L#Bh=-_~(o zgDe=VVnecu? + + + org.collectionspace.services + org.collectionspace.services.pottag + 4.5-SNAPSHOT + + + 4.0.0 + org.collectionspace.services + org.collectionspace.services.pottag.client + services.pottag.client + + + + + org.slf4j + slf4j-api + test + + + org.slf4j + slf4j-log4j12 + test + + + + org.collectionspace.services + org.collectionspace.services.jaxb + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.common + true + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.client + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.pottag.jaxb + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.person.client + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.authority.jaxb + ${project.version} + + + + org.testng + testng + + + org.jboss.resteasy + resteasy-jaxrs + + + + tjws + webserver + + + + + org.jboss.resteasy + resteasy-jaxb-provider + + + org.jboss.resteasy + resteasy-multipart-provider + + + commons-httpclient + commons-httpclient + 3.1 + + + + + collectionspace-services-pottag-client + + diff --git a/services/pottag/client/src/main/java/org/collectionspace/services/client/PottagClient.java b/services/pottag/client/src/main/java/org/collectionspace/services/client/PottagClient.java new file mode 100644 index 000000000..5f8d58759 --- /dev/null +++ b/services/pottag/client/src/main/java/org/collectionspace/services/client/PottagClient.java @@ -0,0 +1,58 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + * + * http://www.collectionspace.org + * http://wiki.collectionspace.org + * + * Copyright (c) 2009 Regents of the University of California + * + * 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.services.client; + +import org.collectionspace.services.pottag.PottagsCommon; + +/** + * PottagClient.java + * + * $LastChangedRevision$ + * $LastChangedDate$ + * + */ +public class PottagClient extends AbstractCommonListPoxServiceClientImpl { + + public static final String SERVICE_NAME = "pottags"; + public static final String SERVICE_PATH_COMPONENT = SERVICE_NAME; + public static final String SERVICE_PATH = "/" + SERVICE_PATH_COMPONENT; + public static final String SERVICE_PATH_PROXY = SERVICE_PATH + "/"; + public static final String SERVICE_PAYLOAD_NAME = SERVICE_NAME; + + public PottagClient() throws Exception { + super(); + } + + /* (non-Javadoc) + * @see org.collectionspace.services.client.AbstractServiceClientImpl#getServicePathComponent() + */ + @Override + public String getServicePathComponent() { + return SERVICE_PATH_COMPONENT; + } + + @Override + public String getServiceName() { + return SERVICE_NAME; + } + + @Override + public Class getProxyClass() { + return PottagProxy.class; + } + +} diff --git a/services/pottag/client/src/main/java/org/collectionspace/services/client/PottagProxy.java b/services/pottag/client/src/main/java/org/collectionspace/services/client/PottagProxy.java new file mode 100644 index 000000000..71708dd79 --- /dev/null +++ b/services/pottag/client/src/main/java/org/collectionspace/services/client/PottagProxy.java @@ -0,0 +1,34 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + * + * http://www.collectionspace.org + * http://wiki.collectionspace.org + * + * Copyright (c) 2009 Regents of the University of California + * + * 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.services.client; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +/** + * PottagProxy.java + * + * $LastChangedRevision$ + * $LastChangedDate$ + * + */ +@Path(PottagClient.SERVICE_PATH_PROXY) +@Produces({"application/xml"}) +@Consumes({"application/xml"}) +public interface PottagProxy extends CollectionSpaceCommonListPoxProxy { +} diff --git a/services/pottag/client/src/test/java/org/collectionspace/services/client/test/PottagAuthRefsTest.java b/services/pottag/client/src/test/java/org/collectionspace/services/client/test/PottagAuthRefsTest.java new file mode 100644 index 000000000..9637b4ab5 --- /dev/null +++ b/services/pottag/client/src/test/java/org/collectionspace/services/client/test/PottagAuthRefsTest.java @@ -0,0 +1,328 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + * + * http://www.collectionspace.org + * http://wiki.collectionspace.org + * + * Copyright © 2009 Regents of the University of California + * + * 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 + * + * 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.collectionspace.services.client.test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.Response; + +import org.collectionspace.services.PersonJAXBSchema; +import org.collectionspace.services.client.CollectionSpaceClient; +import org.collectionspace.services.client.PottagClient; +import org.collectionspace.services.client.PersonAuthorityClient; +import org.collectionspace.services.client.PersonAuthorityClientUtils; +import org.collectionspace.services.client.PayloadOutputPart; +import org.collectionspace.services.client.PoxPayloadIn; +import org.collectionspace.services.client.PoxPayloadOut; +import org.collectionspace.services.common.authorityref.AuthorityRefList; +import org.collectionspace.services.jaxb.AbstractCommonList; +import org.collectionspace.services.pottag.PottagsCommon; +import org.collectionspace.services.person.PersonTermGroup; + +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.Test; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * PottagAuthRefsTest, carries out Authority References tests against a + * deployed and running Pottag (aka Pot Tag) Service. + * + * $LastChangedRevision$ + * $LastChangedDate$ + */ +public class PottagAuthRefsTest extends BaseServiceTest { + + private final String CLASS_NAME = PottagAuthRefsTest.class.getName(); + private final Logger logger = LoggerFactory.getLogger(CLASS_NAME); + + // Instance variables specific to this test. + final String PERSON_AUTHORITY_NAME = "TestPersonAuth"; + private List pottagIdsCreated = new ArrayList(); + private List personIdsCreated = new ArrayList(); + private String personAuthCSID = null; + private String taggedByAuthRef; + + /* (non-Javadoc) + * @see org.collectionspace.services.client.test.BaseServiceTest#getClientInstance() + */ + @Override + protected CollectionSpaceClient getClientInstance() { + throw new UnsupportedOperationException(); //method not supported (or needed) in this test class + } + + /* (non-Javadoc) + * @see org.collectionspace.services.client.test.BaseServiceTest#getAbstractCommonList(org.jboss.resteasy.client.ClientResponse) + */ + @Override + protected AbstractCommonList getCommonList(Response response) { + throw new UnsupportedOperationException(); //method not supported (or needed) in this test class + } + + // --------------------------------------------------------------- + // CRUD tests : CREATE tests + // --------------------------------------------------------------- + // Success outcomes + @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class) + public void createWithAuthRefs(String testName) throws Exception { + testSetup(STATUS_CREATED, ServiceRequestType.CREATE); + + // Submit the request to the service and store the response. + String identifier = createIdentifier(); + + // Create all the person refs and entities + createPersonRefs(); + + // Create a new Loans In resource. + // + // One or more fields in this resource will be PersonAuthority + // references, and will refer to Person resources by their refNames. + PottagClient pottagClient = new PottagClient(); + PoxPayloadOut pottageInstance = createPottagInstance("familyName-" + identifier, this.taggedByAuthRef, + "commonName-" + identifier); + Response response = pottagClient.create(pottageInstance); + try { + assertStatusCode(response, testName); + // Store the ID returned from the first resource created for additional tests below. + if (knownResourceId == null) { + knownResourceId = extractId(response); + } + + // Store the IDs from every resource created by tests, + // so they can be deleted after tests have been run. + pottagIdsCreated.add(extractId(response)); + } finally { + response.close(); + } + } + + protected void createPersonRefs() throws Exception { + PersonAuthorityClient personAuthClient = new PersonAuthorityClient(); + // Create a temporary PersonAuthority resource, and its corresponding + // refName by which it can be identified. + PoxPayloadOut multipart = PersonAuthorityClientUtils.createPersonAuthorityInstance( + PERSON_AUTHORITY_NAME, PERSON_AUTHORITY_NAME, personAuthClient.getCommonPartName()); + Response res = personAuthClient.create(multipart); + try { + int statusCode = res.getStatus(); + Assert.assertTrue(testRequestType.isValidStatusCode(statusCode), invalidStatusCodeMessage(testRequestType, statusCode)); + Assert.assertEquals(statusCode, STATUS_CREATED); + personAuthCSID = extractId(res); + } finally { + res.close(); + } + String authRefName = PersonAuthorityClientUtils.getAuthorityRefName(personAuthCSID, personAuthClient); + + // Create temporary Person resource, and a corresponding refName from which it can be identified. + String csid = createPerson("Harry", "Potter", "harryPotter", authRefName); + this.personIdsCreated.add(csid); + this.taggedByAuthRef = PersonAuthorityClientUtils.getPersonRefName(personAuthCSID, csid, personAuthClient); + } + + protected String createPerson(String firstName, String surName, String shortId, String authRefName ) throws Exception { + PersonAuthorityClient personAuthClient = new PersonAuthorityClient(); + Map personInfo = new HashMap(); + personInfo.put(PersonJAXBSchema.FORE_NAME, firstName); + personInfo.put(PersonJAXBSchema.SUR_NAME, surName); + personInfo.put(PersonJAXBSchema.SHORT_IDENTIFIER, shortId); + List personTerms = new ArrayList(); + PersonTermGroup term = new PersonTermGroup(); + String termName = firstName + " " + surName; + term.setTermDisplayName(termName); + term.setTermName(termName); + personTerms.add(term); + PoxPayloadOut multipart = + PersonAuthorityClientUtils.createPersonInstance(personAuthCSID, + authRefName, personInfo, personTerms, personAuthClient.getItemCommonPartName()); + + Response res = personAuthClient.createItem(personAuthCSID, multipart); + try { + int statusCode = res.getStatus(); + Assert.assertTrue(testRequestType.isValidStatusCode(statusCode), + invalidStatusCodeMessage(testRequestType, statusCode)); + Assert.assertEquals(statusCode, STATUS_CREATED); + return extractId(res); + } finally { + res.close(); + } + } + + // Success outcomes + @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class, + dependsOnMethods = {"createWithAuthRefs"}) + public void readAndCheckAuthRefs(String testName) throws Exception { + // Perform setup. + testSetup(STATUS_OK, ServiceRequestType.READ); + + // Submit the request to the service and store the response. + PottagClient pottagClient = new PottagClient(); + Response res = pottagClient.read(knownResourceId); + try { + assertStatusCode(res, testName); + // Extract the common part from the response. + PoxPayloadIn input = new PoxPayloadIn(res.readEntity(String.class)); + PottagsCommon pottagCommon = (PottagsCommon) extractPart(input, pottagClient.getCommonPartName(), + PottagsCommon.class); + Assert.assertNotNull(pottagCommon); + } finally { + if (res != null) { + res.close(); + } + } + + // Get the auth refs and check them + res = pottagClient.getAuthorityRefs(knownResourceId); + AuthorityRefList list = null; + try { + assertStatusCode(res, testName); + list = res.readEntity(AuthorityRefList.class); + Assert.assertNotNull(list); + } finally { + if (res != null) { + res.close(); + } + } + + int expectedAuthRefs = personIdsCreated.size(); + List items = list.getAuthorityRefItem(); + int numAuthRefsFound = items.size(); + if (logger.isDebugEnabled()) { + logger.debug("Expected " + expectedAuthRefs + " authority references, found " + numAuthRefsFound); + } + + // Optionally output additional data about list members for debugging. + boolean iterateThroughList = true; + if (iterateThroughList && logger.isDebugEnabled()) { + int i = 0; + for(AuthorityRefList.AuthorityRefItem item : items){ + logger.debug(testName + ": list-item[" + i + "] Field:" + + item.getSourceField() + "= " + + item.getAuthDisplayName() + + item.getItemDisplayName()); + logger.debug(testName + ": list-item[" + i + "] refName=" + + item.getRefName()); + logger.debug(testName + ": list-item[" + i + "] URI=" + + item.getUri()); + i++; + } + } + + Assert.assertEquals(numAuthRefsFound, expectedAuthRefs, + "Did not find all expected authority references! " + "Expected " + expectedAuthRefs + ", found " + numAuthRefsFound); + } + + + // --------------------------------------------------------------- + // Cleanup of resources created during testing + // --------------------------------------------------------------- + + /** + * Deletes all resources created by tests, after all tests have been run. + * + * This cleanup method will always be run, even if one or more tests fail. + * For this reason, it attempts to remove all resources created + * at any point during testing, even if some of those resources + * may be expected to be deleted by certain tests. + * @throws Exception + */ + @AfterClass(alwaysRun=true) + public void cleanUp() throws Exception { + String noTest = System.getProperty("noTestCleanup"); + if (Boolean.TRUE.toString().equalsIgnoreCase(noTest)) { + if (logger.isDebugEnabled()) { + logger.debug("Skipping Cleanup phase ..."); + } + return; + } + if (logger.isDebugEnabled()) { + logger.debug("Cleaning up temporary resources created for testing ..."); + } + + // + // Delete all the pottag records we created + PottagClient pottagClient = new PottagClient(); + for (String resourceId : pottagIdsCreated) { + // Note: Any non-success responses are ignored and not reported. + pottagClient.delete(resourceId).close(); // alternative to pottagClient.delete(resourceId).releaseConnection(); + } + + // + // Delete Person resource(s) (before PersonAuthority resources). + PersonAuthorityClient personAuthClient = new PersonAuthorityClient(); + for (String resourceId : personIdsCreated) { + // Note: Any non-success responses are ignored and not reported. + personAuthClient.deleteItem(personAuthCSID, resourceId).close(); + } + if (personAuthCSID != null) { + personAuthClient.delete(personAuthCSID).close(); + } + } + + // --------------------------------------------------------------- + // Utility methods used by tests above + // --------------------------------------------------------------- + public String getServiceName() { + return PottagClient.SERVICE_NAME; + } + + @Override + public String getServicePathComponent() { + return PottagClient.SERVICE_PATH_COMPONENT; + } + + private PoxPayloadOut createPottagInstance(String familyName, + String taggedBy, + String commonName) throws Exception { + PottagsCommon pottagCommon = new PottagsCommon(); + pottagCommon.setFamily(familyName); + pottagCommon.setTaggedBy(taggedBy); + pottagCommon.setCommonName(commonName); + + PoxPayloadOut multipart = new PoxPayloadOut(this.getServicePathComponent()); + PayloadOutputPart commonPart = + multipart.addPart(new PottagClient().getCommonPartName(), pottagCommon); + + if(logger.isDebugEnabled()){ + logger.debug("to be created, pottag common"); + logger.debug(objectAsXmlString(pottagCommon, PottagsCommon.class)); + } + + return multipart; + } + + @Override + protected Class getCommonListType() { + return AbstractCommonList.class; + } + + @Override + protected CollectionSpaceClient getClientInstance(String clientPropertiesFilename) throws Exception { + // TODO Auto-generated method stub + return null; + } +} diff --git a/services/pottag/client/src/test/java/org/collectionspace/services/client/test/PottagServiceTest.java b/services/pottag/client/src/test/java/org/collectionspace/services/client/test/PottagServiceTest.java new file mode 100644 index 000000000..fcd0b7de8 --- /dev/null +++ b/services/pottag/client/src/test/java/org/collectionspace/services/client/test/PottagServiceTest.java @@ -0,0 +1,177 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + * + * http://www.collectionspace.org + * http://wiki.collectionspace.org + * + * Copyright © 2009 Regents of the University of California + * + * 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 + * + * 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.collectionspace.services.client.test; + +import org.collectionspace.services.client.CollectionSpaceClient; +import org.collectionspace.services.client.PottagClient; +import org.collectionspace.services.client.PayloadOutputPart; +import org.collectionspace.services.client.PoxPayloadOut; +import org.collectionspace.services.common.api.GregorianCalendarDateTimeUtils; +import org.collectionspace.services.jaxb.AbstractCommonList; +import org.collectionspace.services.pottag.PottagsCommon; + +import org.testng.Assert; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * PottagServiceTest, carries out tests against a + * deployed and running Pottag (aka Pot Tag) Service. + * + * $LastChangedRevision$ + * $LastChangedDate$ + */ +public class PottagServiceTest extends AbstractPoxServiceTestImpl { + + /** The logger. */ + private final String CLASS_NAME = PottagServiceTest.class.getName(); + private final Logger logger = LoggerFactory.getLogger(CLASS_NAME); + // Instance variables specific to this test. + private final static String CURRENT_DATE_UTC = GregorianCalendarDateTimeUtils.currentDateUTC(); + + /* (non-Javadoc) + * @see org.collectionspace.services.client.test.BaseServiceTest#getClientInstance() + */ + @Override + protected CollectionSpaceClient getClientInstance() throws Exception { + return new PottagClient(); + } + + @Override + protected void compareReadInstances(PottagsCommon original, PottagsCommon pottagCommon) throws Exception { + if (logger.isDebugEnabled()) { + logger.debug("UTF-8 data sent=" + getUTF8DataFragment() + "\n" + + "UTF-8 data received=" + pottagCommon.getLabelData()); + } + + Assert.assertEquals(pottagCommon.getLabelData(), getUTF8DataFragment(), + "UTF-8 data retrieved '" + pottagCommon.getLabelData() + + "' does not match expected data '" + getUTF8DataFragment()); + } + + @Override + protected void compareUpdatedInstances(PottagsCommon pottagCommon, + PottagsCommon updatedPottagCommon) throws Exception { + // Check selected fields in the updated common part. + Assert.assertEquals(updatedPottagCommon.getFamily(), pottagCommon.getFamily(), + "Data in updated object did not match submitted data."); + + if (logger.isDebugEnabled()) { + logger.debug("UTF-8 data sent=" + pottagCommon.getLabelData() + "\n" + + "UTF-8 data received=" + updatedPottagCommon.getLabelData()); + } + Assert.assertTrue(updatedPottagCommon.getLabelData().contains(getUTF8DataFragment()), + "UTF-8 data retrieved '" + updatedPottagCommon.getLabelData() + "' does not contain expected data '" + getUTF8DataFragment()); + Assert.assertEquals(updatedPottagCommon.getLabelData(), + pottagCommon.getLabelData(), "Data in updated object did not match submitted data."); + } + + // --------------------------------------------------------------- + // Utility methods used by tests above + // --------------------------------------------------------------- + + @Override + public String getServiceName() { + return PottagClient.SERVICE_NAME; + } + + /* (non-Javadoc) + * @see org.collectionspace.services.client.test.BaseServiceTest#getServicePathComponent() + */ + @Override + public String getServicePathComponent() { + return PottagClient.SERVICE_PATH_COMPONENT; + } + + @Override + protected PoxPayloadOut createInstance(String identifier) throws Exception { + return createPottagInstance(identifier); + } + + /** + * Creates the pottag instance. + * + * @param identifier the identifier + * @return the multipart output + * @throws Exception + */ + private PoxPayloadOut createPottagInstance(String identifier) throws Exception { + return createPottagInstance( + "family-" + identifier, + "returnDate-" + identifier); + } + + /** + * Creates the pottag instance. + * + * @param familyName the pottag family + * @param returnDate the return date + * @return the multipart output + * @throws Exception + */ + private PoxPayloadOut createPottagInstance(String familyName, + String returnDate) throws Exception { + + PottagsCommon pottagCommon = new PottagsCommon(); + pottagCommon.setFamily(familyName); + pottagCommon.setLocale("Mexico"); + pottagCommon.setLabelData(getUTF8DataFragment()); + + PoxPayloadOut multipart = new PoxPayloadOut(this.getServicePathComponent()); + PayloadOutputPart commonPart = + multipart.addPart(new PottagClient().getCommonPartName(), pottagCommon); + + if (logger.isDebugEnabled()) { + logger.debug("to be created, pottag common"); + logger.debug(objectAsXmlString(pottagCommon, PottagsCommon.class)); + } + + return multipart; + } + + @Override + public void CRUDTests(String testName) { + // TODO Auto-generated method stub + + } + + @Override + protected PoxPayloadOut createInstance(String commonPartName, + String identifier) throws Exception { + PoxPayloadOut result = createPottagInstance(identifier); + return result; + } + + @Override + protected PottagsCommon updateInstance(PottagsCommon commonPartObject) { + // TODO Auto-generated method stub + return null; + } + + @Override + protected CollectionSpaceClient getClientInstance(String clientPropertiesFilename) throws Exception { + // TODO Auto-generated method stub + return null; + } +} diff --git a/services/pottag/client/src/test/resources/log4j.properties b/services/pottag/client/src/test/resources/log4j.properties new file mode 100644 index 000000000..148a3e865 --- /dev/null +++ b/services/pottag/client/src/test/resources/log4j.properties @@ -0,0 +1,23 @@ +log4j.rootLogger=debug, stdout, R + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + +# Pattern to output the caller's file name and line number. +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%t] [%c:%L] %m%n + +log4j.appender.R=org.apache.log4j.RollingFileAppender +log4j.appender.R.File=target/test-client.log + +log4j.appender.R.MaxFileSize=100KB +# Keep one backup file +log4j.appender.R.MaxBackupIndex=1 + +log4j.appender.R.layout=org.apache.log4j.PatternLayout +log4j.appender.R.layout.ConversionPattern=%d %-5p [%t] [%c:%L] %m%n + +#packages +log4j.logger.org.collectionspace=DEBUG +log4j.logger.org.apache=INFO +log4j.logger.httpclient=INFO +log4j.logger.org.jboss.resteasy=INFO diff --git a/services/pottag/jaxb/bin/org/collectionspace/services/PottagJAXBSchema.class b/services/pottag/jaxb/bin/org/collectionspace/services/PottagJAXBSchema.class new file mode 100644 index 0000000000000000000000000000000000000000..6de202cf70f73a5e0a6700e308ade93d36f3d2e2 GIT binary patch literal 442 zcmZ9I%TB{E5JkrY+BBu56w31(Ad4;bH=xGgLg%p~v{F)-L}$e_3h5Lw-y}j7&aE~E;~PF7hT=)4 z*nn*Y-4$-oQJBR}uvn3gL4C@-Yxj{s&;1ialP6hx#h^W+V1t=?K(&?@G|8~UG*Pb% zTBE@80zdM(H?d%sfh}~J>dePfQkxr$xJ$ww-IW%osA$jl{MXTA&@}k+W3XL5oVd}L z&$tDB(iASF8lypaw}0;qCwEaWjlR1=3r-kxvUR$Ud9ZZL&Vu4Yz0QT4CgrUD{|#K0 x|En`#uti&}!zt|0$)G`01B=+MaErKI;SO=P!u!Mr6+R?Bs_-$TGOCr?XJ4)5T^;}c literal 0 HcmV?d00001 diff --git a/services/pottag/jaxb/bin/org/collectionspace/services/PottagListItemJAXBSchema.class b/services/pottag/jaxb/bin/org/collectionspace/services/PottagListItemJAXBSchema.class new file mode 100644 index 0000000000000000000000000000000000000000..08477a86d0e3cfd5c0fadac71b6392d760aa15e5 GIT binary patch literal 399 zcmZ|LOHRWu5C-6XN@+tMP|AD3703dyV%3&b3Q8YpD1uex8aKFdk}7d3aWxhkfI}e$ zBp@3Wmi)={&)9zdczpwKiPI`dgsUVCU6~l8q}55Bt%X!>rqYd;Dsv}^wIcL&W<9H- z+s;Ebkk2X-Rg?*>mDmW^h&Xh|ixuYyl|g6d`HzIQ|92SJRL9{pq1odBR>byRm|Qv7 z;lNTv+Pn}Nlc_hloBN$^-wzx#2=z6`tS=VIWISv%8+QBC`FQYKvq#vC^Jt;c@$#pv z+zY%L2W>)GX8MV+HJf@4j(C%&hENNVJe6vojUt@?_q>a*%U1}T;;-7q8LtYf$|}KO a*9xw)n+5N(TLtg44+=iyE`;OnUMHVj!B>p{ literal 0 HcmV?d00001 diff --git a/services/pottag/jaxb/pom.xml b/services/pottag/jaxb/pom.xml new file mode 100644 index 000000000..880e28136 --- /dev/null +++ b/services/pottag/jaxb/pom.xml @@ -0,0 +1,35 @@ + + + + org.collectionspace.services.pottag + org.collectionspace.services + 4.5-SNAPSHOT + + + 4.0.0 + org.collectionspace.services + org.collectionspace.services.pottag.jaxb + services.pottag.jaxb + + + + org.collectionspace.services + org.collectionspace.services.jaxb + ${project.version} + + + + + collectionspace-services-pottag-jaxb + install + + + org.jvnet.jaxb2.maven2 + maven-jaxb2-plugin + + + + + diff --git a/services/pottag/jaxb/src/main/java/org/collectionspace/services/PottagJAXBSchema.java b/services/pottag/jaxb/src/main/java/org/collectionspace/services/PottagJAXBSchema.java new file mode 100644 index 000000000..8d03a1707 --- /dev/null +++ b/services/pottag/jaxb/src/main/java/org/collectionspace/services/PottagJAXBSchema.java @@ -0,0 +1,15 @@ +/** + * + */ +package org.collectionspace.services; + +public interface PottagJAXBSchema { + final static String FAMILY = "family"; + final static String COMMON_NAME = "commonName"; + final static String LOCALE = "locale"; + final static String TAXON_NAME = "taxonName"; + final static String LABEL_DATA = "labelData"; + final static String NUMBER_OF_LABELS = "numberOfLabels"; +} + + diff --git a/services/pottag/jaxb/src/main/java/org/collectionspace/services/PottagListItemJAXBSchema.java b/services/pottag/jaxb/src/main/java/org/collectionspace/services/PottagListItemJAXBSchema.java new file mode 100644 index 000000000..6ff53881b --- /dev/null +++ b/services/pottag/jaxb/src/main/java/org/collectionspace/services/PottagListItemJAXBSchema.java @@ -0,0 +1,9 @@ +package org.collectionspace.services; + +public interface PottagListItemJAXBSchema { + final static String FAMILY = "family"; + final static String PRINT_LABELS = "printLabels"; + final static String NUMBER_OF_LABELS = "numberOfLabels"; + final static String CSID = "csid"; + final static String URI = "url"; +} diff --git a/services/pottag/jaxb/src/main/resources/pottags-common.xsd b/services/pottag/jaxb/src/main/resources/pottags-common.xsd new file mode 100644 index 000000000..711ddc27d --- /dev/null +++ b/services/pottag/jaxb/src/main/resources/pottags-common.xsd @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/pottag/pom.xml b/services/pottag/pom.xml new file mode 100644 index 000000000..decb0fd9b --- /dev/null +++ b/services/pottag/pom.xml @@ -0,0 +1,26 @@ + + + + + org.collectionspace.services + org.collectionspace.services.main + 4.5-SNAPSHOT + + + 4.0.0 + org.collectionspace.services.pottag + services.pottag + pom + + + + + + jaxb + service + 3rdparty + client + + + + diff --git a/services/pottag/service/pom.xml b/services/pottag/service/pom.xml new file mode 100644 index 000000000..7378d309b --- /dev/null +++ b/services/pottag/service/pom.xml @@ -0,0 +1,106 @@ + + + + + org.collectionspace.services + org.collectionspace.services.pottag + 4.5-SNAPSHOT + + + 4.0.0 + org.collectionspace.services + org.collectionspace.services.pottag.service + services.pottag.service + jar + + + + org.collectionspace.services + org.collectionspace.services.common + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.pottag.jaxb + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.pottag.client + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.collectionobject.jaxb + ${project.version} + + + + junit + junit + 4.1 + test + + + org.testng + testng + + + + + + javax.security + jaas + 1.0.01 + provided + + + + dom4j + dom4j + 1.6.1 + provided + + + + + + org.jboss.resteasy + resteasy-jaxrs + + + tjws + webserver + + + + + org.jboss.resteasy + resteasy-jaxb-provider + + + org.jboss.resteasy + resteasy-multipart-provider + + + + + + org.nuxeo.ecm.core + nuxeo-core-api + ${nuxeo.core.version} + + + jboss-remoting + jboss + + + + + + + + collectionspace-services-pottag + + + diff --git a/services/pottag/service/profiles.xml b/services/pottag/service/profiles.xml new file mode 100644 index 000000000..347b9df22 --- /dev/null +++ b/services/pottag/service/profiles.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/services/pottag/service/src/main/java/org/collectionspace/services/pottag/PottagResource.java b/services/pottag/service/src/main/java/org/collectionspace/services/pottag/PottagResource.java new file mode 100644 index 000000000..ff014cf62 --- /dev/null +++ b/services/pottag/service/src/main/java/org/collectionspace/services/pottag/PottagResource.java @@ -0,0 +1,63 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + + * http://www.collectionspace.org + * http://wiki.collectionspace.org + + * Copyright 2009 University of California at Berkeley + + * 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 + + * 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.collectionspace.services.pottag; + +import org.collectionspace.services.client.PottagClient; +import org.collectionspace.services.common.NuxeoBasedResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +@Path(PottagClient.SERVICE_PATH) +@Consumes("application/xml") +@Produces("application/xml") +public class PottagResource extends NuxeoBasedResource { + + final Logger logger = LoggerFactory.getLogger(PottagResource.class); + + @Override + protected String getVersionString() { + final String lastChangeRevision = "$LastChangedRevision$"; + return lastChangeRevision; + } + + @Override + public String getServiceName() { + return PottagClient.SERVICE_NAME; + } + + @Override + public Class getCommonPartClass() { + return PottagsCommon.class; + } +} + + + + + + diff --git a/services/pottag/service/src/main/java/org/collectionspace/services/pottag/nuxeo/PottagConstants.java b/services/pottag/service/src/main/java/org/collectionspace/services/pottag/nuxeo/PottagConstants.java new file mode 100644 index 000000000..181d969d5 --- /dev/null +++ b/services/pottag/service/src/main/java/org/collectionspace/services/pottag/nuxeo/PottagConstants.java @@ -0,0 +1,42 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + + * http://www.collectionspace.org + * http://wiki.collectionspace.org + + * Copyright 2009 University of California at Berkeley + + * 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 + + * 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.collectionspace.services.pottag.nuxeo; + +/** + * PottagConstants specifies constants for the Loans In service + * + */ +public class PottagConstants { + + public final static String NUXEO_DOCTYPE = "Pottag"; + public final static String NUXEO_SCHEMA_NAME = "pottag"; + public final static String NUXEO_DC_TITLE = "CollectionSpace-Pottag"; + + public static final String COMMON_SCHEMA_NAME = "pottags_common"; + + public final static String LABEL_REQUESTED_SCHEMA_NAME = COMMON_SCHEMA_NAME; + public final static String LABEL_REQUESTED_FIELD_NAME = "printLabels"; + public final static String LABEL_REQUESTED_YES_VALUE = "yes"; + public final static String LABEL_REQUESTED_NO_VALUE = "no"; +} diff --git a/services/pottag/service/src/main/java/org/collectionspace/services/pottag/nuxeo/PottagDocumentModelHandler.java b/services/pottag/service/src/main/java/org/collectionspace/services/pottag/nuxeo/PottagDocumentModelHandler.java new file mode 100644 index 000000000..62d3c93b4 --- /dev/null +++ b/services/pottag/service/src/main/java/org/collectionspace/services/pottag/nuxeo/PottagDocumentModelHandler.java @@ -0,0 +1,36 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + + * http://www.collectionspace.org + * http://wiki.collectionspace.org + + * Copyright 2009 University of California at Berkeley + + * 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 + + * 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.collectionspace.services.pottag.nuxeo; + +import org.collectionspace.services.pottag.PottagsCommon; +import org.collectionspace.services.nuxeo.client.java.NuxeoDocumentModelHandler; + +/** PottagDocumentModelHandler + * $LastChangedRevision$ + * $LastChangedDate$ + */ +public class PottagDocumentModelHandler + extends NuxeoDocumentModelHandler { +} + diff --git a/services/pottag/service/src/main/java/org/collectionspace/services/pottag/nuxeo/PottagValidatorHandler.java b/services/pottag/service/src/main/java/org/collectionspace/services/pottag/nuxeo/PottagValidatorHandler.java new file mode 100644 index 000000000..d1ec840b3 --- /dev/null +++ b/services/pottag/service/src/main/java/org/collectionspace/services/pottag/nuxeo/PottagValidatorHandler.java @@ -0,0 +1,18 @@ +package org.collectionspace.services.pottag.nuxeo; + +import org.collectionspace.services.common.context.ServiceContext; +import org.collectionspace.services.common.document.InvalidDocumentException; +import org.collectionspace.services.common.document.ValidatorHandler; +import org.collectionspace.services.common.document.DocumentHandler.Action; + +public class PottagValidatorHandler implements ValidatorHandler { + + @Override + public void validate(Action action, ServiceContext ctx) + throws InvalidDocumentException { + // TODO Auto-generated method stub + System.out.println("PottagValidatorHandler executed."); + + } + +} diff --git a/services/pottag/service/src/test/java/org/collectionspace/services/test/PottagServiceTest.java b/services/pottag/service/src/test/java/org/collectionspace/services/test/PottagServiceTest.java new file mode 100644 index 000000000..c89883e19 --- /dev/null +++ b/services/pottag/service/src/test/java/org/collectionspace/services/test/PottagServiceTest.java @@ -0,0 +1,13 @@ +package org.collectionspace.services.test; + +//import org.collectionspace.services.pottag.Pottag; +//import org.collectionspace.services.pottag.PottagList; + +/** + * Placeholder for server-side testing of Loan In service code. + * + * @version $Revision$ + */ +public class PottagServiceTest { + //empty +} diff --git a/services/pottag/service/src/test/resources/log4j.xml b/services/pottag/service/src/test/resources/log4j.xml new file mode 100644 index 000000000..52121cb83 --- /dev/null +++ b/services/pottag/service/src/test/resources/log4j.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/propagation/3rdparty/build.xml b/services/propagation/3rdparty/build.xml new file mode 100644 index 000000000..62850ac73 --- /dev/null +++ b/services/propagation/3rdparty/build.xml @@ -0,0 +1,131 @@ + + + + propagation service 3rdparty + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/propagation/3rdparty/nuxeo-platform-cs-propagation/build.xml b/services/propagation/3rdparty/nuxeo-platform-cs-propagation/build.xml new file mode 100644 index 000000000..d8c32c28b --- /dev/null +++ b/services/propagation/3rdparty/nuxeo-platform-cs-propagation/build.xml @@ -0,0 +1,161 @@ + + + + propagation nuxeo document type + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/propagation/3rdparty/nuxeo-platform-cs-propagation/pom.xml b/services/propagation/3rdparty/nuxeo-platform-cs-propagation/pom.xml new file mode 100644 index 000000000..2b8c6ceb7 --- /dev/null +++ b/services/propagation/3rdparty/nuxeo-platform-cs-propagation/pom.xml @@ -0,0 +1,56 @@ + + + + + org.collectionspace.services + org.collectionspace.services.propagation.3rdparty + 4.5-SNAPSHOT + + + 4.0.0 + org.collectionspace.services + org.collectionspace.services.propagation.3rdparty.nuxeo + services.propagation.3rdparty.nuxeo + jar + + Propagation Nuxeo Document Type + + + + propagation + Propagation + propagations_common + cs_default + + + + + + src/main/resources + true + + + ../../../../3rdparty/nuxeo/nuxeo-doctype/src/main/resources + true + + + + + org.apache.maven.plugins + maven-jar-plugin + + + target/classes/META-INF/MANIFEST.MF + + ${eclipseVersion} + 2 + + + + + + + + diff --git a/services/propagation/3rdparty/nuxeo-platform-cs-propagation/src/main/resources/OSGI-INF/layouts-contrib.xml b/services/propagation/3rdparty/nuxeo-platform-cs-propagation/src/main/resources/OSGI-INF/layouts-contrib.xml new file mode 100644 index 000000000..c7685ab48 --- /dev/null +++ b/services/propagation/3rdparty/nuxeo-platform-cs-propagation/src/main/resources/OSGI-INF/layouts-contrib.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + propNumber + propType + + germinationDate + propComments + propReason + + + + + + + true + + propNumber + + + dataInputText + + + + + + + + true + + propType + + + dataInputText + + + + + + + + + + true + + germinationDate + + + dataInputText + + + + + + + + true + + propComments + + + dataInputText + + + + + + + + true + + propReason + + + dataInputText + + + + + + diff --git a/services/propagation/3rdparty/nuxeo-platform-cs-propagation/src/main/resources/schemas/propagations_common.xsd b/services/propagation/3rdparty/nuxeo-platform-cs-propagation/src/main/resources/schemas/propagations_common.xsd new file mode 100644 index 000000000..f5b1629c9 --- /dev/null +++ b/services/propagation/3rdparty/nuxeo-platform-cs-propagation/src/main/resources/schemas/propagations_common.xsd @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/propagation/3rdparty/pom.xml b/services/propagation/3rdparty/pom.xml new file mode 100644 index 000000000..42a624a9f --- /dev/null +++ b/services/propagation/3rdparty/pom.xml @@ -0,0 +1,32 @@ + + + + org.collectionspace.services.propagation + org.collectionspace.services + 4.5-SNAPSHOT + + + 4.0.0 + org.collectionspace.services.propagation.3rdparty + services.propagation.3rdparty + pom + + + 3rd party build for propagation service + + + + propagation + Propagation + propagations_common + Tenant + + + + + + diff --git a/services/propagation/build.xml b/services/propagation/build.xml new file mode 100644 index 000000000..4b1fc69eb --- /dev/null +++ b/services/propagation/build.xml @@ -0,0 +1,124 @@ + + + + propagation service + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/propagation/client/.gitignore b/services/propagation/client/.gitignore new file mode 100644 index 000000000..ae3c17260 --- /dev/null +++ b/services/propagation/client/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/services/propagation/client/pom.xml b/services/propagation/client/pom.xml new file mode 100644 index 000000000..9cfebe9ea --- /dev/null +++ b/services/propagation/client/pom.xml @@ -0,0 +1,92 @@ + + + + org.collectionspace.services + org.collectionspace.services.propagation + 4.5-SNAPSHOT + + + 4.0.0 + org.collectionspace.services.propagation.client + services.propagation.client + + + + + org.slf4j + slf4j-api + test + + + org.slf4j + slf4j-log4j12 + test + + + + org.collectionspace.services + org.collectionspace.services.jaxb + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.common + true + + + org.collectionspace.services + org.collectionspace.services.client + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.propagation.jaxb + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.person.client + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.authority.jaxb + ${project.version} + + + + org.testng + testng + + + org.jboss.resteasy + resteasy-jaxrs + + + + tjws + webserver + + + + + org.jboss.resteasy + resteasy-jaxb-provider + + + org.jboss.resteasy + resteasy-multipart-provider + + + commons-httpclient + commons-httpclient + 3.1 + + + + + collectionspace-services-propagation-client + + diff --git a/services/propagation/client/src/main/java/org/collectionspace/services/client/PropagationClient.java b/services/propagation/client/src/main/java/org/collectionspace/services/client/PropagationClient.java new file mode 100644 index 000000000..ee89c398d --- /dev/null +++ b/services/propagation/client/src/main/java/org/collectionspace/services/client/PropagationClient.java @@ -0,0 +1,56 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + * + * http://www.collectionspace.org + * http://wiki.collectionspace.org + * + * Copyright (c) 2009 Regents of the University of California + * + * 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.services.client; + +/** + * PropagationClient.java + * + * $LastChangedRevision$ + * $LastChangedDate$ + * + */ +public class PropagationClient extends AbstractCommonListPoxServiceClientImpl { + + public static final String SERVICE_NAME = "propagations"; + public static final String SERVICE_PATH_COMPONENT = SERVICE_NAME; + public static final String SERVICE_PATH = "/" + SERVICE_PATH_COMPONENT; + public static final String SERVICE_PATH_PROXY = SERVICE_PATH + "/"; + public static final String SERVICE_PAYLOAD_NAME = SERVICE_NAME; + + public PropagationClient() throws Exception { + super(); + } + + /* (non-Javadoc) + * @see org.collectionspace.services.client.AbstractServiceClientImpl#getServicePathComponent() + */ + @Override + public String getServicePathComponent() { + return SERVICE_PATH_COMPONENT; + } + + @Override + public String getServiceName() { + return SERVICE_NAME; + } + + @Override + public Class getProxyClass() { + return PropagationProxy.class; + } + +} diff --git a/services/propagation/client/src/main/java/org/collectionspace/services/client/PropagationProxy.java b/services/propagation/client/src/main/java/org/collectionspace/services/client/PropagationProxy.java new file mode 100644 index 000000000..606da11a4 --- /dev/null +++ b/services/propagation/client/src/main/java/org/collectionspace/services/client/PropagationProxy.java @@ -0,0 +1,34 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + * + * http://www.collectionspace.org + * http://wiki.collectionspace.org + * + * Copyright (c) 2009 Regents of the University of California + * + * 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.services.client; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +/** + * PropagationProxy.java + * + * $LastChangedRevision$ + * $LastChangedDate$ + * + */ +@Path(PropagationClient.SERVICE_PATH_PROXY) +@Produces({"application/xml"}) +@Consumes({"application/xml"}) +public interface PropagationProxy extends CollectionSpaceCommonListPoxProxy { +} diff --git a/services/propagation/client/src/test/java/org/collectionspace/services/client/test/PropagationAuthRefsTest.java b/services/propagation/client/src/test/java/org/collectionspace/services/client/test/PropagationAuthRefsTest.java new file mode 100644 index 000000000..274c427fd --- /dev/null +++ b/services/propagation/client/src/test/java/org/collectionspace/services/client/test/PropagationAuthRefsTest.java @@ -0,0 +1,353 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + * + * http://www.collectionspace.org + * http://wiki.collectionspace.org + * + * Copyright © 2009 Regents of the University of California + * + * 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 + * + * 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.collectionspace.services.client.test; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.Response; + +import org.collectionspace.services.PersonJAXBSchema; +import org.collectionspace.services.client.CollectionSpaceClient; +import org.collectionspace.services.client.PropagationClient; +import org.collectionspace.services.client.PersonAuthorityClient; +import org.collectionspace.services.client.PersonAuthorityClientUtils; +import org.collectionspace.services.client.PayloadOutputPart; +import org.collectionspace.services.client.PoxPayloadIn; +import org.collectionspace.services.client.PoxPayloadOut; +import org.collectionspace.services.common.authorityref.AuthorityRefList; +import org.collectionspace.services.common.api.GregorianCalendarDateTimeUtils; +import org.collectionspace.services.jaxb.AbstractCommonList; +import org.collectionspace.services.propagation.PropActivityGroup; +import org.collectionspace.services.propagation.PropActivityGroupList; +import org.collectionspace.services.propagation.PropagationsCommon; +import org.collectionspace.services.person.PersonTermGroup; + +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.Test; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * PropagationAuthRefsTest, carries out Authority References tests against a + * deployed and running Propagation (aka Loans In) Service. + * + * $LastChangedRevision$ + * $LastChangedDate$ + */ +public class PropagationAuthRefsTest extends BaseServiceTest { + + private final String CLASS_NAME = PropagationAuthRefsTest.class.getName(); + private final Logger logger = LoggerFactory.getLogger(CLASS_NAME); + + // Instance variables specific to this test. + final String PERSON_AUTHORITY_NAME = "TestPersonAuth"; + private String knownResourceId = null; + private List propagationIdsCreated = new ArrayList(); + private List personIdsCreated = new ArrayList(); + private String personAuthCSID = null; + private String propagatedByRefName = null; // an authRef field + private final static String CURRENT_DATE_UTC = GregorianCalendarDateTimeUtils.currentDateUTC(); + + /* (non-Javadoc) + * @see org.collectionspace.services.client.test.BaseServiceTest#getClientInstance() + */ + @Override + protected CollectionSpaceClient getClientInstance() { + throw new UnsupportedOperationException(); //method not supported (or needed) in this test class + } + + // --------------------------------------------------------------- + // CRUD tests : CREATE tests + // --------------------------------------------------------------- + // Success outcomes + @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class) + public void createWithAuthRefs(String testName) throws Exception { + testSetup(STATUS_CREATED, ServiceRequestType.CREATE); + + // Submit the request to the service and store the response. + String identifier = createIdentifier(); + + // Create all the person refs and entities + createPersonRefs(); + + // Create a new Loans In resource. + // + // One or more fields in this resource will be PersonAuthority + // references, and will refer to Person resources by their refNames. + PropagationClient propagationClient = new PropagationClient(); + PoxPayloadOut propagationInstance = createPropagationInstance( + "propagationNumber-" + identifier, + this.propagatedByRefName, + CURRENT_DATE_UTC); + Response response = propagationClient.create(propagationInstance); + try { + int statusCode = response.getStatus(); + if (logger.isDebugEnabled()) { + logger.debug(testName + ": status = " + statusCode); + } + Assert.assertTrue(testRequestType.isValidStatusCode(statusCode), invalidStatusCodeMessage(testRequestType, statusCode)); + Assert.assertEquals(statusCode, testExpectedStatusCode); + + // Store the ID returned from the first resource created + // for additional tests below. + if (knownResourceId == null) { + knownResourceId = extractId(response); + } + + // Store the IDs from every resource created by tests, + // so they can be deleted after tests have been run. + propagationIdsCreated.add(extractId(response)); + } finally { + response.close(); + } + } + + /** + * Create one or more Person records that will be used to create refNames (referenced terms) in our + * test propagation records. + * + * @throws Exception + */ + protected void createPersonRefs() throws Exception { + PersonAuthorityClient personAuthClient = new PersonAuthorityClient(); + // Create a temporary PersonAuthority resource, and its corresponding + // refName by which it can be identified. + PoxPayloadOut multipart = PersonAuthorityClientUtils.createPersonAuthorityInstance( + PERSON_AUTHORITY_NAME, PERSON_AUTHORITY_NAME, personAuthClient.getCommonPartName()); + Response res = personAuthClient.create(multipart); + try { + int statusCode = res.getStatus(); + Assert.assertTrue(testRequestType.isValidStatusCode(statusCode), + invalidStatusCodeMessage(testRequestType, statusCode)); + Assert.assertEquals(statusCode, STATUS_CREATED); + personAuthCSID = extractId(res); + } finally { + res.close(); + } + + // Create temporary Person resources, and their corresponding refNames + // by which they can be identified. + String authRefName = PersonAuthorityClientUtils.getAuthorityRefName(personAuthCSID, personAuthClient); + String csid = createPerson("Propye", "ThePropagator", "proppy", authRefName); + personIdsCreated.add(csid); + + // Safe the refName for later use -see createWithAuthRefs() method + this.propagatedByRefName = PersonAuthorityClientUtils.getPersonRefName(personAuthCSID, csid, personAuthClient); + } + + protected String createPerson(String firstName, String surName, String shortId, String authRefName ) throws Exception { + PersonAuthorityClient personAuthClient = new PersonAuthorityClient(); + Map personInfo = new HashMap(); + personInfo.put(PersonJAXBSchema.FORE_NAME, firstName); + personInfo.put(PersonJAXBSchema.SUR_NAME, surName); + personInfo.put(PersonJAXBSchema.SHORT_IDENTIFIER, shortId); + List personTerms = new ArrayList(); + PersonTermGroup term = new PersonTermGroup(); + String termName = firstName + " " + surName; + term.setTermDisplayName(termName); + term.setTermName(termName); + personTerms.add(term); + PoxPayloadOut multipart = + PersonAuthorityClientUtils.createPersonInstance(personAuthCSID, + authRefName, personInfo, personTerms, personAuthClient.getItemCommonPartName()); + + Response res = personAuthClient.createItem(personAuthCSID, multipart); + try { + int statusCode = res.getStatus(); + + Assert.assertTrue(testRequestType.isValidStatusCode(statusCode), + invalidStatusCodeMessage(testRequestType, statusCode)); + Assert.assertEquals(statusCode, STATUS_CREATED); + return extractId(res); + } finally { + res.close(); + } + } + + // Success outcomes + @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class, + dependsOnMethods = {"createWithAuthRefs"}) + public void readAndCheckAuthRefs(String testName) throws Exception { + // Perform setup. + testSetup(STATUS_OK, ServiceRequestType.READ); + + PropagationClient propagationClient = new PropagationClient(); + Response res = propagationClient.read(knownResourceId); + try { + assertStatusCode(res, testName); + // Extract the common part from the response. + PoxPayloadIn input = new PoxPayloadIn((String)res.readEntity(String.class)); + PropagationsCommon propagationCommon = (PropagationsCommon) extractPart(input, + propagationClient.getCommonPartName(), PropagationsCommon.class); + Assert.assertNotNull(propagationCommon); + if (logger.isDebugEnabled()){ + logger.debug(objectAsXmlString(propagationCommon, PropagationsCommon.class)); + } + } finally { + if (res != null) { + res.close(); + } + } + + // Get the authority references + res = propagationClient.getAuthorityRefs(knownResourceId); // AuthorityRefList + AuthorityRefList list = null; + try { + assertStatusCode(res, testName); + list = (AuthorityRefList) res.getEntity(); + Assert.assertNotNull(list); + } finally { + if (res != null) { + res.close(); + } + } + + int expectedAuthRefs = personIdsCreated.size(); + List items = list.getAuthorityRefItem(); + int numAuthRefsFound = items.size(); + if(logger.isDebugEnabled()){ + logger.debug("Expected " + expectedAuthRefs + " authority references, found " + numAuthRefsFound); + } + + // Optionally output additional data about list members for debugging. + boolean iterateThroughList = true; + if(iterateThroughList && logger.isDebugEnabled()){ + int i = 0; + for(AuthorityRefList.AuthorityRefItem item : items){ + logger.debug(testName + ": list-item[" + i + "] Field:" + + item.getSourceField() + "= " + + item.getAuthDisplayName() + + item.getItemDisplayName()); + logger.debug(testName + ": list-item[" + i + "] refName=" + + item.getRefName()); + logger.debug(testName + ": list-item[" + i + "] URI=" + + item.getUri()); + i++; + } + } + + Assert.assertEquals(numAuthRefsFound, expectedAuthRefs, + "Did not find all expected authority references! " + "Expected " + expectedAuthRefs + ", found " + numAuthRefsFound); + } + + + // --------------------------------------------------------------- + // Cleanup of resources created during testing + // --------------------------------------------------------------- + + /** + * Deletes all resources created by tests, after all tests have been run. + * + * This cleanup method will always be run, even if one or more tests fail. + * For this reason, it attempts to remove all resources created + * at any point during testing, even if some of those resources + * may be expected to be deleted by certain tests. + * @throws Exception + */ + @AfterClass(alwaysRun=true) + public void cleanUp() throws Exception { + String noTest = System.getProperty("noTestCleanup"); + if (Boolean.TRUE.toString().equalsIgnoreCase(noTest)) { + if (logger.isDebugEnabled()) { + logger.debug("Skipping Cleanup phase ..."); + } + return; + } + if (logger.isDebugEnabled()) { + logger.debug("Cleaning up temporary resources created for testing ..."); + } + + // + // Delete all the propagation records we created + PropagationClient propagationClient = new PropagationClient(); + for (String resourceId : propagationIdsCreated) { + // Note: Any non-success responses are ignored and not reported. + propagationClient.delete(resourceId).close(); // alternative to propagationClient.delete(resourceId).releaseConnection(); + } + + // + // Delete Person resource(s) (before PersonAuthority resources). + PersonAuthorityClient personAuthClient = new PersonAuthorityClient(); + for (String resourceId : personIdsCreated) { + // Note: Any non-success responses are ignored and not reported. + personAuthClient.deleteItem(personAuthCSID, resourceId).close(); + } + if (personAuthCSID != null) { + personAuthClient.delete(personAuthCSID).close(); + } + } + + // --------------------------------------------------------------- + // Utility methods used by tests above + // --------------------------------------------------------------- + public String getServiceName() { + return PropagationClient.SERVICE_NAME; + } + + @Override + public String getServicePathComponent() { + return PropagationClient.SERVICE_PATH_COMPONENT; + } + + private PoxPayloadOut createPropagationInstance(String propagationNumber, + String propagatedBy, + String returnDate) throws Exception { + PropagationsCommon propagationCommon = new PropagationsCommon(); + propagationCommon.setPropNumber(propagationNumber); + propagationCommon.setPropBy(propagatedBy); + propagationCommon.setPropNumber(returnDate); + + PropActivityGroupList propActivityGroupList = new PropActivityGroupList(); + PropActivityGroup propActivityGroup = new PropActivityGroup(); + propActivityGroup.setOrder(BigInteger.valueOf(42)); + propActivityGroupList.getPropActivityGroup().add(propActivityGroup); + propagationCommon.setPropActivityGroupList(propActivityGroupList); + + PoxPayloadOut multipart = new PoxPayloadOut(this.getServicePathComponent()); + PayloadOutputPart commonPart = multipart.addPart(new PropagationClient().getCommonPartName(), propagationCommon); + + if (logger.isDebugEnabled()) { + logger.debug("to be created, propagation common"); + logger.debug(objectAsXmlString(propagationCommon, PropagationsCommon.class)); + } + + return multipart; + } + + @Override + protected Class getCommonListType() { + return AbstractCommonList.class; + } + + @Override + protected CollectionSpaceClient getClientInstance(String clientPropertiesFilename) throws Exception { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); //method not supported (or needed) in this test class + } +} diff --git a/services/propagation/client/src/test/java/org/collectionspace/services/client/test/PropagationServiceTest.java b/services/propagation/client/src/test/java/org/collectionspace/services/client/test/PropagationServiceTest.java new file mode 100644 index 000000000..1024b62c9 --- /dev/null +++ b/services/propagation/client/src/test/java/org/collectionspace/services/client/test/PropagationServiceTest.java @@ -0,0 +1,262 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + * + * http://www.collectionspace.org + * http://wiki.collectionspace.org + * + * Copyright © 2009 Regents of the University of California + * + * 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 + * + * 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.collectionspace.services.client.test; + +//import java.util.ArrayList; +import java.util.List; +import javax.ws.rs.core.Response; + +import org.collectionspace.services.client.CollectionSpaceClient; +import org.collectionspace.services.client.PropagationClient; +import org.collectionspace.services.client.PayloadOutputPart; +import org.collectionspace.services.client.PoxPayloadOut; +import org.collectionspace.services.jaxb.AbstractCommonList; +import org.collectionspace.services.propagation.PropActivityGroup; +import org.collectionspace.services.propagation.PropActivityGroupList; +import org.collectionspace.services.propagation.PropagationsCommon; + +import org.testng.Assert; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * PropagationServiceTest, carries out tests against a + * deployed and running Propagation (aka Loans In) Service. + * + * $LastChangedRevision$ + * $LastChangedDate$ + */ +public class PropagationServiceTest extends AbstractPoxServiceTestImpl { + + /** The logger. */ + private final String CLASS_NAME = PropagationServiceTest.class.getName(); + private final Logger logger = LoggerFactory.getLogger(CLASS_NAME); + // Instance variables specific to this test. + /** The service path component. */ + + /* (non-Javadoc) + * @see org.collectionspace.services.client.test.BaseServiceTest#getClientInstance() + */ + @Override + protected CollectionSpaceClient getClientInstance() throws Exception { + return new PropagationClient(); + } + + // --------------------------------------------------------------- + // CRUD tests : CREATE tests + // --------------------------------------------------------------- + + // Success outcomes + + /* (non-Javadoc) + * @see org.collectionspace.services.client.test.ServiceTest#create(java.lang.String) + */ + @Override +// @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class) + public void create(String testName) throws Exception { + // Perform setup, such as initializing the type of service request + // (e.g. CREATE, DELETE), its valid and expected status codes, and + // its associated HTTP method name (e.g. POST, DELETE). + setupCreate(); + + // Submit the request to the service and store the response. + PropagationClient client = new PropagationClient(); + String identifier = createIdentifier(); + PoxPayloadOut multipart = createPropagationInstance(identifier); + String newID = null; + Response res = client.create(multipart); + try { + int statusCode = res.getStatus(); + + // Check the status code of the response: does it match + // the expected response(s)? + // + // Specifically: + // Does it fall within the set of valid status codes? + // Does it exactly match the expected status code? + if (logger.isDebugEnabled()) { + logger.debug(testName + ": status = " + statusCode); + } + Assert.assertTrue(testRequestType.isValidStatusCode(statusCode), + invalidStatusCodeMessage(testRequestType, statusCode)); + Assert.assertEquals(statusCode, testExpectedStatusCode); + + newID = extractId(res); + } finally { + if (res != null) { + res.close(); + } + } + + // Store the ID returned from the first resource created + // for additional tests below. + if (knownResourceId == null) { + knownResourceId = newID; + if (logger.isDebugEnabled()) { + logger.debug(testName + ": knownResourceId=" + knownResourceId); + } + } + + // Store the IDs from every resource created by tests, + // so they can be deleted after tests have been run. + allResourceIdsCreated.add(newID); + } + + // --------------------------------------------------------------- + // CRUD tests : READ tests + // --------------------------------------------------------------- + + @Override + protected void compareReadInstances(PropagationsCommon original, PropagationsCommon fromRead) throws Exception { + PropActivityGroupList propActivityGroupList = fromRead.getPropActivityGroupList(); + Assert.assertNotNull(propActivityGroupList); + + List propActivityGroups = propActivityGroupList.getPropActivityGroup(); + Assert.assertNotNull(propActivityGroups); + Assert.assertTrue(propActivityGroups.size() > 0); + + if (logger.isDebugEnabled()) { + logger.debug("UTF-8 data sent=" + getUTF8DataFragment() + "\n" + + "UTF-8 data received=" + fromRead.getPropComments()); + } + + Assert.assertEquals(fromRead.getPropComments(), getUTF8DataFragment(), + "UTF-8 data retrieved '" + fromRead.getPropComments() + "' does not match expected data '" + getUTF8DataFragment()); + } + + @Override + protected void compareUpdatedInstances(PropagationsCommon propagationCommon, + PropagationsCommon updatedPropagationCommon) throws Exception { + // Check selected fields in the updated common part. + Assert.assertEquals(updatedPropagationCommon.getPropNumber(), + propagationCommon.getPropNumber(), + "Data in updated object did not match submitted data."); + + if (logger.isDebugEnabled()) { + logger.debug("UTF-8 data sent=" + propagationCommon.getPropComments() + "\n" + + "UTF-8 data received=" + updatedPropagationCommon.getPropComments()); + } + Assert.assertTrue(updatedPropagationCommon.getPropComments().contains(getUTF8DataFragment()), + "UTF-8 data retrieved '" + updatedPropagationCommon.getPropComments() + + "' does not contain expected data '" + getUTF8DataFragment()); + Assert.assertEquals(updatedPropagationCommon.getPropComments(), + propagationCommon.getPropComments(), + "Data in updated object did not match submitted data."); + } + + // --------------------------------------------------------------- + // Utility methods used by tests above + // --------------------------------------------------------------- + + @Override + public String getServiceName() { + return PropagationClient.SERVICE_NAME; + } + + /* (non-Javadoc) + * @see org.collectionspace.services.client.test.BaseServiceTest#getServicePathComponent() + */ + @Override + public String getServicePathComponent() { + return PropagationClient.SERVICE_PATH_COMPONENT; + } + + @Override + protected PoxPayloadOut createInstance(String identifier) throws Exception { + return createPropagationInstance(identifier); + } + + /** + * Creates the propagation instance. + * + * @param identifier the identifier + * @return the multipart output + * @throws Exception + */ + private PoxPayloadOut createPropagationInstance(String identifier) throws Exception { + return createPropagationInstance( + "propNumber-" + identifier, + "returnDate-" + identifier); + } + + /** + * Creates the propagation instance. + * + * @param propNumber the propagation number + * @param returnDate the return date + * @return the multipart output + * @throws Exception + */ + private PoxPayloadOut createPropagationInstance(String propNumber, String returnDate) throws Exception { + + PropagationsCommon propagationCommon = new PropagationsCommon(); + propagationCommon.setPropNumber(propNumber); + PropActivityGroupList propActivityGroupList = new PropActivityGroupList(); + PropActivityGroup propActivityGroup = new PropActivityGroup(); + propActivityGroupList.getPropActivityGroup().add(propActivityGroup); + propagationCommon.setPropActivityGroupList(propActivityGroupList); + propagationCommon.setPropReason("For Surfboards of the 1960s exhibition."); + propagationCommon.setPropComments(getUTF8DataFragment()); + + PoxPayloadOut multipart = new PoxPayloadOut(this.getServicePathComponent()); + PayloadOutputPart commonPart = multipart.addPart(new PropagationClient().getCommonPartName(), propagationCommon); + + if (logger.isDebugEnabled()) { + logger.debug("to be created, propagation common"); + logger.debug(objectAsXmlString(propagationCommon, PropagationsCommon.class)); + } + + return multipart; + } + + /* + * For convenience and terseness, this test method is the base of the test execution dependency chain. Other test methods may + * refer to this method in their @Test annotation declarations. + */ + @Override + public void CRUDTests(String testName) { + // // Needed for TestNG dependency chain. + + } + + @Override + protected PoxPayloadOut createInstance(String commonPartName, String identifier) throws Exception { + PoxPayloadOut result = createPropagationInstance(identifier); + return result; + } + + @Override + protected PropagationsCommon updateInstance(PropagationsCommon propagationCommon) { + // Update the content of this resource. + propagationCommon.setPropNumber("updated-" + propagationCommon.getPropNumber()); + propagationCommon.setPropComments("updated-" + propagationCommon.getPropComments()); + + return propagationCommon; + } + + @Override + protected CollectionSpaceClient getClientInstance(String clientPropertiesFilename) throws Exception { + return new PropagationClient(); + } +} diff --git a/services/propagation/client/src/test/resources/log4j.properties b/services/propagation/client/src/test/resources/log4j.properties new file mode 100644 index 000000000..148a3e865 --- /dev/null +++ b/services/propagation/client/src/test/resources/log4j.properties @@ -0,0 +1,23 @@ +log4j.rootLogger=debug, stdout, R + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + +# Pattern to output the caller's file name and line number. +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%t] [%c:%L] %m%n + +log4j.appender.R=org.apache.log4j.RollingFileAppender +log4j.appender.R.File=target/test-client.log + +log4j.appender.R.MaxFileSize=100KB +# Keep one backup file +log4j.appender.R.MaxBackupIndex=1 + +log4j.appender.R.layout=org.apache.log4j.PatternLayout +log4j.appender.R.layout.ConversionPattern=%d %-5p [%t] [%c:%L] %m%n + +#packages +log4j.logger.org.collectionspace=DEBUG +log4j.logger.org.apache=INFO +log4j.logger.httpclient=INFO +log4j.logger.org.jboss.resteasy=INFO diff --git a/services/propagation/jaxb/bin/META-INF/sun-jaxb.episode b/services/propagation/jaxb/bin/META-INF/sun-jaxb.episode new file mode 100644 index 000000000..31e71a54b --- /dev/null +++ b/services/propagation/jaxb/bin/META-INF/sun-jaxb.episode @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/propagation/jaxb/bin/org/collectionspace/services/PropagationJAXBSchema.class b/services/propagation/jaxb/bin/org/collectionspace/services/PropagationJAXBSchema.class new file mode 100644 index 0000000000000000000000000000000000000000..4dcf73a1c436a98412596ef51f5e58d01f796e17 GIT binary patch literal 429 zcmZvY%T9wp6o${K+EUuuTJJuDB)W7(TQoIt382wcGbIzGff+JD6JO1R58y)?4@4si z7qj^C&-u>$^ZE7u0RY!<)`Jehl~l12$u#9rnMhIOEaFDN)i#NEVR%aBEM|3bZ$8h0 z=#^)z2fGN94cjs!Wg<4*^@fHK2A=PFE9cRkSw6z_;qOLJszk&$2%|ZzD48gqSXyxn z`UrYXJDe(8bM>q6Ei-T(4Te9d&siY_LcduMzIc`fV+5_94Bv82-X@v5wrx3KpurSj zSf_KDWn7d6z4y-Y?WJReOV?Q~%#akv2;-QmED=q?1uMy<2eMKTzfDqti+}rH*2TIA p2s`wVdvH#tOVlIkfJUshctAXA@jmgS#RtTPEj}U(!b#&g{RUUKV*UUC literal 0 HcmV?d00001 diff --git a/services/propagation/jaxb/bin/org/collectionspace/services/PropagationListItemJAXBSchema.class b/services/propagation/jaxb/bin/org/collectionspace/services/PropagationListItemJAXBSchema.class new file mode 100644 index 0000000000000000000000000000000000000000..a425c021c177c29a8c07fbd3abf8a1bca1be6c4a GIT binary patch literal 441 zcmaKo&rZTX5XNT_p@ImaC|-R4d+?^G@<*};+NLc;PtCG!>XQD;c1z-`dGG;zDC3li zJ$cw#5G5O~N`Wb!PI1=li5i=1&`7DDYM7lrAnEN2m`I!jq- zszvf_y-a-mE)v#;CV|0@?U@;~G%}s-4owJjUC(htd%c>>Jp#k!pTgHlrqLsT-V{e^ zmg)_QOJTqXfliLI>@wL3MPPieu%-ds(BL&(c+T1lm(&joI3+Nuq!#AgOZ~A>S(#Vi zFw0`@t*C7U)Um^v70eCj6X-`mB{DrMF=JW~Xij}PGhm1nFXS76qqRp37-3PW7^C=E zsknHOF^X^hy}@0zT#EpH#iJg>4Zapq8>s;Xa;L^!kOwtBN4}`>CAtu}I(S|G E0?k8b(EtDd literal 0 HcmV?d00001 diff --git a/services/propagation/jaxb/bin/org/collectionspace/services/propagation/ObjectFactory.class b/services/propagation/jaxb/bin/org/collectionspace/services/propagation/ObjectFactory.class new file mode 100644 index 0000000000000000000000000000000000000000..0fa02dacf4aed4c05748e9ea5fea5de41a0e5ca7 GIT binary patch literal 1791 zcmbW1?@!Y}9LAp;gN=$Z5#*POD9DCNjT$v>K@oy)8a0e8`tsIY=8^tbuIzm%Uz56_U?z#5+`_GSG0G{DV0Xc>jzG|Dc?>NE^rSAnDZVNLI zsw-^~m>uPJc$@1P^JDXb)*Ny>^i{8bJVWV(ce&|suRWY(n5s!nhPw!7zq`n_M%!glP!0^iYKX+AzC0hv|icc3C z2=;x~^*x3+kQS@&f*FNN|m3 zd6S`*+GK)+fd%by7-Ze%iWc#(Z_tuKm@&w>0C`-^5@cM#z>2oGdU4i!^c8ny*t_KN zU6v^IVZ}7a=v{{0)L|xy7+BL@=ZA+r5+|jQ#pGxZ57I7BsG#f&-2569rtewxs#phgF6xdzfhy`)bS?C|C8G+M+&)9`I~6$? zbTw!M1(?Ssu23~ISVZGhx?{M8*`dHwTB!w!KT-N}hPiK44>a^EfJc}3;yHf<^KpKG z_{DMlc_P1rtQ*zwz%Fap^ zQye+u2k-;<0h~Al3aa2j6;+)00sH_?oH$eTzO0orlC=rtlMa%0wtK$m?wOwc)W84x zS(@JBA=8tMRwbF_c=5&Fk{rNq`Q^K?Qpen0e5V^Evq~)}Q$0iY9mwDOVvQyOD z)N#&gH#~-9nveiuXQmE1Lau_vp0TMWjO2M&>lxyaNHd4#W zan$sKTIY-7sM`8=kUC_AKyEhQaN%W|hjmuz>SgkcH)>|?Fb2z{KhychIZU%*rBld9 z7~MvTP60lH+o`uij~9h0h)w0iOEf#NRtbF7MPh|YfG1%^ST4%mKNkX*TJDVI+M1c$ zh`|OSzja}Im_%=0r}zquw0Qthtbv-WRuRr+S8dx)?ks=K+Qw>rg}}wmD|y-xUDj}cu}X9M z%xfQc1aERf$KDWU+`_)kI-{m+D&^xS+`|@-o9PO)dOjSSrQTPmdNdCzG zlM)4t%5X#4VRvhjagI$a+ei)<0CybQXbFhG01V@&DHaUFIk8}* z9n@&M8Ds4Psdj?#c7lm^f=RJp27Q=6r19I1 z55Oo!4V;kJaeE0a;t20bcv*;WS%6R!BFw^E0HF_2T^mFQ6(Pc_a3z3H_dgAn0bh&cQzM7SOz z4u1#{-U?yEpF)JUL)h?_5aFE=HvA(*c(;2S@VJR`^G5eJkfab{A%qRnLWE2R8!ibE z7DL!@RfzCj2pblK2umSsSQjF^4>tqZJ#cc%Wn1q_l5c~M3n0|*hZOYsDluL}?!w?ViaK&anx9|#cs79d1m6Fu*PEvMezz^84*yF=kaxC=Wt+~u$b`}ZNi rVSvLBhhYvQ97Z{eaY%6(=PDimub>Wp-*dN$KH%CX0`St z?>z7;`Zt(?85myqC;SP9Zza2lZ8fr!KJs8myE=Qm%YJ+G`R{*!`vbt|s9Bg|SP50r zaYN4&u9jgCwYe*tNT{xKMdY+q*yc@c-Z+oxxkjL_)cfD5u+y%~NL$D;%3B3L8$G9u>0%(2WF&a?WYd+qLj?pI<&d1_uHaaxX+`O*|H2+QgQXr zx$kSw?u@yh%a!C*z{SEOiR$P6b%j46 zpNVhmB`+f^qJ(oL6fnmyUry8{7A1;82F^4tL4g&+>u~C9+^C$2IlcD!@|+RM!H5)DdH2!aJO6^sFUjO z@IX2fT(v$}h}FuEd3?a|&(T(6IUTT+al$6S4tkV9oOv>{07J#rON&XvhYUZDk%o!f zJC%s^0dP6%NU+cX#IE+#8BJ} zJIWR7(rC-Yk&|y!Vac2!h6_(RftJ45mXRbSs+le#ZaGMJuE*lA=j{2O(~!ZA6ZaTN zANI`mYA!WrQK+`)LrJOFeWh;653cAL;5$Jk-XS z`A2F;D8g(6F7=T#F9LmE3dNP%+P83vR3@o(3tuyw9jtl_6i2?lM;5-JcCkO|e}pW2 z%W!6(;uuN~WE7FQ8%HB7fU1JJTk7^Ojnj0uXf%a<{9HhhX5OHgn}lL?FaD0w4|8XK z#q6Ik?oGOyRlqFH)3ubKFpsxr#PBxWA-06`LJHHx6s8BMOz+`+;_Le|P3(y;mr@wE zUcq1=Vwif#u#mzaQyCU<`4EH2TjIl&6bAnl4A%}Z9Avbd!myXZki$o0bqd#UgYK8; p>0{zs5^(SdKBeJj5&cuS`5Y{w{rrM3z$*PpH2M->;Wn1B@*n3!h)N6vtnOO*Xp;NeBrffVc=k!bjE*L}fALgOCIlh}jTAP;oNb?oQm9S*B-~ z1iulHlSdC;tkNov9K2YCRe1Bj(St`19z9s)#e)aR*YlB`&GvpYc-%vFx2L;*{qNUr zdfuDP!$dyHLA12{8(-3MQ#q9~Wdz^6x+ckHHJodO+sQtcdn4GAu&a18g-Cm0&OgBS`-T% zYQeVA&aUEc*0gY~Q2SQZG*r}$I&eJ+gm0JyBh8gZ^0QPcb$dR#4HK*}PL~JEa+#JxXB9(C{L*nKaWh3_~cd_GU z%rkYppyZgqaxvSCDKYLG(a4O#vV6(vG;{Z&q^T;lY5k9FU5(DoOf&0sz~1neY)v?= zXr$SI-&jpMxEoB<4oy3hKQQX<4^rFFx$&im20IiLeEQ-8E*g>9Nd3LJxjhD-`gIqv z*`UFvfTVwMe{ZH0RDDBkEbL}oc(d`OsvTY;u&;Ksioq(7eH!n_F_({fJFf|)&+_KyjGN)5}1%#6CneaWq=PS=1)qjj1ZCF2JgiQ zOm9x2k9gkdzA~woM)#t|3dB*@iyI+u8GM0L!JL{eaM^`6>f~|+aNcDRL}GKXlytl8 z63OxqB{96*4)2oINbRl7NyN)Ws*{_^v@KUu2i$y`H~2Jf8meiiW}M7lZp$Too5E6W z+vMA%M&Nb>+oaTX_Bcr+Mh02$H64Ki6;FW8F?nKmwjFfRa<2jHGTZj%ahe-<^Ico% zW&9IAew-5o^xQmwyA61*q&GfZ@`~p|DYIay^sL6S(w#h%EYBxLc)&&ANZ{l}aGEmi z9GIAvcb?K(RB{F6)j5N4d-;9N&Q2xI5ZGUPB(F8_Qz#UB(Mt1E!^5>Z&6}oOItbj( zJNmP{hNm=m-i4dwtbqq!3=iAz%xgxYmSU;d^)P&dM`i8~7=}+UmMz~6!*C0CFMqAV za2roJ{gVG$D-3rqB`VvAFmF1qG3K4RG1!Om;m4BOAOx+@hNUo;I`F$4|02r}#flE- z6jyAm)}yOhMR&D|9+V&)ycf2MEBc@xrG{XK_r0?kdZ1dxV6}=}9&&fJ27BNsv8cW9 zv{%swEhQVq@ZvrO&!F7>V&)-iaR8ph&x1ItqZ1ZD`OZUeqN}M2@pzl$W{_s zz7c9zh4%sk9w56>P)IK06bev;8(4wi z4L48UE`So_#r4F8i4`WWnBu#@4-ei&r1i@|ONdl>9x7=qqJ76RUf X4{#8GFYvDuRT>tdJWeN4aKIA1zISrOG_?J<6@{pE-K}P^?GDav=?_~Z6)uO z=)d8C2PBZ-74a+hC5W@@yFIbv2&t{agLgfibG~z(Z;t=^=hxo>+`w0HOflT@WyQ38 z$KiG$d{5OZo12QuhOoIZYtpY-6)W5^cWg`U1kwr~NWWf-BgSy{z-m~gV|f+xQR#pX z7?P?j#;{oI?vtkQDmjLwZ|hzlT)r=qC^>x1^ZXz(pct-23Ww&Q>zF0sl})R&Yd&_J zyu)4Y1q70i{ApcCUY@2<7_G~7a*VuWxYRW2T0zxZ7nOn+@CuitGuxKg^8`71=B}Xk zJ%;H_cAp`(;g@+Fiwp~0r}I+!GJy*$_T!!>x$>O`FB`V+)`Sz8Gblk6gsbitspM{z z8^W#mGB7Bqs|O8_2ZkMmV3fGg*8g8{OnUN9>z1Q5nKJ_4u#DiS##6~{OI5>?jS*|M zWzn~-z?c7_Y}3c+IPl8R7~q!qy+CNgN+n+jfr&zXxb}GcgbEEy3X2X=Dmh89+Vy{V zN-&{EVTMku(#cT@=WuBj^SI1#F*D4sA(br@g~z|CyCp7n!^DO}Ob5W(Zza0*V=Sl& z;@sQCQ{Dg}rB-;*xfU2MWU|8^CGa-G@@V=b_9QUX2bL!6%?_(yX+sb5dYbgQf=jKW z49Z1`>49us0t-F5t!sft?8C5;DfabaZgAPXCYo}&Vt>QuvQH9tpD}lI?9tWHXK4(Z zOd{Lw(HP)Yf`!I8IdCjmr3p*%A;Ys1lwzWkordOUTB)`!FNT$&E*s>8T(*$F#|(d; z!1qa7W6;hEZ;ogZqgZb;JU>CK6Z(HTx@Ac1_;qRXEfMnQ`3~7dGw^Ci0SwniPpxY< zP14GjyEL~DXU;S%r%qT+syueqZECf3whH;;Cd1XymRiy=&vMDYd}k7moTbq`oaP_N zGM98n3Z6~VzEA-T)Z2nfOWM_=IM!$|Zk~WRHVC8tG{ljo?rmp2jtBG?puK8e#BmfD z=K4C3xczIP6wUvkhYHJ}TGQY@jTmNdhE{P}rRklZPx1*;w2{WFeq&Cr5vtE)fu3gx z>?VOTdZ&KFx$hRw|ALEu&}s@-=ouOWNiEScv_VpM4X@Li;SIb=&tZhOGzi;T1Ou0Q z5T-)($Oj$5t`=b#@AM#aqtGGzphdWfl^%rAJ~�cPGfhp%&rV1bKL-MR*UHo+xHI znapZx@%$Ks>pckFJba)*_(_Yf3bO|xY}#fuKhhxldV0Q?fdR y&!R)JLf>u?-sc>*aR)hC-c|Sl_rAxp!i+*%VGj3619(WESvp`}VjY{+Pw{N^npQ}xyxcOSe{a#CIQ3wYG=?zeXd zh2f0Oh>}bz5$YQmc=lkEp|UAE5jTA$7``2Qz1%vcQOy-XKDjI-X;v6c)EnCjr91JC zaB!Mo?!eMDrD9da96RyXgGdP-2LrL=`f=EkLE^1Tp0@)L>RWELvegx?412LME*ZA8 zJBWmFeI7-zaofTjo&R63AUtb7_j#amBIlae<*wQ9iE8B@*Ik>k0N?M@YNb$MDs|tiKKydR0|Txkrvk24rU}$2_Ako)Defi@AsI7>Y_27=u;=ZCf!CJNj+e|8>B#jk3|4)b z!*r~+=;KI?F+1QvpLC^UQgCW?=I;7h>nnyUM{62Nmm(foe$38r))^Yw>^yj1?FdC} zupap|FVen<%snX-SAKWj!6(EoX^lJhjAp@Xy*nT$%A@Q!xJ|?1Xf^ukIQX35bf(rA zY8z5(qH}Ad#v-U5Xo{ur4zoB(cZXIpC@0SqRB7iF?c5|OM)&Gt)P7hv^E>AMOypjo zt33tG<7K+mQYoCnd0J5_agoqc%u6{)ujC+Y*9mWA%PgU%wp`9Z*m?$ndkA6X z3BqCyg3Lv@f~$uRY}`^C-pWA;pMmhsA%ug7)^iZ{au7;*m$c5{8s4M(WqP_!cuN8v jZs2`dexT_e$<3d@H0ARnk^#P;UyW8D<5S$iGFJWrI&;}0 literal 0 HcmV?d00001 diff --git a/services/propagation/jaxb/bin/org/collectionspace/services/propagation/StructuredDateGroup.class b/services/propagation/jaxb/bin/org/collectionspace/services/propagation/StructuredDateGroup.class new file mode 100644 index 0000000000000000000000000000000000000000..2eb4ce971924ead1b706145def94ca66b70817f9 GIT binary patch literal 13057 zcmeHNO>h)d5bn1LY_ds6NJ2;m!tx^|psWgt8U#ZUAhP@hA_RY!%szJqXJ?jqvyq63 zKlqD3Ra%w@4<0;l^x(lNtPg^S3*b?b+-@iz=%2ke@f*{dK>7 z{eHUN`{|!=zXyQb@KF*H4EC8;KBJnt&Q)784N+25o)O$y&{QrmCCe-+dBve*W^Ajh z+GUI9CKa0>w#;%V39SrzE-4F2Mpuk{=EVFZgkjK~Lyk#Jlyqg$VP?>qt#TodX5_~h z44p0;wpQe`n$YHTK5iJM?dlQ?wz&dVGFOUvW?nOL8KpwaoGa>6Ixlj=Mvyj(UoLBC zRSWit`MSr2FjdXPVld!XGo@I%#)UnDs&#&zD;5$wql$`M$Q;!2(}vCS+(OK?wGzin z!!9sbQ%jjt7Rm2Zmcn4TmVbgR)!u9dA&XBEASZrxai<*cFM zP^@w|viKuxuhZ_>cpv;ebyZp1zJ%ItANs2FknN$b6{{EwR(UI5eOq0pdWtI~&&HG^ zbdp<|nZxmP8plm1XrjZQD!O9%SfionKz_wuEb+{_W7)$N&zqK}7!!)ljhx~PW|)_V zq#3oqi^>@UK;BkEDWc+5#|D2;J&R|;+WSSdB__;bscdt!oXs&>Z)`}(5 zveP(KcU@XAxSdwr*^r*+=|H~8xQu(IF5}D+gy+(>nO4$H1f|-KD5BsD*)nY2I+rz7 z#Wt<~k=L^k*GCM${NggezWTGaMs_RJ-ehnlR?`pO0@L(E(+|-Nqq#oZLFD%qNzM{y);26snYSs}0hvzN4)i4FO_ zUMl7IdmX9HPq){RpgwVslyuYbK~l~mky38*PD{C@4Wv{k#os_`3^R}jmF%7YjSynk zQXj!j_qULSRA79r&GoUoTEpi0P#)82AIZ!8y}HS2UoiYF2te0w&Trv}mQIiLdSxi*M? zlGx>I4QfGMzMi1n;nThHP=`n<80_@txK~eHu3{9}NJ8D?&HF}>oTVU` z&|ZHdWu#zg5~Kl5gjhp0CM+vqm1!;ZoTj&q%sERYf!{Q@xPkjasw}@NL-Fn<|>;yym&zxsPcU6sSskbA7m7 z>%)?i$KS%7Eer3@t~an{No_5+QdYqWt%854z+h|5ZoD+#8ylT&g9X-NH(NbN|I9d7%W0!M= z#~degCr;dRkE{5gLztF-)4gKBdqJ?w?YJiu$Q+uU%1$!aQeTtL-6v{IWzg zVpSZWJC4v3r(<;-p*N1u7pJ2?PGU`*#6TQjZ5&}RPRCFjVO<^mVK0erM8bv%5@A}xh7%;h6OG3MyFelw zm57HtiIA1>gCG%(N%-L!iEv!P4>w4J6B2&-j6^soVZ)as!YK(Gz9JD$OW5!oi7+E! z!;d7w8HsrKg+w?j5f8tS2(uD4{6Qi-DPhB3B*MAIZAi3`2Yk$#Z<7emN!aiSiIA7D;R_O>09vRQ_5E;@M7RXchY;%ffnKY1D25PL zR8Hnb?b$x@HEDwmr4T~ZhRd}GEgzBy0_+fillgktL771KI0oT`5JJ_HS85SjI|zhU zcoDTG;HsNxU&Q}jLcEtbyaLzYRcvD3^%}f>0k1bjVr7STEo%fiDhVqkydf?uIu|8^DM7(}@}Wb$AQjf%oA3e*p-{ Bz8wGn literal 0 HcmV?d00001 diff --git a/services/propagation/jaxb/bin/org/collectionspace/services/propagation/package-info.class b/services/propagation/jaxb/bin/org/collectionspace/services/propagation/package-info.class new file mode 100644 index 0000000000000000000000000000000000000000..bdc1fc2ca792e663489af390c0574f963a040c9a GIT binary patch literal 293 zcmZvXJx&8b427S=&yu8p28jbu5*LUDDjF&zBqAYN?_f3)=4Z7tiE=gy4#1%hGg}~p zuw=XV-n0BZzurFpTw>l}Lb!@U$0jy&Wv+p(x(!r9{{Wb5_GCfho_4I$?7d?K9RSIv%h z-f?Tg3(F7XXYZC~rCx3b(;!~QM+v;jxxePW2^KZge<@A@!d}T=Yl4Gu9-=9GUdU1D HX~OIqGp< + + + org.collectionspace.services.propagation + org.collectionspace.services + 4.5-SNAPSHOT + + + 4.0.0 + org.collectionspace.services.propagation.jaxb + services.propagation.jaxb + + + + org.collectionspace.services + org.collectionspace.services.jaxb + ${project.version} + + + + + collectionspace-services-propagation-jaxb + install + + + org.jvnet.jaxb2.maven2 + maven-jaxb2-plugin + + + + + diff --git a/services/propagation/jaxb/src/main/java/org/collectionspace/services/PropagationJAXBSchema.java b/services/propagation/jaxb/src/main/java/org/collectionspace/services/PropagationJAXBSchema.java new file mode 100644 index 000000000..a526315d1 --- /dev/null +++ b/services/propagation/jaxb/src/main/java/org/collectionspace/services/PropagationJAXBSchema.java @@ -0,0 +1,15 @@ +/** + * + */ +package org.collectionspace.services; + +public interface PropagationJAXBSchema { + final static String PROP_NUMBER = "propNumber"; + // final static String PROP_DATE = "propDate"; + final static String PROP_REASON = "propReason"; + final static String PROP_TYPE = "propType"; + final static String PROP_COMMENTS = "propComments"; + final static String GERMINATION_DATE = "germinationDate"; +} + + diff --git a/services/propagation/jaxb/src/main/java/org/collectionspace/services/PropagationListItemJAXBSchema.java b/services/propagation/jaxb/src/main/java/org/collectionspace/services/PropagationListItemJAXBSchema.java new file mode 100644 index 000000000..9078dd47a --- /dev/null +++ b/services/propagation/jaxb/src/main/java/org/collectionspace/services/PropagationListItemJAXBSchema.java @@ -0,0 +1,9 @@ +package org.collectionspace.services; + +public interface PropagationListItemJAXBSchema { + final static String PROP_NUMBER = "propNumber"; + final static String PROP_ACTIVITY_GROUP_LIST = "propActivityGroupList"; + final static String GERMINATION_DATE = "germinationDate"; + final static String CSID = "csid"; + final static String URI = "url"; +} diff --git a/services/propagation/jaxb/src/main/resources/propagations-common.xsd b/services/propagation/jaxb/src/main/resources/propagations-common.xsd new file mode 100644 index 000000000..69cb7cfaa --- /dev/null +++ b/services/propagation/jaxb/src/main/resources/propagations-common.xsd @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/propagation/pom.xml b/services/propagation/pom.xml new file mode 100644 index 000000000..6b0b54f01 --- /dev/null +++ b/services/propagation/pom.xml @@ -0,0 +1,27 @@ + + + + + org.collectionspace.services + org.collectionspace.services.main + 4.5-SNAPSHOT + + + 4.0.0 + org.collectionspace.services + org.collectionspace.services.propagation + services.propagation + pom + + + + + + jaxb + service + 3rdparty + client + + + + diff --git a/services/propagation/service/.gitignore b/services/propagation/service/.gitignore new file mode 100644 index 000000000..ae3c17260 --- /dev/null +++ b/services/propagation/service/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/services/propagation/service/pom.xml b/services/propagation/service/pom.xml new file mode 100644 index 000000000..0581759bc --- /dev/null +++ b/services/propagation/service/pom.xml @@ -0,0 +1,106 @@ + + + + + org.collectionspace.services + org.collectionspace.services.propagation + 4.5-SNAPSHOT + + + 4.0.0 + org.collectionspace.services.propagation.service + services.propagation.service + jar + + + + org.collectionspace.services + org.collectionspace.services.common + + + org.collectionspace.services + org.collectionspace.services.client + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.propagation.jaxb + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.propagation.client + ${project.version} + + + org.collectionspace.services + org.collectionspace.services.collectionobject.jaxb + ${project.version} + + + + junit + junit + test + + + org.testng + testng + + + + + + javax.security + jaas + 1.0.01 + provided + + + + dom4j + dom4j + provided + + + + + + org.jboss.resteasy + resteasy-jaxrs + + + tjws + webserver + + + + + org.jboss.resteasy + resteasy-jaxb-provider + + + org.jboss.resteasy + resteasy-multipart-provider + + + + + + org.nuxeo.ecm.core + nuxeo-core-api + + + jboss-remoting + jboss + + + + + + + + collectionspace-services-propagation + + + diff --git a/services/propagation/service/profiles.xml b/services/propagation/service/profiles.xml new file mode 100644 index 000000000..347b9df22 --- /dev/null +++ b/services/propagation/service/profiles.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/services/propagation/service/src/main/java/org/collectionspace/services/propagation/PropagationResource.java b/services/propagation/service/src/main/java/org/collectionspace/services/propagation/PropagationResource.java new file mode 100644 index 000000000..7261b97de --- /dev/null +++ b/services/propagation/service/src/main/java/org/collectionspace/services/propagation/PropagationResource.java @@ -0,0 +1,63 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + + * http://www.collectionspace.org + * http://wiki.collectionspace.org + + * Copyright 2009 University of California at Berkeley + + * 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 + + * 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.collectionspace.services.propagation; + +import org.collectionspace.services.client.PropagationClient; +import org.collectionspace.services.common.NuxeoBasedResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +@Path(PropagationClient.SERVICE_PATH) +@Consumes("application/xml") +@Produces("application/xml") +public class PropagationResource extends NuxeoBasedResource { + + final Logger logger = LoggerFactory.getLogger(PropagationResource.class); + + @Override + protected String getVersionString() { + final String lastChangeRevision = "$LastChangedRevision$"; + return lastChangeRevision; + } + + @Override + public String getServiceName() { + return PropagationClient.SERVICE_NAME; + } + + @Override + public Class getCommonPartClass() { + return PropagationsCommon.class; + } +} + + + + + + diff --git a/services/propagation/service/src/main/java/org/collectionspace/services/propagation/nuxeo/PropagationConstants.java b/services/propagation/service/src/main/java/org/collectionspace/services/propagation/nuxeo/PropagationConstants.java new file mode 100644 index 000000000..03a8640c4 --- /dev/null +++ b/services/propagation/service/src/main/java/org/collectionspace/services/propagation/nuxeo/PropagationConstants.java @@ -0,0 +1,35 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + + * http://www.collectionspace.org + * http://wiki.collectionspace.org + + * Copyright 2009 University of California at Berkeley + + * 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 + + * 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.collectionspace.services.propagation.nuxeo; + +/** + * PropagationConstants specifies constants for the Loans In service + * + */ +public class PropagationConstants { + + public final static String NUXEO_DOCTYPE = "Propagation"; + public final static String NUXEO_SCHEMA_NAME = "propagation"; + public final static String NUXEO_DC_TITLE = "CollectionSpace-Propagation"; +} diff --git a/services/propagation/service/src/main/java/org/collectionspace/services/propagation/nuxeo/PropagationDocumentModelHandler.java b/services/propagation/service/src/main/java/org/collectionspace/services/propagation/nuxeo/PropagationDocumentModelHandler.java new file mode 100644 index 000000000..89597cb9c --- /dev/null +++ b/services/propagation/service/src/main/java/org/collectionspace/services/propagation/nuxeo/PropagationDocumentModelHandler.java @@ -0,0 +1,36 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + + * http://www.collectionspace.org + * http://wiki.collectionspace.org + + * Copyright 2009 University of California at Berkeley + + * 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 + + * 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.collectionspace.services.propagation.nuxeo; + +import org.collectionspace.services.propagation.PropagationsCommon; +import org.collectionspace.services.nuxeo.client.java.NuxeoDocumentModelHandler; + +/** PropagationDocumentModelHandler + * $LastChangedRevision$ + * $LastChangedDate$ + */ +public class PropagationDocumentModelHandler + extends NuxeoDocumentModelHandler { +} + diff --git a/services/propagation/service/src/main/java/org/collectionspace/services/propagation/nuxeo/PropagationValidatorHandler.java b/services/propagation/service/src/main/java/org/collectionspace/services/propagation/nuxeo/PropagationValidatorHandler.java new file mode 100644 index 000000000..cba0bdec3 --- /dev/null +++ b/services/propagation/service/src/main/java/org/collectionspace/services/propagation/nuxeo/PropagationValidatorHandler.java @@ -0,0 +1,18 @@ +package org.collectionspace.services.propagation.nuxeo; + +import org.collectionspace.services.common.context.ServiceContext; +import org.collectionspace.services.common.document.InvalidDocumentException; +import org.collectionspace.services.common.document.ValidatorHandler; +import org.collectionspace.services.common.document.DocumentHandler.Action; + +public class PropagationValidatorHandler implements ValidatorHandler { + + @Override + public void validate(Action action, ServiceContext ctx) + throws InvalidDocumentException { + // TODO Auto-generated method stub + System.out.println("PropagationValidatorHandler executed."); + + } + +} diff --git a/services/propagation/service/src/test/java/org/collectionspace/services/test/PropagationServiceTest.java b/services/propagation/service/src/test/java/org/collectionspace/services/test/PropagationServiceTest.java new file mode 100644 index 000000000..b1340c3fd --- /dev/null +++ b/services/propagation/service/src/test/java/org/collectionspace/services/test/PropagationServiceTest.java @@ -0,0 +1,13 @@ +package org.collectionspace.services.test; + +//import org.collectionspace.services.propagation.Propagation; +//import org.collectionspace.services.propagation.PropagationList; + +/** + * Placeholder for server-side testing of Loan In service code. + * + * @version $Revision$ + */ +public class PropagationServiceTest { + //empty +} diff --git a/services/propagation/service/src/test/resources/log4j.xml b/services/propagation/service/src/test/resources/log4j.xml new file mode 100644 index 000000000..52121cb83 --- /dev/null +++ b/services/propagation/service/src/test/resources/log4j.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/report/service/src/main/java/org/collectionspace/services/report/nuxeo/ReportDocumentModelHandler.java b/services/report/service/src/main/java/org/collectionspace/services/report/nuxeo/ReportDocumentModelHandler.java index f320340d9..ae572bc94 100644 --- a/services/report/service/src/main/java/org/collectionspace/services/report/nuxeo/ReportDocumentModelHandler.java +++ b/services/report/service/src/main/java/org/collectionspace/services/report/nuxeo/ReportDocumentModelHandler.java @@ -23,12 +23,12 @@ */ package org.collectionspace.services.report.nuxeo; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.InputStream; +import java.nio.file.Files; import java.sql.Connection; import java.sql.SQLException; import java.util.HashMap; @@ -271,9 +271,6 @@ public class ReportDocumentModelHandler extends NuxeoDocumentModelHandlerorg.collectionspace.services.authorization-mgt.client ${project.version} + + org.collectionspace.services + org.collectionspace.services.authentication.jaxb + ${project.version} + org.collectionspace.services org.collectionspace.services.collectionobject.client diff --git a/services/security/client/src/test/resources/META-INF/persistence.xml b/services/security/client/src/test/resources/META-INF/persistence.xml index 61ab30d52..9577e7319 100644 --- a/services/security/client/src/test/resources/META-INF/persistence.xml +++ b/services/security/client/src/test/resources/META-INF/persistence.xml @@ -3,8 +3,7 @@ http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:orm="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> org.collectionspace.services.authentication.User - org.collectionspace.services.authentication.Role - org.collectionspace.services.authentication.UserRole + org.collectionspace.services.authorization.Role diff --git a/services/structureddate/README.txt b/services/structureddate/README.txt new file mode 100644 index 000000000..2e070590e --- /dev/null +++ b/services/structureddate/README.txt @@ -0,0 +1,7 @@ +This project contains classes that model CollectionSpace structured dates, and a parser to create structured dates from a display date string. + +Maven 3 is required to build. ANTLR 4 is used for parser generation. + +The ANTLR 4 grammar is located in src/main/antlr4. During the build, source code is generated into target/generated-sources/antlr4. Be sure to add this as a source folder in your IDE, so that it will be able to find the generated classes. + +Parser tests are located in src/test/resources/test-dates.yaml. The comments in that file describe how to add tests. \ No newline at end of file diff --git a/services/structureddate/pom.xml b/services/structureddate/pom.xml new file mode 100644 index 000000000..29c853e3b --- /dev/null +++ b/services/structureddate/pom.xml @@ -0,0 +1,121 @@ + + + + org.collectionspace.services + org.collectionspace.services.main + 4.5-SNAPSHOT + + + 4.0.0 + org.collectionspace.services.structureddate + services.structureddate + jar + + + + org.slf4j + slf4j-api + provided + + + org.slf4j + slf4j-log4j12 + provided + + + org.testng + testng + provided + + + org.yaml + snakeyaml + 1.6 + test + + + commons-beanutils + commons-beanutils-core + 1.8.3 + test + + + commons-lang + commons-lang + 2.6 + + + joda-time + joda-time + 2.3 + + + org.antlr + antlr4-runtime + 4.1 + + + org.antlr + antlr4-maven-plugin + 4.6 + + + org.sonatype.m2e.extras + org.sonatype.m2e.antlr + 0.13.0.20110622-1538 + + + + + + + org.antlr + antlr4-maven-plugin + 4.1 + + + + antlr4 + + + + + + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.antlr + + antlr4-maven-plugin + + + [4.1,) + + + antlr4 + + + + + org.sonatype.m2e.antlr.antlrConfigurator + + + + + + + + + + + diff --git a/services/structureddate/src/main/antlr4/org/collectionspace/services/structureddate/antlr/StructuredDate.g4 b/services/structureddate/src/main/antlr4/org/collectionspace/services/structureddate/antlr/StructuredDate.g4 new file mode 100644 index 000000000..430d6bea6 --- /dev/null +++ b/services/structureddate/src/main/antlr4/org/collectionspace/services/structureddate/antlr/StructuredDate.g4 @@ -0,0 +1,164 @@ +grammar StructuredDate; + +/* + * This is a grammar for ANTLR 4 (http://www.antlr.org/). + * + */ + +/* + * Parser rules + */ + +oneDisplayDate: displayDate ( DOT | QUESTION )? EOF ; + +displayDate: uncertainDate +| certainDate +/* TODO: Need to decide what "before" and "after" actually mean +| beforeOrAfterDate +*/ +; + +uncertainDate: CIRCA certainDate ; + +certainDate: hyphenatedRange +| singleInterval +; + +beforeOrAfterDate: ( BEFORE | AFTER ) singleInterval ; + +hyphenatedRange: singleInterval ( HYPHEN | DASH ) singleInterval +| nthCenturyRange +| monthInYearRange +| quarterInYearRange +| strDayInMonthRange +| numDayInMonthRange +; + +singleInterval: yearSpanningWinter +| partialYear +| quarterYear +| halfYear +| millennium +| partialCentury +| quarterCentury +| halfCentury +| century +| partialDecade +| decade +| year +| month +| date +; + +quarterInYearRange: nthQuarterInYearRange +| strSeasonInYearRange +; + +date: numDate +| strDate +| invStrDate +; + +month: monthYear +| invMonthYear +; + +yearSpanningWinter: WINTER COMMA? numYear SLASH numYear era ; + +partialYear: partOf numYear era ; + +quarterYear: seasonYear +| invSeasonYear +| nthQuarterYear +; + +halfYear: nthHalf numYear era ; + +year: numYear era ; + +partialDecade: partOf numDecade era ; + +decade: numDecade era ; + +partialCentury: partOf ( strCentury | numCentury ) era ; + +quarterCentury: nthQuarter ( strCentury | numCentury ) era ; + +halfCentury: nthHalf ( strCentury | numCentury ) era ; + +century: ( strCentury | numCentury ) era ; + +millennium: nth MILLENNIUM era ; + +strDate: strMonth ( numDayOfMonth | nth ) COMMA? numYear era; +invStrDate: era numYear COMMA? strMonth numDayOfMonth ; +strDayInMonthRange: strMonth numDayOfMonth ( HYPHEN | DASH ) numDayOfMonth COMMA? numYear era ; +monthInYearRange: strMonth ( HYPHEN | DASH ) strMonth COMMA? numYear era ; +nthQuarterInYearRange: nthQuarter ( HYPHEN | DASH ) nthQuarter COMMA? numYear era ; +strSeasonInYearRange: strSeason ( HYPHEN | DASH ) strSeason COMMA? numYear era ; +numDayInMonthRange: numMonth SLASH numDayOfMonth ( HYPHEN | DASH ) numDayOfMonth SLASH numYear era ; +numDate: num SLASH num SLASH num era +| num HYPHEN num HYPHEN num era ; +monthYear: strMonth COMMA? numYear era ; +invMonthYear: era numYear COMMA? strMonth ; +seasonYear: strSeason COMMA? numYear era ; +invSeasonYear: era numYear COMMA? strSeason ; +nthQuarterYear: nthQuarter numYear era ; +nthQuarter: ( nth | LAST ) QUARTER ; +nthHalf: ( nth | LAST ) HALF ; +numDecade: TENS ; +strCentury: nth CENTURY ; +numCentury: HUNDREDS ; +nthCenturyRange: allOrPartOf nth ( HYPHEN | DASH ) allOrPartOf nth CENTURY era ; +strSeason: SPRING | SUMMER | FALL | WINTER ; +allOrPartOf: partOf | ; +partOf: EARLY | MIDDLE | LATE ; +nth: NTHSTR | FIRST | SECOND | THIRD | FOURTH ; +strMonth: MONTH | SHORTMONTH DOT? ; +era: BC | AD | ; +numYear: NUMBER ; +numMonth: NUMBER ; +numDayOfMonth: NUMBER ; +num: NUMBER ; + + +/* + * Lexer rules + */ + +WS: [ \t\r\n]+ -> skip; +CIRCA: ('c' | 'ca') DOT? | 'circa' ; +SPRING: 'spring' | 'spr' ; +SUMMER: 'summer' | 'sum' ; +WINTER: 'winter' | 'win' ; +FALL: 'fall' | 'autumn' | 'fal' | 'aut' ; +EARLY: 'early' ; +MIDDLE: 'middle' | 'mid' ( HYPHEN | DOT )?; +LATE: 'late' ; +BEFORE: 'before' | 'pre' HYPHEN? ; +AFTER: 'after' | 'post' HYPHEN? ; +FIRST: 'first' ; +SECOND: 'second' ; +THIRD: 'third' ; +FOURTH: 'fourth' ; +LAST: 'last' ; +QUARTER: 'quarter' ; +HALF: 'half' ; +CENTURY: 'century' ; +MILLENNIUM: 'millennium' ; +MONTH: 'january' | 'february' | 'march' | 'april' | 'may' | 'june' | 'july' | 'august' | 'september' | 'october' | 'november' | 'december' ; +SHORTMONTH: 'jan' | 'feb' | 'mar' | 'apr' | 'jun' | 'jul' | 'aug' | 'sep' | 'sept' | 'oct' | 'nov' | 'dec' ; +BC: 'bc' | 'bce' | 'b.c.' | 'b.c.e.' ; +AD: 'ad' | 'a.d.' | 'ce' | 'c.e.'; +NTHSTR: [0-9]*? ([0456789] 'th' | '1st' | '2nd' | '3rd' | '11th' | '12th' | '13th') ; +HUNDREDS: [0-9]*? '00' '\''? 's'; +TENS: [0-9]*? '0' '\''? 's'; +NUMBER: [0-9]+ ; +COMMA: ',' ; +HYPHEN: '-' ; +DASH: [—–] ; /* EM DASH, EN DASH */ +SLASH: '/' ; +DOT: '.' ; +QUESTION: '?' ; +STRING: [a-z]+ ; +OTHER: . ; diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/Certainty.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/Certainty.java new file mode 100644 index 000000000..9a079bdc3 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/Certainty.java @@ -0,0 +1,20 @@ +package org.collectionspace.services.structureddate; + +public enum Certainty { + AFTER ("urn:cspace:botgarden.cspace.berkeley.edu:vocabularies:name(datecertainty):item:name(after)'After'"), + APPROXIMATELY ("urn:cspace:botgarden.cspace.berkeley.edu:vocabularies:name(datecertainty):item:name(approximate)'Approximate'"), + BEFORE ("urn:cspace:botgarden.cspace.berkeley.edu:vocabularies:name(datecertainty):item:name(before)'Before'"), + CIRCA ("urn:cspace:botgarden.cspace.berkeley.edu:vocabularies:name(datecertainty):item:name(circa)'Circa'"), + POSSIBLY ("urn:cspace:botgarden.cspace.berkeley.edu:vocabularies:name(datecertainty):item:name(possibly)'Possibly'"), + PROBABLY ("urn:cspace:botgarden.cspace.berkeley.edu:vocabularies:name(datecertainty):item:name(probably)'Probably'"); + + private final String value; + + private Certainty(String value) { + this.value = value; + } + + public String toString() { + return value; + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/Date.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/Date.java new file mode 100644 index 000000000..f83e153eb --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/Date.java @@ -0,0 +1,181 @@ +package org.collectionspace.services.structureddate; + +import org.apache.commons.lang.builder.EqualsBuilder; + +public class Date { + public static final Era DEFAULT_ERA = Era.CE; + + private Integer year; + private Integer month; + private Integer day; + private Era era; + private Certainty certainty; + private QualifierType qualifierType; + private Integer qualifierValue; + private QualifierUnit qualifierUnit; + + public Date() { + this(null, null, null, null, null, null, null, null); + } + + public Date(Integer year, Integer month, Integer day) { + this(year, month, day, null, null, null, null, null); + } + + public Date(Integer year, Integer month, Integer day, Era era) { + this(year, month, day, era, null, null, null, null); + } + + public Date(Integer year, Integer month, Integer day, Era era, Certainty certainty, QualifierType qualifierType, Integer qualifierValue, QualifierUnit qualifierUnit) { + this.year = year; + this.month = month; + this.day = day; + this.era = era; + this.certainty = certainty; + this.qualifierType = qualifierType; + this.qualifierValue = qualifierValue; + this.qualifierUnit = qualifierUnit; + } + + public Date withEra(Era era) { + setEra(era); + + return this; + } + + public Date withQualifier(QualifierType qualifierType, Integer qualifierValue, QualifierUnit qualifierUnit) { + this.setQualifier(qualifierType, qualifierValue, qualifierUnit); + + return this; + } + + public String toString() { + return + "\t\tyear: " + getYear() + "\n" + + "\t\tmonth: " + getMonth() + "\n" + + "\t\tday: " + getDay() + "\n" + + "\t\tera: " + getEra() + "\n" + + "\t\tcertainty: " + getCertainty() + "\n" + + "\t\tqualifierType: " + getQualifierType() + "\n" + + "\t\tqualifierValue: " + getQualifierValue() + "\n" + + "\t\tqualifierUnit: " + getQualifierUnit() + "\n"; + } + + public Date copy() { + Date newDate = new Date(); + + newDate.setYear(getYear()); + newDate.setMonth(getMonth()); + newDate.setDay(getDay()); + newDate.setEra(getEra()); + newDate.setCertainty(getCertainty()); + newDate.setQualifierType(getQualifierType()); + newDate.setQualifierValue(getQualifierValue()); + newDate.setQualifierUnit(getQualifierUnit()); + + return newDate; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + + if (obj == this) { + return true; + } + + // Consider all subclasses of Date to be equal to each other, as long + // as the Date fields are equal. + + if (!Date.class.isAssignableFrom(obj.getClass())) { + return false; + } + + Date that = (Date) obj; + + return + new EqualsBuilder() + .append(this.getYear(), that.getYear()) + .append(this.getMonth(), that.getMonth()) + .append(this.getDay(), that.getDay()) + .append(this.getEra(), that.getEra()) + .append(this.getCertainty(), that.getCertainty()) + .append(this.getQualifierType(), that.getQualifierType()) + .append(this.getQualifierValue(), that.getQualifierValue()) + .append(this.getQualifierUnit(), that.getQualifierUnit()) + .isEquals(); + } + + public Integer getYear() { + return year; + } + + public void setYear(Integer year) { + this.year = year; + } + + public Integer getMonth() { + return month; + } + + public void setMonth(Integer month) { + this.month = month; + } + + public Integer getDay() { + return day; + } + + public void setDay(Integer day) { + this.day = day; + } + + public Era getEra() { + return era; + } + + public void setEra(Era era) { + this.era = era; + } + + public Certainty getCertainty() { + return certainty; + } + + public void setCertainty(Certainty certainty) { + this.certainty = certainty; + } + + public QualifierType getQualifierType() { + return qualifierType; + } + + public void setQualifierType(QualifierType qualifierType) { + this.qualifierType = qualifierType; + } + + public Integer getQualifierValue() { + return qualifierValue; + } + + public void setQualifierValue(Integer qualifierValue) { + this.qualifierValue = qualifierValue; + } + + public QualifierUnit getQualifierUnit() { + return qualifierUnit; + } + + public void setQualifierUnit(QualifierUnit qualifierUnit) { + this.qualifierUnit = qualifierUnit; + } + + public void setQualifier(QualifierType qualifierType, Integer qualifierValue, QualifierUnit qualifierUnit) { + this.qualifierType = qualifierType; + this.qualifierValue = qualifierValue; + this.qualifierUnit = qualifierUnit; + } +} + diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DateUtils.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DateUtils.java new file mode 100644 index 000000000..4daeb359f --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DateUtils.java @@ -0,0 +1,1315 @@ +package org.collectionspace.services.structureddate; + +import org.joda.time.Chronology; +import org.joda.time.DateTime; +import org.joda.time.DateTimeConstants; +import org.joda.time.Days; +import org.joda.time.IllegalFieldValueException; +import org.joda.time.MutableDateTime; +import org.joda.time.Years; +import org.joda.time.chrono.GJChronology; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +public class DateUtils { + private static final DateTimeFormatter monthFormatter = DateTimeFormat.forPattern("MMMM"); + private static final DateTimeFormatter scalarDateFormatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"); + + // The chronology to use for date calculations, which are done using the joda-time library. + // See http://www.joda.org/joda-time/apidocs/org/joda/time/Chronology.html for descriptions of + // the chronologies supported by joda-time. + + // GJChronology (http://www.joda.org/joda-time/apidocs/org/joda/time/chrono/GJChronology.html) + // seems best for representing a mix of modern and historical dates, as might be seen by an + // anthropology museum. + + private static final Chronology chronology = GJChronology.getInstance(); + + // Define the DateTime that serves as the basis for circa calculations, using the algorithm + // ported from the XDB date parser. Its comment states: + // + // We define circa year/century specifications offsets + // as +/- 5% of the difference between that year/century + // and the present (2100), so that the farther we go back + // in time, the wider the range of meaning of "circa." + + private static final DateTime circaBaseDateTime = new DateTime(2100, 12, 31, 0, 0, 0, 0, chronology); + + /** + * Gets the number (1-12) of a month for a given name. + * + * @param monthName The name of the month + * @return The number of the month, between 1 and 12 + */ + public static int getMonthByName(String monthName) { + // Normalize "sept" to "sep", since DateTimeFormat doesn't + // understand the former. + + if (monthName.equals("sept")) { + monthName = "sep"; + } + + return monthFormatter.parseDateTime(monthName).getMonthOfYear(); + } + + /** + * Gets the number of days in a given month. + * + * @param month The month number, between 1 and 12 + * @param year The year (in order to account for leap years) + * @return The number of days in the month + */ + public static int getDaysInMonth(int month, int year, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + DateTime dateTime = new DateTime(chronology) + .withEra((era == Era.BCE) ? DateTimeConstants.BC : DateTimeConstants.AD) + .withYearOfEra(year) + .withMonthOfYear(month); + + return dateTime.dayOfMonth().getMaximumValue(); + } + + /** + * Gets the Date representing the first day of a given quarter year. + * + * @param year The year + * @param quarter The quarter, between 1 and 4 + * @return The first day of the quarter year + */ + public static Date getQuarterYearStartDate(int quarter, int year) { + int startMonth = getQuarterYearStartMonth(quarter); + + return new Date(year, startMonth, 1); + } + + /** + * Gets the Date representing the last day of a given quarter year. + * + * @param year The year + * @param quarter The quarter, between 1 and 4 + * @return The last day of the quarter year + */ + public static Date getQuarterYearEndDate(int quarter, int year, Era era) { + int endMonth = getQuarterYearEndMonth(quarter); + + return new Date(year, endMonth, DateUtils.getDaysInMonth(endMonth, year, era)); + } + + /** + * Gets the first month of a given quarter in a year. + * + * @param quarter The quarter, between 1 and 4 + * @return The number of the first month in the quarter + */ + public static int getQuarterYearStartMonth(int quarter) { + return ((3 * (quarter-1)) + 1); + } + + /** + * Gets the last month of a given quarter in a year. + * + * @param quarter The quarter, between 1 and 4 + * @return The number of the last month in the quarter + */ + public static int getQuarterYearEndMonth(int quarter) { + return (getQuarterYearStartMonth(quarter) + 2); + } + + /** + * Gets the Date representing the first day of a given half year. + * + * @param year The year + * @param half The half, between 1 and 2 + * @return The first day of the half year + */ + public static Date getHalfYearStartDate(int half, int year) { + int startMonth = getHalfYearStartMonth(half); + + return new Date(year, startMonth, 1); + } + + + /** + * Gets the Date representing the last day of a given half year. + * + * @param year The year + * @param half The half, between 1 and 2 + * @return The last day of the half year + */ + public static Date getHalfYearEndDate(int half, int year, Era era) { + int endMonth = getHalfYearEndMonth(half); + + return new Date(year, endMonth, DateUtils.getDaysInMonth(endMonth, year, era)); + } + + /** + * Gets the first month of a given half in a year. + * + * @param half The half, between 1 and 2 + * @return The number of the first month in the half + */ + public static int getHalfYearStartMonth(int half) { + return ((6 * (half-1)) + 1); + } + + /** + * Gets the last month of a given half in a year. + * + * @param half The half, between 1 and 2 + * @return The number of the last month in the half + */ + public static int getHalfYearEndMonth(int half) { + return (getHalfYearStartMonth(half) + 5); + } + + /** + * Gets the Date representing the first day of a given partial year. + * + * @param year The year + * @param part The part + * @return The first day of the partial year + */ + public static Date getPartialYearStartDate(Part part, int year) { + int startMonth = getPartialYearStartMonth(part); + + return new Date(year, startMonth, 1); + } + + /** + * Gets the Date representing the last day of a given partial year. + * + * @param year The year + * @param part The part + * @return The last day of the partial year + */ + public static Date getPartialYearEndDate(Part part, int year, Era era) { + int endMonth = getPartialYearEndMonth(part); + + return new Date(year, endMonth, DateUtils.getDaysInMonth(endMonth, year, era)); + } + + /** + * Gets the first month of a given part of a year. + * + * @param part The part + * @return The number of the first month in the part + */ + public static int getPartialYearStartMonth(Part part) { + int month; + + if (part == Part.EARLY) { + month = 1; + } + else if (part == Part.MIDDLE) { + month = 5; + } + else if (part == Part.LATE) { + month = 9; + } + else { + throw new IllegalArgumentException("unexpected part"); + } + + return month; + } + + /** + * Gets the last month of a given part of a year. + * + * @param part The part + * @return The number of the last month in the part + */ + public static int getPartialYearEndMonth(Part part) { + int month; + + if (part == Part.EARLY) { + month = 4; + } + else if (part == Part.MIDDLE) { + month = 8; + } + else if (part == Part.LATE) { + month = 12; + } + else { + throw new IllegalArgumentException("unexpected part"); + } + + return month; + } + + /** + * Gets the Date representing the first day of a given partial decade. + * + * @param decade The decade, specified as a number ending in 0. + * For decades A.D., this is the first year of the decade. For + * decades B.C., this is the last year of the decade. + * @param part The part + * @param era The era of the decade. If null, Date.DEFAULT_ERA is assumed. + * @return The first day of the partial decade + */ + public static Date getPartialDecadeStartDate(int decade, Part part, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int startYear = getPartialDecadeStartYear(decade, part, era); + + return new Date(startYear, 1, 1, era); + } + + /** + * Gets the Date representing the last day of a given partial decade. + * + * @param decade The decade, specified as a number ending in 0. + * For decades A.D., this is the first year of the decade. For + * decades B.C., this is the last year of the decade. + * @param part The part + * @param era The era of the decade. If null, Date.DEFAULT_ERA is assumed. + * @return The last day of the partial decade + */ + public static Date getPartialDecadeEndDate(int decade, Part part, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int endYear = getPartialDecadeEndYear(decade, part, era); + + return new Date(endYear, 12, 31, era); + } + + /** + * Gets the first year of a given part of a decade. + * + * @param decade The decade, specified as a number ending in 0. + * For decades A.D., this is the first year of the decade. For + * decades B.C., this is the last year of the decade. + * @param part The part + * @param era The era of the decade. If null, Date.DEFAULT_ERA is assumed. + * @return The first year in the part + */ + public static int getPartialDecadeStartYear(int decade, Part part, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int startYear; + + if (era == Era.BCE) { + if (part == Part.EARLY) { + startYear = decade + 9; + } + else if (part == Part.MIDDLE) { + startYear = decade + 6; + } + else if (part == Part.LATE) { + startYear = decade + 3; + } + else { + throw new IllegalArgumentException("unexpected part"); + } + } + else { + if (part == Part.EARLY) { + startYear = decade; + } + else if (part == Part.MIDDLE) { + startYear = decade + 4; + } + else if (part == Part.LATE) { + startYear = decade + 7; + } + else { + throw new IllegalArgumentException("unexpected part"); + } + } + + return startYear; + } + + /** + * Gets the last year of a given part of a decade. + * + * @param decade The decade, specified as a number ending in 0. + * For decades A.D., this is the first year of the decade. For + * decades B.C., this is the last year of the decade. + * @param part The part + * @param era The era of the decade. If null, Date.DEFAULT_ERA is assumed. + * @return The last year in the part + */ + public static int getPartialDecadeEndYear(int decade, Part part, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int endYear; + + if (era == Era.BCE) { + if (part == Part.EARLY) { + endYear = decade + 7; + } + else if (part == Part.MIDDLE) { + endYear = decade + 4; + } + else if (part == Part.LATE) { + endYear = decade; + } + else { + throw new IllegalArgumentException("unexpected part"); + } + } + else { + if (part == Part.EARLY) { + endYear = decade + 3; + } + else if (part == Part.MIDDLE) { + endYear = decade + 6; + } + else if (part == Part.LATE) { + endYear = decade + 9; + } + else { + throw new IllegalArgumentException("unexpected part"); + } + } + + return endYear; + } + + /** + * Gets the Date representing the first day of a given decade. + * + * @param decade The decade, specified as a number ending in 0. + * For decades A.D., this is the first year of the decade. For + * decades B.C., this is the last year of the decade. + * @param era The era of the decade. If null, Date.DEFAULT_ERA is assumed. + * @return The first day of the decade + */ + public static Date getDecadeStartDate(int decade, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int startYear = getDecadeStartYear(decade, era); + + return new Date(startYear, 1, 1, era); + } + + /** + * Gets the Date representing the last day of a given decade. + * + * @param decade The decade, specified as a number ending in 0. + * For decades A.D., this is the first year of the decade. For + * decades B.C., this is the last year of the decade. + * @param era The era of the decade. If null, Date.DEFAULT_ERA is assumed. + * @return The last day of the decade + */ + public static Date getDecadeEndDate(int decade, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int endYear = getDecadeEndYear(decade, era); + + return new Date(endYear, 12, 31, era); + } + + /** + * Gets the first year of a given decade. + * + * @param decade The decade, specified as a number ending in 0. + * For decades A.D., this is the first year of the decade. For + * decades B.C., this is the last year of the decade. + * @param era The era of the decade. If null, Date.DEFAULT_ERA is assumed. + * @return The first year of the decade + */ + public static int getDecadeStartYear(int decade, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int startYear; + + if (era == Era.BCE) { + startYear = decade + 9; + } + else { + startYear = decade; + } + + return startYear; + } + + /** + * Gets the last year of a given decade. + * + * @param decade The decade, specified as a number ending in 0. + * For decades A.D., this is the first year of the decade. For + * decades B.C., this is the last year of the decade. + * @param era The era of the decade. If null, Date.DEFAULT_ERA is assumed. + * @return The last year of the decade + */ + public static int getDecadeEndYear(int decade, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int endYear; + + if (era == Era.BCE) { + endYear = decade; + } + else { + endYear = decade + 9; + } + + return endYear; + } + + /** + * Gets the Date representing the first day of a given century. + * + * @param century The century, specified as a number ending in 00 or 01. + * For centuries A.D., this is the first year of the century. For + * centuries B.C., this is the last year of the century. For example, + * the "21st century" would be specified as 2001, whereas the "2000's" + * would be specified as 2000. The "2nd century B.C." would be specified + * as 101. The "100's B.C." would be specified as 100. + * @param era The era of the century. If null, Date.DEFAULT_ERA is assumed. + * @return The first day of the century + */ + public static Date getCenturyStartDate(int century, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int startYear = getCenturyStartYear(century, era); + + return new Date(startYear, 1, 1, era); + } + + /** + * Gets the Date representing the last day of a given century. + * + * @param century The century, specified as a number ending in 00 or 01. + * For centuries A.D., this is the first year of the century. For + * centuries B.C., this is the last year of the century. For example, + * the "21st century" would be specified as 2001, whereas the "2000's" + * would be specified as 2000. The "2nd century B.C." would be specified + * as 101. The "100's B.C." would be specified as 100. + * @param era The era of the century. If null, Date.DEFAULT_ERA is assumed. + * @return The last day of the century + */ + public static Date getCenturyEndDate(int century, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int endYear = getCenturyEndYear(century, era); + + return new Date(endYear, 12, 31, era); + } + + /** + * Gets the first year of a given century. + * + * @param century The century, specified as a number ending in 00 or 01. + * For centuries A.D., this is the first year of the century. For + * centuries B.C., this is the last year of the century. For example, + * the "21st century" would be specified as 2001, whereas the "2000's" + * would be specified as 2000. The "2nd century B.C." would be specified + * as 101. The "100's B.C." would be specified as 100. + * @param era The era of the century. If null, Date.DEFAULT_ERA is assumed. + * @return The first year of the century + */ + public static int getCenturyStartYear(int century, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int startYear; + + if (era == Era.BCE) { + startYear = century + 99; + } + else { + startYear = century; + } + + return startYear; + } + + /** + * Gets the last year of a given century. + * + * @param century The century, specified as a number ending in 00 or 01. + * For centuries A.D., this is the first year of the century. For + * centuries B.C., this is the last year of the century. For example, + * the "21st century" would be specified as 2001, whereas the "2000's" + * would be specified as 2000. The "2nd century B.C." would be specified + * as 101. The "100's B.C." would be specified as 100. + * @param era The era of the century. If null, Date.DEFAULT_ERA is assumed. + * @return The last year of the century + */ + public static int getCenturyEndYear(int century, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int endYear; + + if (era == Era.BCE) { + endYear = century; + } + else { + endYear = century + 99; + } + + return endYear; + } + + /** + * Gets the Date representing the first day of a given partial century. + * + * @param century The century, specified as a number ending in 00 or 01. + * For centuries A.D., this is the first year of the century. For + * centuries B.C., this is the last year of the century. For example, + * the "21st century" would be specified as 2001, whereas the "2000's" + * would be specified as 2000. The "2nd century B.C." would be specified + * as 101. The "100's B.C." would be specified as 100. + * @param part The part + * @param era The era of the century. If null, Date.DEFAULT_ERA is assumed. + * @return The first day of the partial century + */ + public static Date getPartialCenturyStartDate(int century, Part part, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int startYear = getPartialCenturyStartYear(century, part, era); + + return new Date(startYear, 1, 1, era); + } + + /** + * Gets the Date representing the last day of a given partial century. + * + * @param century The century, specified as a number ending in 00 or 01. + * For centuries A.D., this is the first year of the century. For + * centuries B.C., this is the last year of the century. For example, + * the "21st century" would be specified as 2001, whereas the "2000's" + * would be specified as 2000. The "2nd century B.C." would be specified + * as 101. The "100's B.C." would be specified as 100. + * @param part The part + * @param era The era of the century. If null, Date.DEFAULT_ERA is assumed. + * @return The last day of the partial century + */ + public static Date getPartialCenturyEndDate(int century, Part part, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int endYear = getPartialCenturyEndYear(century, part, era); + + return new Date(endYear, 12, 31, era); + } + + /** + * Gets the first year of a given partial century. + * + * @param century The century, specified as a number ending in 00 or 01. + * For centuries A.D., this is the first year of the century. For + * centuries B.C., this is the last year of the century. For example, + * the "21st century" would be specified as 2001, whereas the "2000's" + * would be specified as 2000. The "2nd century B.C." would be specified + * as 101. The "100's B.C." would be specified as 100. + * @param part The part + * @param era The era of the century. If null, Date.DEFAULT_ERA is assumed. + * @return The first year of the partial century + */ + public static int getPartialCenturyStartYear(int century, Part part, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int startYear; + + if (era == Era.BCE) { + if (part == Part.EARLY) { + startYear = century + 99; + } + else if (part == Part.MIDDLE) { + startYear = century + 66; + } + else if (part == Part.LATE) { + startYear = century + 33; + } + else { + throw new IllegalArgumentException("unexpected part"); + } + } + else { + if (part == Part.EARLY) { + startYear = century; + } + else if (part == Part.MIDDLE) { + startYear = century + 33; + } + else if (part == Part.LATE) { + startYear = century + 66; + } + else { + throw new IllegalArgumentException("unexpected part"); + } + } + + return startYear; + } + + /** + * Gets the last year of a given partial century. + * + * @param century The century, specified as a number ending in 00 or 01. + * For centuries A.D., this is the first year of the century. For + * centuries B.C., this is the last year of the century. For example, + * the "21st century" would be specified as 2001, whereas the "2000's" + * would be specified as 2000. The "2nd century B.C." would be specified + * as 101. The "100's B.C." would be specified as 100. + * @param part The part + * @param era The era of the century. If null, Date.DEFAULT_ERA is assumed. + * @return The last year of the partial century + */ + public static int getPartialCenturyEndYear(int century, Part part, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int endYear; + + if (era == Era.BCE) { + if (part == Part.EARLY) { + endYear = century + 66; + } + else if (part == Part.MIDDLE) { + endYear = century + 33; + } + else if (part == Part.LATE) { + endYear = century; + } + else { + throw new IllegalArgumentException("unexpected part"); + } + } + else { + if (part == Part.EARLY) { + endYear = century + 33; + } + else if (part == Part.MIDDLE) { + endYear = century + 66; + } + else if (part == Part.LATE) { + endYear = century + 99; + } + else { + throw new IllegalArgumentException("unexpected part"); + } + } + + return endYear; + } + + /** + * Gets the Date representing the first day of a given half century. + * + * @param century The century, specified as a number ending in 00 or 01. + * For centuries A.D., this is the first year of the century. For + * centuries B.C., this is the last year of the century. For example, + * the "21st century" would be specified as 2001, whereas the "2000's" + * would be specified as 2000. The "2nd century B.C." would be specified + * as 101. The "100's B.C." would be specified as 100. + * @param half The half + * @param era The era of the century. If null, Date.DEFAULT_ERA is assumed. + * @return The first day of the half century + */ + public static Date getHalfCenturyStartDate(int century, int half, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int startYear = getHalfCenturyStartYear(century, half, era); + + return new Date(startYear, 1, 1, era); + } + + /** + * Gets the Date representing the last day of a given half century. + * + * @param century The century, specified as a number ending in 00 or 01. + * For centuries A.D., this is the first year of the century. For + * centuries B.C., this is the last year of the century. For example, + * the "21st century" would be specified as 2001, whereas the "2000's" + * would be specified as 2000. The "2nd century B.C." would be specified + * as 101. The "100's B.C." would be specified as 100. + * @param half The half + * @param era The era of the century. If null, Date.DEFAULT_ERA is assumed. + * @return The last day of the half century + */ + public static Date getHalfCenturyEndDate(int century, int half, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int endYear = getHalfCenturyEndYear(century, half, era); + + return new Date(endYear, 12, 31, era); + } + + /** + * Gets the first year of a given half century. + * + * @param century The century, specified as a number ending in 00 or 01. + * For centuries A.D., this is the first year of the century. For + * centuries B.C., this is the last year of the century. For example, + * the "21st century" would be specified as 2001, whereas the "2000's" + * would be specified as 2000. The "2nd century B.C." would be specified + * as 101. The "100's B.C." would be specified as 100. + * @param half The half + * @param era The era of the century. If null, Date.DEFAULT_ERA is assumed. + * @return The first year of the half century + */ + public static int getHalfCenturyStartYear(int century, int half, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int startYear; + + if (era == Era.BCE) { + startYear = (century + 99) - (50 * (half - 1)); + } + else { + startYear = century + (50 * (half - 1)); + } + + return startYear; + } + + /** + * Gets the last year of a given half century. + * + * @param century The century, specified as a number ending in 00 or 01. + * For centuries A.D., this is the first year of the century. For + * centuries B.C., this is the last year of the century. For example, + * the "21st century" would be specified as 2001, whereas the "2000's" + * would be specified as 2000. The "2nd century B.C." would be specified + * as 101. The "100's B.C." would be specified as 100. + * @param half The half + * @param era The era of the century. If null, Date.DEFAULT_ERA is assumed. + * @return The last year of the half century + */ + public static int getHalfCenturyEndYear(int century, int half, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int endYear; + + if (era == Era.BCE) { + endYear = (century + 99) - (50 * half) + 1; + } + else { + endYear = century + (50 * half) - 1; + } + + return endYear; + } + + /** + * Gets the Date representing the first day of a given quarter century. + * + * @param century The century, specified as a number ending in 00 or 01. + * For centuries A.D., this is the first year of the century. For + * centuries B.C., this is the last year of the century. For example, + * the "21st century" would be specified as 2001, whereas the "2000's" + * would be specified as 2000. The "2nd century B.C." would be specified + * as 101. The "100's B.C." would be specified as 100. + * @param quarter The quarter + * @param era The era of the century. If null, Date.DEFAULT_ERA is assumed. + * @return The first day of the quarter century + */ + public static Date getQuarterCenturyStartDate(int century, int quarter, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int startYear = getQuarterCenturyStartYear(century, quarter, era); + + return new Date(startYear, 1, 1, era); + } + + /** + * Gets the Date representing the last day of a given quarter century. + * + * @param century The century, specified as a number ending in 00 or 01. + * For centuries A.D., this is the first year of the century. For + * centuries B.C., this is the last year of the century. For example, + * the "21st century" would be specified as 2001, whereas the "2000's" + * would be specified as 2000. The "2nd century B.C." would be specified + * as 101. The "100's B.C." would be specified as 100. + * @param quarter The quarter + * @param era The era of the century. If null, Date.DEFAULT_ERA is assumed. + * @return The last day of the quarter century + */ + public static Date getQuarterCenturyEndDate(int century, int quarter, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int endYear = getQuarterCenturyEndYear(century, quarter, era); + + return new Date(endYear, 12, 31, era); + } + + /** + * Gets the first year of a given quarter century. + * + * @param century The century, specified as a number ending in 00 or 01. + * For centuries A.D., this is the first year of the century. For + * centuries B.C., this is the last year of the century. For example, + * the "21st century" would be specified as 2001, whereas the "2000's" + * would be specified as 2000. The "2nd century B.C." would be specified + * as 101. The "100's B.C." would be specified as 100. + * @param quarter The quarter + * @param era The era of the century. If null, Date.DEFAULT_ERA is assumed. + * @return The first year of the quarter century + */ + public static int getQuarterCenturyStartYear(int century, int quarter, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int startYear; + + if (era == Era.BCE) { + startYear = (century + 99) - (25 * (quarter - 1)); + } + else { + startYear = century + (25 * (quarter - 1)); + } + + return startYear; + } + + /** + * Gets the last year of a given quarter century. + * + * @param century The century, specified as a number ending in 00 or 01. + * For centuries A.D., this is the first year of the century. For + * centuries B.C., this is the last year of the century. For example, + * the "21st century" would be specified as 2001, whereas the "2000's" + * would be specified as 2000. The "2nd century B.C." would be specified + * as 101. The "100's B.C." would be specified as 100. + * @param quarter The quarter + * @param era The era of the century. If null, Date.DEFAULT_ERA is assumed. + * @return The last year of the quarter century + */ + public static int getQuarterCenturyEndYear(int century, int quarter, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int endYear; + + if (era == Era.BCE) { + endYear = (century + 99) - (25 * quarter) + 1; + } + else { + endYear = century + (25 * quarter) - 1; + } + + return endYear; + } + + /** + * Converts an nth century number to a year. For example, to convert "21st century" + * to a year, call nthCenturyToYear(21), which returns 2001. For centuries A.D., the + * year returned is the first year of the nth century. For centuries B.C., the + * year returned is the last year of the nth century. + * + * @param n The nth century number + * @return The first year in the nth century, for centuries A.D. + * The last year of the nth century, for centuries B.C. + */ + public static int nthCenturyToYear(int n) { + int year = (n-1) * 100 + 1; + + return year; + } + + /** + * Gets the Date representing the first day of a given millennium. + * + * @param n The nth millennium number + * @param era The era of the millennium. If null, Date.DEFAULT_ERA is assumed. + * @return The first day of the millennium + */ + public static Date getMillenniumStartDate(int n, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int startYear = getMillenniumStartYear(n, era); + + return new Date(startYear, 1, 1, era); + } + + /** + * Gets the Date representing the last day of a given millennium. + * + * @param n The nth millennium number + * @param era The era of the millennium. If null, Date.DEFAULT_ERA is assumed. + * @return The last day of the millennium + */ + public static Date getMillenniumEndDate(int n, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int endYear = getMillenniumEndYear(n, era); + + return new Date(endYear, 12, 31, era); + } + + /** + * Gets the first year of a given millennium. + * + * @param n The nth millennium number + * @param era The era of the millennium. If null, Date.DEFAULT_ERA is assumed. + * @return The first year of the millennium + */ + public static int getMillenniumStartYear(int n, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int year; + + if (era == Era.BCE) { + year = n * 1000; + } + else { + year = (n - 1) * 1000 + 1; + } + + return year; + } + + /** + * Gets the last year of a given millennium. + * + * @param n The nth millennium number + * @param era The era of the millennium. If null, Date.DEFAULT_ERA is assumed. + * @return The last year of the millennium + */ + public static int getMillenniumEndYear(int n, Era era) { + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int year; + + if (era == Era.BCE) { + year = (n - 1) * 1000 + 1; + } + else { + year = n * 1000; + } + + return year; + } + + /** + * Calculates the earliest date that may be considered to be "before" + * a given date. + * + * @param date The date + * @return The earliest date "before" the date + */ + public static Date getEarliestBeforeDate(Date date) { + return getEarliestBeforeDate(date, null); + } + + /** + * Calculates the latest date that may be considered to be "after" + * a given date. + * + * @param date The date + * @return The latest date "after" the date + */ + public static Date getLatestAfterDate(Date date) { + return getLatestAfterDate(date, null); + } + + /** + * Calculates the earliest date that may be considered to be "before" + * a given date range. + * + * @param startDate The first date in the range + * @param endDate The last date in the range + * @return The earliest date "before" the range + */ + public static Date getEarliestBeforeDate(Date startDate, Date endDate) { + // TODO + return null; + + /* + // This algorithm is inherited from the XDB fuzzydate parser, + // which considers "before" to mean "within a lifetime before". + + if (endDate == null) { + endDate = startDate; + } + + int difference = getYearsBetween(startDate, endDate); + + Date earliestDate = startDate.copy(); + subtractYears(earliestDate, 1); + earliestDate.setMonth(1); + earliestDate.setDay(1); + + if (difference < 100) { + // The comment from the XDB fuzzydate parser states: + // + // Before/after years are really about birth/death dates + // so we use average life-span of 75 years + + subtractYears(earliestDate, 75); + } + else { + // The comment from the XDB fuzzydate parser states: + // + // Before/after years are really about birth/death dates + // so we use average life-span of 75 years + // but since the spec was a century, e.g. pre 20th century + // we'll make the range a bit bigger + // sheesh... + + subtractYears(earliestDate, 175); + } + + return earliestDate; + */ + } + + /** + * Calculates the latest date that may be considered to be "after" + * a given date range. + * + * @param startDate The first date in the range + * @param endDate The last date in the range + * @return The latest date "after" the range + */ + public static Date getLatestAfterDate(Date startDate, Date endDate) { + // TODO + return null; + } + + public static int getYearsBetween(Date startDate, Date endDate) { + if (startDate == null || endDate == null) { + throw new InvalidDateException("date must not be null"); + } + + Integer startYear = startDate.getYear(); + Integer endYear = endDate.getYear(); + + if (startYear == null || endYear == null) { + throw new IllegalArgumentException("year must not be null"); + } + + Era startEra = startDate.getEra(); + Era endEra = endDate.getEra(); + + if (startEra == null || endEra == null) { + throw new IllegalArgumentException("era must not be null"); + } + + MutableDateTime startDateTime = convertToDateTime(startDate); + MutableDateTime endDateTime = convertToDateTime(endDate); + + int years = Years.yearsBetween(startDateTime, endDateTime).getYears(); + + return years; + } + + /** + * Calculates the interval, in years, that should be padded around a date so + * that any date within that interval may be considered to be "circa" the + * given date. + * + * @param year The year of the date + * @param era The era of the date. If null, Date.DEFAULT_ERA is assumed. + * @return The number of "circa" years before and after the date + */ + public static int getCircaIntervalYears(int year, Era era) { + /* + * This algorithm is inherited from the fuzzydate parser + * in XDB. Its comment states: + * + * We define circa year/century specifications offsets + * as +/- 5% of the difference between that year/century + * and the present (2100), so that the farther we go back + * in time, the wider the range of meaning of "circa." + * + */ + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + MutableDateTime dateTime = new MutableDateTime(chronology); + dateTime.era().set((era == Era.BCE) ? DateTimeConstants.BC : DateTimeConstants.AD); + dateTime.yearOfEra().set(year); + dateTime.monthOfYear().set(1); + dateTime.dayOfMonth().set(1); + dateTime.setTime(0, 0, 0, 0); + + int years = Years.yearsBetween(dateTime, circaBaseDateTime).getYears(); + + return ((int) Math.round(years * 0.05)); + } + + /** + * Adds a number of days to a date. + * + * @param date The date + * @param days The number of days to add to the date + */ + public static void addDays(Date date, int days) { + MutableDateTime dateTime = convertToDateTime(date); + + dateTime.add(Days.days(days)); + + setFromDateTime(date, dateTime); + } + + /** + * Adds a number of years to a date's year. + * + * @param date The date + * @param years The number of years to add to the date + */ + public static void addYears(Date date, int years) { + MutableDateTime dateTime = convertToDateTime(date); + + dateTime.add(Years.years(years)); + + setFromDateTime(date, dateTime); + } + + /** + * Subtracts a number of years from a date's year. + * + * @param date The date + * @param years The number of years to subtract from the date + */ + public static void subtractYears(Date date, int years) { + addYears(date, -years); + } + + public static String getEarliestTimestamp(Date date) { + Era era = date.getEra(); + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + MutableDateTime dateTime = null; + + try { + dateTime = convertToDateTime(date); + } + catch(IllegalFieldValueException e) { + throw new InvalidDateException(e.getMessage()); + } + + String scalarDate = scalarDateFormatter.print(dateTime); + + return scalarDate; + } + + public static String getLatestTimestamp(Date date) { + Era era = date.getEra(); + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + MutableDateTime dateTime = null; + + try { + dateTime = convertToDateTime(date); + } + catch(IllegalFieldValueException e) { + throw new InvalidDateException(e.getMessage()); + } + + dateTime.setTime(23, 59, 59, 999); + + String scalarDate = scalarDateFormatter.print(dateTime); + + return scalarDate; + } + + public static boolean isValidDate(int year, int month, int day, Era era) { + boolean isValid = true; + + try { + convertToDateTime(new Date(year, month,day, era)); + } + catch(IllegalFieldValueException e) { + isValid = false; + } + + return isValid; + } + + /** + * Converts a Date to a joda-time DateTime. + * + * @param date The Date + * @return A MutableDateTime representing the same date + */ + private static MutableDateTime convertToDateTime(Date date) { + Era era = date.getEra(); + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + MutableDateTime dateTime = new MutableDateTime(chronology); + dateTime.era().set((era == Era.BCE) ? DateTimeConstants.BC : DateTimeConstants.AD); + dateTime.yearOfEra().set(date.getYear()); + dateTime.monthOfYear().set(date.getMonth()); + dateTime.dayOfMonth().set(date.getDay()); + dateTime.setTime(0, 0, 0, 0); + + return dateTime; + } + + /** + * Sets the fields in a Date so that it represents the same date + * as a given DateTime. + * + * @param date The Date to set + * @param dateTime A MutableDateTime representing the desired date + */ + private static void setFromDateTime(Date date, MutableDateTime dateTime) { + date.setYear(dateTime.getYearOfEra()); + date.setMonth(dateTime.getMonthOfYear()); + date.setDay(dateTime.getDayOfMonth()); + date.setEra((dateTime.getEra() == DateTimeConstants.BC) ? Era.BCE : Era.CE); + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredCenturyDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredCenturyDate.java new file mode 100644 index 000000000..8642a6226 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredCenturyDate.java @@ -0,0 +1,12 @@ +package org.collectionspace.services.structureddate; + +/** + * A deferred date that represents an endpoint of a century. + */ +public abstract class DeferredCenturyDate extends DeferredDate { + protected int century; + + public DeferredCenturyDate(int century) { + this.century = century; + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredCenturyEndDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredCenturyEndDate.java new file mode 100644 index 000000000..bce8d1836 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredCenturyEndDate.java @@ -0,0 +1,30 @@ +package org.collectionspace.services.structureddate; + + +/** + * A deferred date that represents the end of a century. The end year + * can not be determined until the era of the century is known. Once the + * era is known, resolveDate() may be called to calculate the year. + */ +public class DeferredCenturyEndDate extends DeferredCenturyDate { + + public DeferredCenturyEndDate(int century) { + super(century); + } + + @Override + public void resolveDate() { + Era era = getEra(); + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + Date endDate = DateUtils.getCenturyEndDate(century, era); + + setYear(endDate.getYear()); + setMonth(endDate.getMonth()); + setDay(endDate.getDay()); + setEra(endDate.getEra()); + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredCenturyStartDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredCenturyStartDate.java new file mode 100644 index 000000000..47400926c --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredCenturyStartDate.java @@ -0,0 +1,30 @@ +package org.collectionspace.services.structureddate; + + +/** + * A deferred date that represents the start of a century. The start year + * can not be determined until the era of the century is known. Once the + * era is known, resolveDate() may be called to calculate the year. + */ +public class DeferredCenturyStartDate extends DeferredCenturyDate { + + public DeferredCenturyStartDate(int century) { + super(century); + } + + @Override + public void resolveDate() { + Era era = getEra(); + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + Date startDate = DateUtils.getCenturyStartDate(century, era); + + setYear(startDate.getYear()); + setMonth(startDate.getMonth()); + setDay(startDate.getDay()); + setEra(startDate.getEra()); + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredDate.java new file mode 100644 index 000000000..23da883f1 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredDate.java @@ -0,0 +1,16 @@ +package org.collectionspace.services.structureddate; + + +/** + * Represents a date that is not completely known, and whose + * unknown parts require some future calculation to be determined. + */ +public abstract class DeferredDate extends Date { + + /** + * Resolves the date by executing the deferred + * calculation. This causes all of the date parts + * to be determined. + */ + public abstract void resolveDate(); +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredDecadeDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredDecadeDate.java new file mode 100644 index 000000000..41e49ee7b --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredDecadeDate.java @@ -0,0 +1,12 @@ +package org.collectionspace.services.structureddate; + +/** + * A deferred date that represents an endpoint of a decade. + */ +public abstract class DeferredDecadeDate extends DeferredDate { + protected int decade; + + public DeferredDecadeDate(int decade) { + this.decade = decade; + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredDecadeEndDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredDecadeEndDate.java new file mode 100644 index 000000000..4da639352 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredDecadeEndDate.java @@ -0,0 +1,30 @@ +package org.collectionspace.services.structureddate; + + +/** + * A deferred date that represents the end of a decade. The end year + * can not be determined until the era of the decade is known. Once the + * era is known, resolveDate() may be called to calculate the year. + */ +public class DeferredDecadeEndDate extends DeferredDecadeDate { + + public DeferredDecadeEndDate(int decade) { + super(decade); + } + + @Override + public void resolveDate() { + Era era = getEra(); + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + Date endDate = DateUtils.getDecadeEndDate(decade, era); + + setYear(endDate.getYear()); + setMonth(endDate.getMonth()); + setDay(endDate.getDay()); + setEra(endDate.getEra()); + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredDecadeStartDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredDecadeStartDate.java new file mode 100644 index 000000000..8ea550813 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredDecadeStartDate.java @@ -0,0 +1,30 @@ +package org.collectionspace.services.structureddate; + + +/** + * A deferred date that represents the start of a decade. The start year + * can not be determined until the era of the decade is known. Once the + * era is known, resolveDate() may be called to calculate the year. + */ +public class DeferredDecadeStartDate extends DeferredDecadeDate { + + public DeferredDecadeStartDate(int decade) { + super(decade); + } + + @Override + public void resolveDate() { + Era era = getEra(); + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + Date startDate = DateUtils.getDecadeStartDate(decade, era); + + setYear(startDate.getYear()); + setMonth(startDate.getMonth()); + setDay(startDate.getDay()); + setEra(startDate.getEra()); + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredHalfCenturyDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredHalfCenturyDate.java new file mode 100644 index 000000000..cd75ae4d8 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredHalfCenturyDate.java @@ -0,0 +1,18 @@ +package org.collectionspace.services.structureddate; + +/** + * A deferred date that represents an endpoint of a half century. + */ +public abstract class DeferredHalfCenturyDate extends DeferredCenturyDate { + protected int half; + + public DeferredHalfCenturyDate(int century) { + super(century); + } + + public DeferredHalfCenturyDate(int century, int half) { + this(century); + + this.half = half; + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredHalfCenturyEndDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredHalfCenturyEndDate.java new file mode 100644 index 000000000..02de8b361 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredHalfCenturyEndDate.java @@ -0,0 +1,30 @@ +package org.collectionspace.services.structureddate; + + +/** + * A deferred date that represents the end of a half century. The end year + * can not be determined until the era of the century is known. Once the + * era is known, resolveDate() may be called to calculate the year. + */ +public class DeferredHalfCenturyEndDate extends DeferredHalfCenturyDate { + + public DeferredHalfCenturyEndDate(int century, int half) { + super(century, half); + } + + @Override + public void resolveDate() { + Era era = getEra(); + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + Date startDate = DateUtils.getHalfCenturyEndDate(century, half, era); + + setYear(startDate.getYear()); + setMonth(startDate.getMonth()); + setDay(startDate.getDay()); + setEra(startDate.getEra()); + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredHalfCenturyStartDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredHalfCenturyStartDate.java new file mode 100644 index 000000000..78f739202 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredHalfCenturyStartDate.java @@ -0,0 +1,30 @@ +package org.collectionspace.services.structureddate; + + +/** + * A deferred date that represents the start of a half century. The start year + * can not be determined until the era of the century is known. Once the + * era is known, resolveDate() may be called to calculate the year. + */ +public class DeferredHalfCenturyStartDate extends DeferredHalfCenturyDate { + + public DeferredHalfCenturyStartDate(int century, int half) { + super(century, half); + } + + @Override + public void resolveDate() { + Era era = getEra(); + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + Date startDate = DateUtils.getHalfCenturyStartDate(century, half, era); + + setYear(startDate.getYear()); + setMonth(startDate.getMonth()); + setDay(startDate.getDay()); + setEra(startDate.getEra()); + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredMillenniumDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredMillenniumDate.java new file mode 100644 index 000000000..9557a2bbf --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredMillenniumDate.java @@ -0,0 +1,12 @@ +package org.collectionspace.services.structureddate; + +/** + * A deferred date that represents an endpoint of a millennium. + */ +public abstract class DeferredMillenniumDate extends DeferredDate { + protected int millennium; + + public DeferredMillenniumDate(int millennium) { + this.millennium = millennium; + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredMillenniumEndDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredMillenniumEndDate.java new file mode 100644 index 000000000..ce3a1b280 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredMillenniumEndDate.java @@ -0,0 +1,29 @@ +package org.collectionspace.services.structureddate; + +/** + * A deferred date that represents the end of a millennium. The end year + * can not be determined until the era of the millennium is known. Once the + * era is known, resolveDate() may be called to calculate the year. + */ +public class DeferredMillenniumEndDate extends DeferredMillenniumDate { + + public DeferredMillenniumEndDate(int millennium) { + super(millennium); + } + + @Override + public void resolveDate() { + Era era = getEra(); + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + Date startDate = DateUtils.getMillenniumEndDate(millennium, era); + + setYear(startDate.getYear()); + setMonth(startDate.getMonth()); + setDay(startDate.getDay()); + setEra(startDate.getEra()); + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredMillenniumStartDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredMillenniumStartDate.java new file mode 100644 index 000000000..a41cc41c0 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredMillenniumStartDate.java @@ -0,0 +1,29 @@ +package org.collectionspace.services.structureddate; + +/** + * A deferred date that represents the start of a millennium. The start year + * can not be determined until the era of the millennium is known. Once the + * era is known, resolveDate() may be called to calculate the year. + */ +public class DeferredMillenniumStartDate extends DeferredMillenniumDate { + + public DeferredMillenniumStartDate(int millennium) { + super(millennium); + } + + @Override + public void resolveDate() { + Era era = getEra(); + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + Date startDate = DateUtils.getMillenniumStartDate(millennium, era); + + setYear(startDate.getYear()); + setMonth(startDate.getMonth()); + setDay(startDate.getDay()); + setEra(startDate.getEra()); + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredMonthEndDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredMonthEndDate.java new file mode 100644 index 000000000..64bcefe40 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredMonthEndDate.java @@ -0,0 +1,22 @@ +package org.collectionspace.services.structureddate; + +/** + * A deferred date that represents the end of a month. The end day + * can not be determined until the year and era of the month are known. Once the + * year and era are known, resolveDate() may be called to calculate the day. + */ +public class DeferredMonthEndDate extends DeferredDate { + + @Override + public void resolveDate() { + Era era = getEra(); + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int day = DateUtils.getDaysInMonth(getMonth(), getYear(), era); + + setDay(day); + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialCenturyDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialCenturyDate.java new file mode 100644 index 000000000..dfec3a024 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialCenturyDate.java @@ -0,0 +1,18 @@ +package org.collectionspace.services.structureddate; + +/** + * A deferred date that represents an endpoint of a partial century. + */ +public abstract class DeferredPartialCenturyDate extends DeferredCenturyDate { + protected Part part; + + public DeferredPartialCenturyDate(int century) { + super(century); + } + + public DeferredPartialCenturyDate(int century, Part part) { + this(century); + + this.part = part; + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialCenturyEndDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialCenturyEndDate.java new file mode 100644 index 000000000..9b503ac31 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialCenturyEndDate.java @@ -0,0 +1,30 @@ +package org.collectionspace.services.structureddate; + + +/** + * A deferred date that represents the end of a partial century. The end year + * can not be determined until the era of the century is known. Once the + * era is known, resolveDate() may be called to calculate the year. + */ +public class DeferredPartialCenturyEndDate extends DeferredPartialCenturyDate { + + public DeferredPartialCenturyEndDate(int century, Part part) { + super(century, part); + } + + @Override + public void resolveDate() { + Era era = getEra(); + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + Date startDate = DateUtils.getPartialCenturyEndDate(century, part, era); + + setYear(startDate.getYear()); + setMonth(startDate.getMonth()); + setDay(startDate.getDay()); + setEra(startDate.getEra()); + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialCenturyStartDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialCenturyStartDate.java new file mode 100644 index 000000000..ba39e1325 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialCenturyStartDate.java @@ -0,0 +1,29 @@ +package org.collectionspace.services.structureddate; + +/** + * A deferred date that represents the start of a partial century. The start year + * can not be determined until the era of the century is known. Once the + * era is known, resolveDate() may be called to calculate the year. + */ +public class DeferredPartialCenturyStartDate extends DeferredPartialCenturyDate { + + public DeferredPartialCenturyStartDate(int century, Part part) { + super(century, part); + } + + @Override + public void resolveDate() { + Era era = getEra(); + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + Date startDate = DateUtils.getPartialCenturyStartDate(century, part, era); + + setYear(startDate.getYear()); + setMonth(startDate.getMonth()); + setDay(startDate.getDay()); + setEra(startDate.getEra()); + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialDecadeDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialDecadeDate.java new file mode 100644 index 000000000..b33298c1e --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialDecadeDate.java @@ -0,0 +1,18 @@ +package org.collectionspace.services.structureddate; + +/** + * A deferred date that represents an endpoint of a partial decade. + */ +public abstract class DeferredPartialDecadeDate extends DeferredDecadeDate { + protected Part part; + + public DeferredPartialDecadeDate(int decade) { + super(decade); + } + + public DeferredPartialDecadeDate(int decade, Part part) { + this(decade); + + this.part = part; + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialDecadeEndDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialDecadeEndDate.java new file mode 100644 index 000000000..dc98265f2 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialDecadeEndDate.java @@ -0,0 +1,29 @@ +package org.collectionspace.services.structureddate; + +/** + * A deferred date that represents the end of a partial decade. The end year + * can not be determined until the era of the decade is known. Once the + * era is known, resolveDate() may be called to calculate the year. + */ +public class DeferredPartialDecadeEndDate extends DeferredPartialDecadeDate { + + public DeferredPartialDecadeEndDate(int decade, Part part) { + super(decade, part); + } + + @Override + public void resolveDate() { + Era era = getEra(); + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + Date startDate = DateUtils.getPartialDecadeEndDate(decade, part, era); + + setYear(startDate.getYear()); + setMonth(startDate.getMonth()); + setDay(startDate.getDay()); + setEra(startDate.getEra()); + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialDecadeStartDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialDecadeStartDate.java new file mode 100644 index 000000000..b8c131aa2 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredPartialDecadeStartDate.java @@ -0,0 +1,29 @@ +package org.collectionspace.services.structureddate; + +/** + * A deferred date that represents the start of a partial decade. The start year + * can not be determined until the era of the decade is known. Once the + * era is known, resolveDate() may be called to calculate the year. + */ +public class DeferredPartialDecadeStartDate extends DeferredPartialDecadeDate { + + public DeferredPartialDecadeStartDate(int decade, Part part) { + super(decade, part); + } + + @Override + public void resolveDate() { + Era era = getEra(); + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + Date startDate = DateUtils.getPartialDecadeStartDate(decade, part, era); + + setYear(startDate.getYear()); + setMonth(startDate.getMonth()); + setDay(startDate.getDay()); + setEra(startDate.getEra()); + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredQuarterCenturyDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredQuarterCenturyDate.java new file mode 100644 index 000000000..685a001ed --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredQuarterCenturyDate.java @@ -0,0 +1,18 @@ +package org.collectionspace.services.structureddate; + +/** + * A deferred date that represents an endpoint of a quarter century. + */ +public abstract class DeferredQuarterCenturyDate extends DeferredCenturyDate { + protected int quarter; + + public DeferredQuarterCenturyDate(int century) { + super(century); + } + + public DeferredQuarterCenturyDate(int century, int quarter) { + this(century); + + this.quarter = quarter; + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredQuarterCenturyEndDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredQuarterCenturyEndDate.java new file mode 100644 index 000000000..0b7bffb97 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredQuarterCenturyEndDate.java @@ -0,0 +1,30 @@ +package org.collectionspace.services.structureddate; + + +/** + * A deferred date that represents the end of a quarter century. The end year + * can not be determined until the era of the century is known. Once the + * era is known, resolveDate() may be called to calculate the year. + */ +public class DeferredQuarterCenturyEndDate extends DeferredQuarterCenturyDate { + + public DeferredQuarterCenturyEndDate(int century, int quarter) { + super(century, quarter); + } + + @Override + public void resolveDate() { + Era era = getEra(); + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + Date endDate = DateUtils.getQuarterCenturyEndDate(century, quarter, era); + + setYear(endDate.getYear()); + setMonth(endDate.getMonth()); + setDay(endDate.getDay()); + setEra(endDate.getEra()); + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredQuarterCenturyStartDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredQuarterCenturyStartDate.java new file mode 100644 index 000000000..234dd7474 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/DeferredQuarterCenturyStartDate.java @@ -0,0 +1,30 @@ +package org.collectionspace.services.structureddate; + + +/** + * A deferred date that represents the start of a quarter century. The start year + * can not be determined until the era of the century is known. Once the + * era is known, resolveDate() may be called to calculate the year. + */ +public class DeferredQuarterCenturyStartDate extends DeferredQuarterCenturyDate { + + public DeferredQuarterCenturyStartDate(int century, int quarter) { + super(century, quarter); + } + + @Override + public void resolveDate() { + Era era = getEra(); + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + Date startDate = DateUtils.getQuarterCenturyStartDate(century, quarter, era); + + setYear(startDate.getYear()); + setMonth(startDate.getMonth()); + setDay(startDate.getDay()); + setEra(startDate.getEra()); + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/Era.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/Era.java new file mode 100644 index 000000000..63db1e2e1 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/Era.java @@ -0,0 +1,22 @@ +package org.collectionspace.services.structureddate; + +public enum Era { + BCE ("urn:cspace:botgarden.cspace.berkeley.edu:vocabularies:name(dateera):item:name(bce)'BCE'"), + CE ("urn:cspace:botgarden.cspace.berkeley.edu:vocabularies:name(dateera):item:name(ce)'CE'"); + + private final String value; + + private Era(String value) { + this.value = value; + } + + public String toString() { + return value; + } + + public String toDisplayString() { + int index = value.indexOf("'"); + + return value.substring(index + 1, value.length() - 1); + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/InvalidDateException.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/InvalidDateException.java new file mode 100644 index 000000000..246b3e0b9 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/InvalidDateException.java @@ -0,0 +1,14 @@ +package org.collectionspace.services.structureddate; + +public class InvalidDateException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + InvalidDateException() { + super(); + } + + InvalidDateException(String message) { + super(message); + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/ParseDates.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/ParseDates.java new file mode 100644 index 000000000..930779567 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/ParseDates.java @@ -0,0 +1,112 @@ +package org.collectionspace.services.structureddate; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; + +import org.apache.commons.lang.StringUtils; +import org.joda.time.IllegalFieldValueException; + +public class ParseDates { + + /** + * Parse a newline-separated list of strings from a file (or standard input), + * and print the results to standard output. + * + * @param args The first argument to the program is the name of the file + * containing strings to parse. If not supplied, strings are + * read from standard input. + */ + public static void main(String[] args) { + BufferedReader in = null; + + if (args.length > 0) { + String filename = args[0]; + + try { + in = new BufferedReader(new FileReader(filename)); + } catch (FileNotFoundException e) { + System.err.println("File not found: " + filename); + } + } + else { + in = new BufferedReader(new InputStreamReader(System.in)); + } + + if (in == null) { + return; + } + + try { + for(String line; (line = in.readLine()) != null; ) { + line = StringUtils.trim(line); + + if (StringUtils.isNotEmpty(line)) { + parse(line); + } + } + } + catch(IOException e) { + System.err.println("Error reading file: " + e.getLocalizedMessage()); + } + + try { + in.close(); + } + catch(IOException e) { + System.err.println("Error closing file: " + e.getLocalizedMessage()); + } + } + + private static void parse(String displayDate) { + System.out.print(displayDate + "\t"); + + String result = ""; + String scalar = ""; + + try { + StructuredDate structuredDate = StructuredDate.parse(displayDate); + Date earliestSingleDate = structuredDate.getEarliestSingleDate(); + Date latestDate = structuredDate.getLatestDate(); + + result = + earliestSingleDate.getYear() + "-" + + earliestSingleDate.getMonth() + "-" + + earliestSingleDate.getDay() + " " + + earliestSingleDate.getEra().toDisplayString(); // use toString() to get the data value (refname) + + // These don't get filled in by the parser, so no need to print. + + // earliestSingleDate.getCertainty(); + // earliestSingleDate.getQualifierType(); + // earliestSingleDate.getQualifierValue(); + // earliestSingleDate.getQualifierUnit(); + // earliestSingleDate.getScalarValue(); + + if (latestDate != null) { + result += " - " + + latestDate.getYear() + "-" + + latestDate.getMonth() + "-" + + latestDate.getDay() + " " + + latestDate.getEra().toDisplayString(); // use toString() to get the data value (refname) + } + + try { + structuredDate.computeScalarValues(); + + scalar = structuredDate.getEarliestScalarDate() + " - " + structuredDate.getLatestScalarDate(); + } + catch(InvalidDateException e) { + scalar = "[invalid date: " + e.getMessage() + "]"; + } + } + catch(StructuredDateFormatException e) { + result = "[unable to parse]"; + scalar = ""; + } + + System.out.println(result + "\t" + scalar); + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/Part.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/Part.java new file mode 100644 index 000000000..b0ed7f0fa --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/Part.java @@ -0,0 +1,7 @@ +package org.collectionspace.services.structureddate; + +public enum Part { + EARLY, + MIDDLE, + LATE; +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/QualifierType.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/QualifierType.java new file mode 100644 index 000000000..4151b4624 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/QualifierType.java @@ -0,0 +1,17 @@ +package org.collectionspace.services.structureddate; + +public enum QualifierType { + MINUS ("-"), + PLUS ("+"), + PLUS_OR_MINUS ("+/-"); + + private final String value; + + private QualifierType(String value) { + this.value = value; + } + + public String toString() { + return value; + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/QualifierUnit.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/QualifierUnit.java new file mode 100644 index 000000000..2e21c35ee --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/QualifierUnit.java @@ -0,0 +1,17 @@ +package org.collectionspace.services.structureddate; + +public enum QualifierUnit { + DAYS ("urn:cspace:botgarden.cspace.berkeley.edu:vocabularies:name(datequalifier):item:name(days)'Day(s)'"), + MONTHS ("urn:cspace:botgarden.cspace.berkeley.edu:vocabularies:name(datequalifier):item:name(month)'Month(s)'"), + YEARS ("urn:cspace:botgarden.cspace.berkeley.edu:vocabularies:name(datequalifier):item:name(years)'Year(s)'"); + + private final String value; + + private QualifierUnit(String value) { + this.value = value; + } + + public String toString() { + return value; + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/StructuredDate.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/StructuredDate.java new file mode 100644 index 000000000..b37bb606b --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/StructuredDate.java @@ -0,0 +1,241 @@ +package org.collectionspace.services.structureddate; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.collectionspace.services.structureddate.antlr.ANTLRStructuredDateEvaluator; + + +/** + * A CollectionSpace structured date. + */ +public class StructuredDate { + // The UI layer is interpreting scalarValuesComputed as follows: + // - If true, the UI should compute scalar values + // - If false (or null), the UI should not compute scalar values + // Given that interpretation, scalarValuesComputed should default + // to true. + public static final boolean DEFAULT_SCALAR_VALUES_COMPUTED = true; + + private String displayDate; + private String note; + private String association; + private String period; + + private Date earliestSingleDate; + private Date latestDate; + + private String earliestScalarDate; + private String latestScalarDate; + private Boolean scalarValuesComputed; + + public StructuredDate() { + scalarValuesComputed = DEFAULT_SCALAR_VALUES_COMPUTED; + } + + public String toString() { + String string = + "\n" + + "\tdisplayDate: " + getDisplayDate() + "\n" + + "\tnote: " + getNote() + "\n" + + "\tassociation: " + getAssociation() + "\n" + + "\tperiod: " + getPeriod() + "\n"; + + if (getEarliestSingleDate() != null) { + string += + "\n" + + "\tearliestSingleDate: \n" + + getEarliestSingleDate().toString() + "\n"; + } + + if (getLatestDate() != null) { + string += + "\n" + + "\tlatestDate: \n" + + getLatestDate().toString() + "\n"; + } + + return string; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + + if (obj == this) { + return true; + } + + if (obj.getClass() != getClass()) { + return false; + } + + StructuredDate that = (StructuredDate) obj; + + return + new EqualsBuilder() + .append(this.getDisplayDate(), that.getDisplayDate()) + .append(this.getAssociation(), that.getAssociation()) + .append(this.getNote(), that.getNote()) + .append(this.getPeriod(), that.getPeriod()) + .append(this.getEarliestSingleDate(), that.getEarliestSingleDate()) + .append(this.getLatestDate(), that.getLatestDate()) + .append(this.areScalarValuesComputed(), that.areScalarValuesComputed()) + .isEquals(); + } + + public void computeScalarValues() { + Date earliestDate = getEarliestSingleDate(); + Date latestDate = getLatestDate(); + + if (earliestDate == null && latestDate == null) { + setEarliestScalarDate(null); + setLatestScalarDate(null); + + return; + } + + if (earliestDate == null) { + earliestDate = latestDate.copy(); + } + else { + earliestDate = earliestDate.copy(); + } + + if (latestDate == null) { + latestDate = earliestDate.copy(); + } + else { + latestDate = latestDate.copy(); + } + + if (earliestDate.getYear() == null || latestDate.getYear() == null) { + // The dates must at least specify a year. + throw new InvalidDateException("year must not be null"); + } + + if (earliestDate.getDay() != null && earliestDate.getMonth() == null) { + // If a day is specified, the month must be specified. + throw new InvalidDateException("month may not be null when day is not null"); + } + + if (latestDate.getDay() != null && latestDate.getMonth() == null) { + // If a day is specified, the month must be specified. + throw new InvalidDateException("month may not be null when day is not null"); + } + + if (earliestDate.getEra() == null) { + earliestDate.setEra(Date.DEFAULT_ERA); + } + + if (latestDate.getEra() == null) { + latestDate.setEra(Date.DEFAULT_ERA); + } + + if (earliestDate.getMonth() == null) { + earliestDate.setMonth(1); + earliestDate.setDay(1); + } + + if (latestDate.getMonth() == null) { + latestDate.setMonth(12); + latestDate.setDay(31); + } + + if (earliestDate.getDay() == null) { + earliestDate.setDay(1); + } + + if (latestDate.getDay() == null) { + latestDate.setDay(DateUtils.getDaysInMonth(latestDate.getMonth(), latestDate.getYear(), latestDate.getEra())); + } + + // Add one day to the latest day, since that's what the UI does. + // DateUtils.addDays(latestDate, 1); + + setEarliestScalarDate(DateUtils.getEarliestTimestamp(earliestDate)); + setLatestScalarDate(DateUtils.getLatestTimestamp(latestDate)); + } + + public static StructuredDate parse(String displayDate) throws StructuredDateFormatException { + StructuredDateEvaluator evaluator = new ANTLRStructuredDateEvaluator(); + + return evaluator.evaluate(displayDate); + } + + public String getDisplayDate() { + return displayDate; + } + + public void setDisplayDate(String displayDate) { + this.displayDate = displayDate; + } + + public String getNote() { + return note; + } + + public void setNote(String note) { + this.note = note; + } + + public String getAssociation() { + return association; + } + + public void setAssociation(String association) { + this.association = association; + } + + public String getPeriod() { + return period; + } + + public void setPeriod(String period) { + this.period = period; + } + + public Date getEarliestSingleDate() { + return earliestSingleDate; + } + + public void setEarliestSingleDate(Date earliestSingleDate) { + this.earliestSingleDate = earliestSingleDate; + } + + public Date getLatestDate() { + return latestDate; + } + + public void setLatestDate(Date latestDate) { + this.latestDate = latestDate; + } + + public boolean isRange() { + return (getLatestDate() != null); + } + + public String getEarliestScalarDate() { + return earliestScalarDate; + } + + public void setEarliestScalarDate(String earliestScalarDate) { + this.earliestScalarDate = earliestScalarDate; + } + + public Boolean areScalarValuesComputed() { + return scalarValuesComputed; + } + + public String getLatestScalarDate() { + return latestScalarDate; + } + + public void setLatestScalarDate(String latestScalarDate) { + this.latestScalarDate = latestScalarDate; + } + + public void setScalarValuesComputed(Boolean scalarValuesComputed) { + this.scalarValuesComputed = scalarValuesComputed; + } +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/StructuredDateEvaluator.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/StructuredDateEvaluator.java new file mode 100644 index 000000000..0544e53af --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/StructuredDateEvaluator.java @@ -0,0 +1,5 @@ +package org.collectionspace.services.structureddate; + +public interface StructuredDateEvaluator { + public StructuredDate evaluate(String displayDate) throws StructuredDateFormatException; +} diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/StructuredDateFormatException.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/StructuredDateFormatException.java new file mode 100644 index 000000000..05df32c68 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/StructuredDateFormatException.java @@ -0,0 +1,21 @@ +package org.collectionspace.services.structureddate; + +public class StructuredDateFormatException extends IllegalArgumentException { + private static final long serialVersionUID = 1L; + + public StructuredDateFormatException() { + super(); + } + + public StructuredDateFormatException(Throwable cause) { + super(cause); + } + + public StructuredDateFormatException(String message) { + super(message); + } + + public StructuredDateFormatException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/services/structureddate/src/main/java/org/collectionspace/services/structureddate/antlr/ANTLRStructuredDateEvaluator.java b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/antlr/ANTLRStructuredDateEvaluator.java new file mode 100644 index 000000000..0374c9960 --- /dev/null +++ b/services/structureddate/src/main/java/org/collectionspace/services/structureddate/antlr/ANTLRStructuredDateEvaluator.java @@ -0,0 +1,1116 @@ +package org.collectionspace.services.structureddate.antlr; + +import java.util.Stack; + +import org.antlr.v4.runtime.ANTLRInputStream; +import org.antlr.v4.runtime.BailErrorStrategy; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.FailedPredicateException; +import org.antlr.v4.runtime.InputMismatchException; +import org.antlr.v4.runtime.NoViableAltException; +import org.antlr.v4.runtime.Parser; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.TokenStream; +import org.antlr.v4.runtime.misc.ParseCancellationException; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.collectionspace.services.structureddate.Date; +import org.collectionspace.services.structureddate.DateUtils; +import org.collectionspace.services.structureddate.DeferredCenturyEndDate; +import org.collectionspace.services.structureddate.DeferredCenturyStartDate; +import org.collectionspace.services.structureddate.DeferredDate; +import org.collectionspace.services.structureddate.DeferredDecadeEndDate; +import org.collectionspace.services.structureddate.DeferredDecadeStartDate; +import org.collectionspace.services.structureddate.DeferredHalfCenturyEndDate; +import org.collectionspace.services.structureddate.DeferredHalfCenturyStartDate; +import org.collectionspace.services.structureddate.DeferredMillenniumEndDate; +import org.collectionspace.services.structureddate.DeferredMillenniumStartDate; +import org.collectionspace.services.structureddate.DeferredPartialCenturyEndDate; +import org.collectionspace.services.structureddate.DeferredPartialCenturyStartDate; +import org.collectionspace.services.structureddate.DeferredPartialDecadeEndDate; +import org.collectionspace.services.structureddate.DeferredPartialDecadeStartDate; +import org.collectionspace.services.structureddate.DeferredQuarterCenturyEndDate; +import org.collectionspace.services.structureddate.DeferredQuarterCenturyStartDate; +import org.collectionspace.services.structureddate.Era; +import org.collectionspace.services.structureddate.Part; +import org.collectionspace.services.structureddate.StructuredDate; +import org.collectionspace.services.structureddate.StructuredDateEvaluator; +import org.collectionspace.services.structureddate.StructuredDateFormatException; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.AllOrPartOfContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.BeforeOrAfterDateContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.CenturyContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.CertainDateContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DateContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DecadeContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DisplayDateContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.EraContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.HalfCenturyContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.HalfYearContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.HyphenatedRangeContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvMonthYearContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvSeasonYearContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvStrDateContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MillenniumContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MonthContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MonthInYearRangeContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthCenturyRangeContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthHalfContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthQuarterContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumCenturyContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDateContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDayInMonthRangeContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDayOfMonthContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDecadeContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumMonthContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumYearContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartOfContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialCenturyContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialDecadeContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialYearContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterCenturyContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterInYearRangeContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterYearContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrCenturyContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDateContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDayInMonthRangeContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrMonthContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrSeasonContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UncertainDateContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearContext; +import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearSpanningWinterContext; + +/** + * A StructuredDateEvaluator that uses an ANTLR parser to parse the display date, + * and an ANTLR listener to generate a structured date from the resulting parse + * tree. + */ +public class ANTLRStructuredDateEvaluator extends StructuredDateBaseListener implements StructuredDateEvaluator { + /** + * The result of the evaluation. + */ + protected StructuredDate result; + + /** + * The operation stack. The parse listener methods that are implemented here + * pop input parameters off the stack, and push results back on to the stack. + */ + protected Stack stack; + + public ANTLRStructuredDateEvaluator() { + + } + + @Override + public StructuredDate evaluate(String displayDate) throws StructuredDateFormatException { + stack = new Stack(); + + result = new StructuredDate(); + result.setDisplayDate(displayDate); + + // Instantiate a parser from the lowercased display date, so that parsing will be + // case insensitive. + ANTLRInputStream inputStream = new ANTLRInputStream(displayDate.toLowerCase()); + StructuredDateLexer lexer = new StructuredDateLexer(inputStream); + CommonTokenStream tokenStream = new CommonTokenStream(lexer); + StructuredDateParser parser = new StructuredDateParser(tokenStream); + + // Don't try to recover from parse errors, just bail. + parser.setErrorHandler(new BailErrorStrategy()); + + // Don't print error messages to the console. + parser.removeErrorListeners(); + + // Generate our own custom error messages. + parser.addParseListener(this); + + try { + // Attempt to fulfill the oneDisplayDate rule of the grammar. + parser.oneDisplayDate(); + } + catch(ParseCancellationException e) { + // ParseCancellationException is thrown by the BailErrorStrategy when there is a + // parse error, with the underlying RecognitionException as the cause. + RecognitionException re = (RecognitionException) e.getCause(); + + throw new StructuredDateFormatException(getErrorMessage(re), re); + } + + // The parsing was successful. Return the result. + return result; + } + + @Override + public void exitDisplayDate(DisplayDateContext ctx) { + if (ctx.exception != null) return; + + Date latestDate = (Date) stack.pop(); + Date earliestDate = (Date) stack.pop(); + + // If the earliest date and the latest date are the same, it's just a "single" date. + // There's no need to have the latest, so set it to null. + + if (earliestDate.equals(latestDate)) { + latestDate = null; + } + + result.setEarliestSingleDate(earliestDate); + result.setLatestDate(latestDate); + } + + @Override + public void exitBeforeOrAfterDate(BeforeOrAfterDateContext ctx) { + if (ctx.exception != null) return; + + Date latestDate = (Date) stack.pop(); + Date earliestDate = (Date) stack.pop(); + + // Set null eras to the default. + + if (earliestDate.getEra() == null) { + earliestDate.setEra(Date.DEFAULT_ERA); + } + + if (latestDate.getEra() == null) { + latestDate.setEra(Date.DEFAULT_ERA); + } + + // Finalize any deferred calculations. + + if (latestDate instanceof DeferredDate) { + ((DeferredDate) latestDate).resolveDate(); + } + + if (earliestDate instanceof DeferredDate) { + ((DeferredDate) earliestDate).resolveDate(); + } + + // Calculate the earliest date or end date. + + if (ctx.BEFORE() != null) { + latestDate = earliestDate; + earliestDate = DateUtils.getEarliestBeforeDate(earliestDate, latestDate); + } + else if (ctx.AFTER() != null) { + earliestDate = latestDate; + latestDate = DateUtils.getLatestAfterDate(earliestDate, latestDate); + } + + stack.push(earliestDate); + stack.push(latestDate); + } + + @Override + public void exitUncertainDate(UncertainDateContext ctx) { + if (ctx.exception != null) return; + + Date latestDate = (Date) stack.pop(); + Date earliestDate = (Date) stack.pop(); + + int earliestInterval = DateUtils.getCircaIntervalYears(earliestDate.getYear(), earliestDate.getEra()); + int latestInterval = DateUtils.getCircaIntervalYears(latestDate.getYear(), latestDate.getEra()); + + // Express the circa interval as a qualifier. + + // stack.push(earliestDate.withQualifier(QualifierType.MINUS, earliestInterval, QualifierUnit.YEARS)); + // stack.push(latestDate.withQualifier(QualifierType.PLUS, latestInterval, QualifierUnit.YEARS)); + + // OR: + + // Express the circa interval as an offset calculated into the year. + + DateUtils.subtractYears(earliestDate, earliestInterval); + DateUtils.addYears(latestDate, latestInterval); + + stack.push(earliestDate); + stack.push(latestDate); + } + + @Override + public void exitCertainDate(CertainDateContext ctx) { + if (ctx.exception != null) return; + + Date latestDate = (Date) stack.pop(); + Date earliestDate = (Date) stack.pop(); + + // Set null eras to the default. + + if (earliestDate.getEra() == null) { + earliestDate.setEra(Date.DEFAULT_ERA); + } + + if (latestDate.getEra() == null) { + latestDate.setEra(Date.DEFAULT_ERA); + } + + // Finalize any deferred calculations. + + if (latestDate instanceof DeferredDate) { + ((DeferredDate) latestDate).resolveDate(); + } + + if (earliestDate instanceof DeferredDate) { + ((DeferredDate) earliestDate).resolveDate(); + } + + stack.push(earliestDate); + stack.push(latestDate); + } + + @Override + public void exitHyphenatedRange(HyphenatedRangeContext ctx) { + if (ctx.exception != null) return; + + Date latestEndDate = (Date) stack.pop(); + stack.pop(); // latestStartDate + stack.pop(); // earliestEndDate + Date earliestStartDate = (Date) stack.pop(); + + // If no era was explicitly specified for the first date, + // make it inherit the era of the second date. + + if (earliestStartDate.getEra() == null && latestEndDate.getEra() != null) { + earliestStartDate.setEra(latestEndDate.getEra()); + } + + // Finalize any deferred calculations. + + if (earliestStartDate instanceof DeferredDate) { + ((DeferredDate) earliestStartDate).resolveDate(); + } + + if (latestEndDate instanceof DeferredDate) { + ((DeferredDate) latestEndDate).resolveDate(); + } + + stack.push(earliestStartDate); + stack.push(latestEndDate); + } + + @Override + public void exitNthCenturyRange(NthCenturyRangeContext ctx) { + if (ctx.exception != null) return; + + Era era = (Era) stack.pop(); + Integer endN = (Integer) stack.pop(); + Part endPart = (Part) stack.pop(); + Integer startN = (Integer) stack.pop(); + Part startPart = (Part) stack.pop(); + + if (era == null) { + era = Date.DEFAULT_ERA; + } + + int startYear = DateUtils.nthCenturyToYear(startN); + int endYear = DateUtils.nthCenturyToYear(endN); + + stack.push(startPart == null ? DateUtils.getCenturyStartDate(startYear, era) : DateUtils.getPartialCenturyStartDate(startYear, startPart, era)); + stack.push(startPart == null ? DateUtils.getCenturyEndDate(startYear, era) : DateUtils.getPartialCenturyEndDate(startYear, startPart, era)); + stack.push(endPart == null ? DateUtils.getCenturyStartDate(endYear, era) : DateUtils.getPartialCenturyStartDate(endYear, endPart, era)); + stack.push(endPart == null ? DateUtils.getCenturyEndDate(endYear, era) : DateUtils.getPartialCenturyEndDate(endYear, endPart, era)); + } + + @Override + public void exitMonthInYearRange(MonthInYearRangeContext ctx) { + if (ctx.exception != null) return; + + Era era = (Era) stack.pop(); + Integer year = (Integer) stack.pop(); + Integer numMonthEnd = (Integer) stack.pop(); + Integer numMonthStart = (Integer) stack.pop(); + + stack.push(new Date(year, numMonthStart, 1, era)); + stack.push(new Date(year, numMonthStart, DateUtils.getDaysInMonth(numMonthStart, year, era), era)); + stack.push(new Date(year, numMonthEnd, 1, era)); + stack.push(new Date(year, numMonthEnd, DateUtils.getDaysInMonth(numMonthEnd, year, era), era)); + } + + @Override + public void exitQuarterInYearRange(QuarterInYearRangeContext ctx) { + if (ctx.exception != null) return; + + Era era = (Era) stack.pop(); + Integer year = (Integer) stack.pop(); + Integer lastQuarter = (Integer) stack.pop(); + Integer firstQuarter = (Integer) stack.pop(); + + stack.push(DateUtils.getQuarterYearStartDate(firstQuarter, year).withEra(era)); + stack.push(DateUtils.getQuarterYearEndDate(firstQuarter, year, era).withEra(era)); + stack.push(DateUtils.getQuarterYearStartDate(lastQuarter, year).withEra(era)); + stack.push(DateUtils.getQuarterYearEndDate(lastQuarter, year, era).withEra(era)); + } + + @Override + public void exitStrDayInMonthRange(StrDayInMonthRangeContext ctx) { + if (ctx.exception != null) return; + + Era era = (Era) stack.pop(); + Integer year = (Integer) stack.pop(); + Integer dayOfMonthEnd = (Integer) stack.pop(); + Integer dayOfMonthStart = (Integer) stack.pop(); + Integer numMonth = (Integer) stack.pop(); + + stack.push(new Date(year, numMonth, dayOfMonthStart, era)); + stack.push(new Date(year, numMonth, dayOfMonthStart, era)); + stack.push(new Date(year, numMonth, dayOfMonthEnd, era)); + stack.push(new Date(year, numMonth, dayOfMonthEnd, era)); + } + + @Override + public void exitNumDayInMonthRange(NumDayInMonthRangeContext ctx) { + if (ctx.exception != null) return; + + Era era = (Era) stack.pop(); + Integer year = (Integer) stack.pop(); + Integer dayOfMonthEnd = (Integer) stack.pop(); + Integer dayOfMonthStart = (Integer) stack.pop(); + Integer numMonth = (Integer) stack.pop(); + + stack.push(new Date(year, numMonth, dayOfMonthStart, era)); + stack.push(new Date(year, numMonth, dayOfMonthStart, era)); + stack.push(new Date(year, numMonth, dayOfMonthEnd, era)); + stack.push(new Date(year, numMonth, dayOfMonthEnd, era)); + } + + @Override + public void exitDate(DateContext ctx) { + if (ctx.exception != null) return; + + // Expect the canonical year-month-day-era ordering + // to be on the stack. + + Era era = (Era) stack.pop(); + Integer dayOfMonth = (Integer) stack.pop(); + Integer numMonth = (Integer) stack.pop(); + Integer year = (Integer) stack.pop(); + + // For the latest date we could either return null, or a copy of the earliest date, + // since the UI doesn't care. Use a copy of the earliest date, since it makes + // things easier here if we don't have to test for null up the tree. + + stack.push(new Date(year, numMonth, dayOfMonth, era)); + stack.push(new Date(year, numMonth, dayOfMonth, era)); + } + + + @Override + public void exitNumDate(NumDateContext ctx) { + if (ctx.exception != null) return; + + // This could either be year-month-day, or + // month-day-year. Try to determine which, + // and reorder the stack into the canonical + // year-month-day-era ordering. + + Era era = (Era) stack.pop(); + Integer num3 = (Integer) stack.pop(); + Integer num2 = (Integer) stack.pop(); + Integer num1 = (Integer) stack.pop(); + + // Default to a year-month-day interpretation. + + int year = num1; + int numMonth = num2; + int dayOfMonth = num3; + + if (DateUtils.isValidDate(num1, num2, num3, era)) { + // Interpreting as year-month-day produces a valid date. Go with it. + } + else if (DateUtils.isValidDate(num3, num1, num2, era)) { + // Interpreting as year-month-day doesn't produce a valid date, but + // month-day-year does. Go with month-day-year. + + year = num3; + numMonth = num1; + dayOfMonth = num2; + } + + stack.push(year); + stack.push(numMonth); + stack.push(dayOfMonth); + stack.push(era); + } + + @Override + public void exitStrDate(StrDateContext ctx) { + if (ctx.exception != null) return; + + // Reorder the stack into a canonical ordering, + // year-month-day-era. + + Era era = (Era) stack.pop(); + Integer year = (Integer) stack.pop(); + Integer dayOfMonth = (Integer) stack.pop(); + Integer numMonth = (Integer) stack.pop(); + + stack.push(year); + stack.push(numMonth); + stack.push(dayOfMonth); + stack.push(era); + } + + @Override + public void exitInvStrDate(InvStrDateContext ctx) { + if (ctx.exception != null) return; + + // Reorder the stack into a canonical ordering, + // year-month-day-era. + + Integer dayOfMonth = (Integer) stack.pop(); + Integer numMonth = (Integer) stack.pop(); + Integer year = (Integer) stack.pop(); + Era era = (Era) stack.pop(); + + stack.push(year); + stack.push(numMonth); + stack.push(dayOfMonth); + stack.push(era); + } + + @Override + public void exitMonth(MonthContext ctx) { + if (ctx.exception != null) return; + + Era era = (Era) stack.pop(); + Integer year = (Integer) stack.pop(); + Integer numMonth = (Integer) stack.pop(); + + stack.push(new Date(year, numMonth, 1, era)); + stack.push(new Date(year, numMonth, DateUtils.getDaysInMonth(numMonth, year, era), era)); + } + + @Override + public void exitInvMonthYear(InvMonthYearContext ctx) { + if (ctx.exception != null) return; + + // Invert the arguments. + + Integer numMonth = (Integer) stack.pop(); + Integer year = (Integer) stack.pop(); + Era era = (Era) stack.pop(); + + stack.push(numMonth); + stack.push(year); + stack.push(era); + } + + @Override + public void exitYearSpanningWinter(YearSpanningWinterContext ctx) { + if (ctx.exception != null) return; + + Era era = (Era) stack.pop(); + Integer endYear = (Integer) stack.pop(); + Integer startYear = (Integer) stack.pop(); + + stack.push(new Date(startYear, 12, 1).withEra(era)); + stack.push(DateUtils.getQuarterYearEndDate(1, endYear, era).withEra(era)); + } + + @Override + public void exitPartialYear(PartialYearContext ctx) { + if (ctx.exception != null) return; + + Era era = (Era) stack.pop(); + Integer year = (Integer) stack.pop(); + Part part = (Part) stack.pop(); + + stack.push(DateUtils.getPartialYearStartDate(part, year).withEra(era)); + stack.push(DateUtils.getPartialYearEndDate(part, year, era).withEra(era)); + } + + @Override + public void exitQuarterYear(QuarterYearContext ctx) { + if (ctx.exception != null) return; + + Era era = (Era) stack.pop(); + Integer year = (Integer) stack.pop(); + Integer quarter = (Integer) stack.pop(); + + stack.push(DateUtils.getQuarterYearStartDate(quarter, year).withEra(era)); + stack.push(DateUtils.getQuarterYearEndDate(quarter, year, era).withEra(era)); + } + + @Override + public void exitHalfYear(HalfYearContext ctx) { + if (ctx.exception != null) return; + + Era era = (Era) stack.pop(); + Integer year = (Integer) stack.pop(); + Integer half = (Integer) stack.pop(); + + stack.push(DateUtils.getHalfYearStartDate(half, year).withEra(era)); + stack.push(DateUtils.getHalfYearEndDate(half, year, era).withEra(era)); + } + + @Override + public void exitInvSeasonYear(InvSeasonYearContext ctx) { + if (ctx.exception != null) return; + + // Invert the arguments. + + Integer quarter = (Integer) stack.pop(); + Integer year = (Integer) stack.pop(); + Era era = (Era) stack.pop(); + + stack.push(quarter); + stack.push(year); + stack.push(era); + } + + @Override + public void exitYear(YearContext ctx) { + if (ctx.exception != null) return; + + Era era = (Era) stack.pop(); + Integer year = (Integer) stack.pop(); + + stack.push(new Date(year, 1, 1, era)); + stack.push(new Date(year, 12, 31, era)); + } + + @Override + public void exitPartialDecade(PartialDecadeContext ctx) { + if (ctx.exception != null) return; + + Era era = (Era) stack.pop(); + Integer year = (Integer) stack.pop(); + Part part = (Part) stack.pop(); + + if (era != null) { + // If the era was explicitly specified, the start and end years + // may be calculated now. + + stack.push(DateUtils.getPartialDecadeStartDate(year, part, era)); + stack.push(DateUtils.getPartialDecadeEndDate(year, part, era)); + } + else { + // If the era was not explicitly specified, the start and end years + // can't be calculated yet. The calculation must be deferred until + // later. For example, this partial decade may be the start of a hyphenated + // range, where the era will be inherited from the era of the end of + // the range; this era won't be known until farther up the parse tree, + // when both sides of the range will have been parsed. + + stack.push(new DeferredPartialDecadeStartDate(year, part)); + stack.push(new DeferredPartialDecadeEndDate(year, part)); + } + } + + @Override + public void exitDecade(DecadeContext ctx) { + if (ctx.exception != null) return; + + Era era = (Era) stack.pop(); + Integer year = (Integer) stack.pop(); + + // Calculate the start and end year of the decade, which depends on the era. + + if (era != null) { + // If the era was explicitly specified, the start and end years + // may be calculated now. + + stack.push(DateUtils.getDecadeStartDate(year, era)); + stack.push(DateUtils.getDecadeEndDate(year, era)); + } + else { + // If the era was not explicitly specified, the start and end years + // can't be calculated yet. The calculation must be deferred until + // later. For example, this decade may be the start of a hyphenated + // range, where the era will be inherited from the era of the end of + // the range; this era won't be known until farther up the parse tree, + // when both sides of the range will have been parsed. + + stack.push(new DeferredDecadeStartDate(year)); + stack.push(new DeferredDecadeEndDate(year)); + } + } + + @Override + public void exitPartialCentury(PartialCenturyContext ctx) { + if (ctx.exception != null) return; + + Era era = (Era) stack.pop(); + Integer year = (Integer) stack.pop(); + Part part = (Part) stack.pop(); + + if (era != null) { + // If the era was explicitly specified, the start and end years + // may be calculated now. + + stack.push(DateUtils.getPartialCenturyStartDate(year, part, era)); + stack.push(DateUtils.getPartialCenturyEndDate(year, part, era)); + } + else { + // If the era was not explicitly specified, the start and end years + // can't be calculated yet. The calculation must be deferred until + // later. For example, this partial century may be the start of a hyphenated + // range, where the era will be inherited from the era of the end of + // the range; this era won't be known until farther up the parse tree, + // when both sides of the range will have been parsed. + + stack.push(new DeferredPartialCenturyStartDate(year, part)); + stack.push(new DeferredPartialCenturyEndDate(year, part)); + } + } + + @Override + public void exitQuarterCentury(QuarterCenturyContext ctx) { + if (ctx.exception != null) return; + + Era era = (Era) stack.pop(); + Integer year = (Integer) stack.pop(); + Integer quarter = (Integer) stack.pop(); + + if (era != null) { + // If the era was explicitly specified, the start and end years + // may be calculated now. + + stack.push(DateUtils.getQuarterCenturyStartDate(year, quarter, era)); + stack.push(DateUtils.getQuarterCenturyEndDate(year, quarter, era)); + } + else { + // If the era was not explicitly specified, the start and end years + // can't be calculated yet. The calculation must be deferred until + // later. For example, this century may be the start of a hyphenated + // range, where the era will be inherited from the era of the end of + // the range; this era won't be known until farther up the parse tree, + // when both sides of the range will have been parsed. + + stack.push(new DeferredQuarterCenturyStartDate(year, quarter)); + stack.push(new DeferredQuarterCenturyEndDate(year, quarter)); + } + } + + @Override + public void exitHalfCentury(HalfCenturyContext ctx) { + if (ctx.exception != null) return; + + Era era = (Era) stack.pop(); + Integer year = (Integer) stack.pop(); + Integer half = (Integer) stack.pop(); + + if (era != null) { + // If the era was explicitly specified, the start and end years + // may be calculated now. + + stack.push(DateUtils.getHalfCenturyStartDate(year, half, era)); + stack.push(DateUtils.getHalfCenturyEndDate(year, half, era)); + } + else { + // If the era was not explicitly specified, the start and end years + // can't be calculated yet. The calculation must be deferred until + // later. For example, this half century may be the start of a hyphenated + // range, where the era will be inherited from the era of the end of + // the range; this era won't be known until farther up the parse tree, + // when both sides of the range will have been parsed. + + stack.push(new DeferredHalfCenturyStartDate(year, half)); + stack.push(new DeferredHalfCenturyEndDate(year, half)); + } + } + + @Override + public void exitCentury(CenturyContext ctx) { + if (ctx.exception != null) return; + + Era era = (Era) stack.pop(); + Integer year = (Integer) stack.pop(); + + if (era != null) { + // If the era was explicitly specified, the start and end years + // may be calculated now. + + stack.push(DateUtils.getCenturyStartDate(year, era)); + stack.push(DateUtils.getCenturyEndDate(year, era)); + } + else { + // If the era was not explicitly specified, the start and end years + // can't be calculated yet. The calculation must be deferred until + // later. For example, this quarter century may be the start of a hyphenated + // range, where the era will be inherited from the era of the end of + // the range; this era won't be known until farther up the parse tree, + // when both sides of the range will have been parsed. + + stack.push(new DeferredCenturyStartDate(year)); + stack.push(new DeferredCenturyEndDate(year)); + } + } + + @Override + public void exitMillennium(MillenniumContext ctx) { + if (ctx.exception != null) return; + + Era era = (Era) stack.pop(); + Integer n = (Integer) stack.pop(); + + if (era != null) { + // If the era was explicitly specified, the start and end years + // may be calculated now. + + stack.push(DateUtils.getMillenniumStartDate(n, era)); + stack.push(DateUtils.getMillenniumEndDate(n, era)); + } + else { + // If the era was not explicitly specified, the start and end years + // can't be calculated yet. The calculation must be deferred until + // later. For example, this millennium may be the start of a hyphenated + // range, where the era will be inherited from the era of the end of + // the range; this era won't be known until farther up the parse tree, + // when both sides of the range will have been parsed. + + stack.push(new DeferredMillenniumStartDate(n)); + stack.push(new DeferredMillenniumEndDate(n)); + } + } + + @Override + public void exitStrCentury(StrCenturyContext ctx) { + if (ctx.exception != null) return; + + Integer n = (Integer) stack.pop(); + + // Convert the nth number to a year number, + // and push on the stack. + + Integer year = DateUtils.nthCenturyToYear(n); + + stack.push(year); + } + + @Override + public void exitNumCentury(NumCenturyContext ctx) { + if (ctx.exception != null) return; + + // Convert the string to a number, + // and push on the stack. + + Integer year = new Integer(stripEndLetters(ctx.HUNDREDS().getText())); + + if (year == 0) { + throw new StructuredDateFormatException("unexpected century '" + ctx.HUNDREDS().getText() + "'"); + } + + stack.push(year); + } + + @Override + public void exitNumDecade(NumDecadeContext ctx) { + if (ctx.exception != null) return; + + // Convert the string to a number, + // and push on the stack. + + Integer year = new Integer(stripEndLetters(ctx.TENS().getText())); + + if (year == 0) { + throw new StructuredDateFormatException("unexpected decade '" + ctx.TENS().getText() + "'"); + } + + stack.push(year); + } + + @Override + public void exitNumYear(NumYearContext ctx) { + if (ctx.exception != null) return; + + // Convert the string to a number, + // and push on the stack. + + Integer year = new Integer(ctx.NUMBER().getText()); + + if (year == 0) { + throw new StructuredDateFormatException("unexpected year '" + ctx.NUMBER().getText() + "'"); + } + + stack.push(year); + } + + @Override + public void exitNumMonth(NumMonthContext ctx) { + if (ctx.exception != null) return; + + // Convert the string a number, + // and push on the stack. + + Integer month = new Integer(ctx.NUMBER().getText()); + + if (month < 1 || month > 12) { + throw new StructuredDateFormatException("unexpected month '" + ctx.NUMBER().getText() + "'"); + } + + stack.push(month); + } + + @Override + public void exitNthHalf(NthHalfContext ctx) { + if (ctx.exception != null) return; + + // Convert LAST to a number (the last half + // is the 2nd). If this rule matched the + // alternative with nth instead of LAST, + // the nth handler will already have pushed + // a number on the stack. + + if (ctx.LAST() != null) { + stack.push(new Integer(2)); + } + + // Check for a valid half. + + Integer n = (Integer) stack.peek(); + + if (n < 1 || n > 2) { + throw new StructuredDateFormatException("unexpected half '" + n + "'"); + } + } + + @Override + public void exitNthQuarter(NthQuarterContext ctx) { + if (ctx.exception != null) return; + + // Convert LAST to a number (the last quarter + // is the 4th). If this rule matched the + // alternative with nth instead of LAST, + // the nth handler will already have pushed + // a number on the stack. + + if (ctx.LAST() != null) { + stack.push(new Integer(4)); + } + + // Check for a valid quarter. + + Integer n = (Integer) stack.peek(); + + if (n < 1 || n > 4) { + throw new StructuredDateFormatException("unexpected quarter '" + n + "'"); + } + } + + @Override + public void exitNth(NthContext ctx) { + if (ctx.exception != null) return; + + // Convert the string to a number, + // and push on the stack. + + Integer n = null; + + if (ctx.NTHSTR() != null) { + n = new Integer(stripEndLetters(ctx.NTHSTR().getText())); + } + else if (ctx.FIRST() != null) { + n = 1; + } + else if (ctx.SECOND() != null) { + n = 2; + } + else if (ctx.THIRD() != null) { + n = 3; + } + else if (ctx.FOURTH() != null) { + n = 4; + } + + stack.push(n); + } + + @Override + public void exitStrMonth(StrMonthContext ctx) { + if (ctx.exception != null) return; + + // Convert the month name to a number, + // and push on the stack. + + TerminalNode monthNode = ctx.MONTH(); + + if (monthNode == null) { + monthNode = ctx.SHORTMONTH(); + } + + String monthStr = monthNode.getText(); + + stack.push(DateUtils.getMonthByName(monthStr)); + } + + @Override + public void exitStrSeason(StrSeasonContext ctx) { + if (ctx.exception != null) return; + + // Convert the season to a quarter number, + // and push on the stack. + + Integer quarter = null; + + if (ctx.WINTER() != null) { + quarter = 1; + } + else if (ctx.SPRING() != null) { + quarter = 2; + } + else if (ctx.SUMMER() != null) { + quarter = 3; + } + else if (ctx.FALL() != null) { + quarter = 4; + } + + stack.push(quarter); + } + + @Override + public void exitAllOrPartOf(AllOrPartOfContext ctx) { + if (ctx.exception != null) return; + + // If a part was specified, it will have been + // pushed on the stack in exitPartOf(). If not, + // push null on the stack. + + if (ctx.partOf() == null) { + stack.push(null); + } + } + + @Override + public void exitPartOf(PartOfContext ctx) { + if (ctx.exception != null) return; + + // Convert the token to a Part, + // and push on the stack. + + Part part = null; + + if (ctx.EARLY() != null) { + part = Part.EARLY; + } + else if (ctx.MIDDLE() != null) { + part = Part.MIDDLE; + } + else if (ctx.LATE() != null) { + part = Part.LATE; + } + + stack.push(part); + } + + @Override + public void exitEra(EraContext ctx) { + if (ctx.exception != null) return; + + // Convert the token to an Era, + // and push on the stack. + + Era era = null; + + if (ctx.BC() != null) { + era = Era.BCE; + } + else if (ctx.AD() != null) { + era = Era.CE; + } + + stack.push(era); + } + + @Override + public void exitNumDayOfMonth(NumDayOfMonthContext ctx) { + if (ctx.exception != null) return; + + // Convert the numeric string to an Integer, + // and push on the stack. + + Integer dayOfMonth = new Integer(ctx.NUMBER().getText()); + + if (dayOfMonth == 0 || dayOfMonth > 31) { + throw new StructuredDateFormatException("unexpected day of month '" + ctx.NUMBER().getText() + "'"); + } + + stack.push(dayOfMonth); + } + + @Override + public void exitNum(NumContext ctx) { + if (ctx.exception != null) return; + + // Convert the numeric string to an Integer, + // and push on the stack. + + Integer num = new Integer(ctx.NUMBER().getText()); + + stack.push(num); + } + + protected String getErrorMessage(RecognitionException re) { + String message = ""; + + Parser recognizer = (Parser) re.getRecognizer(); + TokenStream tokens = recognizer.getInputStream(); + + if (re instanceof NoViableAltException) { + NoViableAltException e = (NoViableAltException) re; + Token startToken = e.getStartToken(); + String input = (startToken.getType() == Token.EOF ) ? "end of text" : quote(tokens.getText(startToken, e.getOffendingToken())); + + message = "no viable date format found at " + input; + } + else if (re instanceof InputMismatchException) { + InputMismatchException e = (InputMismatchException) re; + message = "did not expect " + getTokenDisplayString(e.getOffendingToken()) + " while looking for " + + e.getExpectedTokens().toString(recognizer.getTokenNames()); + } + else if (re instanceof FailedPredicateException) { + FailedPredicateException e = (FailedPredicateException) re; + String ruleName = recognizer.getRuleNames()[recognizer.getContext().getRuleIndex()]; + + message = "failed predicate " + ruleName + ": " + e.getMessage(); + } + + return message; + } + + protected String quote(String text) { + return "'" + text + "'"; + } + + protected String getTokenDisplayString(Token token) { + String string; + + if (token == null) { + string = "[no token]"; + } + else { + String text = token.getText(); + + if (text == null) { + if (token.getType() == Token.EOF ) { + string = "end of text"; + } + else { + string = "[" + token.getType() + "]"; + } + } + else { + string = quote(text); + } + } + + return string; + } + + protected String stripEndLetters(String input) { + return input.replaceAll("[^\\d]+$", ""); + } + + public static void main(String[] args) { + StructuredDateEvaluator evaluator = new ANTLRStructuredDateEvaluator(); + + for (String displayDate : args) { + try { + evaluator.evaluate(displayDate); + } catch (StructuredDateFormatException e) { + e.printStackTrace(); + } + } + } +} \ No newline at end of file diff --git a/services/structureddate/src/test/java/org/collectionspace/services/structureddate/StructuredDateEvaluatorTest.java b/services/structureddate/src/test/java/org/collectionspace/services/structureddate/StructuredDateEvaluatorTest.java new file mode 100644 index 000000000..dfbe32dfa --- /dev/null +++ b/services/structureddate/src/test/java/org/collectionspace/services/structureddate/StructuredDateEvaluatorTest.java @@ -0,0 +1,118 @@ +package org.collectionspace.services.structureddate; + +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.beanutils.PropertyUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import org.yaml.snakeyaml.Yaml; + +public class StructuredDateEvaluatorTest { + public static final String TEST_CASE_FILE = "/test-dates.yaml"; + public static final List YAML_DATE_SPEC = Arrays.asList("year", "month", "day", "era", "certainty", "qualifierType", "qualifierValue", "qualifierUnit"); + + final Logger logger = LoggerFactory.getLogger(StructuredDateEvaluatorTest.class); + + @BeforeClass + public void setUp() { + + }; + + @Test + public void test() { + Yaml yaml = new Yaml(); + Map testCases = (Map) yaml.load(getClass().getResourceAsStream(TEST_CASE_FILE)); + + for (String displayDate : testCases.keySet()) { + logger.debug("Testing input: " + displayDate); + + Map expectedStructuredDateFields = (Map) testCases.get(displayDate); + + StructuredDate expectedStructuredDate = createStructuredDateFromYamlSpec(displayDate, expectedStructuredDateFields); + StructuredDate actualStructuredDate = null; + + try { + actualStructuredDate = StructuredDate.parse(displayDate); + } + catch(StructuredDateFormatException e) { + logger.debug(e.getMessage()); + } + + Assert.assertEquals(actualStructuredDate, expectedStructuredDate); + } + } + + private StructuredDate createStructuredDateFromYamlSpec(String displayDate, Map structuredDateFields) { + StructuredDate structuredDate = null; + + if (structuredDateFields != null) { + structuredDate = new StructuredDate(); + + for (String propertyName : structuredDateFields.keySet()) { + Object value = structuredDateFields.get(propertyName); + + try { + Class propertyType = PropertyUtils.getPropertyType(structuredDate, propertyName); + + if (propertyType.equals(Date.class)) { + value = createDateFromYamlSpec((List) value); + } + + PropertyUtils.setProperty(structuredDate, propertyName, value); + } + catch(NoSuchMethodException e) { + logger.warn(propertyName + " is not a property"); + } + catch(InvocationTargetException e) { + logger.error(propertyName + " accessor threw an exception"); + } + catch(IllegalAccessException e) { + logger.error("could not access property " + propertyName); + } + } + + if (structuredDate.getDisplayDate() == null) { + structuredDate.setDisplayDate(displayDate); + } + } + + return structuredDate; + } + + private Date createDateFromYamlSpec(List dateFields) { + Date date = new Date(); + Iterator fieldIterator = dateFields.iterator(); + + for (String propertyName : YAML_DATE_SPEC) { + Object value = fieldIterator.hasNext() ? fieldIterator.next() : null; + + try { + Class propertyType = PropertyUtils.getPropertyType(date, propertyName); + + if (value != null && Enum.class.isAssignableFrom(propertyType)) { + value = Enum.valueOf(propertyType, (String) value); + } + + PropertyUtils.setProperty(date, propertyName, value); + } + catch(NoSuchMethodException e) { + logger.warn(propertyName + " is not a property"); + } + catch(InvocationTargetException e) { + logger.error(propertyName + " accessor threw an exception"); + } + catch(IllegalAccessException e) { + logger.error("could not access property " + propertyName); + } + } + + return date; + } +} diff --git a/services/structureddate/src/test/java/org/collectionspace/services/structureddate/StructuredDateTest.java b/services/structureddate/src/test/java/org/collectionspace/services/structureddate/StructuredDateTest.java new file mode 100644 index 000000000..4a49df5a2 --- /dev/null +++ b/services/structureddate/src/test/java/org/collectionspace/services/structureddate/StructuredDateTest.java @@ -0,0 +1,20 @@ +package org.collectionspace.services.structureddate; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +public class StructuredDateTest { + final Logger logger = LoggerFactory.getLogger(StructuredDateTest.class); + + @BeforeClass + public void setUp() { + + }; + + @Test + public void testScalarDates() { + + } +} diff --git a/services/structureddate/src/test/resources/log4j.properties b/services/structureddate/src/test/resources/log4j.properties new file mode 100644 index 000000000..d3e4810ae --- /dev/null +++ b/services/structureddate/src/test/resources/log4j.properties @@ -0,0 +1,21 @@ +log4j.rootLogger=DEBUG, STDOUT, R + +log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender +log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout + +# Pattern to output the caller's file name and line number. +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%t] [%c:%L] %m%n + +log4j.appender.R=org.apache.log4j.RollingFileAppender +log4j.appender.R.File=target/test.log + +log4j.appender.R.MaxFileSize=100KB +# Keep one backup file +log4j.appender.R.MaxBackupIndex=1 + +log4j.appender.R.layout=org.apache.log4j.PatternLayout +log4j.appender.R.layout.ConversionPattern=%d %-5p [%t] [%c:%L] %m%n + +#packages +log4j.logger.org.collectionspace=DEBUG +log4j.logger.org.apache.commons.beanutils=WARN \ No newline at end of file diff --git a/services/structureddate/src/test/resources/test-dates.yaml b/services/structureddate/src/test/resources/test-dates.yaml new file mode 100644 index 000000000..3a0448d92 --- /dev/null +++ b/services/structureddate/src/test/resources/test-dates.yaml @@ -0,0 +1,1151 @@ +# This file contains test cases for structured date evaluation. +# +# Specify an input display date, and an expected output structured date. Fields expected to be null +# in the structured date may be omitted. +# +# If the structured date's displayDate field is omitted, it will be expected to be equal to the +# input display date. This is the usual case. +# +# The StructuredDate's earliestSingleDate and latestDate should be specified as a +# list: [year, month, day, era, certainty, qualifierType, qualifierValue, qualifierUnit] +# Values at the end of the list may be omitted, in which case they will be expected to be null. +# +# If an input is expected to fail, specify null for the expected structured date. +# +# Input Display Date Expected Structured Date +# ======================================================================================================= +# +# Valid dates +# ------------------------------------------------------------------------------------------------------- + + '1997': # year + earliestSingleDate: [1997, 1, 1, CE] + latestDate: [1997, 12, 31, CE] + + '53 BC': # year + earliestSingleDate: [ 53, 1, 1, BCE] + latestDate: [ 53, 12, 31, BCE] + + '53 bc': # year + earliestSingleDate: [ 53, 1, 1, BCE] + latestDate: [ 53, 12, 31, BCE] + + '53 B.C.': # year + earliestSingleDate: [ 53, 1, 1, BCE] + latestDate: [ 53, 12, 31, BCE] + + '53 b.c.': # year + earliestSingleDate: [ 53, 1, 1, BCE] + latestDate: [ 53, 12, 31, BCE] + + '53 BCE': # year + earliestSingleDate: [ 53, 1, 1, BCE] + latestDate: [ 53, 12, 31, BCE] + + '53 bce': # year + earliestSingleDate: [ 53, 1, 1, BCE] + latestDate: [ 53, 12, 31, BCE] + + '53 B.C.E.': # year + earliestSingleDate: [ 53, 1, 1, BCE] + latestDate: [ 53, 12, 31, BCE] + + '53 b.c.e.': # year + earliestSingleDate: [ 53, 1, 1, BCE] + latestDate: [ 53, 12, 31, BCE] + + '1974 AD': # year + earliestSingleDate: [1974, 1, 1, CE] + latestDate: [1974, 12, 31, CE] + + '1974 ad': # year + earliestSingleDate: [1974, 1, 1, CE] + latestDate: [1974, 12, 31, CE] + + '1974 A.D.': # year + earliestSingleDate: [1974, 1, 1, CE] + latestDate: [1974, 12, 31, CE] + + '1974 a.d.': # year + earliestSingleDate: [1974, 1, 1, CE] + latestDate: [1974, 12, 31, CE] + + '1974 c.e.': # year + earliestSingleDate: [1974, 1, 1, CE] + latestDate: [1974, 12, 31, CE] + + '1974 ce': # year + earliestSingleDate: [1974, 1, 1, CE] + latestDate: [1974, 12, 31, CE] + + "1800's-1900's": # hyphenatedRange, century + earliestSingleDate: [1800, 1, 1, CE] + latestDate: [1999, 12, 31, CE] + + '19th century-20th century': # hyphenatedRange, century + earliestSingleDate: [1801, 1, 1, CE] + latestDate: [2000, 12, 31, CE] + + "1700's-20th century": # hyphenatedRange, century + earliestSingleDate: [1700, 1, 1, CE] + latestDate: [2000, 12, 31, CE] + + "2nd century BC—first century AD": # hyphenatedRange, century (em dash) + earliestSingleDate: [ 200, 1, 1, BCE] + latestDate: [ 100, 12, 31, CE] + + "fourth century-2nd century BC": # hyphenatedRange, century + earliestSingleDate: [ 400, 1, 1, BCE] + latestDate: [ 101, 12, 31, BCE] + + "300s-200s BC": # hyphenatedRange, century + earliestSingleDate: [ 399, 1, 1, BCE] + latestDate: [ 200, 12, 31, BCE] + + "1970's-1980's": # hyphenatedRange, decade + earliestSingleDate: [1970, 1, 1, CE] + latestDate: [1989, 12, 31, CE] + + "1920s - 1940's": # hyphenatedRange, decade + earliestSingleDate: [1920, 1, 1, CE] + latestDate: [1949, 12, 31, CE] + + '30s BC - 20s CE': # hyphenatedRange, decade + earliestSingleDate: [ 39, 1, 1, BCE] + latestDate: [ 29, 12, 31, CE] + + '30s-20s BC': # hyphenatedRange, decade + earliestSingleDate: [ 39, 1, 1, BCE] + latestDate: [ 20, 12, 31, BCE] + + '30s BC-20s BC': # hyphenatedRange, decade + earliestSingleDate: [ 39, 1, 1, BCE] + latestDate: [ 20, 12, 31, BCE] + + '1974-2013': # hyphenatedRange, year + earliestSingleDate: [1974, 1, 1, CE] + latestDate: [2013, 12, 31, CE] + + '1974-2013 AD': # hyphenatedRange, year + earliestSingleDate: [1974, 1, 1, CE] + latestDate: [2013, 12, 31, CE] + + '100 BC-12 BC': # hyphenatedRange, year + earliestSingleDate: [ 100, 1, 1, BCE] + latestDate: [ 12, 12, 31, BCE] + + '100-12 BC': # hyphenatedRange, year + earliestSingleDate: [ 100, 1, 1, BCE] + latestDate: [ 12, 12, 31, BCE] + + '12 BC - 30 AD': # hyphenatedRange, year + earliestSingleDate: [ 12, 1, 1, BCE] + latestDate: [ 30, 12, 31, CE] + + '12 BC - 30': # hyphenatedRange, year + earliestSingleDate: [ 12, 1, 1, BCE] + latestDate: [ 30, 12, 31, CE] + + 'Spring 1974-Summer 1975': # hyphenatedRange, quarterYear + earliestSingleDate: [1974, 4, 1, CE] + latestDate: [1975, 9, 30, CE] + + 'last quarter 1974-Summer 1975': # hyphenatedRange, quarterYear + earliestSingleDate: [1974, 10, 1, CE] + latestDate: [1975, 9, 30, CE] + + '4th quarter 1974-first quarter 1975': # hyphenatedRange, quarterYear + earliestSingleDate: [1974, 10, 1, CE] + latestDate: [1975, 3, 31, CE] + + '4th quarter 1974-first quarter 1975 A.D.': # hyphenatedRange, quarterYear + earliestSingleDate: [1974, 10, 1, CE] + latestDate: [1975, 3, 31, CE] + + 'Spring 12 BC-Summer 10': # hyphenatedRange, quarterYear + earliestSingleDate: [ 12, 4, 1, BCE] + latestDate: [ 10, 9, 30, CE] + + 'Spring 12-Summer 10 BC': # hyphenatedRange, quarterYear + earliestSingleDate: [ 12, 4, 1, BCE] + latestDate: [ 10, 9, 30, BCE] + + '1st half 2000 - 1st half 2001': # hyphenatedRange, halfYear + earliestSingleDate: [2000, 1, 1, CE] + latestDate: [2001, 6, 30, CE] + + 'second half 1998 - last half 2003': # hyphenatedRange, halfYear + earliestSingleDate: [1998, 7, 1, CE] + latestDate: [2003, 12, 31, CE] + + 'second half 1998 - last half 2003 ad': # hyphenatedRange, halfYear + earliestSingleDate: [1998, 7, 1, CE] + latestDate: [2003, 12, 31, CE] + + '2nd half 12 - first half 11 BC': # hyphenatedRange, halfYear + earliestSingleDate: [ 12, 7, 1, BCE] + latestDate: [ 11, 6, 30, BCE] + + '2nd half 12 b.c. - first half 2 AD': # hyphenatedRange, halfYear + earliestSingleDate: [ 12, 7, 1, BCE] + latestDate: [ 2, 6, 30, CE] + + '1st half 2000': # halfYear + earliestSingleDate: [2000, 1, 1, CE] + latestDate: [2000, 6, 30, CE] + + '2nd half 2000': # halfYear + earliestSingleDate: [2000, 7, 1, CE] + latestDate: [2000, 12, 31, CE] + + 'last half 2000': # halfYear + earliestSingleDate: [2000, 7, 1, CE] + latestDate: [2000, 12, 31, CE] + + 'early 1960s': # partialDecade + earliestSingleDate: [1960, 1, 1, CE] + latestDate: [1963, 12, 31, CE] + + "mid 1960's": # partialDecade + earliestSingleDate: [1964, 1, 1, CE] + latestDate: [1966, 12, 31, CE] + + 'late 1960s': # partialDecade + earliestSingleDate: [1967, 1, 1, CE] + latestDate: [1969, 12, 31, CE] + + 'early 1960s ce': # partialDecade + earliestSingleDate: [1960, 1, 1, CE] + latestDate: [1963, 12, 31, CE] + + "mid-1960's a.d.": # partialDecade + earliestSingleDate: [1964, 1, 1, CE] + latestDate: [1966, 12, 31, CE] + + 'late 1960s AD': # partialDecade + earliestSingleDate: [1967, 1, 1, CE] + latestDate: [1969, 12, 31, CE] + + 'early 530s bc': # partialDecade + earliestSingleDate: [ 539, 1, 1, BCE] + latestDate: [ 537, 12, 31, BCE] + + "middle 530's b.c.": # partialDecade + earliestSingleDate: [ 536, 1, 1, BCE] + latestDate: [ 534, 12, 31, BCE] + + 'late 530s B.C.': # partialDecade + earliestSingleDate: [ 533, 1, 1, BCE] + latestDate: [ 530, 12, 31, BCE] + + "mid 1960's-early 1970's": # hyphenatedRange, partialDecade + earliestSingleDate: [1964, 1, 1, CE] + latestDate: [1973, 12, 31, CE] + + "mid 1960's-early 1970's a.d.": # hyphenatedRange, partialDecade + earliestSingleDate: [1964, 1, 1, CE] + latestDate: [1973, 12, 31, CE] + + "mid 520's-early 480's b.c.": # hyphenatedRange, partialDecade + earliestSingleDate: [ 526, 1, 1, BCE] + latestDate: [ 487, 12, 31, BCE] + + '1960s': # decade + earliestSingleDate: [1960, 1, 1, CE] + latestDate: [1969, 12, 31, CE] + + "1920's": # decade + earliestSingleDate: [1920, 1, 1, CE] + latestDate: [1929, 12, 31, CE] + + '1920S': # decade + earliestSingleDate: [1920, 1, 1, CE] + latestDate: [1929, 12, 31, CE] + + "20's BC": # decade + earliestSingleDate: [ 29, 1, 1, BCE] + latestDate: [ 20, 12, 31, BCE] + + '1960s-1980s': # hyphenatedRange, decade + earliestSingleDate: [1960, 1, 1, CE] + latestDate: [1989, 12, 31, CE] + + '1960s-1980s A.D.': # hyphenatedRange, decade + earliestSingleDate: [1960, 1, 1, CE] + latestDate: [1989, 12, 31, CE] + + '1960s–1920s B.C.': # hyphenatedRange, decade (en dash) + earliestSingleDate: [1969, 1, 1, BCE] + latestDate: [1920, 12, 31, BCE] + + "1960's-early 1980's": # hyphenatedRange, decade, partialDecade + earliestSingleDate: [1960, 1, 1, CE] + latestDate: [1983, 12, 31, CE] + + "late 1820's - 1880's": # hyphenatedRange, partialDecade, decade + earliestSingleDate: [1827, 1, 1, CE] + latestDate: [1889, 12, 31, CE] + + 'early 1900s': # partialCentury + earliestSingleDate: [1900, 1, 1, CE] + latestDate: [1933, 12, 31, CE] + + 'mid 1900s': # partialCentury + earliestSingleDate: [1933, 1, 1, CE] + latestDate: [1966, 12, 31, CE] + + "Late 1900's": # partialCentury + earliestSingleDate: [1966, 1, 1, CE] + latestDate: [1999, 12, 31, CE] + + 'early 1900s ad': # partialCentury + earliestSingleDate: [1900, 1, 1, CE] + latestDate: [1933, 12, 31, CE] + + 'mid 1900s CE': # partialCentury + earliestSingleDate: [1933, 1, 1, CE] + latestDate: [1966, 12, 31, CE] + + "Late 1900's A.D.": # partialCentury + earliestSingleDate: [1966, 1, 1, CE] + latestDate: [1999, 12, 31, CE] + + 'early 18th century': # partialCentury + earliestSingleDate: [1701, 1, 1, CE] + latestDate: [1734, 12, 31, CE] + + 'mid-18th century': # partialCentury + earliestSingleDate: [1734, 1, 1, CE] + latestDate: [1767, 12, 31, CE] + + 'Late 18th century': # partialCentury + earliestSingleDate: [1767, 1, 1, CE] + latestDate: [1800, 12, 31, CE] + + 'early 100s b.c.': # partialCentury + earliestSingleDate: [ 199, 1, 1, BCE] + latestDate: [ 166, 12, 31, BCE] + + 'middle 100s bc': # partialCentury + earliestSingleDate: [ 166, 1, 1, BCE] + latestDate: [ 133, 12, 31, BCE] + + "Late 100's B.C.": # partialCentury + earliestSingleDate: [ 133, 1, 1, BCE] + latestDate: [ 100, 12, 31, BCE] + + 'early 2nd century b.c.': # partialCentury + earliestSingleDate: [ 200, 1, 1, BCE] + latestDate: [ 167, 12, 31, BCE] + + 'mid 2nd century bc': # partialCentury + earliestSingleDate: [ 167, 1, 1, BCE] + latestDate: [ 134, 12, 31, BCE] + + 'Late 2nd Century B.C.': # partialCentury + earliestSingleDate: [ 134, 1, 1, BCE] + latestDate: [ 101, 12, 31, BCE] + + 'mid-17th century - late 18th century': # hyphenatedRange, partialCentury + earliestSingleDate: [1634, 1, 1, CE] + latestDate: [1800, 12, 31, CE] + + 'mid 17th century - late 18th century a.d.': # hyphenatedRange, partialCentury + earliestSingleDate: [1634, 1, 1, CE] + latestDate: [1800, 12, 31, CE] + + 'middle 17th century AD - late 18th century a.d.': # hyphenatedRange, partialCentury + earliestSingleDate: [1634, 1, 1, CE] + latestDate: [1800, 12, 31, CE] + + 'late 1800s - early 1900s': # hyphenatedRange, partialCentury + earliestSingleDate: [1866, 1, 1, CE] + latestDate: [1933, 12, 31, CE] + + 'late 1800s - early 1900s ce': # hyphenatedRange, partialCentury + earliestSingleDate: [1866, 1, 1, CE] + latestDate: [1933, 12, 31, CE] + + "mid 200s-late 100's BCE": # hyphenatedRange, partialCentury + earliestSingleDate: [ 266, 1, 1, BCE] + latestDate: [ 100, 12, 31, BCE] + + 'late 4th century - early 3rd century bc': # hyphenatedRange, partialCentury + earliestSingleDate: [ 334, 1, 1, BCE] + latestDate: [ 267, 12, 31, BCE] + + 'late 4th century B.C. - early 3rd century BC': # hyphenatedRange, partialCentury + earliestSingleDate: [ 334, 1, 1, BCE] + latestDate: [ 267, 12, 31, BCE] + + 'late 2nd century B.C. - mid 1st century': # hyphenatedRange, partialCentury + earliestSingleDate: [ 134, 1, 1, BCE] + latestDate: [ 67, 12, 31, CE] + + 'late 2nd century B.C. - early 3rd century A.D.': # hyphenatedRange, partialCentury + earliestSingleDate: [ 134, 1, 1, BCE] + latestDate: [ 234, 12, 31, CE] + + '1st quarter 1900s': # quarterCentury + earliestSingleDate: [1900, 1, 1, CE] + latestDate: [1924, 12, 31, CE] + + 'second quarter 1900s': # quarterCentury + earliestSingleDate: [1925, 1, 1, CE] + latestDate: [1949, 12, 31, CE] + + "3rd quarter 1900's": # quarterCentury + earliestSingleDate: [1950, 1, 1, CE] + latestDate: [1974, 12, 31, CE] + + "fourth quarter 1900's": # quarterCentury + earliestSingleDate: [1975, 1, 1, CE] + latestDate: [1999, 12, 31, CE] + + 'first quarter 18th century': # quarterCentury + earliestSingleDate: [1701, 1, 1, CE] + latestDate: [1725, 12, 31, CE] + + '2nd quarter 18th century': # quarterCentury + earliestSingleDate: [1726, 1, 1, CE] + latestDate: [1750, 12, 31, CE] + + 'third quarter 18th century': # quarterCentury + earliestSingleDate: [1751, 1, 1, CE] + latestDate: [1775, 12, 31, CE] + + '4th quarter 18th century': # quarterCentury + earliestSingleDate: [1776, 1, 1, CE] + latestDate: [1800, 12, 31, CE] + + 'first quarter 1st century a.d.': # quarterCentury + earliestSingleDate: [ 1, 1, 1, CE] + latestDate: [ 25, 12, 31, CE] + + '4th quarter 22nd century CE': # quarterCentury + earliestSingleDate: [2176, 1, 1, CE] + latestDate: [2200, 12, 31, CE] + + 'First quarter 300s BC': # quarterCentury + earliestSingleDate: [ 399, 1, 1, BCE] + latestDate: [ 375, 12, 31, BCE] + + "2nd quarter 300's BC": # quarterCentury + earliestSingleDate: [ 374, 1, 1, BCE] + latestDate: [ 350, 12, 31, BCE] + + 'third quarter 300s BC': # quarterCentury + earliestSingleDate: [ 349, 1, 1, BCE] + latestDate: [ 325, 12, 31, BCE] + + '4th quarter 300s B.C.': # quarterCentury + earliestSingleDate: [ 324, 1, 1, BCE] + latestDate: [ 300, 12, 31, BCE] + + 'First quarter 1st century BC': # quarterCentury + earliestSingleDate: [ 100, 1, 1, BCE] + latestDate: [ 76, 12, 31, BCE] + + '2nd quarter 1st century BC': # quarterCentury + earliestSingleDate: [ 75, 1, 1, BCE] + latestDate: [ 51, 12, 31, BCE] + + 'third quarter 1st century BC': # quarterCentury + earliestSingleDate: [ 50, 1, 1, BCE] + latestDate: [ 26, 12, 31, BCE] + + '4th quarter 1st century BC': # quarterCentury + earliestSingleDate: [ 25, 1, 1, BCE] + latestDate: [ 1, 12, 31, BCE] + + '1st quarter 2nd century B.C.': # quarterCentury + earliestSingleDate: [ 200, 1, 1, BCE] + latestDate: [ 176, 12, 31, BCE] + + 'second quarter 2nd century BCE': # quarterCentury + earliestSingleDate: [ 175, 1, 1, BCE] + latestDate: [ 151, 12, 31, BCE] + + '3rd quarter 2nd century b.c.': # quarterCentury + earliestSingleDate: [ 150, 1, 1, BCE] + latestDate: [ 126, 12, 31, BCE] + + 'fourth quarter 2nd century b.c.e.': # quarterCentury + earliestSingleDate: [ 125, 1, 1, BCE] + latestDate: [ 101, 12, 31, BCE] + + 'last quarter 2nd century b.c.e.': # quarterCentury + earliestSingleDate: [ 125, 1, 1, BCE] + latestDate: [ 101, 12, 31, BCE] + + '1st quarter 18th century - 3rd quarter 19th century': # hyphenatedRange, quarterCentury + earliestSingleDate: [1701, 1, 1, CE] + latestDate: [1875, 12, 31, CE] + + '1st quarter 18th century - 3rd quarter 19th century a.d.': # hyphenatedRange, quarterCentury + earliestSingleDate: [1701, 1, 1, CE] + latestDate: [1875, 12, 31, CE] + + '4th quarter 3rd century - 3rd quarter 2nd century b.c.': # hyphenatedRange, quarterCentury + earliestSingleDate: [ 225, 1, 1, BCE] + latestDate: [ 126, 12, 31, BCE] + + '4th quarter 1st century b.c.-1st quarter 1st century ce': # hyphenatedRange, quarterCentury + earliestSingleDate: [ 25, 1, 1, BCE] + latestDate: [ 25, 12, 31, CE] + + '1st half 1900s': # halfCentury + earliestSingleDate: [1900, 1, 1, CE] + latestDate: [1949, 12, 31, CE] + + 'second half 1900s': # halfCentury + earliestSingleDate: [1950, 1, 1, CE] + latestDate: [1999, 12, 31, CE] + + 'last half 1900s': # halfCentury + earliestSingleDate: [1950, 1, 1, CE] + latestDate: [1999, 12, 31, CE] + + 'first half 20th century': # halfCentury + earliestSingleDate: [1901, 1, 1, CE] + latestDate: [1950, 12, 31, CE] + + '2nd half 20th century': # halfCentury + earliestSingleDate: [1951, 1, 1, CE] + latestDate: [2000, 12, 31, CE] + + '1st half 1900s A.D.': # halfCentury + earliestSingleDate: [1900, 1, 1, CE] + latestDate: [1949, 12, 31, CE] + + "second half 1900's c.e.": # halfCentury + earliestSingleDate: [1950, 1, 1, CE] + latestDate: [1999, 12, 31, CE] + + '1st half 1900s B.C.': # halfCentury + earliestSingleDate: [1999, 1, 1, BCE] + latestDate: [1950, 12, 31, BCE] + + "second half 1900's b.c.e.": # halfCentury + earliestSingleDate: [1949, 1, 1, BCE] + latestDate: [1900, 12, 31, BCE] + + 'first half first century b.c.': # halfCentury + earliestSingleDate: [ 100, 1, 1, BCE] + latestDate: [ 51, 12, 31, BCE] + + "second half 1st century BCE": # halfCentury + earliestSingleDate: [ 50, 1, 1, BCE] + latestDate: [ 1, 12, 31, BCE] + + '1st half 1900s - 1st half 2000s': # hyphenatedRange, halfCentury + earliestSingleDate: [1900, 1, 1, CE] + latestDate: [2049, 12, 31, CE] + + '1st half 1900s - 1st half 2000s ad': # hyphenatedRange, halfCentury + earliestSingleDate: [1900, 1, 1, CE] + latestDate: [2049, 12, 31, CE] + + '2nd half 900s - 1st half 700s bc': # hyphenatedRange, halfCentury + earliestSingleDate: [ 949, 1, 1, BCE] + latestDate: [ 750, 12, 31, BCE] + + '2nd half 200s bc - 1st half 100s': # hyphenatedRange, halfCentury + earliestSingleDate: [ 249, 1, 1, BCE] + latestDate: [ 149, 12, 31, CE] + + '1900s': # century + earliestSingleDate: [1900, 1, 1, CE] + latestDate: [1999, 12, 31, CE] + + '20th century': # century + earliestSingleDate: [1901, 1, 1, CE] + latestDate: [2000, 12, 31, CE] + + '20th century AD': # century + earliestSingleDate: [1901, 1, 1, CE] + latestDate: [2000, 12, 31, CE] + + "1600's": # century + earliestSingleDate: [1600, 1, 1, CE] + latestDate: [1699, 12, 31, CE] + + '21st century': # century + earliestSingleDate: [2001, 1, 1, CE] + latestDate: [2100, 12, 31, CE] + + '1st century BC': # century + earliestSingleDate: [ 100, 1, 1, BCE] + latestDate: [ 1, 12, 31, BCE] + + '8th century BC': # century + earliestSingleDate: [ 800, 1, 1, BCE] + latestDate: [ 701, 12, 31, BCE] + + "100's BC": # century + earliestSingleDate: [ 199, 1, 1, BCE] + latestDate: [ 100, 12, 31, BCE] + + '19th century - 20th century': # hyphenatedRange, century + earliestSingleDate: [1801, 1, 1, CE] + latestDate: [2000, 12, 31, CE] + + '19th century - 20th century ad': # hyphenatedRange, century + earliestSingleDate: [1801, 1, 1, CE] + latestDate: [2000, 12, 31, CE] + + '3rd century - 2nd century B.C.': # hyphenatedRange, century + earliestSingleDate: [ 300, 1, 1, BCE] + latestDate: [ 101, 12, 31, BCE] + + '3rd century BC - 1st century A.D.': # hyphenatedRange, century + earliestSingleDate: [ 300, 1, 1, BCE] + latestDate: [ 100, 12, 31, CE] + + 'late 1880s - early 1900s': # hyphenatedRange, partialDecade, partialCentury + earliestSingleDate: [1887, 1, 1, CE] + latestDate: [1933, 12, 31, CE] + + '1st millennium': # millennium + earliestSingleDate: [ 1, 1, 1, CE] + latestDate: [1000, 12, 31, CE] + + '2nd millennium': # millennium + earliestSingleDate: [1001, 1, 1, CE] + latestDate: [2000, 12, 31, CE] + + '3rd millennium': # millennium + earliestSingleDate: [2001, 1, 1, CE] + latestDate: [3000, 12, 31, CE] + + '5th millennium': # millennium + earliestSingleDate: [4001, 1, 1, CE] + latestDate: [5000, 12, 31, CE] + + '1st Millennium BC': # millennium + earliestSingleDate: [1000, 1, 1, BCE] + latestDate: [ 1, 12, 31, BCE] + + '2nd millennium B.C.E.': # millennium + earliestSingleDate: [2000, 1, 1, BCE] + latestDate: [1001, 12, 31, BCE] + + '1st millennium-2nd millennium': # hyphenatedRange, millennium + earliestSingleDate: [ 1, 1, 1, CE] + latestDate: [2000, 12, 31, CE] + + '1st millennium-2nd millennium ad': # hyphenatedRange, millennium + earliestSingleDate: [ 1, 1, 1, CE] + latestDate: [2000, 12, 31, CE] + + '3rd millennium-2nd millennium bc': # hyphenatedRange, millennium + earliestSingleDate: [3000, 1, 1, BCE] + latestDate: [1001, 12, 31, BCE] + + 'Winter 2000': # quarterYear, seasonYear + earliestSingleDate: [2000, 1, 1, CE] + latestDate: [2000, 3, 31, CE] + + '2000, Winter': # quarterYear, seasonYear + earliestSingleDate: [2000, 1, 1, CE] + latestDate: [2000, 3, 31, CE] + + 'Spring 2000': # quarterYear, seasonYear + earliestSingleDate: [2000, 4, 1, CE] + latestDate: [2000, 6, 30, CE] + + 'Spr 2000': # quarterYear, seasonYear + earliestSingleDate: [2000, 4, 1, CE] + latestDate: [2000, 6, 30, CE] + + 'Summer 2000': # quarterYear, seasonYear + earliestSingleDate: [2000, 7, 1, CE] + latestDate: [2000, 9, 30, CE] + + 'Sum 2000': # quarterYear, seasonYear + earliestSingleDate: [2000, 7, 1, CE] + latestDate: [2000, 9, 30, CE] + + 'Fall 2000': # quarterYear, seasonYear + earliestSingleDate: [2000, 10, 1, CE] + latestDate: [2000, 12, 31, CE] + + 'Fal 2000': # quarterYear, seasonYear + earliestSingleDate: [2000, 10, 1, CE] + latestDate: [2000, 12, 31, CE] + + 'Autumn 2000': # quarterYear, seasonYear + earliestSingleDate: [2000, 10, 1, CE] + latestDate: [2000, 12, 31, CE] + + 'Aut 2000': # quarterYear, seasonYear + earliestSingleDate: [2000, 10, 1, CE] + latestDate: [2000, 12, 31, CE] + + 'Sum 12 BC': # quarterYear, seasonYear + earliestSingleDate: [ 12, 7, 1, BCE] + latestDate: [ 12, 9, 30, BCE] + + 'BC 12, Summer': # quarterYear, seasonYear + earliestSingleDate: [ 12, 7, 1, BCE] + latestDate: [ 12, 9, 30, BCE] + + 'Winter 2000/2001': # yearSpanningWinter + earliestSingleDate: [2000, 12, 1, CE] + latestDate: [2001, 3, 31, CE] + + 'Winter, 2000/2001': # yearSpanningWinter + earliestSingleDate: [2000, 12, 1, CE] + latestDate: [2001, 3, 31, CE] + + 'Winter 10/9 BC': # yearSpanningWinter + earliestSingleDate: [ 10, 12, 1, BCE] + latestDate: [ 9, 3, 31, BCE] + + 'Winter 2000/2001 - Winter 2003/2004': # hyphenatedRange, yearSpanningWinter + earliestSingleDate: [2000, 12, 1, CE] + latestDate: [2004, 3, 31, CE] + + 'Winter 2000/2001 - Winter 2003/2004 CE': # hyphenatedRange, yearSpanningWinter + earliestSingleDate: [2000, 12, 1, CE] + latestDate: [2004, 3, 31, CE] + + 'Winter 10/9 BC - Winter 9/8 BC': # hyphenatedRange, yearSpanningWinter + earliestSingleDate: [ 10, 12, 1, BCE] + latestDate: [ 8, 3, 31, BCE] + + 'Winter 10/9 - Winter 9/8 BC': # hyphenatedRange, yearSpanningWinter + earliestSingleDate: [ 10, 12, 1, BCE] + latestDate: [ 8, 3, 31, BCE] + + '1st Quarter 2000': # quarterYear + earliestSingleDate: [2000, 1, 1, CE] + latestDate: [2000, 3, 31, CE] + + '2nd quarter 2000': # quarterYear + earliestSingleDate: [2000, 4, 1, CE] + latestDate: [2000, 6, 30, CE] + + '3rd quarter 2000': # quarterYear + earliestSingleDate: [2000, 7, 1, CE] + latestDate: [2000, 9, 30, CE] + + '4th quarter 2000': # quarterYear + earliestSingleDate: [2000, 10, 1, CE] + latestDate: [2000, 12, 31, CE] + + 'last quarter 2000': # quarterYear + earliestSingleDate: [2000, 10, 1, CE] + latestDate: [2000, 12, 31, CE] + + '1st quarter - 3rd quarter 2000': # hyphenatedRange, quarterYear + earliestSingleDate: [2000, 1, 1, CE] + latestDate: [2000, 9, 30, CE] + + '2nd quarter - last quarter 2000': # hyphenatedRange, quarterYear + earliestSingleDate: [2000, 4, 1, CE] + latestDate: [2000, 12, 31, CE] + + 'Spring - Summer 2000': # hyphenatedRange, quarterYear, seasonYear + earliestSingleDate: [2000, 4, 1, CE] + latestDate: [2000, 9, 30, CE] + + 'Spring-Summer 12 BC': # hyphenatedRange, quarterYear, seasonYear + earliestSingleDate: [ 12, 4, 1, BCE] + latestDate: [ 12, 9, 30, BCE] + + 'early 2000': # partialYear + earliestSingleDate: [2000, 1, 1, CE] + latestDate: [2000, 4, 30, CE] + + 'mid-2000': # partialYear + earliestSingleDate: [2000, 5, 1, CE] + latestDate: [2000, 8, 31, CE] + + 'Middle 2000': # partialYear + earliestSingleDate: [2000, 5, 1, CE] + latestDate: [2000, 8, 31, CE] + + 'mid 2000': # partialYear + earliestSingleDate: [2000, 5, 1, CE] + latestDate: [2000, 8, 31, CE] + + 'Late 2000': # partialYear + earliestSingleDate: [2000, 9, 1, CE] + latestDate: [2000, 12, 31, CE] + + 'Late 1200 BC': # partialYear + earliestSingleDate: [1200, 9, 1, BCE] + latestDate: [1200, 12, 31, BCE] + + 'early 2000 - mid-2001': # hyphenatedRange, partialYear + earliestSingleDate: [2000, 1, 1, CE] + latestDate: [2001, 8, 31, CE] + + 'early 2000 - mid-2001 ad': # hyphenatedRange, partialYear + earliestSingleDate: [2000, 1, 1, CE] + latestDate: [2001, 8, 31, CE] + + 'late 157 - early 155 bc': # hyphenatedRange, partialYear + earliestSingleDate: [ 157, 9, 1, BCE] + latestDate: [ 155, 4, 30, BCE] + + 'late 2 b.c. - early 2 a.d.': # hyphenatedRange, partialYear + earliestSingleDate: [ 2, 9, 1, BCE] + latestDate: [ 2, 4, 30, CE] + + 'May 2007': # month + earliestSingleDate: [2007, 5, 1, CE] + latestDate: [2007, 5, 31, CE] + + 'May 2007 AD': # month + earliestSingleDate: [2007, 5, 1, CE] + latestDate: [2007, 5, 31, CE] + + 'Feb. 2007': # month + earliestSingleDate: [2007, 2, 1, CE] + latestDate: [2007, 2, 28, CE] + + 'Feb. 2007 BC': # month + earliestSingleDate: [2007, 2, 1, BCE] + latestDate: [2007, 2, 28, BCE] + + '2000, February': # month (leap year) + earliestSingleDate: [2000, 2, 1, CE] + latestDate: [2000, 2, 29, CE] + + '1900 Feb': # month + earliestSingleDate: [1900, 2, 1, CE] + latestDate: [1900, 2, 28, CE] + + 'B.C.E. 960 Feb': # month + earliestSingleDate: [ 960, 2, 1, BCE] + latestDate: [ 960, 2, 28, BCE] + + '2000 Feb-2001 June': # hyphenatedRange, month + earliestSingleDate: [2000, 2, 1, CE] + latestDate: [2001, 6, 30, CE] + + '2000 Feb - AD 2001 June': # hyphenatedRange, month + earliestSingleDate: [2000, 2, 1, CE] + latestDate: [2001, 6, 30, CE] + + 'Dec 58 BC-Feb 3 AD': # hyphenatedRange, month + earliestSingleDate: [ 58, 12, 1, BCE] + latestDate: [ 3, 2, 28, CE] + + 'Dec 58 BC-Feb 3': # hyphenatedRange, month + earliestSingleDate: [ 58, 12, 1, BCE] + latestDate: [ 3, 2, 28, CE] + + 'May 130-May 120 BC': # hyphenatedRange, month + earliestSingleDate: [ 130, 5, 1, BCE] + latestDate: [ 120, 5, 31, BCE] + + '3/12/2013': # date + earliestSingleDate: [2013, 3, 12, CE] + + '03/05/13': # date + earliestSingleDate: [ 3, 5, 13, CE] + + '7-6-1773': # date + earliestSingleDate: [1773, 7, 6, CE] + + '7-6-1773 AD': # date + earliestSingleDate: [1773, 7, 6, CE] + + '07-06-1773': # date + earliestSingleDate: [1773, 7, 6, CE] + + '1773-07-06': # date + earliestSingleDate: [1773, 7, 6, CE] + + '1773/07/06': # date + earliestSingleDate: [1773, 7, 6, CE] + + '13-12-3': # date + earliestSingleDate: [ 13, 12, 3, CE] + + '12-12-3': # date + earliestSingleDate: [ 12, 12, 3, CE] + + '3-12-12': # date + earliestSingleDate: [ 3, 12, 12, CE] + + '3-12-32': # date + earliestSingleDate: [ 32, 3, 12, CE] + + 'May 06 1952': # date + earliestSingleDate: [1952, 5, 6, CE] + + 'May 12 1952': # date + earliestSingleDate: [1952, 5, 12, CE] + + 'May 12, 1952': # date + earliestSingleDate: [1952, 5, 12, CE] + + 'may 12, 1952': # date + earliestSingleDate: [1952, 5, 12, CE] + + 'September 19, 1821': # date + earliestSingleDate: [1821, 9, 19, CE] + + 'Sep 19, 1821': # date + earliestSingleDate: [1821, 9, 19, CE] + + 'SEPT 19, 1821': # date + earliestSingleDate: [1821, 9, 19, CE] + + 'Sept. 19, 1821': # date + earliestSingleDate: [1821, 9, 19, CE] + + 'July 1, 560 BC': # date + earliestSingleDate: [ 560, 7, 1, BCE] + + 'July 4th, 1776': # date + earliestSingleDate: [1776, 7, 4, CE] + + 'September 11th, 2001': # date + earliestSingleDate: [2001, 9, 11, CE] + + '2000, April 1': # date + earliestSingleDate: [2000, 4, 1, CE] + + '2000 , april 1': # date + earliestSingleDate: [2000, 4, 1, CE] + + '2000 Apr 1': # date + earliestSingleDate: [2000, 4, 1, CE] + + 'BC 560 July 1': # date + earliestSingleDate: [ 560, 7, 1, BCE] + + '5/3/1962-4/5/2013': # hyphenatedRange, date + earliestSingleDate: [1962, 5, 3, CE] + latestDate: [2013, 4, 5, CE] + + '5/3/1962-4/5/2013 BC': # hyphenatedRange, date + earliestSingleDate: [1962, 5, 3, BCE] + latestDate: [2013, 4, 5, BCE] + + '5/3/1962 BC-4/5/2013': # hyphenatedRange, date + earliestSingleDate: [1962, 5, 3, BCE] + latestDate: [2013, 4, 5, CE] + + '19th-20th century': # hyphenatedRange, nthCenturyRange + earliestSingleDate: [1801, 1, 1, CE] + latestDate: [2000, 12, 31, CE] + + '19th-20th century a.d.': # hyphenatedRange, nthCenturyRange + earliestSingleDate: [1801, 1, 1, CE] + latestDate: [2000, 12, 31, CE] + + '5th - 1st century bc': # hyphenatedRange, nthCenturyRange + earliestSingleDate: [ 500, 1, 1, BCE] + latestDate: [ 1, 12, 31, BCE] + + 'late 19th - mid-20th century': # hyphenatedRange, nthCenturyRange (partial) + earliestSingleDate: [1867, 1, 1, CE] + latestDate: [1967, 12, 31, CE] + + 'late 18th - 19th century': # hyphenatedRange, nthCenturyRange (partial) + earliestSingleDate: [1767, 1, 1, CE] + latestDate: [1900, 12, 31, CE] + + '18th - early 19th century': # hyphenatedRange, nthCenturyRange (partial) + earliestSingleDate: [1701, 1, 1, CE] + latestDate: [1834, 12, 31, CE] + + 'late 10th - early 8th century bc': # hyphenatedRange, nthCenturyRange (partial) + earliestSingleDate: [ 934, 1, 1, BCE] + latestDate: [ 767, 12, 31, BCE] + + '10th - early 8th century bc': # hyphenatedRange, nthCenturyRange (partial) + earliestSingleDate: [1000, 1, 1, BCE] + latestDate: [ 767, 12, 31, BCE] + + 'late 10th - 9th century bc': # hyphenatedRange, nthCenturyRange (partial) + earliestSingleDate: [ 934, 1, 1, BCE] + latestDate: [ 801, 12, 31, BCE] + + 'Apr-Oct 2013': # hyphenatedRange, monthInYearRange (31 day end month) + earliestSingleDate: [2013, 4, 1, CE] + latestDate: [2013, 10, 31, CE] + + 'Apr-Sept 2013': # hyphenatedRange, monthInYearRange (30 day end month) + earliestSingleDate: [2013, 4, 1, CE] + latestDate: [2013, 9, 30, CE] + + 'Jan-Feb 2003': # hyphenatedRange, monthInYearRange (28 day end month) + earliestSingleDate: [2003, 1, 1, CE] + latestDate: [2003, 2, 28, CE] + + 'January - Feb 2004': # hyphenatedRange, monthInYearRange (leap year: divisible by 4) + earliestSingleDate: [2004, 1, 1, CE] + latestDate: [2004, 2, 29, CE] + + 'Jan-Feb, 1900': # hyphenatedRange, monthInYearRange (not a leap year: divisible by 100) + earliestSingleDate: [1900, 1, 1, CE] + latestDate: [1900, 2, 28, CE] + + 'january-feb, 2000': # hyphenatedRange, monthInYearRange (leap year: divisible by 400) + earliestSingleDate: [2000, 1, 1, CE] + latestDate: [2000, 2, 29, CE] + + 'jan-february, 1100': # hyphenatedRange, monthInYearRange (leap year: divisible by 100, but before Gregorian calendar) + earliestSingleDate: [1100, 1, 1, CE] + latestDate: [1100, 2, 29, CE] + + 'Jan-Mar 100 B.C.': # hyphenatedRange, monthInYearRange + earliestSingleDate: [ 100, 1, 1, BCE] + latestDate: [ 100, 3, 31, BCE] + + 'December 3-9, 2004': # hyphenatedRange, strDayInMonthRange + earliestSingleDate: [2004, 12, 3, CE] + latestDate: [2004, 12, 9, CE] + + 'Jan 5 - 29 1850': # hyphenatedRange, strDayInMonthRange + earliestSingleDate: [1850, 1, 5, CE] + latestDate: [1850, 1, 29, CE] + + 'Jan 5 - 29 100 BCE': # hyphenatedRange, strDayInMonthRange + earliestSingleDate: [ 100, 1, 5, BCE] + latestDate: [ 100, 1, 29, BCE] + + '1/5-29/1850': # hyphenatedRange, numDayInMonthRange + earliestSingleDate: [1850, 1, 5, CE] + latestDate: [1850, 1, 29, CE] + + '09/05 - 18/1776': # hyphenatedRange, numDayInMonthRange + earliestSingleDate: [1776, 9, 5, CE] + latestDate: [1776, 9, 18, CE] + + '09/05 - 18/300 BC': # hyphenatedRange, numDayInMonthRange + earliestSingleDate: [ 300, 9, 5, BCE] + latestDate: [ 300, 9, 18, BCE] + + # 'circa 2013': # uncertainDate, year + # earliestSingleDate: [2013, 1, 1, CE, null, MINUS, 4, YEARS] + # latestDate: [2013, 12, 31, CE, null, PLUS, 4, YEARS] + + 'circa 2013': # uncertainDate, year + earliestSingleDate: [2009, 1, 1, CE] + latestDate: [2017, 12, 31, CE] + + # 'ca 2013': # uncertainDate, year - using qualifier/value/unit fields + # earliestSingleDate: [2013, 1, 1, CE, null, MINUS, 4, YEARS] + # latestDate: [2013, 12, 31, CE, null, PLUS, 4, YEARS] + + 'ca 2013': # uncertainDate, year + earliestSingleDate: [2009, 1, 1, CE] + latestDate: [2017, 12, 31, CE] + + # 'ca. 2013': # uncertainDate, year - using qualifier/value/unit fields + # earliestSingleDate: [2013, 1, 1, CE, null, MINUS, 4, YEARS] + # latestDate: [2013, 12, 31, CE, null, PLUS, 4, YEARS] + + 'ca. 2013': # uncertainDate, year - calculating the uncertainty into the year field + earliestSingleDate: [2009, 1, 1, CE] + latestDate: [2017, 12, 31, CE] + + # 'c. 1950': # uncertainDate, year - using qualifier/value/unit fields + # earliestSingleDate: [1950, 1, 1, CE, null, MINUS, 8, YEARS] + # latestDate: [1950, 12, 31, CE, null, PLUS, 8, YEARS] + + 'c. 1950': # uncertainDate, year - calculating the uncertainty into the year field + earliestSingleDate: [1942, 1, 1, CE] + latestDate: [1958, 12, 31, CE] + + # 'c1950': # uncertainDate, year - using qualifier/value/unit fields + # earliestSingleDate: [1950, 1, 1, CE, null, MINUS, 8, YEARS] + # latestDate: [1950, 12, 31, CE, null, PLUS, 8, YEARS] + + 'c1950': # uncertainDate, year - calculating the uncertainty into the year field + earliestSingleDate: [1942, 1, 1, CE] + latestDate: [1958, 12, 31, CE] + + # 'Circa 10 BC': # uncertainDate, year - using qualifier/value/unit fields + # earliestSingleDate: [ 10, 1, 1, BCE, null, MINUS, 106, YEARS] + # latestDate: [ 10, 12, 31, BCE, null, PLUS, 106, YEARS] + + 'Circa 10 BC': # uncertainDate, year - calculating the uncertainty into the year field + earliestSingleDate: [ 115, 1, 1, BCE] + latestDate: [ 96, 12, 31, CE] + + # 'Circa 10': # uncertainDate, year - using qualifier/value/unit fields + # earliestSingleDate: [ 10, 1, 1, CE, null, MINUS, 105, YEARS] + # latestDate: [ 10, 12, 31, CE, null, PLUS, 105, YEARS] + + 'Circa 10': # uncertainDate, year - calculating the uncertainty into the year field + earliestSingleDate: [ 96, 1, 1, BCE] + latestDate: [ 115, 12, 31, CE] + + # 'circa 2100': # uncertainDate, year - using qualifier/value/unit fields + # earliestSingleDate: [2100, 1, 1, CE, null, MINUS, 0, YEARS] + # latestDate: [2100, 12, 31, CE, null, PLUS, 0, YEARS] + + 'circa 2100': # uncertainDate, year - calculating the uncertainty into the year field + earliestSingleDate: [2100, 1, 1, CE] + latestDate: [2100, 12, 31, CE] + + # 'c 1950-2013': # uncertainDate, hyphenatedRange, year - using qualifier/value/unit fields + # earliestSingleDate: [1950, 1, 1, CE, null, MINUS, 8, YEARS] + # latestDate: [2013, 12, 31, CE, null, PLUS, 4, YEARS] + + 'c 1950-2013': # uncertainDate, hyphenatedRange, year - calculating the uncertainty into the year field + earliestSingleDate: [1942, 1, 1, CE] + latestDate: [2017, 12, 31, CE] + + "ca. 1730's": # uncertainDate, decade - calculating the uncertainty into the year field + earliestSingleDate: [1711, 1, 1, CE] + latestDate: [1757, 12, 31, CE] + + "3/4/2000.": # oneDisplayDate - with period + earliestSingleDate: [2000, 3, 4, CE] + + "3/4/2000?": # oneDisplayDate - with question mark + earliestSingleDate: [2000, 3, 4, CE] + +# ------------------------------------------------------------------------------------------------------- +# Invalid dates +# ------------------------------------------------------------------------------------------------------- + + '': # empty + null + + 'Octo 19, 1821': # invalid month + null + + 'October. 19, 1821': # long month with a dot + null + + 'May-Foo 2000': # invalid month + null + + 'Something else': # totally not a date + null + + 'BC 12, Sumer': # invalid season + null + + '5th quarter 1900': # invalid quarter number + null + + '0th quarter 1900': # invalid quarter number + null + + 'third half 1900': # invalid half number + null + + '0th half 1900': # invalid half number + null + + '00s': # invalid century + null + + '3/4/2005 (something)': # parentheses + null + + '3/4?/2005': # question mark + null + \ No newline at end of file diff --git a/services/taxonomy/service/src/main/java/org/collectionspace/services/taxonomy/nuxeo/TaxonBotGardenConstants.java b/services/taxonomy/service/src/main/java/org/collectionspace/services/taxonomy/nuxeo/TaxonBotGardenConstants.java new file mode 100644 index 000000000..62554cbd3 --- /dev/null +++ b/services/taxonomy/service/src/main/java/org/collectionspace/services/taxonomy/nuxeo/TaxonBotGardenConstants.java @@ -0,0 +1,18 @@ +package org.collectionspace.services.taxonomy.nuxeo; + +import org.collectionspace.services.client.CollectionSpaceClient; +import org.collectionspace.services.client.TaxonomyAuthorityClient; + +public class TaxonBotGardenConstants { + public static final String NATURALHISTORY_SCHEMA_NAME = TaxonomyAuthorityClient.SERVICE_ITEM_NAME + CollectionSpaceClient.PART_LABEL_SEPARATOR + CollectionSpaceClient.NATURALHISTORY_EXTENSION_NAME; + + public final static String CONSERVATION_CATEGORY_SCHEMA_NAME = NATURALHISTORY_SCHEMA_NAME; + public final static String CONSERVATION_CATEGORY_FIELD_NAME = "plantAttributesGroupList/plantAttributesGroup/conservationCategory"; + + public final static String ACCESS_CODE_SCHEMA_NAME = NATURALHISTORY_SCHEMA_NAME; + public final static String ACCESS_CODE_FIELD_NAME = "accessRestrictions"; + public final static String ACCESS_CODE_DEAD_VALUE = "Dead"; + public final static String ACCESS_CODE_UNRESTRICTED_VALUE = "Unrestricted"; + + public final static String COMMON_VOCABULARY_SHORTID = "common"; +} diff --git a/services/taxonomy/service/src/main/java/org/collectionspace/services/taxonomy/nuxeo/TaxonConstants.java b/services/taxonomy/service/src/main/java/org/collectionspace/services/taxonomy/nuxeo/TaxonConstants.java index b0cc06fc7..ed2ac881f 100644 --- a/services/taxonomy/service/src/main/java/org/collectionspace/services/taxonomy/nuxeo/TaxonConstants.java +++ b/services/taxonomy/service/src/main/java/org/collectionspace/services/taxonomy/nuxeo/TaxonConstants.java @@ -23,6 +23,9 @@ */ package org.collectionspace.services.taxonomy.nuxeo; +import org.collectionspace.services.client.CollectionSpaceClient; +import org.collectionspace.services.client.TaxonomyAuthorityClient; + /** * TaxonConstants provides constants for Taxonomy documents * @@ -32,4 +35,22 @@ public class TaxonConstants { public final static String NUXEO_DOCTYPE = "Taxon"; public final static String NUXEO_SCHEMA_NAME = "taxon"; public final static String NUXEO_DC_TITLE = "CollectionSpace-Taxon"; + + public final static String CORE_SCHEMA_NAME = CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA; + public final static String COMMON_SCHEMA_NAME = TaxonomyAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME; //"taxon_common"; + + public final static String IN_AUTHORITY_SCHEMA_NAME = COMMON_SCHEMA_NAME; + public final static String IN_AUTHORITY_FIELD_NAME = "inAuthority"; + + public final static String DISPLAY_NAME_SCHEMA_NAME = COMMON_SCHEMA_NAME; + public final static String DISPLAY_NAME_FIELD_NAME = "taxonTermGroupList/taxonTermGroup/termDisplayName"; + + public final static String FORMATTED_DISPLAY_NAME_SCHEMA_NAME = COMMON_SCHEMA_NAME; + public final static String FORMATTED_DISPLAY_NAME_FIELD_NAME = "taxonTermGroupList/taxonTermGroup/termFormattedDisplayName"; + + public final static String REFNAME_SCHEMA_NAME = COMMON_SCHEMA_NAME; + public final static String REFNAME_FIELD_NAME = "refName"; + + public static final String WORKFLOW_STATE_SCHEMA_NAME = CORE_SCHEMA_NAME; + public static final String WORKFLOW_STATE_FIELD_NAME = CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE; //"workflowState"; } -- 2.47.3