diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ce2be6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +## Ignore the MVN compiled output directories from version tracking +target/ + +## Ignore tags index files created by Exuberant Ctags +tags + +## Ignore project files created by Eclipse +.settings/ +.project +.classpath + +## Ignore project files created by IntelliJ IDEA +*.iml +*.ipr +*.iws +.idea/ +overlays/ + +## Ignore project files created by NetBeans +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +nbactions.xml +nb-configuration.xml +META-INF/ + +## Ignore all *.properties file in root folder, EXCEPT build.properties (the default) +## KEPT FOR BACKWARDS COMPATIBILITY WITH 5.x (build.properties is now replaced with local.cfg) +/*.properties +!/build.properties + +# Ignore a local.cfg file in root folder, if it exists +#/local.cfg +# Also ignore it under dspace/config +#/dspace/config/local.cfg + +##Mac noise +.DS_Store diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 5660dd9..c1c27da 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -753,7 +753,7 @@ event.dispatcher.default.class = org.dspace.event.BasicDispatcher # Add doi here if you are using org.dspace.identifier.DOIIdentifierProvider to generate DOIs. # Adding doi here makes DSpace send metadata updates to your doi registration agency. # Add rdf here, if you are using dspace-rdf to export your repository content as RDF. -event.dispatcher.default.consumers = versioning, discovery, eperson, harvester +event.dispatcher.default.consumers = fcd, datefoa, versioning, discovery, eperson, harvester # The noindex dispatcher will not create search or browse indexes (useful for batch item imports) event.dispatcher.noindex.class = org.dspace.event.BasicDispatcher @@ -799,6 +799,12 @@ event.consumer.versioning.filters = Item+Install event.consumer.authority.class = org.dspace.authority.indexer.AuthorityConsumer event.consumer.authority.filters = Item+Modify|Modify_Metadata +event.consumer.fcd.class = com.atmire.consumer.metadata.FirstComplianceDepositConsumer +event.consumer.fcd.filters = Item+Modify|Modify_Metadata + +event.consumer.datefoa.class = com.atmire.openaccess.DateOfCompliantOpenAccessConsumer +event.consumer.datefoa.filters = Item|Bitstream+Install|Modify + # ...set to true to enable testConsumer messages to standard output #testConsumer.verbose = true diff --git a/dspace/config/input-forms.xml b/dspace/config/input-forms.xml index feb5d96..35df15f 100644 --- a/dspace/config/input-forms.xml +++ b/dspace/config/input-forms.xml @@ -4,403 +4,619 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - dc - contributor - author - true - - name - Enter the names of the authors of this item. - - - - - dc - title - - false - - onebox - Enter the main title of the item. - You must enter a main title for this item. - - - - dc - title - alternative - true - - onebox - If the item has any alternative titles, please enter them here. - - - - - dc - date - issued - false - - date - Please give the date of previous publication or public distribution. - You can leave out the day and/or month if they aren't - applicable. - You must enter at least the year. - - - - dc - publisher - - false - - onebox - Enter the name of the publisher of the previously issued instance of this item. - - - - - dc - identifier - citation - false - - onebox - Enter the standard citation for the previously issued instance of this item. - - - - - dc - relation - ispartofseries - true - - series - Enter the series and number assigned to this item by your community. - - - - - dc - identifier - - - true - - qualdrop_value - If the item has any identification numbers or codes associated with -it, please enter the types and the actual numbers or codes. - - - - - dc - type - - true - - dropdown - Select the type(s) of content of the item. To select more than one value in the list, you may have to hold down the "CTRL" or "Shift" key. - - - - - dc - language - iso - false - - dropdown - Select the language of the main content of the item. If the language does not appear in the list, please select 'Other'. If the content does not really have a language (for example, if it is a dataset or an image) please select 'N/A'. - - - - - - - dc - subject - - - true - - twobox - Enter appropriate subject keywords or phrases. - - srsc - - - - dc - description - abstract - false - - textarea - Enter the abstract of the item. - - - - - dc - description - sponsorship - false - - textarea - Enter the names of any sponsors and/or funding codes in the box. - - - - - dc - description - - false - - textarea - Enter any other description or comments in this box. - - - -
- -
- - - dc - contributor - author - true - - name - Enter the names of the authors of this item. - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + rioxxterms + type + + false + + dropdown + Select the content type of the item. Only 'Conference Paper/Proceeding/Abstract' and 'Journal + Article/Review' are applicable for REF compliance. + + You must select a type + + + + + + dc + contributor + author + true + + name + Enter the names of the authors of this item below. + You must enter at least one author + + + + dc + title + + false + + onebox + Enter the main title of the item. + You must enter a main title for this item. + + + + dc + title + alternative + true + + onebox + If the item has any alternative titles, please enter them below. + + + + + dc + date + issued + false + + date + Please give the date of print publication or public distribution + below. You can leave out the day and/or month if they aren't + applicable. + + + + + + refterms + dateFirstOnline + + false + + date + Please give the date of online publication or public distribution + here. You can leave out the day and/or month if they aren't + applicable. + + + + + + dc + publisher + + false + + onebox + Enter the name of the publisher of the previously issued instance of this item. + + + + + dc + identifier + citation + false + + onebox + Enter the standard citation for the previously issued instance of this item. + + + + + dc + relation + ispartofseries + true + + series + Enter the series and number assigned to this item by your community. + + + + + dc + identifier + + + true + + qualdrop_value + If the item has any identification numbers or codes associated with + it, please enter the types and the actual numbers or codes below. + + + + + + dc + language + iso + false + + dropdown + Select the language of the main content of the item. If the language does not appear in the + list below, please select 'Other'. If the content does not really have a language (for example, + if it is a dataset or an image) please select 'N/A'. + + + + + + + rioxxterms + licenseref + uri + false + + onebox + xmlui.Submission.submit.input.rioxxterms.licenseref.uri.hint + + + + + rioxxterms + licenseref + startdate + false + + date + Please complete the date upon which the usage license takes effect. + + + + + dcterms + dateAccepted + + false + + fulldate + Enter the date of acceptation, all three fields are required (year, month and day) + + + + + rioxxterms + version + + false + + dropdown + Select which version of the resource you are describing and for which you will upload the + file. + + + + + + rioxxterms + versionofrecord + + false + + onebox + Please enter the persistent identifier for the published version of the resource. If a DOI has + been issued by the publisher then this must be used. + + + + + + refterms + panel + + false + + Conference Paper/Proceeding/Abstract,Journal Article/Review + dropdown + aspect.submission.StepTransformer.field.refterms.panel.hint + + + + + + + + dc + subject + + + true + + twobox + Enter appropriate subject keywords or phrases below. + + srsc + + + + dc + description + abstract + false + + textarea + Enter the abstract of the item below. + + + + + dc + description + sponsorship + false + + textarea + Enter the names of any sponsors and/or funding codes in the box below. + + + + + dc + description + + false + + textarea + Enter any other description or comments in this box. + + + - - - - - - - - - - - - - - - - - - - - ISSN - issn - - - Other - other - - - ISMN - ismn - - - Gov't Doc # - govdoc - - - URI - uri - - - ISBN - isbn - - - - - - Animation - Animation - - - Article - Article - - - Book - Book - - - Book chapter - Book chapter - - - Dataset - Dataset - - - Learning Object - Learning Object - - - Image - Image - - - Image, 3-D - Image, 3-D - - - Map - Map - - - Musical Score - Musical Score - - - Plan or blueprint - Plan or blueprint - - - Preprint - Preprint - - - Presentation - Presentation - - - Recording, acoustical - Recording, acoustical - - - Recording, musical - Recording, musical - - - Recording, oral - Recording, oral - - - Software - Software - - - Technical Report - Technical Report - - - Thesis - Thesis - - - Video - Video - - - Working Paper - Working Paper - - - Other - Other - - - - - - - N/A - - - - English (United States) - en_US - - - English - en - - - Spanish - es - - - German - de - - - French - fr - - - Italian - it - - - Japanese - ja - - - Chinese - zh - - - Turkish - tr - - - (Other) - other - - - - - -
+ +
+ + + dc + contributor + author + true + + name + Enter the names of the authors of this item below. + + + +
+ + + + + + + + + + + + + + + + + + + + ISSN + issn + + + Other + other + + + ISMN + ismn + + + Gov't Doc # + govdoc + + + URI + uri + + + ISBN + isbn + + + + + + Animation + Animation + + + Article + Article + + + Book + Book + + + Book chapter + Book chapter + + + Dataset + Dataset + + + Learning Object + Learning Object + + + Image + Image + + + Image, 3-D + Image, 3-D + + + Map + Map + + + Musical Score + Musical Score + + + Plan or blueprint + Plan or blueprint + + + Preprint + Preprint + + + Presentation + Presentation + + + Recording, acoustical + Recording, acoustical + + + Recording, musical + Recording, musical + + + Recording, oral + Recording, oral + + + Software + Software + + + Technical Report + Technical Report + + + Thesis + Thesis + + + Video + Video + + + Working Paper + Working Paper + + + Other + Other + + + + + + Conference Paper/Proceeding/Abstract + Conference Paper/Proceeding/Abstract + + + Journal Article/Review + Journal Article/Review + + + Book + Book + + + Book chapter + Book chapter + + + Book edited + Book edited + + + Manual/Guide + Manual/Guide + + + Monograph + Monograph + + + Policy briefing report + Policy briefing report + + + Technical Report + Technical Report + + + Technical Standard + Technical Standard + + + Thesis + Thesis + + + Other + Other + + + Consultancy Report + Consultancy Report + + + Working paper + Working paper + + + + + + + + + N/A + + + + English (United States) + en_US + + + English + en + + + Spanish + es + + + German + de + + + French + fr + + + Italian + it + + + Japanese + ja + + + Chinese + zh + + + Turkish + tr + + + (Other) + other + + + + + + Not Applicable (or Unknown) + NA + + + Author's Original + AO + + + Submitted Manuscript Under Review + SMUR + + + Accepted Manuscript + AM + + + Proof + P + + + Version of Record + VoR + + + Corrected Version of Record + CVoR + + + Enhanced Version of Record + EVoR + + + + + + Unspecified + Unspecified + + + REF Main Panel A + A + + + REF Main Panel B + B + + + REF Main Panel C + C + + + REF Main Panel D + D + + + + + + \ No newline at end of file diff --git a/dspace/config/item-submission.dtd b/dspace/config/item-submission.dtd index 26c8220..0116fb4 100644 --- a/dspace/config/item-submission.dtd +++ b/dspace/config/item-submission.dtd @@ -18,7 +18,7 @@ - + @@ -26,7 +26,9 @@ - + + + diff --git a/dspace/config/item-submission.xml b/dspace/config/item-submission.xml index 99e3e10..9b511ec 100644 --- a/dspace/config/item-submission.xml +++ b/dspace/config/item-submission.xml @@ -218,17 +218,16 @@ --> - - submit.progressbar.upload - org.dspace.submit.step.UploadStep - org.dspace.app.webui.submit.step.JSPUploadStep - org.dspace.app.xmlui.aspect.submission.submit.UploadStep - true - - + + + + + + + submit.progressbar.upload org.dspace.submit.step.UploadWithEmbargoStep @@ -236,9 +235,19 @@ org.dspace.app.xmlui.aspect.submission.submit.UploadWithEmbargoStep true - --> - + + + + submit.progressbar.refexception + com.atmire.ref.compliance.submission.REFExceptionStep + com.atmire.xmlui.compliance.submission.REFExceptionStep + true + condition.ref.applicable.step + + + + submit.progressbar.verify org.dspace.submit.step.VerifyStep @@ -259,7 +268,26 @@ --> - + + + submit.progressbar.ref.compliance + org.dspace.ref.compliance.submission.REFComplianceStep + com.atmire.xmlui.compliance.submission.REFComplianceStep + true + condition.ref.applicable.step + + + + submit.progressbar.rioxx.compliance + org.dspace.ref.compliance.submission.RIOXXComplianceStep + com.atmire.xmlui.compliance.submission.RIOXXComplianceStep + true + condition.ref.applicable.step + false + + + + submit.progressbar.license org.dspace.submit.step.LicenseStep @@ -267,9 +295,9 @@ org.dspace.app.xmlui.aspect.submission.submit.LicenseStep false + - diff --git a/dspace/config/item-validation-rules.xsd b/dspace/config/item-validation-rules.xsd new file mode 100644 index 0000000..6940cf4 --- /dev/null +++ b/dspace/config/item-validation-rules.xsd @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dspace/config/modules/curate.cfg b/dspace/config/modules/curate.cfg index 43e7ad4..3bef754 100644 --- a/dspace/config/modules/curate.cfg +++ b/dspace/config/modules/curate.cfg @@ -12,7 +12,8 @@ plugin.named.org.dspace.curate.CurationTask = \ org.dspace.ctask.general.RequiredMetadata = requiredmetadata, \ org.dspace.ctask.general.ClamScan = vscan, \ org.dspace.ctask.general.MicrosoftTranslator = translate, \ - org.dspace.ctask.general.MetadataValueLinkChecker = checklinks + org.dspace.ctask.general.MetadataValueLinkChecker = checklinks, \ + com.atmire.openaccess.ctask.DateOfCompliantOpenAccessTask = openaccess # add new tasks here ## task queue implementation @@ -30,7 +31,8 @@ taskqueue.dir = ${dspace.dir}/ctqueues ui.tasknames = \ profileformats = Profile Bitstream Formats, \ requiredmetadata = Check for Required Metadata, \ - checklinks = Check Links in Metadata + checklinks = Check Links in Metadata, \ + openaccess = Update Date of Compiant Open Access # Tasks may be organized into named groups which display together in UI drop-downs # ui.taskgroups = \ diff --git a/dspace/config/modules/item-compliance.cfg b/dspace/config/modules/item-compliance.cfg new file mode 100644 index 0000000..5aec0c9 --- /dev/null +++ b/dspace/config/modules/item-compliance.cfg @@ -0,0 +1,29 @@ +# If true a submitter will not be able to continue the submission after the compliance +# step until all violations have been resolved +# submission.block.on.rule.violation.default is the default configuration for all collections +# Add a collection specific config by adding a property submission.block.on.rule.violation.{handle} with the handle of the collection +ref.submission.block.on.rule.violation.default = false +#ref.submission.block.on.rule.violation.123456789/3 = true + +rioxx.submission.block.on.rule.violation.default = false +#rioxx.submission.block.on.rule.violation.123456789/3 = false + +# If true a workflow reviewer will not be able to continue the workflow after the compliance +# step until all violations have been resolved. +# workflow.block.on.rule.violation.default is the default configuration for all collections +# Add a collection specific config by adding a property workflow.block.on.rule.violation.{handle} with the handle of the collection +ref.workflow.block.on.rule.violation.default = false +#ref.workflow.block.on.rule.violation.123456789/2 = true + +rioxx.workflow.block.on.rule.violation.default = false +#rioxx.workflow.block.on.rule.violation.123456789/3 = false + +# fields shown in the "REF Compliance Related Data" section during the compiance step +related.data.field.1 = rioxxterms.type +related.data.field.2 = dcterms.dateAccepted +related.data.field.3 = dc.date.issued +related.data.field.4 = refterms.dateFirstOnline +related.data.field.5 = refterms.dateFCD +related.data.field.6 = refterms.versionFCD +related.data.field.7 = refterms.dateFOA +related.data.field.8 = refterms.panel \ No newline at end of file diff --git a/dspace/config/modules/submission.cfg b/dspace/config/modules/submission.cfg new file mode 100644 index 0000000..5a7cbc7 --- /dev/null +++ b/dspace/config/modules/submission.cfg @@ -0,0 +1,2 @@ +#configure the field used to match the type-bind values from input-forms.xml +type.bind.field = rioxxterms.type \ No newline at end of file diff --git a/dspace/config/modules/workflow.cfg b/dspace/config/modules/workflow.cfg index 65c4bff..9fe0bba 100644 --- a/dspace/config/modules/workflow.cfg +++ b/dspace/config/modules/workflow.cfg @@ -8,9 +8,11 @@ # Possible values: # originalworkflow = Traditional DSpace Workflow # xmlworkflow = New (as of 1.8.0) Configurable Reviewer Workflow -workflow.framework=originalworkflow -#workflow.framework=xmlworkflow +#workflow.framework=originalworkflow +workflow.framework=xmlworkflow #Allow the reviewers to add/edit/remove files from the submission #When changing this property you might want to alert submitters in the license that reviewers can alter their files -reviewer.file-edit=false \ No newline at end of file +reviewer.file-edit=true + +compliant.ref.metadata.updates= true \ No newline at end of file diff --git a/dspace/config/ref-validation-rules.xml b/dspace/config/ref-validation-rules.xml new file mode 100644 index 0000000..66825a0 --- /dev/null +++ b/dspace/config/ref-validation-rules.xml @@ -0,0 +1,422 @@ + + + + + + + rioxxterms.type + RIOXX type + + Conference Paper/Proceeding/Abstract + Conference Paper/Proceeding/Abstract + + + Journal Article/Review + Journal Article/Review + + + + + dcterms.dateAccepted + date of acceptance + + 2016-04-01T00:00:00+01:00 + 1 April 2016 + + + + + + + + + + + + rioxxterms.type + RIOXX type + + Conference Paper/Proceeding/Abstract + Conference Paper/Proceeding/Abstract + + + + dc.identifier.issn + ISSN identifier + + + + + bitstream.count + attached files + + 0 + + + + + + refterms.versionFCD + version of the deposited file + + AM + Accepted Manuscript + + + P + Proof + + + VoR + Version of Record + + + CVoR + Corrected Version of Record + + + EVoR + Enhanced Version of Record + + + + + + dcterms.dateAccepted + refterms.dateFCD + time between acceptance and deposit + + 3 + + + + + + + + dcterms.dateAccepted + dateof acceptance + + 2017-04-01T23:59:59+01:00 + 1 April 2017 + + + + dc.date.issued + refterms.dateFCD + time between print publication and deposit + + 3 + + + + + + dcterms.dateAccepted + dateof acceptance + + 2017-04-01T23:59:59+01:00 + 1 April 2017 + + + + refterms.dateFirstOnline + refterms.dateFCD + time between first online publication and deposit + + 3 + + + + + + + + refterms.depositException + deposit exception + + noRepositoryAtAcceptance + The individual whose output is being submitted to the REF was unable to secure the use of a repository at the point of acceptance + + + delaySecuringText + The individual whose output is being submitted to the REF experienced a delay in securing the final peer-reviewed text (for instance, where a paper has multiple authors) + + + notEmployedAtUKHEI + The individual whose output is being submitted to the REF was not employed by a UK HEI at the time of submission for publication + + + unlawfulDeposit + It would be unlawful to deposit, or request the deposit of, the output + + + securityRisk + Depositing the output would present a security risk + + + publishedGoldOA + The output was published as 'gold' open access (e.g. RCUK-funded projects where an open access article processing charge has been paid) + + + + + + + + + + + item.status + item lifecycle status + + workspace + + + workflow + + + + + + + + + + + + + + bitstream.embargo.enabled + released under embargo + + false + + + + refterms.dateFCD + refterms.dateFOA + time between the compliant deposit and the first open access availability + + 1 + + + + + + + + bitstream.embargo.enabled + released under embargo + + true + + + + bitstream.embargo.enddate + refterms.dateFOA + time between the embargo end date and the first open access availability + + 1 + + + + + + + + + bitstream.embargo.enabled + released under embargo + + true + + + + refterms.panel + target REF panel + + A + + + + dc.date.issued + bitstream.embargo.enddate + the embargo period + + 12 + + + + + + + + bitstream.embargo.enabled + released under embargo + + true + + + + refterms.panel + target REF panel + + B + + + + dc.date.issued + bitstream.embargo.enddate + the embargo period + + 12 + + + + + + + + bitstream.embargo.enabled + released under embargo + + true + + + + refterms.panel + target REF panel + + C + + + + dc.date.issued + bitstream.embargo.enddate + the embargo period + + 24 + + + + + + + + bitstream.embargo.enabled + released under embargo + + true + + + + refterms.panel + target REF panel + + D + + + + dc.date.issued + bitstream.embargo.enddate + the embargo period + + 24 + + + + + + refterms.accessException + access exception + + thirdPartyRights + The output depends on the reproduction of third party content for which open access rights could not be granted (either within the specified timescales, or at all) + + + publicationExceedsMaxEmbargo + The publication concerned requires an embargo period that exceeds the stated maxima, and was the most appropriate publication for the output + + + publicationDisallowsOA + The publication concerned actively disallows open-access deposit in a repository, and was the most appropriate publication for the output + + + + + + + + refterms.technicalException + technical exception + + conferenceOutsideDefinition + Output is a conference proceeding, but not within definition (i.e., it does not have an ISSN, or the proceedings are published as part of book series) + + + differentUKHEI + At the point of acceptance, the individual whose output is being submitted to the REF was at a different UK HEI which failed to comply with the criteria + + + technicalFailure + The repository experienced a short term or transient technical failure that prevented compliance with the criteria (this should not apply to systemic issues) + + + externalServiceProvider + An external service provider failure prevented compliance (for instance, a subject repository did not enable open access at the end of the embargo period, or a subject repository ceased to operate) + + + + refterms.exceptionFreeText + other exception + + + diff --git a/dspace/config/registries/refterms-types.xml b/dspace/config/registries/refterms-types.xml new file mode 100644 index 0000000..8e26f7c --- /dev/null +++ b/dspace/config/registries/refterms-types.xml @@ -0,0 +1,97 @@ + + + + REFterms Types Registry + + + + refterms + http://www.dspace.org/refterms + + + + refterms + technicalException + + + + + + refterms + technicalExceptionExplanation + + + + + + refterms + dateFCD + + + + + + refterms + versionFCD + + + + + + refterms + dateFOA + + + + + + refterms + panel + + + + + + refterms + depositException + + + + + + refterms + depositExceptionExplanation + + + + + + refterms + accessException + + + + + + refterms + accessExceptionExplanation + + + + + + refterms + exceptionFreeText + + + + + + refterms + dateFirstOnline + + + + + + diff --git a/dspace/config/registries/rioxxterms-types.xml b/dspace/config/registries/rioxxterms-types.xml new file mode 100644 index 0000000..0ed30cb --- /dev/null +++ b/dspace/config/registries/rioxxterms-types.xml @@ -0,0 +1,77 @@ + + + + RIOXXTerms Types Registry + + + + rioxxterms + http://www.dspace.org/rioxxterms + + + + rioxxterms + funder + + + + + + rioxxterms + identifier + project + + + + + rioxxterms + apc + + + + + + rioxxterms + version + + + + + + + rioxxterms + versionofrecord + + + + + + rioxxterms + licenseref + uri + + + + + rioxxterms + licenseref + startdate + + + + + rioxxterms + type + + + + + + rioxxterms + openaccess + uri + URL to the full text publication + + + + diff --git a/dspace/config/rioxx-validation-rules.xml b/dspace/config/rioxx-validation-rules.xml new file mode 100644 index 0000000..41d9683 --- /dev/null +++ b/dspace/config/rioxx-validation-rules.xml @@ -0,0 +1,150 @@ + + + + + + + dc.title + title + + + dc.contributor.author + author + + + dc.language.iso + language + + + dcterms.dateAccepted + date of acceptance + + + rioxxterms.type + RIOXX type + + + rioxxterms.version + RIOXX version of the deposited file + + AO + Author's Original + + + SMUR + Submitted Manuscript Under Review + + + AM + Accepted Manuscript + + + P + Proof + + + VoR + Version of Record + + + CVoR + Corrected Version of Record + + + EVoR + Enhanced Version of Record + + + NA + Not Applicable (or Unknown) + + + + rioxxterms.licenseref.uri + dc.rights.uri + rights URI + + + + + + + + + bitstream.count + attached files + + 0 + + + + rioxxterms.openaccess.uri + open access uri + + + + + + + item.status + item lifecycle status + + workspace + + + workflow + + + + + + + + + + + + + rioxxterms.identifier.project + project identifier + + + rioxxterms.funder + funder + + + + + + diff --git a/dspace/config/spring/api/authorization-service.xml b/dspace/config/spring/api/authorization-service.xml new file mode 100644 index 0000000..666e98c --- /dev/null +++ b/dspace/config/spring/api/authorization-service.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + REF Compliance Viewers + + + + + diff --git a/dspace/config/spring/api/custom-service-beans.xml b/dspace/config/spring/api/custom-service-beans.xml new file mode 100644 index 0000000..23469cc --- /dev/null +++ b/dspace/config/spring/api/custom-service-beans.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + diff --git a/dspace/config/spring/api/item-validation-services.xml b/dspace/config/spring/api/item-validation-services.xml new file mode 100644 index 0000000..f56fa4c --- /dev/null +++ b/dspace/config/spring/api/item-validation-services.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AM + P + VoR + CVoR + EVoR + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dspace/config/spring/api/refterms-exceptions.xml b/dspace/config/spring/api/refterms-exceptions.xml new file mode 100644 index 0000000..fa0ffce --- /dev/null +++ b/dspace/config/spring/api/refterms-exceptions.xml @@ -0,0 +1,118 @@ + + + + + + + + depositException + accessException + technicalException + exceptionFreeText + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace/config/spring/api/type-dependent-submissions.xml b/dspace/config/spring/api/type-dependent-submissions.xml new file mode 100644 index 0000000..9ea54fa --- /dev/null +++ b/dspace/config/spring/api/type-dependent-submissions.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + Conference Paper/Proceeding/Abstract + Journal Article/Review + + + + + + diff --git a/dspace/config/spring/api/workflow-actions.xml b/dspace/config/spring/api/workflow-actions.xml index c63ffe7..c9daa2b 100644 --- a/dspace/config/spring/api/workflow-actions.xml +++ b/dspace/config/spring/api/workflow-actions.xml @@ -12,6 +12,9 @@ + + + @@ -63,6 +66,20 @@ + + + + + + + + + + + + + + diff --git a/dspace/config/spring/xmlui/item-validation-services.xml b/dspace/config/spring/xmlui/item-validation-services.xml new file mode 100644 index 0000000..9964a64 --- /dev/null +++ b/dspace/config/spring/xmlui/item-validation-services.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dspace/config/spring/xmlui/workflow-actions-xmlui.xml b/dspace/config/spring/xmlui/workflow-actions-xmlui.xml index a7ef40f..6f4c0b9 100644 --- a/dspace/config/spring/xmlui/workflow-actions-xmlui.xml +++ b/dspace/config/spring/xmlui/workflow-actions-xmlui.xml @@ -17,4 +17,8 @@ + + + + diff --git a/dspace/config/workflow.xml b/dspace/config/workflow.xml index 48707d5..7dbdba7 100644 --- a/dspace/config/workflow.xml +++ b/dspace/config/workflow.xml @@ -25,17 +25,39 @@ - finaleditstep + refcompliancestep + + + finaleditstep + editstep + + + + + + + + + + + + + + + + + + diff --git a/dspace/config/xmlui.xconf b/dspace/config/xmlui.xconf index c0a4bab..d3d96f9 100644 --- a/dspace/config/xmlui.xconf +++ b/dspace/config/xmlui.xconf @@ -86,6 +86,7 @@ + - + - + - + diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index ff7c55f..ae5f6f6 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -1,14 +1,15 @@ - - 4.0.0 - org.dspace.modules - additions - DSpace Kernel :: Additions and Local Customizations - - This project allows you to easily create your own "additions.jar" - including custom Java classes. This JAR will be automatically included - in all DSpace web applications. - - jar + + 4.0.0 + org.dspace.modules + additions + DSpace Kernel :: Additions and Local Customizations + + This project allows you to easily create your own "additions.jar" + including custom Java classes. This JAR will be automatically included + in all DSpace web applications. + + jar - ${basedir}/../../.. - + + + ${basedir}/../../.. + - - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - + + + oracle-support + + + db.name + oracle + + + + + com.oracle + ojdbc6 + + + - - - xpdf-mediafilter-support - - false - - - - com.sun.media - jai_imageio - - - javax.media - jai_core - - - + + + xpdf-mediafilter-support + + false + + + + com.sun.media + jai_imageio + + + javax.media + jai_core + + + - + - - - - org.dspace - dspace-api - - - org.dspace - dspace-api-lang - - - javax.servlet - servlet-api - provided - - - ant-contrib - ant-contrib - 1.0b3 - - - ant - ant - - - + + + + org.mockito + mockito-core + 1.10.19 + test + + + org.dspace + dspace-api + + + org.dspace + dspace-api-lang + + + javax.servlet + servlet-api + provided + + + ant-contrib + ant-contrib + 1.0b3 + + + ant + ant + + + + + - + + + + org.codehaus.mojo + jaxb2-maven-plugin + 2.2 + + + xjc-compliance-definition + + xjc + + + org.dspace.ref.compliance.definition.model + + ../../config/item-validation-rules.xsd + + + + + + + diff --git a/dspace/modules/additions/src/main/java/com/atmire/authorization/AuthorizationChecker.java b/dspace/modules/additions/src/main/java/com/atmire/authorization/AuthorizationChecker.java new file mode 100644 index 0000000..fd876bc --- /dev/null +++ b/dspace/modules/additions/src/main/java/com/atmire/authorization/AuthorizationChecker.java @@ -0,0 +1,35 @@ +package com.atmire.authorization; + +import com.atmire.authorization.checks.AuthorizationCheck; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Required; + +import java.util.List; + +/** + * Created by jonas - jonas@atmire.com on 13/04/16. + */ +public class AuthorizationChecker { + + protected List authorizationChecks; + + @Required + public void setAuthorizationChecks(List authorizationChecks) { + this.authorizationChecks = authorizationChecks; + } + + public boolean checkAuthorization(Context context,DSpaceObject dso){ + + if(context.getCurrentUser()==null || dso==null){ + return false; + } + for(AuthorizationCheck check : authorizationChecks){ + if(check.checkAuthorization(context, dso)){ + return true; + } + } + return false; + } + +} diff --git a/dspace/modules/additions/src/main/java/com/atmire/authorization/checks/AuthorizationCheck.java b/dspace/modules/additions/src/main/java/com/atmire/authorization/checks/AuthorizationCheck.java new file mode 100644 index 0000000..6ebeb30 --- /dev/null +++ b/dspace/modules/additions/src/main/java/com/atmire/authorization/checks/AuthorizationCheck.java @@ -0,0 +1,13 @@ +package com.atmire.authorization.checks; + +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; + +/** + * Created by jonas - jonas@atmire.com on 13/04/16. + */ +public interface AuthorizationCheck { + + + public boolean checkAuthorization(Context context, DSpaceObject dso); +} diff --git a/dspace/modules/additions/src/main/java/com/atmire/authorization/checks/GroupAuthorizationCheck.java b/dspace/modules/additions/src/main/java/com/atmire/authorization/checks/GroupAuthorizationCheck.java new file mode 100644 index 0000000..6af1b5a --- /dev/null +++ b/dspace/modules/additions/src/main/java/com/atmire/authorization/checks/GroupAuthorizationCheck.java @@ -0,0 +1,79 @@ +package com.atmire.authorization.checks; + +import org.apache.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.springframework.beans.factory.annotation.Required; + +import javax.annotation.PostConstruct; +import java.sql.SQLException; +import java.util.List; + +/** + * Created by jonas - jonas@atmire.com on 13/04/16. + */ +public class GroupAuthorizationCheck implements AuthorizationCheck{ + + /* Log4j logger*/ + private static final Logger log = Logger.getLogger(GroupAuthorizationCheck.class); + + private List allowedGroups; + + @PostConstruct + public void initGroups(){ + Context context = null; + try { + context = new Context(); + Group adminGroup = Group.findByName(context,"Administrator"); + context.turnOffAuthorisationSystem(); + for(String group : allowedGroups){ + Group g = Group.findByName(context,group); + if(g == null){ + Group createdGroup = Group.create(context); + createdGroup.setName(group); + createdGroup.addMember(adminGroup); + createdGroup.update(); + } + } + context.restoreAuthSystemState(); + context.commit(); + } catch (SQLException e) { + log.error("Error while checking for non-existing groups during the authorization check.",e); + } catch (AuthorizeException e) { + log.error(e); + } finally { + if(context!=null){ + context.abort(); + } + } + } + @Required + public void setAllowedGroups(List allowedGroups){ + this.allowedGroups=allowedGroups; + } + + public List getGroups(){ + return allowedGroups; + } + @Override + public boolean checkAuthorization(Context context,DSpaceObject dso) { + EPerson ePerson = context.getCurrentUser(); + + for(String group:allowedGroups){ + try { + Group g = Group.findByName(context,group); + + if(g!=null && Group.allMemberIDs(context,g).contains(ePerson.getID())){ + return true; + } + } catch (SQLException e) { + log.error(e); + } + + } + return false; + } +} diff --git a/dspace/modules/additions/src/main/java/com/atmire/authorization/checks/SubmitterCheck.java b/dspace/modules/additions/src/main/java/com/atmire/authorization/checks/SubmitterCheck.java new file mode 100644 index 0000000..1065daa --- /dev/null +++ b/dspace/modules/additions/src/main/java/com/atmire/authorization/checks/SubmitterCheck.java @@ -0,0 +1,30 @@ +package com.atmire.authorization.checks; + +import org.apache.log4j.Logger; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.core.Context; + +import java.sql.SQLException; + +/** + * Created by jonas - jonas@atmire.com on 13/04/16. + */ +public class SubmitterCheck implements AuthorizationCheck { + /* Log4j logger*/ + private static final Logger log = Logger.getLogger(SubmitterCheck.class); + + @Override + public boolean checkAuthorization(Context context, DSpaceObject dso) { + if(dso instanceof Item){ + try { + if(context.getCurrentUser()==((Item)dso).getSubmitter()){ + return true; + } + } catch (SQLException e) { + log.error("Error while checking if the current user is the submitter of item with handle: "+dso.getHandle(),e); + } + } + return false; + } +} diff --git a/dspace/modules/additions/src/main/java/com/atmire/consumer/metadata/FirstComplianceDepositConsumer.java b/dspace/modules/additions/src/main/java/com/atmire/consumer/metadata/FirstComplianceDepositConsumer.java new file mode 100644 index 0000000..f4721f3 --- /dev/null +++ b/dspace/modules/additions/src/main/java/com/atmire/consumer/metadata/FirstComplianceDepositConsumer.java @@ -0,0 +1,88 @@ +package com.atmire.consumer.metadata; + +import org.apache.log4j.Logger; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; +import org.dspace.core.ConfigurationManager; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.event.Consumer; +import org.dspace.event.Event; +import org.dspace.ref.compliance.rules.complianceupdaters.ComplianceDepositCheck; +import org.dspace.utils.DSpace; + +import java.util.HashSet; +import java.util.Set; + +/** + * Created by jonas - jonas@atmire.com on 23/03/16. + */ +public class FirstComplianceDepositConsumer implements Consumer { + + /* Log4j logger*/ + private static final Logger log = Logger.getLogger(FirstComplianceDepositConsumer.class); + + private Set itemIDs = new HashSet(); + + @Override + public void initialize() throws Exception { + + } + + @Override + public void consume(Context context, Event event) throws Exception { + int subjectType = event.getSubjectType(); + int eventType = event.getEventType(); + + DSpaceObject dso = event.getSubject(context); + + switch (subjectType) { + case Constants.ITEM: + Item item = (Item) dso; + //If we are updating the metadata and [ the item is archived or the submitter completed his submission ] + if (eventType == Event.MODIFY_METADATA && !item.isWithdrawn() && (item.isArchived() || WorkspaceItem.findByItem(context, item)==null )) { + itemIDs.add(dso.getID()); + } + break; + default: + log.debug("consume() got unrecognized event: " + event.toString()); + } + } + + /** + * Find the objects based on the IDS. + * Process them here. + */ + public void end(Context context) throws Exception { + if (itemIDs.size() >= 0) { + context.turnOffAuthorisationSystem(); + for (Integer itemID : itemIDs) { + Item item = Item.find(context, itemID); + if (item != null) { + process(context, item); + // update objects + item.update(); + // commit context + context.getDBConnection().commit(); + } + } + context.restoreAuthSystemState(); + } + itemIDs.clear(); + } + + protected void process(Context context, Item item) { + if(ConfigurationManager.getBooleanProperty("workflow", "compliant.ref.metadata.updates", true)){ + for(ComplianceDepositCheck complianceUpdate :new DSpace().getServiceManager().getServicesByType(ComplianceDepositCheck.class) ){ + complianceUpdate.checkAndUpdateCompliance(context, item); + } + } + + } + + @Override + public void finish(Context context) throws Exception { + + } +} diff --git a/dspace/modules/additions/src/main/java/com/atmire/openaccess/CompliantOpenAccessServiceImpl.java b/dspace/modules/additions/src/main/java/com/atmire/openaccess/CompliantOpenAccessServiceImpl.java new file mode 100644 index 0000000..de72d9d --- /dev/null +++ b/dspace/modules/additions/src/main/java/com/atmire/openaccess/CompliantOpenAccessServiceImpl.java @@ -0,0 +1,153 @@ +package com.atmire.openaccess; + +import com.atmire.openaccess.service.*; +import java.sql.*; +import java.util.Date; +import java.util.*; +import org.apache.commons.lang3.*; +import org.apache.log4j.*; +import org.dspace.authorize.*; +import org.dspace.content.*; +import org.dspace.core.*; +import org.dspace.core.LogManager; +import org.dspace.eperson.*; + +/** + * @author philip at atmire.com + */ +public class CompliantOpenAccessServiceImpl implements CompliantOpenAccessService { + + private static final Logger log = Logger.getLogger(CompliantOpenAccessServiceImpl.class); + + private final String fullIso = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + + private String groupname; + private String bundle; + private String schema; + private String element; + private String qualifier; + + + public String updateItem(Context context, Item item) throws SQLException, AuthorizeException { + if(item!=null && !hasDate(item)) { + + Group group = getGroup(context, groupname); + + if (group == null) { + log.error("group with name " + groupname + " could not be resolved"); + } else { + Bundle[] bundles = item.getBundles(bundle); + + int backupBitstreamID = -1; + + for (Bundle bundle : bundles) { + int primaryBitstreamID = bundle.getPrimaryBitstreamID(); + + if (primaryBitstreamID > 0) { + return updateDateOfCompliantOpenAccess(context, item, primaryBitstreamID, group); + } else if(bundle.getBitstreams() != null && bundle.getBitstreams().length > 0) { + backupBitstreamID = bundle.getBitstreams()[0].getID(); + } + } + + if(backupBitstreamID > 0) { + return updateDateOfCompliantOpenAccess(context, item, backupBitstreamID, group); + } + } + } + + return null; + } + + private String updateDateOfCompliantOpenAccess(Context context, Item item, int bitstreamId, Group group) throws SQLException, AuthorizeException { + Bitstream bitstream = Bitstream.find(context, bitstreamId); + + List policiesActionFilter = AuthorizeManager.getPoliciesActionFilter(context, bitstream, Constants.READ); + + if (policiesActionFilter.size() > 0) { + + for (ResourcePolicy resourcePolicy : policiesActionFilter) { + Group policyGroup = resourcePolicy.getGroup(); + if(policyGroup!=null) { + Date startDate = resourcePolicy.getStartDate(); + + if(group.getName().equals(policyGroup.getName())) { + if (startDate != null && startDate.before(new Date())) { + addDate(item, new DCDate(startDate)); + } else if (startDate == null) { + addDate(item, DCDate.getCurrent()); + } + } + } + } + } + + return null; + } + + private String addDate(Item item, DCDate startDate) throws SQLException, AuthorizeException { + item.addMetadata(schema, element, qualifier, null, startDate.toString()); +// item.addMetadata(schema, element, qualifier, null, DateFormatUtils.format(startDate,fullIso)); + item.update(); + return "Compliant Open Access date " + startDate.toString() + " added to item " + item.getHandle(); + } + + private boolean hasDate(Item item){ + Metadatum[] metadata = item.getMetadata(schema, element, qualifier, Item.ANY); + + return metadata.length>0; + } + + private Group getGroup(Context context, String name) throws SQLException { + Group group = null; + + if (StringUtils.isNotBlank(name)) { + group = Group.findByName(context, name); + if (group == null) { + log.warn(LogManager.getHeader(context, "group with name " + name + " could not be resolved", "")); + } + } + + return group; + } + + public String getGroupname() { + return groupname; + } + + public void setGroupname(String groupname) { + this.groupname = groupname; + } + + public String getBundle() { + return bundle; + } + + public void setBundle(String bundle) { + this.bundle = bundle; + } + + public String getSchema() { + return schema; + } + + public void setSchema(String schema) { + this.schema = schema; + } + + public String getElement() { + return element; + } + + public void setElement(String element) { + this.element = element; + } + + public String getQualifier() { + return qualifier; + } + + public void setQualifier(String qualifier) { + this.qualifier = qualifier; + } +} diff --git a/dspace/modules/additions/src/main/java/com/atmire/openaccess/DateOfCompliantOpenAccessConsumer.java b/dspace/modules/additions/src/main/java/com/atmire/openaccess/DateOfCompliantOpenAccessConsumer.java new file mode 100644 index 0000000..2e06000 --- /dev/null +++ b/dspace/modules/additions/src/main/java/com/atmire/openaccess/DateOfCompliantOpenAccessConsumer.java @@ -0,0 +1,82 @@ +package com.atmire.openaccess; + +import com.atmire.openaccess.service.*; +import java.sql.*; +import java.util.*; +import org.apache.log4j.*; +import org.dspace.content.*; +import org.dspace.core.*; +import org.dspace.event.*; +import org.dspace.utils.*; + +/** + * @author philip at atmire.com + */ +public class DateOfCompliantOpenAccessConsumer implements Consumer { + + private static final Logger log = Logger.getLogger(DateOfCompliantOpenAccessConsumer.class); + + private Set itemsToUpdate = new HashSet(); + + private CompliantOpenAccessService compliantOpenAccessService = new DSpace().getServiceManager().getServiceByName("CompliantOpenAccessService",CompliantOpenAccessService.class); + + @Override + public void initialize() throws Exception { + + } + + @Override + public void consume(Context ctx, Event event) throws Exception { + DSpaceObject dso = event.getSubject(ctx); + int et = event.getEventType(); + + Item item = null; + + if(dso instanceof Bitstream && et == Event.MODIFY){ + Bitstream bitstream = (Bitstream) dso; + item = getParentItem(bitstream); + } + else if(dso instanceof Item && et == Event.INSTALL){ + item = (Item) dso; + } + + if(item!=null && item.isArchived()){ + itemsToUpdate.add(item.getID()); + } + } + + @Override + public void end(Context ctx) throws Exception { + ctx.turnOffAuthorisationSystem(); + for (Integer id : itemsToUpdate) { + Item item = Item.find(ctx, id); + try { + compliantOpenAccessService.updateItem(ctx, item); + } + catch (Exception e){ + log.error(e.getMessage(), e); + } + } + + ctx.getDBConnection().commit(); + + ctx.restoreAuthSystemState(); + + itemsToUpdate.clear(); + } + + @Override + public void finish(Context ctx) throws Exception { + + } + + private Item getParentItem(Bitstream bitstream) throws SQLException { + DSpaceObject parentObject = bitstream.getParentObject(); + + while(parentObject!=null && parentObject.getType()!=Constants.ITEM){ + parentObject = parentObject.getParentObject(); + } + + return (Item) parentObject; + } +} diff --git a/dspace/modules/additions/src/main/java/com/atmire/openaccess/ctask/DateOfCompliantOpenAccessTask.java b/dspace/modules/additions/src/main/java/com/atmire/openaccess/ctask/DateOfCompliantOpenAccessTask.java new file mode 100644 index 0000000..bf88eff --- /dev/null +++ b/dspace/modules/additions/src/main/java/com/atmire/openaccess/ctask/DateOfCompliantOpenAccessTask.java @@ -0,0 +1,57 @@ +package com.atmire.openaccess.ctask; + +import com.atmire.openaccess.service.*; +import java.io.*; +import org.apache.commons.lang3.*; +import org.apache.log4j.*; +import org.dspace.content.*; +import org.dspace.core.*; +import org.dspace.curate.*; +import org.dspace.utils.*; + +/** + * Created by Philip Vissenaekens (philip at atmire dot com) + * Date: 15/03/16 + * Time: 16:55 + */ +public class DateOfCompliantOpenAccessTask extends AbstractCurationTask { + + private static final Logger log = Logger.getLogger(DateOfCompliantOpenAccessTask.class); + + private CompliantOpenAccessService compliantOpenAccessService = new DSpace().getServiceManager().getServiceByName("CompliantOpenAccessService",CompliantOpenAccessService.class); + + public DateOfCompliantOpenAccessTask() { + + } + + @Override + public void init(Curator curator, String taskId) throws IOException { + super.init(curator, taskId); + } + + @Override + public int perform(DSpaceObject dso) throws IOException { + if (dso instanceof Item) { + try { + Item item = (Item) dso; + Context context = Curator.curationContext(); + + String result = compliantOpenAccessService.updateItem(context, item); + + if(StringUtils.isNotBlank(result)) { + setResult(result); + } + + context.commit(); + } catch (Exception e) { + log.error(e.getMessage(), e); + return Curator.CURATE_ERROR; + } + } + + return Curator.CURATE_SUCCESS; + } + + + +} diff --git a/dspace/modules/additions/src/main/java/com/atmire/openaccess/service/CompliantOpenAccessService.java b/dspace/modules/additions/src/main/java/com/atmire/openaccess/service/CompliantOpenAccessService.java new file mode 100644 index 0000000..d306983 --- /dev/null +++ b/dspace/modules/additions/src/main/java/com/atmire/openaccess/service/CompliantOpenAccessService.java @@ -0,0 +1,14 @@ +package com.atmire.openaccess.service; + +import java.sql.*; +import org.dspace.authorize.*; +import org.dspace.content.*; +import org.dspace.core.*; + +/** + * @author philip at atmire.com + */ +public interface CompliantOpenAccessService { + + public String updateItem(Context context, Item item) throws SQLException, IllegalArgumentException, AuthorizeException; +} diff --git a/dspace/modules/additions/src/main/java/com/atmire/ref/compliance/submission/ExceptionInformation.java b/dspace/modules/additions/src/main/java/com/atmire/ref/compliance/submission/ExceptionInformation.java new file mode 100644 index 0000000..534d0b1 --- /dev/null +++ b/dspace/modules/additions/src/main/java/com/atmire/ref/compliance/submission/ExceptionInformation.java @@ -0,0 +1,31 @@ +package com.atmire.ref.compliance.submission; + +import org.springframework.beans.factory.annotation.Required; + +/** + * Created by jonas - jonas@atmire.com on 03/05/16. + */ +public class ExceptionInformation { + + private String exceptionSubField; + private String helpText; + + public String getExceptionSubField() { + return exceptionSubField; + } + @Required + public void setExceptionSubField(String exceptionSubField) { + this.exceptionSubField = exceptionSubField; + } + + public String getHelpText() { + return helpText; + } + + @Required + public void setHelpText(String helpText) { + this.helpText = helpText; + } + + +} diff --git a/dspace/modules/additions/src/main/java/com/atmire/ref/compliance/submission/REFExceptionStep.java b/dspace/modules/additions/src/main/java/com/atmire/ref/compliance/submission/REFExceptionStep.java new file mode 100644 index 0000000..a640b2d --- /dev/null +++ b/dspace/modules/additions/src/main/java/com/atmire/ref/compliance/submission/REFExceptionStep.java @@ -0,0 +1,63 @@ +package com.atmire.ref.compliance.submission; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.util.SubmissionInfo; +import org.dspace.app.util.Util; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.submit.AbstractProcessingStep; +import org.dspace.utils.DSpace; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.sql.SQLException; + +/** + * Created by jonas - jonas@atmire.com on 08/04/16. + */ +public class REFExceptionStep extends AbstractProcessingStep { + + + + @Override + public int doProcessing(Context context, HttpServletRequest request, HttpServletResponse response, SubmissionInfo subInfo) throws ServletException, IOException, SQLException, AuthorizeException { + String buttonPressed = Util.getSubmitButton(request, NEXT_BUTTON); + Item item = subInfo.getSubmissionItem().getItem(); + + if(StringUtils.isNotBlank(buttonPressed)) { + String selectedOption = request.getParameter("exception-options"); + if(StringUtils.isNotBlank(selectedOption)){ + String explanation = request.getParameter(selectedOption+"Explanation"); + clearExistingExceptions(item); + if(StringUtils.isNotBlank(explanation)){ + item.addMetadata("refterms",selectedOption.equals("exceptionFreeText")?selectedOption:selectedOption+"Explanation",null,null,explanation); + } + String selectedDropdown = request.getParameter(selectedOption+"-dropdown"); + if(StringUtils.isNotBlank(selectedDropdown)){ + item.addMetadata("refterms",selectedOption,null,null,selectedDropdown); + } + item.update(); + context.commit(); + } + } + + return STATUS_COMPLETE; + } + + private void clearExistingExceptions(Item item) { + String[] specifiedExceptions = new DSpace().getServiceManager().getServiceByName("configuredExceptions", String[].class); + for(int i = 0; i conditions = wrapper.getSubmissionStepConditions(); + + List conditionsMet = new ArrayList(); + for (SubmissionStepCondition condition : conditions) { + conditionsMet.add(condition.conditionMet(item)); + } + return !conditionsMet.contains(false); + } + +} diff --git a/dspace/modules/additions/src/main/java/com/atmire/submission/typebound/condition/ConditionWrapper.java b/dspace/modules/additions/src/main/java/com/atmire/submission/typebound/condition/ConditionWrapper.java new file mode 100644 index 0000000..a821613 --- /dev/null +++ b/dspace/modules/additions/src/main/java/com/atmire/submission/typebound/condition/ConditionWrapper.java @@ -0,0 +1,22 @@ +package com.atmire.submission.typebound.condition; + +import org.springframework.beans.factory.annotation.Required; + +import java.util.List; + +/** + * Created by jonas - jonas@atmire.com on 11/04/16. + */ +public class ConditionWrapper { + + private List submissionStepConditions; + + @Required + public void setSubmissionStepConditions(List submissionStepConditions){ + this.submissionStepConditions=submissionStepConditions; + } + + public List getSubmissionStepConditions(){ + return submissionStepConditions; + } +} diff --git a/dspace/modules/additions/src/main/java/com/atmire/submission/typebound/condition/MetadataCondition.java b/dspace/modules/additions/src/main/java/com/atmire/submission/typebound/condition/MetadataCondition.java new file mode 100644 index 0000000..c2b2408 --- /dev/null +++ b/dspace/modules/additions/src/main/java/com/atmire/submission/typebound/condition/MetadataCondition.java @@ -0,0 +1,36 @@ +package com.atmire.submission.typebound.condition; + +import com.atmire.utils.Metadatum; +import java.util.*; +import org.dspace.content.*; +import org.springframework.beans.factory.annotation.*; + +/** + * Created by jonas - jonas@atmire.com on 11/04/16. + */ +public class MetadataCondition implements SubmissionStepCondition { + + private Metadatum metadatum; + private List allowedValues; + + @Required + public void setMetadatum(String metadata) { + this.metadatum = new Metadatum(metadata); + } + + @Required + public void setAllowedValues(List allowedValues) { + this.allowedValues = allowedValues; + } + + @Override + public boolean conditionMet(Item item) { + org.dspace.content.Metadatum[] dcValues = item.getMetadataByMetadataString(metadatum.toString()); + for (org.dspace.content.Metadatum dcValue : dcValues) { + if(allowedValues.contains(dcValue.value)){ + return true; + } + } + return false; + } +} diff --git a/dspace/modules/additions/src/main/java/com/atmire/submission/typebound/condition/SubmissionStepCondition.java b/dspace/modules/additions/src/main/java/com/atmire/submission/typebound/condition/SubmissionStepCondition.java new file mode 100644 index 0000000..590f0f5 --- /dev/null +++ b/dspace/modules/additions/src/main/java/com/atmire/submission/typebound/condition/SubmissionStepCondition.java @@ -0,0 +1,11 @@ +package com.atmire.submission.typebound.condition; + +import org.dspace.content.Item; + +/** + * Created by jonas - jonas@atmire.com on 11/04/16. + */ +public interface SubmissionStepCondition { + + public boolean conditionMet(Item item); +} diff --git a/dspace/modules/additions/src/main/java/com/atmire/utils/EmbargoUtils.java b/dspace/modules/additions/src/main/java/com/atmire/utils/EmbargoUtils.java new file mode 100644 index 0000000..82a9795 --- /dev/null +++ b/dspace/modules/additions/src/main/java/com/atmire/utils/EmbargoUtils.java @@ -0,0 +1,67 @@ +package com.atmire.utils; + +import java.sql.*; +import java.util.Date; +import org.apache.log4j.*; +import org.dspace.authorize.*; +import org.dspace.content.*; +import org.dspace.core.*; + +/** + * Created by Roeland Dillen (roeland at atmire dot com) + * Date: 16/04/13 + * Time: 14:22 + */ +public class EmbargoUtils { + public static final int ANONYMOUS_GROUP_ID = 0; + + private static Logger log = Logger.getLogger(EmbargoUtils.class); + + + public static Date getEmbargo(Bitstream bitstream, Context context) { + java.util.List policiesByDSOAndType = null; + try { + policiesByDSOAndType = AuthorizeManager.getPoliciesActionFilter(context, bitstream, Constants.READ); + } catch (SQLException e) { + return null; + } + + for(ResourcePolicy pol:policiesByDSOAndType){ + //This will be the start date of the Anonymous policy: Anonymous will get read access on + Date date=pol.getStartDate(); + if(date!=null) + return date; + + } + return null; + } + + public static Date getLastEmbargo(Item item, Context context){ + Date lastEmbargo=null; + try{ + Bitstream[] bitstreams = item.getNonInternalBitstreams(); + + for (Bitstream bitstream : bitstreams) { + java.util.List policiesByDSOAndType = AuthorizeManager.getPoliciesActionFilter(context, bitstream, Constants.READ); + + for(ResourcePolicy pol:policiesByDSOAndType){ + if (pol.getGroupID() == ANONYMOUS_GROUP_ID) { + //This will be the start date of the Anonymous policy: Anonymous will get read access on + Date date=pol.getStartDate(); + if(date!=null) + if(lastEmbargo==null){ + lastEmbargo=date; + } else if(date.after(lastEmbargo)) { + lastEmbargo=date; + } + } + } + } + + }catch(Exception e){ + log.error("error in in getting embargo action", e); + + } + return lastEmbargo; + } +} diff --git a/dspace/modules/additions/src/main/java/com/atmire/utils/Metadatum.java b/dspace/modules/additions/src/main/java/com/atmire/utils/Metadatum.java new file mode 100644 index 0000000..d91a939 --- /dev/null +++ b/dspace/modules/additions/src/main/java/com/atmire/utils/Metadatum.java @@ -0,0 +1,102 @@ +package com.atmire.utils; + +import org.dspace.content.MetadataField; + +/** + * Created by Roeland Dillen (roeland at atmire dot com) + * Date: 29/11/12 + * Time: 15:39 + */ +public class Metadatum { + + + private String schema; + private String element; + private String qualifier = null; + + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + + sb.append(schema).append('.'); + sb.append(element); + if(qualifier!=null) sb.append(".").append(qualifier); + + return sb.toString(); + } + + public Metadatum(org.dspace.content.Metadatum value){ + this(value.schema,value.element,value.qualifier); + } + + public Metadatum(String schema, String element, String qualifier) { + this.schema = schema; + this.element = element; + this.qualifier = qualifier; + } + + public Metadatum(String schema, MetadataField field){ + this(schema,field.getElement(),field.getQualifier()); + + } + + public Metadatum(String full) { + String elements[]=full.split("\\."); + if(elements.length==2){ + this.schema = elements[0]; + this.element =elements[1]; + } else if(elements.length==3){ + this.schema = elements[0]; + this.element =elements[1]; + this.qualifier = elements[2]; + } + + } + + public String getSchema() { + return schema; + } + + public void setSchema(String schema) { + this.schema = schema; + } + + public String getElement() { + return element; + } + + public void setElement(String element) { + this.element = element; + } + + public String getQualifier() { + return qualifier; + } + + public void setQualifier(String qualifier) { + this.qualifier = qualifier; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Metadatum that = (Metadatum) o; + + if (!element.equals(that.element)) return false; + if (qualifier != null ? !qualifier.equals(that.qualifier) : that.qualifier != null) return false; + if (!schema.equals(that.schema)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = schema.hashCode(); + result = 31 * result + element.hashCode(); + result = 31 * result + (qualifier != null ? qualifier.hashCode() : 0); + return result; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/app/util/SubmissionConfig.java b/dspace/modules/additions/src/main/java/org/dspace/app/util/SubmissionConfig.java new file mode 100644 index 0000000..0089bd5 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/app/util/SubmissionConfig.java @@ -0,0 +1,177 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.util; + +import com.atmire.submission.typebound.check.*; +import java.io.*; +import java.util.*; +import org.apache.commons.lang3.*; +import org.apache.log4j.*; + +/** + * Class representing a single Item Submission config definition, organized into + * steps. This class represents the structure of a single 'submission-process' + * node in the item-submission.xml configuration file. + * + * Note: Implements Serializable as it will be saved to the current session during submission. + * Please ensure that nothing is added to this class that isn't also serializable + * + * @see SubmissionConfigReader + * @see SubmissionStepConfig + * + * @author Tim Donohue, based on DCInputSet by Brian S. Hughes + * @version $Revision$ + */ + +public class SubmissionConfig implements Serializable +{ + /** name of the item submission process */ + private String submissionName = null; + + /** the configuration classes for the steps in this submission process */ + private SubmissionStepConfig[] submissionSteps = null; + + /** whether or not this submission process is being used in a workflow * */ + private boolean isWorkflow = false; + + /** log4j logger */ + private static Logger log = Logger.getLogger(SubmissionConfig.class); + + /** + * Constructs a new Submission Configuration object, based on the XML + * configuration file (item-submission.xml) + * @param submissionName + * the submission process name + * @param steps + * the vector listing of step information to build + * SubmissionStepConfig objects for this submission process + * @param subInfo + * the submission info + */ + public SubmissionConfig(String submissionName, List> steps, + SubmissionInfo subInfo) + { + this.submissionName = submissionName; + this.isWorkflow = subInfo.isInWorkflow(); + + // initialize a vector of SubmissionStepConfig objects + List stepConfigs = new ArrayList(); + + // loop through our steps, and create SubmissionStepConfig objects + for (int stepNum = 0; stepNum < steps.size(); stepNum++) + { + Map stepInfo = steps.get(stepNum); + SubmissionStepConfig step = new SubmissionStepConfig(stepInfo); + + // Only add this step to the process if either: + // (a) this is not a workflow process OR + // (b) this is a workflow process, and this step is editable in a + // workflow + if ((!this.isWorkflow && step.isSubmissionEditable()) + || ((this.isWorkflow) && step.isWorkflowEditable())) + { + boolean typeBoundAndValid = true; + if(StringUtils.isNotBlank(step.getTypeBindingConfig())){ + String typeBindingConfig = step.getTypeBindingConfig(); + SubmissionStepConditionCheck conditionCheck = new org.dspace.utils.DSpace().getServiceManager().getServiceByName("SubmissionStepConditionCheck",SubmissionStepConditionCheck.class); + typeBoundAndValid= conditionCheck.allConditionsMet(subInfo.getSubmissionItem().getItem(), typeBindingConfig); + } + if (typeBoundAndValid) { + + // set the number of the step (starts at 0) and add it + step.setStepNumber(stepConfigs.size()); + stepConfigs.add(step); + + log.debug("Added step '" + step.getProcessingClassName() + + "' as step #" + step.getStepNumber() + + " of submission process " + submissionName); + } + + } + } + + // get steps as an array of Strings + submissionSteps = stepConfigs + .toArray(new SubmissionStepConfig[stepConfigs.size()]); + } + + /** + * Return the name of the item submission process definition + * + * @return the name of the submission process + */ + public String getSubmissionName() + { + return submissionName; + } + + /** + * Return the number of steps in this submission process + * + * @return number of steps + */ + public int getNumberOfSteps() + { + return submissionSteps.length; + } + + /** + * Return whether or not this submission process is being used in a + * workflow! + * + * @return true, if it's a workflow process. false, otherwise. + */ + public boolean isWorkflow() + { + return isWorkflow; + } + + /** + * Retrieve a particular Step configuration in this Item Submission Process + * configuration. The first step is numbered "0" (although step #0 is the + * implied "select collection" step). + *

+ * If you want to retrieve the step after the "select collection" step, you + * should retrieve step #1. + * + * If the specified step isn't found, null is returned. + * + * @param stepNum + * desired step to retrieve + * + * @return the SubmissionStepConfig object for the step + */ + + public SubmissionStepConfig getStep(int stepNum) + { + if ((stepNum > submissionSteps.length - 1) || (stepNum < 0)) + { + return null; + } + else + { + return submissionSteps[stepNum]; + } + } + + /** + * Returns whether or not there are more steps which follow the specified + * "stepNum". For example, if you specify stepNum=4, then this method checks + * to see if there is a Step #5. The first step is numbered "0". + * + * @param stepNum + * the current step. + * + * @return true, if a step at "stepNum+1" exists. false, otherwise. + */ + + public boolean hasMoreSteps(int stepNum) + { + return (getStep(stepNum + 1) != null); + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/app/util/SubmissionConfigReader.java b/dspace/modules/additions/src/main/java/org/dspace/app/util/SubmissionConfigReader.java new file mode 100644 index 0000000..40f1efd --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/app/util/SubmissionConfigReader.java @@ -0,0 +1,651 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.util; + +import java.io.*; +import java.util.*; +import javax.servlet.*; +import javax.xml.parsers.*; +import org.apache.log4j.*; +import org.dspace.core.*; +import org.w3c.dom.*; +import org.xml.sax.*; + +/** + * Item Submission configuration generator for DSpace. Reads and parses the + * installed submission process configuration file, item-submission.xml, from + * the configuration directory. This submission process definition details the + * ordering of the steps (and number of steps) that occur during the Item + * Submission Process. There may be multiple Item Submission processes defined, + * where each definition is assigned a unique name. + * + * The file also specifies which collections use which Item Submission process. + * At a minimum, the definitions file must define a default mapping from the + * placeholder collection # to the distinguished submission process 'default'. + * Any collections that use a custom submission process are listed paired with + * the name of the item submission process they use. + * + * @see SubmissionConfig + * @see SubmissionStepConfig + * + * @author Tim Donohue based on DCInputsReader by Brian S. Hughes + * @version $Revision$ + */ + +public class SubmissionConfigReader +{ + /** + * The ID of the default collection. Will never be the ID of a named + * collection + */ + public static final String DEFAULT_COLLECTION = "default"; + + /** Prefix of the item submission definition XML file */ + static final String SUBMIT_DEF_FILE_PREFIX = "item-submission"; + + /** Suffix of the item submission definition XML file */ + static final String SUBMIT_DEF_FILE_SUFFIX = ".xml"; + + /** log4j logger */ + private static Logger log = Logger.getLogger(SubmissionConfigReader.class); + + /** The fully qualified pathname of the directory containing the Item Submission Configuration file */ + private String configDir = ConfigurationManager.getProperty("dspace.dir") + + File.separator + "config" + File.separator; + + /** + * Hashmap which stores which submission process configuration is used by + * which collection, computed from the item submission config file + * (specifically, the 'submission-map' tag) + */ + private Map collectionToSubmissionConfig = null; + + /** + * Reference to the global submission step definitions defined in the + * "step-definitions" section + */ + private Map> stepDefns = null; + + /** + * Reference to the item submission definitions defined in the + * "submission-definitions" section + */ + private Map>> submitDefns = null; + + /** + * Mini-cache of last SubmissionConfig object requested (so that we don't + * always reload from scratch) + */ + private SubmissionConfig lastSubmissionConfig = null; + + /** + * Load Submission Configuration from the + * item-submission.xml configuration file + */ + public SubmissionConfigReader() throws ServletException + { + buildInputs(configDir + SUBMIT_DEF_FILE_PREFIX + SUBMIT_DEF_FILE_SUFFIX); + } + + /** + * Parse an XML encoded item submission configuration file. + *

+ * Creates two main hashmaps: + *

    + *
  • Hashmap of Collection to Submission definition mappings - + * defines which Submission process a particular collection uses + *
  • Hashmap of all Submission definitions. List of all valid + * Submision Processes by name. + *
+ */ + private void buildInputs(String fileName) throws ServletException + { + collectionToSubmissionConfig = new HashMap(); + submitDefns = new HashMap>>(); + + String uri = "file:" + new File(fileName).getAbsolutePath(); + + try + { + DocumentBuilderFactory factory = DocumentBuilderFactory + .newInstance(); + factory.setValidating(false); + factory.setIgnoringComments(true); + factory.setIgnoringElementContentWhitespace(true); + + DocumentBuilder db = factory.newDocumentBuilder(); + Document doc = db.parse(uri); + doNodes(doc); + } + catch (FactoryConfigurationError fe) + { + throw new ServletException( + "Cannot create Item Submission Configuration parser", fe); + } + catch (Exception e) + { + throw new ServletException( + "Error creating Item Submission Configuration: " + e); + } + } + + /** + * Returns the Item Submission process config used for a particular + * collection, or the default if none is defined for the collection + * + * @param collectionHandle + * collection's unique Handle + * @param isWorkflow + * whether or not we are loading the submission process for a + * workflow + * @return the SubmissionConfig representing the item submission config + * + * @throws ServletException + * if no default submission process configuration defined + */ + public SubmissionConfig getSubmissionConfig(String collectionHandle, + SubmissionInfo subInfo) throws ServletException + { + // get the name of the submission process config for this collection + String submitName = collectionToSubmissionConfig + .get(collectionHandle); + if (submitName == null) + { + submitName = collectionToSubmissionConfig + .get(DEFAULT_COLLECTION); + } + if (submitName == null) + { + throw new ServletException( + "No item submission process configuration designated as 'default' in 'submission-map' section of 'item-submission.xml'."); + } + + log.debug("Loading submission process config named '" + submitName + + "'"); + + // check mini-cache, and return if match +// if (lastSubmissionConfig != null +// && lastSubmissionConfig.getSubmissionName().equals(submitName) +// && lastSubmissionConfig.isWorkflow() == isWorkflow) +// { +// log.debug("Found submission process config '" + submitName +// + "' in cache."); +// +// return lastSubmissionConfig; +// } + + // cache disabled - construct new SubmissionConfig + List> steps = submitDefns.get(submitName); + + if (steps == null) + { + throw new ServletException( + "Missing the Item Submission process config '" + submitName + + "' (or unable to load) from 'item-submission.xml'."); + } + + log.debug("Submission process config '" + submitName + + "' not in cache. Reloading from scratch."); + + lastSubmissionConfig = new SubmissionConfig(submitName, steps, + subInfo); + + log.debug("Submission process config has " + + lastSubmissionConfig.getNumberOfSteps() + " steps listed."); + + return lastSubmissionConfig; + } + + /** + * Returns a particular global step definition based on its ID. + *

+ * Global step definitions are those defined in the + * section of the configuration file. + * + * @param stepID + * step's identifier + * + * @return the SubmissionStepConfig representing the step + * + * @throws ServletException + * if no default submission process configuration defined + */ + public SubmissionStepConfig getStepConfig(String stepID) + throws ServletException + { + // We should already have the step definitions loaded + if (stepDefns != null) + { + // retreive step info + Map stepInfo = stepDefns.get(stepID); + + if (stepInfo != null) + { + return new SubmissionStepConfig(stepInfo); + } + } + + return null; + } + + /** + * Process the top level child nodes in the passed top-level node. These + * should correspond to the collection-form maps, the form definitions, and + * the display/storage word pairs. + */ + private void doNodes(Node n) throws SAXException, ServletException + { + if (n == null) + { + return; + } + Node e = getElement(n); + NodeList nl = e.getChildNodes(); + int len = nl.getLength(); + boolean foundMap = false; + boolean foundStepDefs = false; + boolean foundSubmitDefs = false; + for (int i = 0; i < len; i++) + { + Node nd = nl.item(i); + if ((nd == null) || isEmptyTextNode(nd)) + { + continue; + } + String tagName = nd.getNodeName(); + if (tagName.equals("submission-map")) + { + processMap(nd); + foundMap = true; + } + else if (tagName.equals("step-definitions")) + { + processStepDefinition(nd); + foundStepDefs = true; + } + else if (tagName.equals("submission-definitions")) + { + processSubmissionDefinition(nd); + foundSubmitDefs = true; + } + // Ignore unknown nodes + } + if (!foundMap) + { + throw new ServletException( + "No collection to item submission map ('submission-map') found in 'item-submission.xml'"); + } + if (!foundStepDefs) + { + throw new ServletException("No 'step-definitions' section found in 'item-submission.xml'"); + } + if (!foundSubmitDefs) + { + throw new ServletException( + "No 'submission-definitions' section found in 'item-submission.xml'"); + } + } + + /** + * Process the submission-map section of the XML file. Each element looks + * like: Extract + * the collection handle and item submission name, put name in hashmap keyed + * by the collection handle. + */ + private void processMap(Node e) throws SAXException + { + NodeList nl = e.getChildNodes(); + int len = nl.getLength(); + for (int i = 0; i < len; i++) + { + Node nd = nl.item(i); + if (nd.getNodeName().equals("name-map")) + { + String id = getAttribute(nd, "collection-handle"); + String value = getAttribute(nd, "submission-name"); + String content = getValue(nd); + if (id == null) + { + throw new SAXException( + "name-map element is missing collection-handle attribute in 'item-submission.xml'"); + } + if (value == null) + { + throw new SAXException( + "name-map element is missing submission-name attribute in 'item-submission.xml'"); + } + if (content != null && content.length() > 0) + { + throw new SAXException( + "name-map element has content in 'item-submission.xml', it should be empty."); + } + collectionToSubmissionConfig.put(id, value); + } // ignore any child node that isn't a "name-map" + } + } + + /** + * Process the "step-definition" section of the XML file. Each element is + * formed thusly: ...step_fields... The valid + * step_fields are: heading, processing-servlet. + *

+ * Extract the step information (from the step_fields) and place in a + * HashMap whose key is the step's unique id. + */ + private void processStepDefinition(Node e) throws SAXException, + ServletException + { + stepDefns = new HashMap>(); + + NodeList nl = e.getChildNodes(); + int len = nl.getLength(); + for (int i = 0; i < len; i++) + { + Node nd = nl.item(i); + // process each step definition + if (nd.getNodeName().equals("step")) + { + String stepID = getAttribute(nd, "id"); + if (stepID == null) + { + throw new SAXException( + "step element has no 'id' attribute in 'item-submission.xml', which is required in the 'step-definitions' section"); + } + else if (stepDefns.containsKey(stepID)) + { + throw new SAXException( + "There are two step elements with the id '" + stepID + "' in 'item-submission.xml'"); + } + + Map stepInfo = processStepChildNodes("step-definition", nd); + + stepDefns.put(stepID, stepInfo); + } // ignore any child that is not a 'step' + } + + // Sanity check number of step definitions + if (stepDefns.size() < 1) + { + throw new ServletException( + "step-definition section has no steps! A step with id='collection' is required in 'item-submission.xml'!"); + } + + // Sanity check to see that the required "collection" step is defined + if (!stepDefns.containsKey(SubmissionStepConfig.SELECT_COLLECTION_STEP)) + { + throw new ServletException( + "The step-definition section is REQUIRED to have a step with id='" + + SubmissionStepConfig.SELECT_COLLECTION_STEP + + "' in 'item-submission.xml'! This step is used to ensure that a new item submission is assigned to a collection."); + } + + // Sanity check to see that the required "complete" step is defined + if (!stepDefns.containsKey(SubmissionStepConfig.COMPLETE_STEP)) + { + throw new ServletException( + "The step-definition section is REQUIRED to have a step with id='" + + SubmissionStepConfig.COMPLETE_STEP + + "' in 'item-submission.xml'! This step is used to perform all processing necessary at the completion of the submission (e.g. starting workflow)."); + } + } + + /** + * Process the "submission-definition" section of the XML file. Each element + * is formed thusly: ...steps... + * Each step subsection is formed: ...step_fields... (with + * optional "id" attribute, to reference a step from the + * section). The valid step_fields are: heading, class-name. + *

+ * Extract the submission-process name and steps and place in a HashMap + * whose key is the submission-process's unique name. + */ + private void processSubmissionDefinition(Node e) throws SAXException, + ServletException + { + int numSubmitProcesses = 0; + List submitNames = new ArrayList(); + + // find all child nodes of the 'submission-definition' node and loop + // through + NodeList nl = e.getChildNodes(); + int len = nl.getLength(); + for (int i = 0; i < len; i++) + { + Node nd = nl.item(i); + + // process each 'submission-process' node + if (nd.getNodeName().equals("submission-process")) + { + numSubmitProcesses++; + String submitName = getAttribute(nd, "name"); + if (submitName == null) + { + throw new SAXException( + "'submission-process' element has no 'name' attribute in 'item-submission.xml'"); + } + else if (submitNames.contains(submitName)) + { + throw new SAXException( + "There are two 'submission-process' elements with the name '" + + submitName + "' in 'item-submission.xml'."); + } + submitNames.add(submitName); + + // the 'submission-process' definition contains steps + List> steps = new ArrayList>(); + submitDefns.put(submitName, steps); + + // loop through all the 'step' nodes of the 'submission-process' + NodeList pl = nd.getChildNodes(); + int lenStep = pl.getLength(); + for (int j = 0; j < lenStep; j++) + { + Node nStep = pl.item(j); + + // process each 'step' definition + if (nStep.getNodeName().equals("step")) + { + // check for an 'id' attribute + String stepID = getAttribute(nStep, "id"); + + Map stepInfo; + + // if this step has an id, load its information from the + // step-definition section + if ((stepID != null) && (stepID.length() > 0)) + { + if (stepDefns.containsKey(stepID)) + { + // load the step information from the + // step-definition + stepInfo = stepDefns.get(stepID); + } + else + { + throw new SAXException( + "The Submission process config named " + + submitName + + " contains a step with id=" + + stepID + + ". There is no step with this 'id' defined in the 'step-definition' section of 'item-submission.xml'."); + } + + // Ignore all children of a step element with an + // "id" + } + else + { + // get information about step from its children + // nodes + stepInfo = processStepChildNodes( + "submission-process", nStep); + } + + steps.add(stepInfo); + + } // ignore any child that is not a 'step' + } + + // sanity check number of steps + if (steps.size() < 1) + { + throw new ServletException( + "Item Submission process config named " + + submitName + " has no steps defined in 'item-submission.xml'"); + } + + // ALL Item Submission processes MUST BEGIN with selecting a + // Collection. So, automatically insert in the "collection" step + // (from the 'step-definition' section) + // Note: we already did a sanity check that this "collection" + // step exists. + steps.add(0, stepDefns + .get(SubmissionStepConfig.SELECT_COLLECTION_STEP)); + + // ALL Item Submission processes MUST END with the + // "Complete" processing step. + // So, automatically append in the "complete" step + // (from the 'step-definition' section) + // Note: we already did a sanity check that this "complete" + // step exists. + steps.add(stepDefns + .get(SubmissionStepConfig.COMPLETE_STEP)); + + } + } + if (numSubmitProcesses == 0) + { + throw new ServletException( + "No 'submission-process' elements/definitions found in 'item-submission.xml'"); + } + } + + /** + * Process the children of the "step" tag of the XML file. Returns a HashMap + * of all the fields under that "step" tag, where the key is the field name, + * and the value is the field value. + * + */ + private Map processStepChildNodes(String configSection, Node nStep) + throws SAXException, ServletException + { + // initialize the HashMap of step Info + Map stepInfo = new HashMap(); + + NodeList flds = nStep.getChildNodes(); + int lenflds = flds.getLength(); + for (int k = 0; k < lenflds; k++) + { + // process each child node of a tag + Node nfld = flds.item(k); + + if (!isEmptyTextNode(nfld)) + { + String tagName = nfld.getNodeName(); + String value = getValue(nfld); + stepInfo.put(tagName, value); + } + }// end for each field + + // check for ID attribute & save to step info + String stepID = getAttribute(nStep, "id"); + if (stepID != null && stepID.length() > 0) + { + stepInfo.put("id", stepID); + } + + // look for REQUIRED 'step' information + String missing = null; + if (stepInfo.get("processing-class") == null) + { + missing = "'processing-class'"; + } + if (missing != null) + { + String msg = "Required field " + missing + + " missing in a 'step' in the " + configSection + + " of the item submission configuration file ('item-submission.xml')"; + throw new SAXException(msg); + } + + return stepInfo; + } + + private Node getElement(Node nd) + { + NodeList nl = nd.getChildNodes(); + int len = nl.getLength(); + for (int i = 0; i < len; i++) + { + Node n = nl.item(i); + if (n.getNodeType() == Node.ELEMENT_NODE) + { + return n; + } + } + return null; + } + + private boolean isEmptyTextNode(Node nd) + { + boolean isEmpty = false; + if (nd.getNodeType() == Node.TEXT_NODE) + { + String text = nd.getNodeValue().trim(); + if (text.length() == 0) + { + isEmpty = true; + } + } + return isEmpty; + } + + /** + * Returns the value of the node's attribute named + */ + private String getAttribute(Node e, String name) + { + NamedNodeMap attrs = e.getAttributes(); + int len = attrs.getLength(); + if (len > 0) + { + int i; + for (i = 0; i < len; i++) + { + Node attr = attrs.item(i); + if (name.equals(attr.getNodeName())) + { + return attr.getNodeValue().trim(); + } + } + } + // no such attribute + return null; + } + + /** + * Returns the value found in the Text node (if any) in the node list that's + * passed in. + */ + private String getValue(Node nd) + { + NodeList nl = nd.getChildNodes(); + int len = nl.getLength(); + for (int i = 0; i < len; i++) + { + Node n = nl.item(i); + short type = n.getNodeType(); + if (type == Node.TEXT_NODE) + { + return n.getNodeValue().trim(); + } + } + // Didn't find a text node + return null; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/app/util/SubmissionInfo.java b/dspace/modules/additions/src/main/java/org/dspace/app/util/SubmissionInfo.java new file mode 100644 index 0000000..86cf791 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/app/util/SubmissionInfo.java @@ -0,0 +1,710 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.util; + +import com.atmire.submission.typebound.check.SubmissionStepConditionCheck; +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.InProgressSubmission; + +import org.dspace.submit.AbstractProcessingStep; +import org.dspace.utils.DSpace; +import org.dspace.workflow.WorkflowItem; +import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Information about an item being editing with the submission UI + * + * @author Robert Tansley + * @version $Revision$ + */ +public class SubmissionInfo extends HashMap +{ + /** log4j logger */ + private static Logger log = Logger.getLogger(SubmissionInfo.class); + + /** The item which is being submitted */ + private InProgressSubmission submissionItem = null; + + /** + * The Submission process config, which holds all info about the submission + * process that this item is going through (including all steps, etc) + */ + private SubmissionConfig submissionConfig = null; + + /** + * Handle of the collection where this item is being submitted + */ + private String collectionHandle = null; + + /*************************************************************************** + * Holds all information used to build the Progress Bar in a key,value set. + * Keys are the number of the step, followed by the number of the page + * within the step (e.g. "2.1" = The first page of Step 2) (e.g. "5.2" = The + * second page of Step 5) Values are the Headings to display for each step + * (e.g. "Describe") + **************************************************************************/ + private Map progressBar = null; + + /** The element or element_qualifier to show more input boxes for */ + private String moreBoxesFor; + + /** The element or element_qualifier to scroll to initially using anchor */ + private String jumpToField; + + /** If non-empty, form-relative indices of missing fields */ + private List missingFields; + + /** Specific bundle we're dealing with */ + private Bundle bundle; + + /** Specific bitstream we're dealing with */ + private Bitstream bitstream; + + /** Reader for submission process configuration file * */ + private static SubmissionConfigReader submissionConfigReader; + + /** + * Default Constructor - PRIVATE + *

+ * Create a SubmissionInfo object + * using the load() method! + * + */ + private SubmissionInfo() + { + } + + /** + * Loads all known submission information based on the given in progress + * submission and request object. + *

+ * If subItem is null, then just loads the default submission information + * for a new submission. + * + * @param request + * The HTTP Servlet Request object + * @param subItem + * The in-progress submission we are loading information for + * + * @return a SubmissionInfo object + * + * @throws ServletException + * if an error occurs + */ + public static SubmissionInfo load(HttpServletRequest request, InProgressSubmission subItem) throws ServletException + { + boolean forceReload = false; + SubmissionInfo subInfo = new SubmissionInfo(); + + // load SubmissionConfigReader only the first time + // or if we're using a different UI now. + if (submissionConfigReader == null) + { + submissionConfigReader = new SubmissionConfigReader(); + forceReload=true; + } + + // save the item which is going through the submission process + subInfo.setSubmissionItem(subItem); + + // Only if the submission item is created can we set its collection + String collectionHandle = SubmissionConfigReader.DEFAULT_COLLECTION; + if (subItem != null) + { + collectionHandle = subItem.getCollection().getHandle(); + } + + // save this collection handle to this submission info object + subInfo.setCollectionHandle(collectionHandle); + + // load Submission Process config for this item's collection + // (Note: this also loads the Progress Bar info, since it is + // dependent on the Submission config) + loadSubmissionConfig(request, subInfo, forceReload); + + return subInfo; + } + + /** + * Is this submission in the workflow process? + * + * @return true if the current submission is in the workflow process + */ + public boolean isInWorkflow() + { + return ((this.submissionItem != null) && (this.submissionItem instanceof WorkflowItem || this.submissionItem instanceof XmlWorkflowItem)); + } + + /** + * Return the current in progress submission + * + * @return the InProgressSubmission object representing the current + * submission + */ + public InProgressSubmission getSubmissionItem() + { + return this.submissionItem; + } + + /** + * Updates the current in progress submission item + * + * @param subItem + * the new InProgressSubmission object + */ + public void setSubmissionItem(InProgressSubmission subItem) + { + this.submissionItem = subItem; + } + + /** + * Return the current submission process config (which includes all steps + * which need to be completed for the submission to be successful) + * + * @return the SubmissionConfig object, which contains info on all the steps + * in the current submission process + */ + public SubmissionConfig getSubmissionConfig() + { + return this.submissionConfig; + } + + /** + * Causes the SubmissionConfig to be completely reloaded from the XML + * configuration file (item-submission.xml). + *

+ * Note: This also reloads the progress bar info, since the progress bar + * depends entirely on the submission process (and its steps). + * + * @param request + * The HTTP Servlet Request object + * + * @throws ServletException + * if an error occurs + */ + public void reloadSubmissionConfig(HttpServletRequest request) + throws ServletException + { + // Only if the submission item is created can we set its collection + String collectionHandle = SubmissionConfigReader.DEFAULT_COLLECTION; + if (this.submissionItem != null) + { + collectionHandle = submissionItem.getCollection().getHandle(); + } + this.setCollectionHandle(collectionHandle); + + // force a reload of the submission process configuration + loadSubmissionConfig(request, this, true); + } + + /** + * Returns a particular global step definition based on its ID. + *

+ * Global step definitions are those defined in the + * section of the configuration file. + * + * @param stepID + * step's identifier + * + * @return the SubmissionStepConfig representing the step + * + * @throws ServletException + * if no default submission process configuration defined + */ + public SubmissionStepConfig getStepConfig(String stepID) + throws ServletException + { + return submissionConfigReader.getStepConfig(stepID); + } + + + /** + * Return text information suitable for logging. + *

+ * This method is used by several of the Step classes + * to log major events during the submission process (e.g. when + * license agreement was accepted, when item was submitted, + * when it was available in DSpace, etc.) + * + * @return the type and ID of the submission, bundle and/or bitstream for + * logging + */ + public String getSubmissionLogInfo() + { + String info = ""; + + if (isInWorkflow()) + { + info = info + "workflow_id=" + getSubmissionItem().getID(); + } + else + { + info = info + "workspace_item_id" + getSubmissionItem().getID(); + } + + if (getBundle() != null) + { + info = info + ",bundle_id=" + getBundle().getID(); + } + + if (getBitstream() != null) + { + info = info + ",bitstream_id=" + getBitstream().getID(); + } + + return info; + } + + /** + * Gets the handle of the collection to which this item is being submitted + * + * @return the collection handle + */ + public String getCollectionHandle() + { + return this.collectionHandle; + } + + /** + * Sets the handle of the collection to which this item is being submitted + * + * @param handle + * the new collection handle + */ + public void setCollectionHandle(String handle) + { + this.collectionHandle = handle; + } + + /** + * Return the information used to build the progress bar (this includes all + * the steps in this submission, as well as the ordering and names of the + * steps). + *

+ * Returns a Hashmap, with the following specifics: + *

+ * Keys are the number of the step, followed by the number of the page + * within the step + *

+ * (e.g. "2.1" = The first page of Step 2) + *

+ * (e.g. "5.2" = The second page of Step 5) + *

+ * Values are the Headings to display for each step (e.g. "Describe") + * + * @return a Hashmap of Progress Bar information. + */ + public Map getProgressBarInfo() + { + return this.progressBar; + } + + /** + * Return the current bitstream we're working with (This is used during + * upload processes, or user interfaces that are dealing with bitstreams) + * + * @return the Bitstream object for the bitstream + */ + public Bitstream getBitstream() + { + return this.bitstream; + } + + /** + * Sets the current bitstream we're working with (This is used during upload + * processes, or user interfaces that are dealing with bitstreams) + * + * @param bits + * the bitstream + */ + public void setBitstream(Bitstream bits) + { + this.bitstream = bits; + } + + /** + * Return the current bundle we're working with (This is used during upload + * processes, or user interfaces that are dealing with bundles/bitstreams) + * + * @return the Bundle object for the bundle + */ + public Bundle getBundle() + { + return this.bundle; + } + + /** + * Sets the current bundle we're working with (This is used during upload + * processes, or user interfaces that are dealing with bundles/bitstreams) + * + * @param bund + * the bundle + */ + public void setBundle(Bundle bund) + { + this.bundle = bund; + } + + /** + * Return form related indices of the required fields which were not filled + * out by the user. + * + * @return a List of empty fields which are required + */ + public List getMissingFields() + { + return this.missingFields; + } + + /** + * Sets the form related indices of the required fields which were not + * filled out by the user. + * + * @param missing + * the List of empty fields which are required + */ + public void setMissingFields(List missing) + { + this.missingFields = missing; + } + + /** + * Return metadata field which user has requested more input boxes be + * displayed (by pressing "Add More" on one of the "Describe" pages) + * + * @return the String name of the field element + */ + public String getMoreBoxesFor() + { + return this.moreBoxesFor; + } + + /** + * Sets the metadata field which user has requested more input boxes be + * displayed (by pressing "Add More" on one of the "Describe" pages) + * + * @param fieldname + * the name of the field element on the page + */ + public void setMoreBoxesFor(String fieldname) + { + this.moreBoxesFor = fieldname; + } + + /** + * Return metadata field which JSP should "jump to" (i.e. set focus on) when + * the JSP next loads. This is used during the Describe step. + * + * @return the String name of the field element + */ + public String getJumpToField() + { + return this.jumpToField; + } + + /** + * Sets metadata field which JSP should "jump to" (i.e. set focus on) when + * the JSP next loads. This is used during the Describe step. + * + * @param fieldname + * the name of the field on the page + */ + public void setJumpToField(String fieldname) + { + this.jumpToField = fieldname; + } + + /** + * Load necessary information to build the Progress Bar for the Item + * Submission Progress. + * + * This information is returned in the form of a HashMap (which is then + * stored as a part of the SubmissionInfo). The HashMap takes the following + * form: + * + * Keys - the number of the step, followed by the number of the page within + * the step (e.g. "2.1" = The first page of Step 2) (e.g. "5.2" = The second + * page of Step 5) + * + * Values - the headings to display for each step (e.g. "Describe", + * "Verify") + * + * @param request + * The HTTP Servlet Request object + * @param subInfo + * the SubmissionInfo object we are loading into + * @param forceReload + * If true, this method reloads from scratch (and overwrites + * cached progress bar info) + * + */ + private static void loadProgressBar(HttpServletRequest request, + SubmissionInfo subInfo, boolean forceReload) + { + Map progressBarInfo = null; + + log.debug("Loading Progress Bar Info"); + + if (!forceReload) + { + // first, attempt to load from cache + progressBarInfo = loadProgressBarFromCache(request + .getSession()); + } + + if (progressBarInfo != null && log.isDebugEnabled()) + { + log.debug("Found Progress Bar Info in cache: " + + progressBarInfo.size() + + " pages to display in progress bar"); + } + // if unable to load from cache, must load from scratch + else + { + progressBarInfo = new LinkedHashMap(); + + // loop through all steps + for (int i = 0; i < subInfo.submissionConfig.getNumberOfSteps(); i++) + { + // get the current step info + SubmissionStepConfig currentStep = subInfo.submissionConfig + .getStep(i); + String stepNumber = Integer.toString(currentStep + .getStepNumber()); + String stepHeading = currentStep.getHeading(); + + // as long as this step is visible, include it in + // the Progress Bar + boolean typeBoundAndValid = true; + if(StringUtils.isNotBlank(currentStep.getTypeBindingConfig())){ + String typeBindingConfig = currentStep.getTypeBindingConfig(); + SubmissionStepConditionCheck conditionCheck = new DSpace().getServiceManager().getServiceByName("SubmissionStepConditionCheck",SubmissionStepConditionCheck.class); + typeBoundAndValid= conditionCheck.allConditionsMet(subInfo.getSubmissionItem().getItem(), typeBindingConfig); + } + if (currentStep.isVisible() && typeBoundAndValid) + { + // default to just one page in this step + int numPages = 1; + + try + { + // load the processing class for this step + ClassLoader loader = subInfo.getClass() + .getClassLoader(); + Class stepClass = (Class)loader.loadClass(currentStep.getProcessingClassName()); + + // call the "getNumberOfPages()" method of the class + // to get it's number of pages + AbstractProcessingStep step = stepClass.newInstance(); + + // get number of pages from servlet + numPages = step.getNumberOfPages(request, subInfo); + } + catch (Exception e) + { + log.error( + "Error loading progress bar information from Step Class '" + + currentStep.getProcessingClassName() + + "' Error:", e); + } + + // save each of the step's pages to the progress bar + for (int j = 1; j <= numPages; j++) + { + String pageNumber = Integer.toString(j); + + // store ("stepNumber.pageNumber", Heading) for each + // page in the step + progressBarInfo.put(stepNumber + "." + pageNumber, + stepHeading); + }// end for each page + } + }// end for each step + + log.debug("Loaded Progress Bar Info from scratch: " + + progressBarInfo.size() + + " pages to display in progress bar"); + + // cache this new progress bar + saveProgressBarToCache(request.getSession(), progressBarInfo); + }// end if null + + // save progressBarInfo to submission Info + subInfo.progressBar = progressBarInfo; + } + + /** + * Saves all progress bar information into session cache. This saves us from + * having to reload this same progress bar over and over again. + * + * @param session + * The HTTP Session object + * @param progressBarInfo + * The progress bar info to cache + * + */ + private static void saveProgressBarToCache(HttpSession session, + Map progressBarInfo) + { + // cache progress bar info to Session + session.setAttribute("submission.progressbar", progressBarInfo); + } + + /** + * Attempts to retrieve progress bar information (for a particular + * collection) from session cache. + * + * If the progress bar info cannot be found, returns null + * + * @param session + * The HTTP Session object + * + * @return progressBarInfo HashMap (if found), or null (if not) + * + */ + private static Map loadProgressBarFromCache(HttpSession session) + { + return (Map) session.getAttribute("submission.progressbar"); + } + + /** + * Loads SubmissionConfig object for the given submission info object. If a + * SubmissionConfig object cannot be loaded, a Servlet Error is thrown. + *

+ * This method just loads this SubmissionConfig object internally, so that + * it is available via a call to "getSubmissionConfig()" + * + * @param request + * The HTTP Servlet Request object + * @param subInfo + * the SubmissionInfo object we are loading into + * @param forceReload + * If true, this method reloads from scratch (and overwrites + * cached SubmissionConfig) + * + */ + private static void loadSubmissionConfig(HttpServletRequest request, + SubmissionInfo subInfo, boolean forceReload) + throws ServletException + { + + log.debug("Loading Submission Config information"); + + if (!forceReload) + { + // first, try to load from cache + subInfo.submissionConfig = loadSubmissionConfigFromCache(request + .getSession(), subInfo.getCollectionHandle(), subInfo + .isInWorkflow()); + } + + if (subInfo.submissionConfig == null || forceReload) + { + // reload the proper Submission process config + // (by reading the XML config file) + subInfo.submissionConfig = submissionConfigReader + .getSubmissionConfig(subInfo.getCollectionHandle(), subInfo); + + // cache this new submission process configuration + saveSubmissionConfigToCache(request.getSession(), + subInfo.submissionConfig, subInfo.getCollectionHandle(), + subInfo.isInWorkflow()); + + // also must force reload Progress Bar info, + // since it's based on the Submission config + loadProgressBar(request, subInfo, true); + } + else + { + log.debug("Found Submission Config in session cache!"); + + // try and reload progress bar from cache + loadProgressBar(request, subInfo, false); + } + } + + /** + * Saves SubmissionConfig object into session cache. This saves us from + * having to reload this object during every "Step". + * + * @param session + * The HTTP Session object + * @param subConfig + * The SubmissionConfig to cache + * @param collectionHandle + * The Collection handle this SubmissionConfig corresponds to + * @param isWorkflow + * Whether this SubmissionConfig corresponds to a workflow + * + * + */ + private static void saveSubmissionConfigToCache(HttpSession session, + SubmissionConfig subConfig, String collectionHandle, + boolean isWorkflow) + { + // cache the submission process config + // and the collection it corresponds to + session.setAttribute("submission.config", subConfig); + session.setAttribute("submission.config.collection", collectionHandle); + session.setAttribute("submission.config.isWorkflow", Boolean.valueOf( + isWorkflow)); + } + + /** + * Loads SubmissionConfig object from session cache for the given + * Collection. If a SubmissionConfig object cannot be found, null is + * returned. + * + * @param session + * The HTTP Session object + * @param collectionHandle + * The Collection handle of the SubmissionConfig to load + * @param isWorkflow + * whether or not we loading the Submission process for a + * workflow item + * + * @return The cached SubmissionConfig for this collection + */ + private static SubmissionConfig loadSubmissionConfigFromCache( + HttpSession session, String collectionHandle, boolean isWorkflow) + { + // attempt to load submission process config + // from cache for the current collection + String cachedHandle = (String) session + .getAttribute("submission.config.collection"); + + Boolean cachedIsWorkflow = (Boolean) session + .getAttribute("submission.config.isWorkflow"); + + // only load from cache if the collection handle and + // workflow item status both match! + if (collectionHandle.equals(cachedHandle) + && isWorkflow == cachedIsWorkflow.booleanValue()) + + { + return (SubmissionConfig) session.getAttribute("submission.config"); + } + else + { + return null; + } + } + +} + diff --git a/dspace/modules/additions/src/main/java/org/dspace/app/util/SubmissionStepConfig.java b/dspace/modules/additions/src/main/java/org/dspace/app/util/SubmissionStepConfig.java new file mode 100644 index 0000000..6f101c7 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/app/util/SubmissionStepConfig.java @@ -0,0 +1,241 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.util; + +import java.util.Map; +import java.io.Serializable; +import org.apache.commons.lang3.*; + +/** + * Class representing configuration for a single step within an Item Submission + * Process. In other words, this is a single step in the SubmissionConfig class. + * This class represents the structure of a single 'step' node in the + * item-submission.xml configuration file. + * + * Note: Implements Serializable as it will be saved to the current session during submission. + * Please ensure that nothing is added to this class that isn't also serializable + * + * @see org.dspace.app.util.SubmissionConfigReader + * @see org.dspace.app.util.SubmissionConfig + * + * @author Tim Donohue + * @version $Revision$ + */ +public class SubmissionStepConfig implements Serializable +{ + /* + * The identifier for the Select Collection step + */ + public static final String SELECT_COLLECTION_STEP = "collection"; + + /* + * The identifier for the Completion step + */ + public static final String COMPLETE_STEP = "complete"; + + /** + * the id for this step ('id' only exists if this step is defined in the + * section) + */ + private String id = null; + + /** the heading for this step */ + private String heading = null; + + /** the name of the java processing class for this step */ + private String processingClassName = null; + + /** whether or not this step is editable during workflow (default=true) */ + private boolean workflowEditable = true; + + private boolean submissionEditable = true; + + /** + * The full name of the JSP-UI binding class for this step. This field is + * ONLY used by the JSP-UI. + **/ + private String jspBindingClassName = null; + + /** + * The full name of the Manakin XML-UI Transformer class which will generate + * the necessary DRI for displaying this class in Manakin. This field is + * ONLY used by the Manakin XML-UI. + */ + private String xmlBindingClassName = null; + + private String typeBindingConfig = null; + + /** The number of this step in the current SubmissionConfig */ + private int number = -1; + + /** + * Class constructor for creating an empty SubmissionStepConfig object + */ + public SubmissionStepConfig() + { + } + + /** + * Class constructor for creating a SubmissionStepConfig object based on the + * contents of a HashMap initialized by the SubmissionConfig object. + * + * @param stepMap + * the HashMap containing all required information about this + * step + */ + public SubmissionStepConfig(Map stepMap) + { + id = stepMap.get("id"); + heading = stepMap.get("heading"); + processingClassName = stepMap.get("processing-class"); + jspBindingClassName = stepMap.get("jspui-binding"); + xmlBindingClassName = stepMap.get("xmlui-binding"); + + String wfEditString = stepMap.get("workflow-editable"); + if (wfEditString != null && wfEditString.length() > 0) + { + workflowEditable = Boolean.parseBoolean(wfEditString); + } + typeBindingConfig = stepMap.get("type-binding"); + + String submissionEditString = stepMap.get("submission-editable"); + if (StringUtils.isNotBlank(submissionEditString)) + { + submissionEditable = Boolean.parseBoolean(submissionEditString); + } + } + + /** + * Get the ID for this step. An ID is only defined if the step exists in the + * section. This ID field is used to reference special + * steps (like the required step with id="collection") + * + * @return the step ID + */ + public String getId() + { + return id; + } + + /** + * Get the heading for this step. This can either be a property from + * Messages.properties, or the actual heading text. If this "heading" + * contains a period(.) it is assumed to reference Messages.properties. + * + * @return the heading + */ + public String getHeading() + { + return heading; + } + + /** + * Get the class which handles all processing for this step. + *

+ * This class must extend the org.dspace.submit.AbstractProcessingStep class, + * and provide processing for BOTH the JSP-UI and XML-UI + * + * @return the class's full class path (e.g. + * "org.dspace.submit.step.MySampleStep") + */ + public String getProcessingClassName() + { + return processingClassName; + } + + /** + * Retrieve the full class name of the Manakin Transformer which will + * generate this step's DRI, for display in Manakin XML-UI. + *

+ * This class must extend the + * org.dspace.app.xmlui.aspect.submission.StepTransformer class. + *

+ * This property is only used by the Manakin XML-UI, and therefore is not + * relevant if you are using the JSP-UI. + * + * @return the full java class name of the Transformer to use for this step + */ + public String getXMLUIClassName() + { + return xmlBindingClassName; + } + + /** + * Retrieve the full class name of the JSP-UI "binding" class which will + * initialize and call the necessary JSPs for display in the JSP-UI + *

+ * This class must extend the + * org.dspace.app.webui.submit.JSPStep class. + *

+ * This property is only used by the JSP-UI, and therefore is not + * relevant if you are using the XML-UI (aka. Manakin). + * + * @return the full java class name of the JSPStep to use for this step + */ + public String getJSPUIClassName() + { + return jspBindingClassName; + } + + /** + * Get the number of this step in the current Submission process config. + * Step numbers start with #0 (although step #0 is ALWAYS the special + * "select collection" step) + * + * @return the number of this step in the current SubmissionConfig + */ + public int getStepNumber() + { + return number; + } + + /** + * Sets the number of this step in the current Submission process config. + * Step numbers start with #0 (although step #0 is ALWAYS the special + * "select collection" step) + * + * @param stepNum + * the step number. + */ + protected void setStepNumber(int stepNum) + { + this.number = stepNum; + } + + /** + * Whether or not this step is editable during workflow processing. If + * "true", then this step will appear in the "Edit Metadata" stage of the + * workflow process. + * + * @return if step is editable in a workflow process + */ + public boolean isWorkflowEditable() + { + return workflowEditable; + } + + /** + * Whether or not this step is visible within the Progress Bar. A step is + * only visible if it has been assigned a Heading, otherwise it's invisible + * + * @return if step is visible within the progress bar + */ + public boolean isVisible() + { + return ((heading != null) && (heading.length() > 0)); + } + + public String getTypeBindingConfig() + { + return typeBindingConfig; + } + + public boolean isSubmissionEditable() { + return submissionEditable; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/result/CategoryComplianceResult.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/result/CategoryComplianceResult.java new file mode 100644 index 0000000..bed8cb8 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/result/CategoryComplianceResult.java @@ -0,0 +1,134 @@ +package org.dspace.ref.compliance.result; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + * TODO TOM UNIT TEST + */ +public class CategoryComplianceResult implements Comparable{ + + private int ordinal; + + private String categoryName = null; + + private String categoryDescription = null; + + private String resolutionHint = null; + + private List exceptionResults = new LinkedList(); + + private List ruleResults = new LinkedList(); + + private boolean applicable = true; + + + public int getOrdinal() { + return ordinal; + } + + public void setOrdinal(final int ordinal) { + this.ordinal = ordinal; + } + + public String getCategoryDescription() { + return categoryDescription; + } + + public void setCategoryDescription(final String categoryDescription) { + this.categoryDescription = categoryDescription; + } + + public String getCategoryName() { + return categoryName; + } + + public void setCategoryName(final String categoryName) { + this.categoryName = categoryName; + } + + public List getCompliantRules() { + List output = new LinkedList(); + for (RuleComplianceResult ruleResult : ruleResults) { + + if(ruleResult.isCompliant()) { + output.add(ruleResult); + } + } + + return output; + } + + public List getViolatedRules() { + List output = new LinkedList(); + for (RuleComplianceResult ruleResult : ruleResults) { + + if(!ruleResult.isCompliant()) { + output.add(ruleResult); + } + } + + return output; + } + + public List getAppliedExceptions() { + List output = new LinkedList(); + for (RuleComplianceResult ruleResult : exceptionResults) { + + if(ruleResult.isCompliant() && ruleResult.isApplicable()) { + output.add(ruleResult); + } + } + + return output; + } + + public boolean isCompliant() { + boolean isCompliant = true; + + if(!isCompliantByException()) { + + Iterator it = ruleResults.iterator(); + while (it.hasNext() && isCompliant) { + RuleComplianceResult next = it.next(); + isCompliant = next.isCompliant(); + } + + } + + return isCompliant; + } + + public boolean isCompliantByException() { + return !getAppliedExceptions().isEmpty(); + } + + public void addRuleResult(final RuleComplianceResult ruleResult) { + ruleResults.add(ruleResult); + } + + public void addExceptionResult(final RuleComplianceResult ruleResult) { + exceptionResults.add(ruleResult); + } + + public void setResolutionHint(final String resolutionHint) { + this.resolutionHint = resolutionHint; + } + + public String getResolutionHint() { + return resolutionHint; + } + + public int compareTo(final CategoryComplianceResult o) { + return getOrdinal() < o.getOrdinal() ? -1 : (getOrdinal() == o.getOrdinal() ? 0 : 1); + } + + public void setApplicable(final boolean applicable) { + this.applicable = applicable; + } + + public boolean isApplicable() { + return applicable; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/result/ComplianceResult.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/result/ComplianceResult.java new file mode 100644 index 0000000..32e3a3d --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/result/ComplianceResult.java @@ -0,0 +1,101 @@ +package org.dspace.ref.compliance.result; + +import org.dspace.util.subclasses.Metadata; + +import java.util.*; + +/** + * Model class that represents the result of a compliance check + */ +public class ComplianceResult { + + private List categoryResults = new LinkedList(); + + private List exceptionResults = new LinkedList(); + + private Map estimatedValues = new HashMap(); + + private boolean isApplicable; + + private List preconditionResults = new LinkedList(); + + public List getOrderedCategoryResults() { + Collections.sort(categoryResults); + + return categoryResults; + } + + public List getAppliedExceptions() { + List output = new LinkedList(); + for (RuleComplianceResult ruleResult : exceptionResults) { + + if(ruleResult.isCompliant() && ruleResult.isApplicable()) { + output.add(ruleResult); + } + } + + return output; + } + + public Map getEstimatedValues() { + return estimatedValues; + } + + public boolean isCompliant() { + boolean isCompliant = true; + + if(!isCompliantByException()) { + + Iterator it = getOrderedCategoryResults().iterator(); + + while (it.hasNext() && isCompliant) { + CategoryComplianceResult next = it.next(); + isCompliant = next.isCompliant() || !next.isApplicable(); + } + } + + return isCompliant; + } + + public void addEstimatedValues(List fakeFields) { + for (Metadata fakeField : fakeFields) + { + estimatedValues.put(fakeField.getField(), fakeField.getValue()); + } + } + + public List getViolatedPreconditions(){ + List output = new LinkedList(); + for (RuleComplianceResult preconditionResult : preconditionResults) { + if(!preconditionResult.isCompliant()){ + output.add(preconditionResult); + } + } + + return output; + } + + public boolean isCompliantByException() { + return !getAppliedExceptions().isEmpty(); + } + + public void addExceptionResult(final RuleComplianceResult ruleResult) { + exceptionResults.add(ruleResult); + } + + public void addCategoryResult(final CategoryComplianceResult categoryResult) { + categoryResults.add(categoryResult); + } + + public void addPreconditionResult(final RuleComplianceResult ruleResult) { + preconditionResults.add(ruleResult); + } + + public boolean isApplicable() { + return isApplicable; + } + + public void setApplicable(boolean applicable) { + isApplicable = applicable; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/result/RuleComplianceResult.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/result/RuleComplianceResult.java new file mode 100644 index 0000000..e484bba --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/result/RuleComplianceResult.java @@ -0,0 +1,143 @@ +package org.dspace.ref.compliance.result; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.LinkedList; +import java.util.List; + +/** + * Model class that represents the compliance result of a single rule. + * This class also contains logic to build a meaningful description explaining the result. + */ +public class RuleComplianceResult { + + private boolean compliant; + + private String ruleDescriptionViolation; + + private String ruleDescriptionCompliant; + + private String preconditionDescription; + + private String exceptionDescription; + + private String exceptionHint; + + private String definitionHint; + + private String resolutionHint; + + private List violationDescriptions = new LinkedList(); + + private boolean applicable = true; + + public boolean isCompliant() { + return compliant; + } + + public void setCompliant(final boolean compliant) { + this.compliant = compliant; + } + + public void setRuleDescriptionViolation(final String ruleDescriptionViolation) { + this.ruleDescriptionViolation = preformat(ruleDescriptionViolation); + } + + public void setRuleDescriptionCompliant(final String ruleDescriptionCompliant) { + this.ruleDescriptionCompliant = preformat(ruleDescriptionCompliant); + } + + public void setPreconditionDescription(final String preconditionDescription) { + this.preconditionDescription = preformat(preconditionDescription); + } + + public void setExceptionDescription(final String exceptionDescription) { + this.exceptionDescription = preformat(exceptionDescription); + } + + public void setExceptionHint(final String exceptionHint) { + this.exceptionHint = preformat(exceptionHint); + } + + public void setDefinitionHint(final String definitionHint) { + this.definitionHint = preformat(definitionHint); + } + + public String getDefinitionHint() { + return format(definitionHint); + } + + public void setResolutionHint(final String resolutionHint) { + this.resolutionHint = preformat(resolutionHint); + } + + public String getResolutionHint() { + return format(resolutionHint); + } + + public String getResultDescription() { + StringBuilder description = new StringBuilder(); + if(StringUtils.isNotBlank(preconditionDescription)) { + description.append("if "); + description.append(preconditionDescription); + description.append(", then "); + } + + if(isCompliant() && !exceptionApplied() && isApplicable()) { + description.append(ruleDescriptionCompliant); + } else { + description.append(ruleDescriptionViolation); + } + + return format(description.toString()); + } + + public String getRuleDescriptionCompliant() { + return ruleDescriptionCompliant; + } + + public void addViolationDescriptions(final List descriptions) { + if(CollectionUtils.isNotEmpty(descriptions)) { + for (String description : descriptions) { + violationDescriptions.add(format(preformat(description))); + } + } + } + + public List getViolationDescriptions() { + return violationDescriptions; + } + + public String getExceptionDescription() { + return format(exceptionDescription); + } + + public String getExceptionHint() { + return format(exceptionHint); + } + + public boolean exceptionApplied() { + return StringUtils.isNotBlank(exceptionDescription); + } + + private String preformat(final String description) { + String result = StringUtils.uncapitalize(description); + if(StringUtils.endsWith(result, ".")) { + result = result.substring(0, result.length() - 1); + } + return result; + } + + private String format(final String str) { + return str == null ? null : StringUtils.capitalize(str) + "."; + } + + public void setApplicable(final boolean applicable) { + this.applicable = applicable; + } + + public boolean isApplicable() { + return applicable; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/AbstractComplianceRule.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/AbstractComplianceRule.java new file mode 100644 index 0000000..17d5f5a --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/AbstractComplianceRule.java @@ -0,0 +1,199 @@ +package org.dspace.ref.compliance.rules; + +import java.sql.*; +import java.util.*; +import org.apache.commons.collections.*; +import org.apache.commons.lang3.*; +import org.apache.commons.lang3.math.*; +import org.dspace.content.*; +import org.dspace.core.*; +import org.dspace.ref.compliance.definition.model.*; +import org.dspace.ref.compliance.result.*; +import org.dspace.util.*; +import org.joda.time.*; +import org.joda.time.format.*; + +/** + * Abstract implementation of a compliance rule + */ +public abstract class AbstractComplianceRule implements ComplianceRule { + + private final List exceptionRules = new LinkedList(); + + private final List preconditionRules = new LinkedList(); + + private List violationDescriptions = new LinkedList(); + + private String definitionHint = null; + + private String resolutionHint = null; + + public void setDefinitionHint(final String definitionHint) { + this.definitionHint = definitionHint; + } + + public void setResolutionHint(final String resolutionHint) { + this.resolutionHint = resolutionHint; + } + + public RuleComplianceResult validate(final Context context, final Item item) { + + RuleComplianceResult result = getComplianceResult(context, item); + + if (!result.isCompliant() && CollectionUtils.isNotEmpty(exceptionRules)) { + + RuleComplianceResult exceptionResult = null; + Iterator it = exceptionRules.iterator(); + do { + ComplianceRule exceptionRule = it.next(); + exceptionResult = exceptionRule.validate(context, item); + } while (!(exceptionResult.isCompliant() && exceptionResult.isApplicable()) && it.hasNext()); + + if (exceptionResult.isCompliant() && exceptionResult.isApplicable()) { + result.setCompliant(true); + result.setExceptionDescription(exceptionResult.getResultDescription()); + result.setExceptionHint(exceptionResult.getDefinitionHint()); + } + } + + result.setDefinitionHint(definitionHint); + result.setResolutionHint(resolutionHint); + + return result; + } + + public void addExceptionRule(final ComplianceRule exceptionRule) { + exceptionRules.add(exceptionRule); + } + + public void addPreconditionRule(final ComplianceRule complianceRule) { + preconditionRules.add(complianceRule); + } + + protected RuleComplianceResult getComplianceResult(final Context context, final Item item) { + RuleComplianceResult result = new RuleComplianceResult(); + result.setApplicable(true); + + if (item == null) { + result.setCompliant(false); + result.setRuleDescriptionViolation("the item cannot be null"); + + } else { + result.setCompliant(true); + + //Always do the validation so that the rule description can be built if necessary + boolean isValid = doValidationAndBuildDescription(context, item); + result.setRuleDescriptionViolation(getRuleDescriptionViolation()); + result.setRuleDescriptionCompliant(getRuleDescriptionCompliant()); + + + if (preconditionsAreMet(context, result, item)) { + result.setCompliant(isValid); + + if (!isValid) { + result.addViolationDescriptions(violationDescriptions); + } + } else { + result.setApplicable(false); + } + } + + return result; + } + + protected abstract String getRuleDescriptionCompliant(); + + protected abstract String getRuleDescriptionViolation(); + + private boolean preconditionsAreMet(final Context context, final RuleComplianceResult parentResult, final Item item) { + boolean conditionsAreMet = true; + + Iterator it = preconditionRules.iterator(); + List preconditionRuleDescriptions = new LinkedList(); + + while (it.hasNext()) { + ComplianceRule rule = it.next(); + RuleComplianceResult complianceResult = rule.validate(context, item); + + conditionsAreMet &= complianceResult.isCompliant(); + + if (StringUtils.isNotBlank(complianceResult.getRuleDescriptionCompliant())) { + preconditionRuleDescriptions.add(complianceResult.getRuleDescriptionCompliant()); + } + } + + parentResult.setPreconditionDescription(StringUtils.join(preconditionRuleDescriptions, " and ")); + + return conditionsAreMet; + } + + protected void addViolationDescription(final String description) { + violationDescriptions.add(description); + } + + protected void addViolationDescription(final String description, final Object... parameters) { + addViolationDescription(String.format(description, parameters)); + } + + protected String getValueDescription(final Value valueObject) { + String valueDescription = StringUtils.isBlank(valueObject.getDescription()) ? valueObject.getValue() : valueObject.getDescription(); + if(NumberUtils.isNumber(valueDescription)) { + return valueDescription; + } else { + return "\"" + valueDescription + "\""; + } + } + + protected List getMetadata(final Context context, final Item item, final String metadataField) { + List output; + + try { + CustomField customField = CustomField.findByField(metadataField); + if (customField == null) { + output = ItemUtils.getMetadata(item, metadataField); + } else { + output = customField.createValueList(context, item); + } + } catch (SQLException e) { + output = null; + } + + return output; + } + + protected DateTime getFirstDateValue(final Context context, final Item item, final String metadataField) { + + try { + List fieldValueList = getMetadata(context, item, metadataField); + if (CollectionUtils.isNotEmpty(fieldValueList)) { + return parseDateTime(fieldValueList.get(0).value); + } + + } catch (IllegalArgumentException ex) { + addViolationDescription("the metadata field %s is invalid because it has too few tokens or contains an invalid date", metadataField); + } + + return null; + } + + protected DateTime parseDateTime(final String inputString) throws IllegalArgumentException { + DateTimeFormatter formatter = getDateTimeFormatter(); + try { + return formatter.parseDateTime(inputString); + } catch(Exception ex) { + //We got an invalid date, ignoring the error + return null; + } + } + + public static DateTimeFormatter getDateTimeFormatter() { + return ISODateTimeFormat.dateTimeParser(); + } + + public static DateTimeFormatter getDateTimePrinter() { + return ISODateTimeFormat.dateTime(); + } + + protected abstract boolean doValidationAndBuildDescription(final Context context, final Item item); + +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/AbstractFieldCheckRule.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/AbstractFieldCheckRule.java new file mode 100644 index 0000000..feae870 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/AbstractFieldCheckRule.java @@ -0,0 +1,39 @@ +package org.dspace.ref.compliance.rules; + +import java.util.*; +import org.apache.commons.lang.*; +import org.dspace.content.*; +import org.dspace.core.*; + +/** + * Abstract implementation of a compliance rule that checks a certain field + */ +public abstract class AbstractFieldCheckRule extends AbstractComplianceRule { + + protected String metadataFieldToCheck; + protected String fieldDescription; + + public AbstractFieldCheckRule(final String fieldDescription, final String metadataField) { + this.fieldDescription = StringUtils.trimToEmpty(fieldDescription); + this.metadataFieldToCheck = StringUtils.trimToNull(metadataField); + } + + @Override + protected boolean doValidationAndBuildDescription(final Context context, final Item item) { + if (metadataFieldToCheck == null) { + addViolationDescription("Cannot validate a blank field"); + return false; + } else { + try { + List fieldValueList = getMetadata(context, item, metadataFieldToCheck); + return checkFieldValues(fieldValueList); + + } catch(IllegalArgumentException ex) { + addViolationDescription("The metadata field %s is invalid because it has too few tokens", metadataFieldToCheck); + return false; + } + } + } + + abstract protected boolean checkFieldValues(final List fieldValueList); +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/AtLeastOneNotBlankRule.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/AtLeastOneNotBlankRule.java new file mode 100644 index 0000000..05287aa --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/AtLeastOneNotBlankRule.java @@ -0,0 +1,48 @@ +package org.dspace.ref.compliance.rules; + +import java.util.*; +import org.apache.commons.lang.*; +import org.dspace.content.*; +import org.dspace.core.*; + +/** + * @author philip at atmire.com + * + * Rule to check if an item has a value in at least one of the specified fields. + */ +public class AtLeastOneNotBlankRule extends AbstractComplianceRule { + + private List fieldsToCheck; + private String fieldDescription; + + public AtLeastOneNotBlankRule(final String fieldDescription, final List fields) { + this.fieldDescription = StringUtils.trimToEmpty(fieldDescription); + fieldsToCheck = new ArrayList(fields.size()); + for (String field : fields) { + fieldsToCheck.add(StringUtils.trimToEmpty(field)); + } + } + + protected String getRuleDescriptionCompliant() { + return String.format("at least one of the %s metadata fields (%s) is filled in", fieldDescription, StringUtils.join(fieldsToCheck, ", ")); + } + + @Override + protected String getRuleDescriptionViolation() { + return String.format("at least one of the %s metadata fields (%s) must be filled in", fieldDescription, StringUtils.join(fieldsToCheck, ", ")); + } + + @Override + protected boolean doValidationAndBuildDescription(Context context, Item item) { + for (String field : fieldsToCheck) { + Metadatum[] metadata = item.getMetadataByMetadataString(field); + + if(ArrayUtils.isNotEmpty(metadata) && StringUtils.isNotBlank(metadata[0].value)){ + return true; + } + } + + return false; + } + +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/ComplianceCategory.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/ComplianceCategory.java new file mode 100644 index 0000000..013fd1d --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/ComplianceCategory.java @@ -0,0 +1,113 @@ +package org.dspace.ref.compliance.rules; + +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.ref.compliance.result.CategoryComplianceResult; +import org.dspace.ref.compliance.result.RuleComplianceResult; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +/** + * TODO TOM UNIT TEST + */ +public class ComplianceCategory implements Comparable { + + private int ordinal; + + private String name; + + private String description; + + private String resolutionHint; + + private final List complianceRules = new LinkedList(); + + private final List exceptionRules = new LinkedList(); + + public int getOrdinal() { + return ordinal; + } + + public void setOrdinal(final int ordinal) { + this.ordinal = ordinal; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(final String description) { + this.description = description; + } + + public String getResolutionHint() { + return resolutionHint; + } + + public void setResolutionHint(final String resolutionHint) { + this.resolutionHint = resolutionHint; + } + + public List getComplianceRules() { + return complianceRules; + } + + public List getExceptionRules() { + return exceptionRules; + } + + public void addComplianceRule(final ComplianceRule rule) { + complianceRules.add(rule); + } + + public void addComplianceRules(final Collection rules) { + complianceRules.addAll(rules); + } + + public void addExceptionRule(final ComplianceRule rule) { + exceptionRules.add(rule); + } + + public void addExceptionRules(final Collection rules) { + exceptionRules.addAll(rules); + } + + public CategoryComplianceResult validate(final Context context, final Item item) { + CategoryComplianceResult result = buildCategoryComplianceResult(); + + for (ComplianceRule rule : complianceRules) { + RuleComplianceResult ruleResult = rule.validate(context, item); + result.addRuleResult(ruleResult); + } + + for (ComplianceRule rule : exceptionRules) { + RuleComplianceResult ruleResult = rule.validate(context, item); + result.addExceptionResult(ruleResult); + } + + return result; + } + + private CategoryComplianceResult buildCategoryComplianceResult() { + CategoryComplianceResult result = new CategoryComplianceResult(); + result.setOrdinal(ordinal); + result.setCategoryName(name); + result.setCategoryDescription(description); + result.setResolutionHint(resolutionHint); + return result; + } + + public int compareTo(final ComplianceCategory o) { + return getOrdinal() < o.getOrdinal() ? -1 : (getOrdinal() == o.getOrdinal() ? 0 : 1); + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/CompliancePolicy.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/CompliancePolicy.java new file mode 100644 index 0000000..9d5a0cb --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/CompliancePolicy.java @@ -0,0 +1,85 @@ +package org.dspace.ref.compliance.rules; + +import java.util.Collection; +import java.util.*; +import org.dspace.content.*; +import org.dspace.core.*; +import org.dspace.ref.compliance.result.*; + +/** + * TODO TOM UNIT TEST + */ +public class CompliancePolicy { + + private final List categories = new LinkedList(); + + private final List exceptionRules = new LinkedList(); + + private final List preconditionRules = new LinkedList(); + + public void addComplianceCategory(final ComplianceCategory category) { + categories.add(category); + } + + public void addExceptionRules(final Collection rules) { + exceptionRules.addAll(rules); + } + + public void addPreconditionRules(final Collection rules) { + preconditionRules.addAll(rules); + } + + public ComplianceResult validate(final Context context, final Item item, ComplianceResult result) { + + Collections.sort(categories); + + boolean exceptionEncountered = false; + + //First check all the exceptions + for (ComplianceRule exceptionRule : exceptionRules) { + RuleComplianceResult ruleResult = exceptionRule.validate(context, item); + result.addExceptionResult(ruleResult); + } + + //If we found an exception, all categories do not matter anymore (they're not applicable) + if(result.isCompliantByException()) { + exceptionEncountered = true; + } + + //Check the categories and indicate if they are applicable or not + for (ComplianceCategory category : categories) { + CategoryComplianceResult categoryResult = category.validate(context, item); + + if(exceptionEncountered) { + categoryResult.setApplicable(false); + } else { + categoryResult.setApplicable(true); + } + + //If this category has an exception, all next categories are not applicable any more + if(categoryResult.isCompliantByException()) { + exceptionEncountered = true; + } + + result.addCategoryResult(categoryResult); + } + + return result; + } + + public ComplianceResult validatePreconditionRules(final Context context, final Item item) { + ComplianceResult result = new ComplianceResult(); + + result.setApplicable(true); + + for (ComplianceRule preconditionRule : preconditionRules) { + RuleComplianceResult ruleResult = preconditionRule.validate(context, item); + if(!ruleResult.isCompliant()) { + result.addPreconditionResult(ruleResult); + result.setApplicable(false); + } + } + + return result; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/ComplianceRule.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/ComplianceRule.java new file mode 100644 index 0000000..e87dd5f --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/ComplianceRule.java @@ -0,0 +1,18 @@ +package org.dspace.ref.compliance.rules; + +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.ref.compliance.result.RuleComplianceResult; + +/** + * Contract of a compliance rule. A compliance rule should be able to validate an item. But a compliance rule + * can also have preconditions (before the rule is applicable) and exceptions. + */ +public interface ComplianceRule { + + RuleComplianceResult validate(Context context, Item item); + + void addExceptionRule(ComplianceRule exceptionRule); + + void addPreconditionRule(ComplianceRule complianceRule); +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/CountGreaterThanRule.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/CountGreaterThanRule.java new file mode 100644 index 0000000..d6f536b --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/CountGreaterThanRule.java @@ -0,0 +1,76 @@ +package org.dspace.ref.compliance.rules; + +import java.sql.*; +import java.util.*; +import org.apache.commons.collections.*; +import org.apache.commons.lang.*; +import org.dspace.content.*; +import org.dspace.core.*; +import org.dspace.ref.compliance.definition.model.*; + +/** + * Rule to check if an item has more than X values for a specified field. + */ +public class CountGreaterThanRule extends AbstractComplianceRule { + + private String fieldToCheck; + private String fieldDescription; + + private Value thresholdValue; + private Integer thresholdNumber; + + public CountGreaterThanRule(final String fieldDescription, final String fieldToCheck, final List thresholdValues) { + this.fieldDescription = StringUtils.trimToEmpty(fieldDescription); + this.fieldToCheck = StringUtils.trimToNull(fieldToCheck); + + thresholdValue = CollectionUtils.isEmpty(thresholdValues) ? null : thresholdValues.get(0); + + try { + this.thresholdNumber = thresholdValue == null ? null : Integer.valueOf(StringUtils.trimToEmpty(thresholdValue.getValue())); + } catch(NumberFormatException ex) { + addViolationDescription("the provided threshold value %s is not a number", thresholdValue); + this.thresholdNumber = null; + } + + } + + protected String getRuleDescriptionCompliant() { + return String.format("the number of %s (field %s) is greater than %s", fieldDescription, fieldToCheck, + thresholdValue == null ? "ERROR" : getValueDescription(thresholdValue)); + } + + protected String getRuleDescriptionViolation() { + return String.format("the number of %s (field %s) must be greater than %s", fieldDescription, fieldToCheck, + thresholdValue == null ? "ERROR" : getValueDescription(thresholdValue)); + } + + protected boolean doValidationAndBuildDescription(final Context context, final Item item) { + boolean valid = false; + + if (fieldToCheck == null) { + addViolationDescription("cannot validate a blank field"); + } else { + if(thresholdNumber != null) { + try { + int count = countFieldValues(context, item); + + if (count > thresholdNumber) { + valid = true; + } else { + addViolationDescription("the number of %s is %d", fieldDescription, count); + } + + } catch (SQLException e) { + addViolationDescription("unable to count values for field %s: %s", fieldDescription, e.getMessage()); + } + } + } + return valid; + } + + private int countFieldValues(final Context context, final Item item) throws SQLException { + List fieldValueList = getMetadata(context, item, fieldToCheck); + return CollectionUtils.size(fieldValueList); + } + +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/CustomField.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/CustomField.java new file mode 100644 index 0000000..f64664d --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/CustomField.java @@ -0,0 +1,110 @@ +package org.dspace.ref.compliance.rules; + +import com.atmire.utils.EmbargoUtils; +import org.apache.commons.lang3.StringUtils; +import org.dspace.authorize.AuthorizeManager; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.content.*; +import org.dspace.core.Constants; +import org.dspace.core.Context; + +import java.sql.SQLException; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +/** + * TODO TOM UNIT TEST + */ +public enum CustomField { + + BITSTREAM_COUNT("bitstream.count") { + public List createValueList(final Context context, final Item item) throws SQLException { + List output = new LinkedList(); + for (Bitstream bitstream : item.getNonInternalBitstreams()) { + java.util.List policiesByDSOAndType = AuthorizeManager.getPoliciesActionFilter(context, bitstream, Constants.READ); + + for (ResourcePolicy pol : policiesByDSOAndType) { + //We are only interested in bitstreams that have a READ policy for Anonymous + if (pol.getGroupID() == EmbargoUtils.ANONYMOUS_GROUP_ID) { + Metadatum value = new Metadatum(); + value.value = bitstream.getName(); + output.add(value); + } + } + } + + return output; + } + }, + BITSTREAM_EMBARGO_ENABLED("bitstream.embargo.enabled") { + public List createValueList(final Context context, final Item item) throws SQLException { + List output = new LinkedList(); + Metadatum value = new Metadatum(); + if (EmbargoUtils.getLastEmbargo(item, context) == null) { + value.value = "false"; + } else { + value.value = "true"; + } + output.add(value); + return output; + } + }, + BITSTREAM_EMBARGO_ENDDATE("bitstream.embargo.enddate") { + public List createValueList(final Context context, final Item item) throws SQLException { + List output = new LinkedList(); + Date embargo = EmbargoUtils.getLastEmbargo(item, context); + if(embargo != null) { + Metadatum value = new Metadatum(); + value.value = AbstractComplianceRule.getDateTimePrinter().print(embargo.getTime()); + output.add(value); + } + return output; + } + }, + ITEM_LIFECYCLE_STATUS("item.status") { + public List createValueList(final Context context, final Item item) throws SQLException { + List output = new LinkedList(); + + Metadatum value = new Metadatum(); + if(item.isArchived()) { + value.value = "archived"; + } else if(item.isWithdrawn()) { + value.value = "withdrawn"; + } else if(WorkspaceItem.findByItem(context, item) != null) { + value.value = "workspace"; + } else { + value.value = "workflow"; + } + + output.add(value); + return output; + } + }; + + private final String fieldName; + + CustomField(final String field) { + this.fieldName = field; + } + + public String getFieldName() { + return fieldName; + } + + public abstract List createValueList(final Context context, final Item item) throws SQLException; + + public static CustomField findByField(final String field) { + CustomField result = null; + + for (CustomField customField : CustomField.values()) { + if(StringUtils.equals(customField.getFieldName(), field)) { + result = customField; + break; + } + } + + return result; + } + +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/DateGreaterThanOrEqualRule.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/DateGreaterThanOrEqualRule.java new file mode 100644 index 0000000..624fb7d --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/DateGreaterThanOrEqualRule.java @@ -0,0 +1,63 @@ +package org.dspace.ref.compliance.rules; + +import java.util.*; +import org.apache.commons.collections.*; +import static org.apache.commons.collections.CollectionUtils.*; +import org.apache.commons.lang3.*; +import org.dspace.content.*; +import org.dspace.ref.compliance.definition.model.*; +import org.joda.time.*; + +/** + * Rule to check if a date value of a given metadata field is greater than or equal to a specified value + */ +public class DateGreaterThanOrEqualRule extends AbstractFieldCheckRule implements ComplianceRule { + + private Value thresholdValue; + + public DateGreaterThanOrEqualRule(final String fieldDescription, final String metadataField, final List thresholdValues) { + super(fieldDescription, metadataField); + + thresholdValue = CollectionUtils.isEmpty(thresholdValues) ? null : thresholdValues.get(0); + } + + protected boolean checkFieldValues(final List fieldValueList) { + boolean valid = false; + if (isEmpty(fieldValueList)) { + addViolationDescription("The %s field has no value", fieldDescription); + + } else if (thresholdValue == null || StringUtils.isBlank(thresholdValue.getValue())) { + addViolationDescription("The threshold value cannot be blank"); + + } else { + try { + DateTime thresholdDate = parseDateTime(thresholdValue.getValue()); + DateTime dateToCheck = parseDateTime(fieldValueList.get(0).value); + + if(dateToCheck == null) { + addViolationDescription("there is no valid value for the field " + metadataFieldToCheck); + } else if (thresholdDate != null && dateToCheck.compareTo(thresholdDate) >= 0) { + valid = true; + } else { + addViolationDescription("the %s is before or equal to %s", fieldDescription, + thresholdValue == null ? "ERROR" : getValueDescription(thresholdValue)); + } + + } catch (IllegalArgumentException ex) { + addViolationDescription("the metadata field %s is invalid because it has too few tokens or contains an invalid date", metadataFieldToCheck); + } + } + + return valid; + } + + protected String getRuleDescriptionCompliant() { + return String.format("the %s (%s) is after %s", fieldDescription, metadataFieldToCheck, + thresholdValue == null ? "ERROR" : getValueDescription(thresholdValue)); + } + + protected String getRuleDescriptionViolation() { + return String.format("the %s (%s) must be after %s", fieldDescription, metadataFieldToCheck, + thresholdValue == null ? "ERROR" : getValueDescription(thresholdValue)); + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/DateRangeSmallerThanRule.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/DateRangeSmallerThanRule.java new file mode 100644 index 0000000..2550355 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/DateRangeSmallerThanRule.java @@ -0,0 +1,84 @@ +package org.dspace.ref.compliance.rules; + +import java.util.*; +import org.apache.commons.collections.*; +import org.apache.commons.lang.*; +import org.dspace.content.*; +import org.dspace.core.*; +import org.dspace.ref.compliance.definition.model.*; +import org.joda.time.*; + +/** + * Rule to check if a date range defined by two metadata fields is smaller than a specified threshold + */ +public class DateRangeSmallerThanRule extends AbstractComplianceRule { + + private String fromField; + private String toField; + private String rangeDescription; + + private Value thresholdValue; + private Integer thresholdNumber; + + public DateRangeSmallerThanRule(final String fromField, final String toField, final String rangeDescription, + final List thresholdValues) { + this.fromField = StringUtils.trimToNull(fromField); + this.toField = StringUtils.trimToNull(toField); + this.rangeDescription = StringUtils.trimToNull(rangeDescription); + + thresholdValue = CollectionUtils.isEmpty(thresholdValues) ? null : thresholdValues.get(0); + + try { + this.thresholdNumber = thresholdValue == null ? null : Integer.valueOf(StringUtils.trimToEmpty(thresholdValue.getValue())); + } catch(NumberFormatException ex) { + addViolationDescription("the provided threshold value %s is not a number", thresholdValue); + this.thresholdNumber = null; + } + } + + protected String getRuleDescriptionCompliant() { + return String.format("the %s (from %s to %s) is smaller than %s month(s)", rangeDescription, fromField, toField, + thresholdValue == null ? "ERROR" : getValueDescription(thresholdValue)); + } + + protected String getRuleDescriptionViolation() { + return String.format("the %s (from %s to %s) must be smaller than %s month(s)", rangeDescription, fromField, toField, + thresholdValue == null ? "ERROR" : getValueDescription(thresholdValue)); + } + + protected boolean doValidationAndBuildDescription(final Context context, final Item item) { + boolean valid = false; + + if (fromField == null || toField == null) { + addViolationDescription("the from and to date fields of a date range validation rule cannot be blank."); + } else { + if(thresholdNumber != null) { + DateTime from = getFirstDateValue(context, item, fromField); + DateTime to = getFirstDateValue(context, item, toField); + + if(from == null) { + addViolationDescription("there is no valid value for the field " + fromField); + } + if(to == null) { + addViolationDescription("there is no valid value for the field " + toField); + } + + if (to != null && from != null) { + if(to.isAfter(from)){ + to = to.minusDays(1); + } + int months = Months.monthsBetween(from.toLocalDate(), to.toLocalDate()).getMonths(); + + if (months < thresholdNumber) { + valid = true; + } else { + addViolationDescription("the %s is %d month(s)", rangeDescription, months); + } + } + } + } + + return valid; + } + +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/DateSmallerThanRule.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/DateSmallerThanRule.java new file mode 100644 index 0000000..f3befd6 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/DateSmallerThanRule.java @@ -0,0 +1,63 @@ +package org.dspace.ref.compliance.rules; + +import java.util.*; +import org.apache.commons.collections.*; +import static org.apache.commons.collections.CollectionUtils.*; +import org.apache.commons.lang3.*; +import org.dspace.content.*; +import org.dspace.ref.compliance.definition.model.*; +import org.joda.time.*; + +/** + * Rule to check if a date value of a given metadata field is smaller than a specified value + */ +public class DateSmallerThanRule extends AbstractFieldCheckRule implements ComplianceRule { + + private Value thresholdValue; + + public DateSmallerThanRule(final String fieldDescription, final String metadataField, final List thresholdValues) { + super(fieldDescription, metadataField); + + thresholdValue = CollectionUtils.isEmpty(thresholdValues) ? null : thresholdValues.get(0); + } + + protected boolean checkFieldValues(final List fieldValueList) { + boolean valid = false; + if (isEmpty(fieldValueList)) { + addViolationDescription("The %s field has no value", fieldDescription); + + } else if (thresholdValue == null || StringUtils.isBlank(thresholdValue.getValue())) { + addViolationDescription("The threshold value cannot be blank"); + + } else { + try { + DateTime thresholdDate = parseDateTime(thresholdValue.getValue()); + DateTime dateToCheck = parseDateTime(fieldValueList.get(0).value); + + if(dateToCheck == null) { + addViolationDescription("there is no valid value for the field " + metadataFieldToCheck); + } else if (thresholdDate != null && dateToCheck.compareTo(thresholdDate) < 0) { + valid = true; + } else { + addViolationDescription("the %s is after %s", fieldDescription, + thresholdValue == null ? "ERROR" : getValueDescription(thresholdValue)); + } + + } catch (IllegalArgumentException ex) { + addViolationDescription("the metadata field %s is invalid because it has too few tokens or contains an invalid date", metadataFieldToCheck); + } + } + + return valid; + } + + protected String getRuleDescriptionCompliant() { + return String.format("the %s (%s) is before %s", fieldDescription, metadataFieldToCheck, + thresholdValue == null ? "ERROR" : getValueDescription(thresholdValue)); + } + + protected String getRuleDescriptionViolation() { + return String.format("the %s (%s) must be before %s", fieldDescription, metadataFieldToCheck, + thresholdValue == null ? "ERROR" : getValueDescription(thresholdValue)); + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/DiscoverableRule.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/DiscoverableRule.java new file mode 100644 index 0000000..dd382e6 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/DiscoverableRule.java @@ -0,0 +1,68 @@ +package org.dspace.ref.compliance.rules; + +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.DiscoverResult; +import org.dspace.discovery.SearchService; +import org.dspace.discovery.SearchServiceException; +import org.dspace.util.ItemUtils; + +import java.sql.SQLException; + +/** + * Validation rule that checks if an item is discoverable by an anonymous user through the DSpace search functionality. + */ +public class DiscoverableRule extends AbstractComplianceRule { + + private SearchService searchService; + + public DiscoverableRule(final SearchService searchService) { + this.searchService = searchService; + } + + protected String getRuleDescriptionCompliant() { + return "the item is discoverable using the search functionality"; + } + + protected String getRuleDescriptionViolation() { + return "the item must be discoverable using the search functionality"; + } + + protected boolean doValidationAndBuildDescription(final Context context, final Item item) { + boolean valid = false; + + DiscoverQuery query = new DiscoverQuery(); + query.setQuery("handle:\"" + item.getHandle() + "\""); + query.setMaxResults(0); + query.setStart(0); + + try { + Context anonymousContext = new Context(); + DiscoverResult result = searchService.search(anonymousContext, query); + if(result != null && result.getTotalSearchResults() > 0) { + valid = true; + } else { + addViolationDescription(item); + } + + anonymousContext.complete(); + + } catch (SearchServiceException e) { + addViolationDescription("unable to query discovery for item %s: %s", item.getHandle(), e.getMessage()); + } catch (SQLException e) { + addViolationDescription("unable to create an anonymous context for querying discovery for item %s: %s", item.getHandle(), e.getMessage()); + } + + return valid; + } + + private void addViolationDescription(final Item item) { + String description = "item with %s %s is not discoverable using the search functionality"; + if(item.getHandle() == null) { + addViolationDescription(description, "title", "\"" + ItemUtils.getMetadataFirstValue(item, "dc", "title", null, Item.ANY) + "\""); + } else { + addViolationDescription(description, "handle", item.getHandle()); + } + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/FieldHasValueRule.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/FieldHasValueRule.java new file mode 100644 index 0000000..8a69dea --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/FieldHasValueRule.java @@ -0,0 +1,77 @@ +package org.dspace.ref.compliance.rules; + +import java.util.Collection; +import java.util.*; +import org.apache.commons.collections.*; +import static org.apache.commons.collections.CollectionUtils.*; +import org.apache.commons.lang.*; +import org.dspace.content.*; +import org.dspace.ref.compliance.definition.model.*; + +/** + * Rule that will check if an item field has a specified value + */ +public class FieldHasValueRule extends AbstractFieldCheckRule { + + private Map possibleValues = new LinkedHashMap(); + + private Value checkedCompliantValue = null; + + public FieldHasValueRule(final String fieldDescription, final String metadataField, final Collection possibleValues) { + super(fieldDescription, metadataField); + + if(CollectionUtils.isNotEmpty(possibleValues)) { + for (Value possibleValue : possibleValues) { + this.possibleValues.put(StringUtils.trimToEmpty(possibleValue.getValue()), possibleValue); + } + } + } + + protected boolean checkFieldValues(final List fieldValueList) { + + if (isEmpty(fieldValueList)) { + addViolationDescription("The %s field has no value", fieldDescription); + return false; + } else if(possibleValues.containsKey(fieldValueList.get(0).value)) { + checkedCompliantValue = possibleValues.get(fieldValueList.get(0).value); + return true; + } else { + addViolationDescription("The %s field has value %s", fieldDescription, fieldValueList.get(0).value); + return false; + } + } + + protected String getRuleDescriptionCompliant() { + return String.format("the %s field (%s) has value %s", fieldDescription, metadataFieldToCheck, + checkedCompliantValue == null ? buildValueString() : getValueDescription(checkedCompliantValue)); + } + + protected String getRuleDescriptionViolation() { + return String.format("the %s field (%s) must have value %s", fieldDescription, metadataFieldToCheck, + checkedCompliantValue == null ? buildValueString() : getValueDescription(checkedCompliantValue)); + } + + private String buildValueString() { + if(possibleValues.size() == 1) { + return getValueDescription(possibleValues.values().iterator().next()); + + } else if(possibleValues.size() > 1) { + StringBuilder output = new StringBuilder(); + int i = 1; + + for (Value value : possibleValues.values()) { + output.append(getValueDescription(value)); + if(i == (possibleValues.size() - 1)) { + output.append(" or "); + } else if(i < possibleValues.size()){ + output.append(", "); + } + i++; + } + + return output.toString(); + } else { + return ""; + } + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/FieldIsNotBlankRule.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/FieldIsNotBlankRule.java new file mode 100644 index 0000000..db3e589 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/FieldIsNotBlankRule.java @@ -0,0 +1,37 @@ +package org.dspace.ref.compliance.rules; + +import java.util.*; +import static org.apache.commons.collections.CollectionUtils.*; +import org.dspace.content.*; +import static org.dspace.util.DcValueUtils.*; + +/** + * Validation rule that will check if a field has a non-blank value. + */ +public class FieldIsNotBlankRule extends AbstractFieldCheckRule { + + public FieldIsNotBlankRule(final String fieldDescription, final String metadataField) { + super(fieldDescription, metadataField); + } + + @Override + protected boolean checkFieldValues(final List fieldValueList) { + if (isEmpty(fieldValueList)) { + addViolationDescription("the %s field has no value", fieldDescription); + return false; + } if(isBlank(fieldValueList.get(0))) { + addViolationDescription("the %s field has a blank value", fieldDescription); + return false; + } else { + return true; + } + } + + protected String getRuleDescriptionCompliant() { + return String.format("the %s field (%s) is filled in", fieldDescription, metadataFieldToCheck); + } + + protected String getRuleDescriptionViolation() { + return String.format("the %s field (%s) must be filled in", fieldDescription, metadataFieldToCheck); + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/complianceupdaters/ComplianceDepositCheck.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/complianceupdaters/ComplianceDepositCheck.java new file mode 100644 index 0000000..a793731 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/complianceupdaters/ComplianceDepositCheck.java @@ -0,0 +1,13 @@ +package org.dspace.ref.compliance.rules.complianceupdaters; + +import org.dspace.content.Item; +import org.dspace.core.Context; + +/** + * Created by jonas - jonas@atmire.com on 23/03/16. + */ +public interface ComplianceDepositCheck { + + void checkAndUpdateCompliance(Context context, Item item); + +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/complianceupdaters/FirstComplianceDepositCheckOnMetadata.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/complianceupdaters/FirstComplianceDepositCheckOnMetadata.java new file mode 100644 index 0000000..7894b79 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/complianceupdaters/FirstComplianceDepositCheckOnMetadata.java @@ -0,0 +1,91 @@ +package org.dspace.ref.compliance.rules.complianceupdaters; + +import com.atmire.utils.Metadatum; +import org.apache.commons.lang3.StringUtils; +import org.dspace.content.DCDate; +import org.dspace.content.Item; +import org.dspace.content.MetadataSchema; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.springframework.beans.factory.annotation.Required; + +import java.util.Set; + +/** + * Created by jonas - jonas@atmire.com on 23/03/16. + */ +public abstract class FirstComplianceDepositCheckOnMetadata implements ComplianceDepositCheck { + + private Metadatum metadatumHelper; + private String compliantField; + private Set compliantValues; + + @Required + public void setMetadata(String metadata){ + this.metadatumHelper=new Metadatum(metadata); + + } + public String getMetadata(){ + return metadatumHelper.toString(); + } + + @Required + public void setCompliantField(String compliantField){ + this.compliantField=compliantField; + } + + public String getCompliantField(){ + return compliantField; + } + + @Required + public void setCompliantValues(Set compliantValues){ + this.compliantValues=compliantValues; + } + + public Set getCompliantValues(){ + return compliantValues; + } + + + public void checkAndUpdateCompliance(Context context, Item item){ + + if(processingRequired(item) && compliantValuesApply(item)){ + + if(item.getMetadataByMetadataString(metadatumHelper.toString()).length==0){ + addProvenanceInformation(context, item); + item.addMetadata(metadatumHelper.getSchema(), metadatumHelper.getElement(), metadatumHelper.getQualifier(), null, complianceMetadataValue(item)); + } + + } + } + + private boolean processingRequired(Item item) { + org.dspace.content.Metadatum[] metadata = item.getMetadataByMetadataString(metadatumHelper.toString()); + return metadata == null || metadata.length==0 || (metadata != null && metadata.length>0 && StringUtils.isBlank(metadata[0].value)); + } + + private void addProvenanceInformation(Context context, Item item) { + String now = DCDate.getCurrent().toString(); + EPerson currentUser = context.getCurrentUser(); + String submitter = currentUser.getFullName(); + + submitter = submitter + "(" + currentUser.getEmail() + ")"; + String provDescription = "Item updated for first compliance. Info stored in metadatafield: \""+ metadatumHelper.toString()+"\" by " + + submitter + " on " + now + " (GMT) "; + item.addMetadata(MetadataSchema.DC_SCHEMA, "description", "provenance", "en", provDescription); + } + + protected boolean compliantValuesApply(Item item) { + org.dspace.content.Metadatum[] metadata = item.getMetadataByMetadataString(getCompliantField()); + for(org.dspace.content.Metadatum dcValue :metadata){ + if(getCompliantValues().contains(dcValue.value)){ + return true; + } + } + return false; + } + + protected abstract String complianceMetadataValue(Item item); + +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/complianceupdaters/FirstComplianceDepositCheckerUseCopiedValue.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/complianceupdaters/FirstComplianceDepositCheckerUseCopiedValue.java new file mode 100644 index 0000000..020c074 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/complianceupdaters/FirstComplianceDepositCheckerUseCopiedValue.java @@ -0,0 +1,15 @@ +package org.dspace.ref.compliance.rules.complianceupdaters; + +import org.dspace.content.Item; + +/** + * Created by jonas - jonas@atmire.com on 24/03/16. + * Uses the value that is checked against to put into a new metadata field. + */ +public class FirstComplianceDepositCheckerUseCopiedValue extends FirstComplianceDepositCheckOnMetadata implements ComplianceDepositCheck { + + @Override + protected String complianceMetadataValue(Item item) { + return item.getMetadataByMetadataString(getCompliantField())[0].value; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/complianceupdaters/FirstComplianceDepositCheckerUseDateValue.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/complianceupdaters/FirstComplianceDepositCheckerUseDateValue.java new file mode 100644 index 0000000..6a8d973 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/complianceupdaters/FirstComplianceDepositCheckerUseDateValue.java @@ -0,0 +1,19 @@ +package org.dspace.ref.compliance.rules.complianceupdaters; + +import org.dspace.content.DCDate; +import org.dspace.content.Item; + +import java.util.Date; + +/** + * Created by jonas - jonas@atmire.com on 24/03/16. + * This class uses the current date to fill in the metadata + */ + +public class FirstComplianceDepositCheckerUseDateValue extends FirstComplianceDepositCheckOnMetadata implements ComplianceDepositCheck { + + @Override + protected String complianceMetadataValue(Item item) { + return new DCDate(new Date()).toString(); + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/exception/ValidationRuleDefinitionException.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/exception/ValidationRuleDefinitionException.java new file mode 100644 index 0000000..f44440f --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/exception/ValidationRuleDefinitionException.java @@ -0,0 +1,15 @@ +package org.dspace.ref.compliance.rules.exception; + +/** + * Exception that is thrown when something is wrong the with Validation Rule definition file (config/item-validation-rules.xml) + */ +public class ValidationRuleDefinitionException extends Exception { + + public ValidationRuleDefinitionException(final String message, final Throwable ex) { + super(message, ex); + } + + public ValidationRuleDefinitionException(final String message) { + super(message); + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/AtLeastOneNotBlankRuleBuilder.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/AtLeastOneNotBlankRuleBuilder.java new file mode 100644 index 0000000..f02bd23 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/AtLeastOneNotBlankRuleBuilder.java @@ -0,0 +1,17 @@ +package org.dspace.ref.compliance.rules.factory; + +import org.dspace.ref.compliance.definition.model.*; +import org.dspace.ref.compliance.rules.*; + +/** + * @author philip at atmire.com + */ +public class AtLeastOneNotBlankRuleBuilder extends ComplianceRuleBuilder { + + public ComplianceRule buildRule(final RuleDefinition ruleDefinition) { + AtLeastOneNotBlankRule rule = new AtLeastOneNotBlankRule(ruleDefinition.getFieldDescription(), ruleDefinition.getField()); + applyDefinitionDescriptionAndResolutionHint(rule, ruleDefinition); + return rule; + } + +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/CategorySetMarshaller.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/CategorySetMarshaller.java new file mode 100644 index 0000000..0f3556f --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/CategorySetMarshaller.java @@ -0,0 +1,15 @@ +package org.dspace.ref.compliance.rules.factory; + +import org.dspace.ref.compliance.definition.model.CategorySet; +import org.dspace.util.XmlMarshaller; + +/** + * XML marshaller to unmarshall or marshall the validation rule definition file + */ +public class CategorySetMarshaller extends XmlMarshaller { + + public CategorySetMarshaller() { + super(CategorySet.class); + } + +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/ComplianceCategoryRulesFactory.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/ComplianceCategoryRulesFactory.java new file mode 100644 index 0000000..a2f599e --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/ComplianceCategoryRulesFactory.java @@ -0,0 +1,14 @@ +package org.dspace.ref.compliance.rules.factory; + +import org.dspace.ref.compliance.rules.CompliancePolicy; +import org.dspace.ref.compliance.rules.exception.ValidationRuleDefinitionException; + +/** + * Interface for a factory that is able to instantiate all required compliance validation categories and their rules based on the + * Validation Rule definition file (config/item-validation-rules.xml) + */ +public interface ComplianceCategoryRulesFactory { + + CompliancePolicy createComplianceRulePolicy() throws ValidationRuleDefinitionException; + +} \ No newline at end of file diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/ComplianceCategoryRulesFactoryBean.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/ComplianceCategoryRulesFactoryBean.java new file mode 100644 index 0000000..8eb034f --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/ComplianceCategoryRulesFactoryBean.java @@ -0,0 +1,192 @@ +package org.dspace.ref.compliance.rules.factory; + +import org.apache.commons.collections.CollectionUtils; +import org.dspace.core.ConfigurationManager; +import org.dspace.ref.compliance.definition.model.CategorySet; +import org.dspace.ref.compliance.definition.model.RuleCategory; +import org.dspace.ref.compliance.definition.model.RuleDefinition; +import org.dspace.ref.compliance.definition.model.RuleSet; +import org.dspace.ref.compliance.rules.ComplianceCategory; +import org.dspace.ref.compliance.rules.CompliancePolicy; +import org.dspace.ref.compliance.rules.ComplianceRule; +import org.dspace.ref.compliance.rules.exception.ValidationRuleDefinitionException; + +import javax.xml.bind.JAXBException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Factory that is able to instantiate all required compliance validation categories with their rules based on the + * Validation Rule definition file (config/item-validation-rules.xml) + */ +public class ComplianceCategoryRulesFactoryBean implements ComplianceCategoryRulesFactory { + + private String ruleDefinitionFile; + + private CategorySetMarshaller marshaller = new CategorySetMarshaller(); + + private Map builderMap = new HashMap(); + + public void setBuilderMap(final Map builderMap) { + this.builderMap = builderMap; + } + + private CategorySet categorySet; + + private long lastCategorySetUpdate; + + private long categorySetDateModified; + + private int minutesBetweentCategorySetupdate; + + public CompliancePolicy createComplianceRulePolicy() throws ValidationRuleDefinitionException { + CompliancePolicy output = new CompliancePolicy(); + + CategorySet categorySet = loadRuleDefinitionSet(); + + if (categorySet != null) { + + if (CollectionUtils.isNotEmpty(categorySet.getCategory())) { + List categories = categorySet.getCategory(); + for (RuleCategory categoryDefinition : categories) { + ComplianceCategory category = convertToComplianceCategory(categoryDefinition); + + if (category != null) { + output.addComplianceCategory(category); + } + } + } + + List exceptions = convertRuleSet(categorySet.getExceptions()); + if(CollectionUtils.isNotEmpty(exceptions)) { + output.addExceptionRules(exceptions); + } + + List preconditionRules = convertRuleSet(categorySet.getPreconditions()); + if(CollectionUtils.isNotEmpty(preconditionRules)) { + output.addPreconditionRules(preconditionRules); + } + } + + return output; + } + + private ComplianceCategory convertToComplianceCategory(final RuleCategory categoryDefinition) throws ValidationRuleDefinitionException { + if (categoryDefinition == null) { + return null; + } else { + ComplianceCategory category = new ComplianceCategory(); + category.setOrdinal(categoryDefinition.getOrdinal()); + category.setName(categoryDefinition.getName()); + category.setDescription(categoryDefinition.getDescription()); + category.setResolutionHint(categoryDefinition.getResolutionHint()); + + List rules = convertRuleSet(categoryDefinition.getRules()); + if (CollectionUtils.isNotEmpty(rules)) { + category.addComplianceRules(rules); + } + + List exceptions = convertRuleSet(categoryDefinition.getExceptions()); + if (CollectionUtils.isNotEmpty(exceptions)) { + category.addExceptionRules(exceptions); + } + + return category; + } + } + + private List convertRuleSet(final RuleSet rules) throws ValidationRuleDefinitionException { + List output = new LinkedList(); + + if(rules != null && CollectionUtils.isNotEmpty(rules.getRule())) { + List definitions = rules.getRule(); + for (RuleDefinition ruleDefinition : definitions) { + ComplianceRule rule = convertToComplianceRule(ruleDefinition); + + if (rule != null) { + output.add(rule); + } + } + } + + return output; + } + + private ComplianceRule convertToComplianceRule(final RuleDefinition ruleDefinition) throws ValidationRuleDefinitionException { + ComplianceRule rule = null; + ComplianceRuleBuilder builder = builderMap.get(ruleDefinition.getType()); + + if (builder == null) { + throw new ValidationRuleDefinitionException("Unable to find a rule builder for rule type " + ruleDefinition.getType()); + } else { + rule = builder.buildRule(ruleDefinition); + + if (ruleDefinition.getExceptions() != null && CollectionUtils.isNotEmpty(ruleDefinition.getExceptions().getRule())) { + for (RuleDefinition ruleDefinition1 : ruleDefinition.getExceptions().getRule()) { + ComplianceRule complianceRule = convertToComplianceRule(ruleDefinition1); + + if (complianceRule != null) { + rule.addExceptionRule(complianceRule); + } + } + } + + if (ruleDefinition.getPreconditions() != null && CollectionUtils.isNotEmpty(ruleDefinition.getPreconditions().getRule())) { + for (RuleDefinition ruleDefinition1 : ruleDefinition.getPreconditions().getRule()) { + ComplianceRule complianceRule = convertToComplianceRule(ruleDefinition1); + + if (complianceRule != null) { + rule.addPreconditionRule(complianceRule); + } + } + } + } + return rule; + } + + private CategorySet loadRuleDefinitionSet() throws ValidationRuleDefinitionException { + // minutes to milliseconds + long millisecondsBetweentCategorySetupdate = minutesBetweentCategorySetupdate * 60 * 1000; + + // update if null or if the last update was longer ago then the configured time between updates + if(categorySet == null || lastCategorySetUpdate < (System.currentTimeMillis() - millisecondsBetweentCategorySetupdate)) { + File file = new File(ConfigurationManager.getProperty("dspace.dir") + File.separator + "config" + File.separator + ruleDefinitionFile); + + if(file.lastModified() != categorySetDateModified) { + + FileInputStream inputStream = null; + try { + inputStream = new FileInputStream(file); + categorySet = marshaller.unmarshal(inputStream); + inputStream.close(); + + lastCategorySetUpdate = System.currentTimeMillis(); + categorySetDateModified = file.lastModified(); + + } catch (FileNotFoundException e) { + throw new ValidationRuleDefinitionException("The validation rule definition file " + ruleDefinitionFile + " was not found.", e); + } catch (IOException e) { + throw new ValidationRuleDefinitionException("There was a problem reading the validation rule definition file " + ruleDefinitionFile + ".", e); + } catch (JAXBException e) { + throw new ValidationRuleDefinitionException("There was a problem unmarshalling the validation rule definitions from file " + ruleDefinitionFile + ".", e); + } + } + } + + return categorySet; + } + + public void setMinutesBetweentCategorySetupdate(int minutesBetweentCategorySetupdate) { + this.minutesBetweentCategorySetupdate = minutesBetweentCategorySetupdate; + } + + public void setRuleDefinitionFile(String ruleDefinitionFile) { + this.ruleDefinitionFile = ruleDefinitionFile; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/ComplianceRuleBuilder.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/ComplianceRuleBuilder.java new file mode 100644 index 0000000..b3386d8 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/ComplianceRuleBuilder.java @@ -0,0 +1,18 @@ +package org.dspace.ref.compliance.rules.factory; + +import org.dspace.ref.compliance.definition.model.RuleDefinition; +import org.dspace.ref.compliance.rules.AbstractComplianceRule; +import org.dspace.ref.compliance.rules.ComplianceRule; + +/** + * Interface for a builder class that is able to instantiate compliance vaidation rules + */ +public abstract class ComplianceRuleBuilder { + + public abstract ComplianceRule buildRule(final RuleDefinition ruleDefinition); + + protected void applyDefinitionDescriptionAndResolutionHint(final AbstractComplianceRule rule, final RuleDefinition ruleDefinition) { + rule.setDefinitionHint(ruleDefinition.getDescription()); + rule.setResolutionHint(ruleDefinition.getResolutionHint()); + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/CountGreaterThanRuleBuilder.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/CountGreaterThanRuleBuilder.java new file mode 100644 index 0000000..ee40bac --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/CountGreaterThanRuleBuilder.java @@ -0,0 +1,18 @@ +package org.dspace.ref.compliance.rules.factory; + +import org.dspace.ref.compliance.definition.model.RuleDefinition; +import org.dspace.ref.compliance.rules.ComplianceRule; +import org.dspace.ref.compliance.rules.CountGreaterThanRule; + +/** + * Builder that will instantiate a CountGreaterThan rule based on a rule definition. + */ +public class CountGreaterThanRuleBuilder extends ComplianceRuleBuilder { + + public ComplianceRule buildRule(final RuleDefinition ruleDefinition) { + CountGreaterThanRule rule = new CountGreaterThanRule(ruleDefinition.getFieldDescription(), ruleDefinition.getField().get(0), ruleDefinition.getFieldValue()); + applyDefinitionDescriptionAndResolutionHint(rule, ruleDefinition); + return rule; + } + +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/DateGreaterThanOrEqualRuleBuilder.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/DateGreaterThanOrEqualRuleBuilder.java new file mode 100644 index 0000000..ea6df8f --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/DateGreaterThanOrEqualRuleBuilder.java @@ -0,0 +1,17 @@ +package org.dspace.ref.compliance.rules.factory; + +import org.dspace.ref.compliance.definition.model.*; +import org.dspace.ref.compliance.rules.*; + +/** + * Builder that will instantiate a DateGreaterThanOrEqual rule based on a rule definition. + */ +public class DateGreaterThanOrEqualRuleBuilder extends ComplianceRuleBuilder { + + public ComplianceRule buildRule(final RuleDefinition ruleDefinition) { + DateGreaterThanOrEqualRule rule = new DateGreaterThanOrEqualRule(ruleDefinition.getFieldDescription(), ruleDefinition.getField().get(0), + ruleDefinition.getFieldValue()); + applyDefinitionDescriptionAndResolutionHint(rule, ruleDefinition); + return rule; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/DateRangeSmallerThanRuleBuilder.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/DateRangeSmallerThanRuleBuilder.java new file mode 100644 index 0000000..cf4ee3f --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/DateRangeSmallerThanRuleBuilder.java @@ -0,0 +1,19 @@ +package org.dspace.ref.compliance.rules.factory; + +import org.dspace.ref.compliance.definition.model.RuleDefinition; +import org.dspace.ref.compliance.rules.ComplianceRule; +import org.dspace.ref.compliance.rules.DateRangeSmallerThanRule; + +/** + * Builder that will instantiate a DateRangeSmallerThan rule based on a rule definition. + */ +public class DateRangeSmallerThanRuleBuilder extends ComplianceRuleBuilder { + + public ComplianceRule buildRule(final RuleDefinition ruleDefinition) { + DateRangeSmallerThanRule rule = new DateRangeSmallerThanRule(ruleDefinition.getFrom(), ruleDefinition.getTo(), + ruleDefinition.getFieldDescription(), ruleDefinition.getFieldValue()); + applyDefinitionDescriptionAndResolutionHint(rule, ruleDefinition); + return rule; + } + +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/DateSmallerThanRuleBuilder.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/DateSmallerThanRuleBuilder.java new file mode 100644 index 0000000..9fd04e5 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/DateSmallerThanRuleBuilder.java @@ -0,0 +1,18 @@ +package org.dspace.ref.compliance.rules.factory; + +import org.dspace.ref.compliance.definition.model.RuleDefinition; +import org.dspace.ref.compliance.rules.ComplianceRule; +import org.dspace.ref.compliance.rules.DateSmallerThanRule; + +/** + * Builder that will instantiate a DateSmallerThan rule based on a rule definition. + */ +public class DateSmallerThanRuleBuilder extends ComplianceRuleBuilder { + + public ComplianceRule buildRule(final RuleDefinition ruleDefinition) { + DateSmallerThanRule rule = new DateSmallerThanRule(ruleDefinition.getFieldDescription(), ruleDefinition.getField().get(0), + ruleDefinition.getFieldValue()); + applyDefinitionDescriptionAndResolutionHint(rule, ruleDefinition); + return rule; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/DiscoverableRuleBuilder.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/DiscoverableRuleBuilder.java new file mode 100644 index 0000000..98b5f3d --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/DiscoverableRuleBuilder.java @@ -0,0 +1,22 @@ +package org.dspace.ref.compliance.rules.factory; + +import org.dspace.discovery.SearchService; +import org.dspace.ref.compliance.definition.model.RuleDefinition; +import org.dspace.ref.compliance.rules.ComplianceRule; +import org.dspace.ref.compliance.rules.DiscoverableRule; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Builder that will instantiate a DiscoverableRule rule based on a rule definition. + */ +public class DiscoverableRuleBuilder extends ComplianceRuleBuilder { + + @Autowired + private SearchService searchService; + + public ComplianceRule buildRule(final RuleDefinition ruleDefinition) { + DiscoverableRule rule = new DiscoverableRule(searchService); + applyDefinitionDescriptionAndResolutionHint(rule, ruleDefinition); + return rule; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/NotBlankRuleBuilder.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/NotBlankRuleBuilder.java new file mode 100644 index 0000000..5b6cefa --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/NotBlankRuleBuilder.java @@ -0,0 +1,17 @@ +package org.dspace.ref.compliance.rules.factory; + +import org.dspace.ref.compliance.definition.model.RuleDefinition; +import org.dspace.ref.compliance.rules.ComplianceRule; +import org.dspace.ref.compliance.rules.FieldIsNotBlankRule; + +/** + * Builder that will instantiate a NotBlank rule based on a rule definition. + */ +public class NotBlankRuleBuilder extends ComplianceRuleBuilder { + + public ComplianceRule buildRule(final RuleDefinition ruleDefinition) { + FieldIsNotBlankRule rule = new FieldIsNotBlankRule(ruleDefinition.getFieldDescription(), ruleDefinition.getField().get(0)); + applyDefinitionDescriptionAndResolutionHint(rule, ruleDefinition); + return rule; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/ValueRuleBuilder.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/ValueRuleBuilder.java new file mode 100644 index 0000000..150ffe1 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/factory/ValueRuleBuilder.java @@ -0,0 +1,19 @@ +package org.dspace.ref.compliance.rules.factory; + +import org.dspace.ref.compliance.definition.model.RuleDefinition; +import org.dspace.ref.compliance.rules.ComplianceRule; +import org.dspace.ref.compliance.rules.FieldHasValueRule; + +/** + * Builder that will instantiate a ValueRule rule based on a rule definition. + */ +public class ValueRuleBuilder extends ComplianceRuleBuilder { + + public ComplianceRule buildRule(final RuleDefinition ruleDefinition) { + FieldHasValueRule rule = new FieldHasValueRule(ruleDefinition.getFieldDescription(), ruleDefinition.getField().get(0), + ruleDefinition.getFieldValue()); + applyDefinitionDescriptionAndResolutionHint(rule, ruleDefinition); + return rule; + } + +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/script/CheckItemComplianceScript.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/script/CheckItemComplianceScript.java new file mode 100644 index 0000000..eaa5528 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/rules/script/CheckItemComplianceScript.java @@ -0,0 +1,172 @@ +package org.dspace.ref.compliance.rules.script; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Options; +import org.apache.commons.collections.CollectionUtils; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.handle.HandleManager; +import org.dspace.ref.compliance.result.CategoryComplianceResult; +import org.dspace.ref.compliance.result.ComplianceResult; +import org.dspace.ref.compliance.result.RuleComplianceResult; +import org.dspace.ref.compliance.service.ComplianceCheckService; +import org.dspace.scripts.ContextScript; +import org.dspace.scripts.Script; +import org.dspace.utils.DSpace; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility script to check for a given handle if the corresponding item is compliant with the validation rules + * defined in the Validation Rule definition file (config/item-validation-rules.xml) + *

+ * E.g.: bin/dspace dsrun org.dspace.ref.compliance.rules.script.CheckItemComplianceScript -i 123456789/3 + */ +public class CheckItemComplianceScript extends ContextScript { + + public static final String ITEM_HANDLE_FLAG = "i"; + public static final String SHOW_ALL_FLAG = "a"; + + private String itemHandle; + private boolean showAll = false; + + public static void main(String[] args) { + Script script = new CheckItemComplianceScript(); + script.mainImpl(args); + } + + @Override + protected int processLine(CommandLine line) { + int status = super.processLine(line); + + if (status == 0) { + // other arguments + if (line.hasOption(ITEM_HANDLE_FLAG)) { + itemHandle = line.getOptionValue(ITEM_HANDLE_FLAG); + } + if (line.hasOption(SHOW_ALL_FLAG)) { + showAll = true; + } + } + return status; + } + + @Override + protected Options createCommandLineOptions() { + Options options = super.createCommandLineOptions(); + options.addOption(ITEM_HANDLE_FLAG, "item-handle", true, "The handle of the item to check"); + options.addOption(SHOW_ALL_FLAG, "show-all-rules", false, "Use this flag to show all rules, including the not applicable ones"); + return options; + } + + @Override + public void run() throws Exception { + print("Checking REF compliance of item with handle " + itemHandle); + DSpaceObject object = HandleManager.resolveToObject(context, itemHandle); + + if (object != null && object instanceof Item) { + Item item = (Item) object; + + ComplianceCheckService complianceCheckService = new DSpace().getServiceManager() + .getServiceByName("refComplianceCheckService", ComplianceCheckService.class); + + ComplianceResult result = complianceCheckService.checkCompliance(context, item); + + print(""); + + if(result.isApplicable()) { + print("The item is " + (result.isCompliant() ? "" : " NOT ") + "compliant"); + + if (result.isCompliantByException()) { + List appliedExceptions = result.getAppliedExceptions(); + + print(""); + print("***** Compliant by Exception *****"); + for (RuleComplianceResult appliedException : appliedExceptions) { + print("Exception: " + appliedException.getResultDescription() + + " (" + appliedException.getDefinitionHint() + ")"); + } + + } + + for (CategoryComplianceResult categoryResult : result.getOrderedCategoryResults()) { + if (categoryResult.isApplicable() || showAll) { + print(""); + print("***** " + categoryResult.getCategoryName() + ": " + + (categoryResult.isApplicable() ? + (categoryResult.isCompliant() ? "Compliant" : "NOT compliant") : "NOT APPLICABLE") + + " *****"); + + + if (categoryResult.isCompliantByException()) { + List appliedExceptions = categoryResult.getAppliedExceptions(); + + for (RuleComplianceResult appliedException : appliedExceptions) { + print("Compliant by exception: " + appliedException.getResultDescription() + + " (" + appliedException.getDefinitionHint() + ")"); + } + } else { + + List compliantRules = filterRules(categoryResult.getCompliantRules()); + if (CollectionUtils.isNotEmpty(compliantRules)) { + print("* Rules that are OK:"); + for (RuleComplianceResult ruleResult : compliantRules) { + print("- " + ruleResult.getResultDescription() + + " (" + ruleResult.getDefinitionHint() + ")"); + + if (ruleResult.exceptionApplied()) { + print("--- Exception: " + ruleResult.getExceptionDescription()); + } + } + } + + List violatedRules = filterRules(categoryResult.getViolatedRules()); + if (CollectionUtils.isNotEmpty(violatedRules)) { + print("* Rules that are NOT OK:"); + for (RuleComplianceResult ruleResult : violatedRules) { + print("- " + ruleResult.getResultDescription() + + " (" + ruleResult.getDefinitionHint() + ")"); + for (String violation : ruleResult.getViolationDescriptions()) { + print("--- " + violation); + } + print("--- RESOLUTION: " + ruleResult.getResolutionHint()); + } + } + } + } + } + } + else { + print("The item is not applicable"); + + List violatedpreconditionRules = filterRules(result.getViolatedPreconditions()); + if (CollectionUtils.isNotEmpty(violatedpreconditionRules)) { + print("* Rules that are not ok:"); + for (RuleComplianceResult ruleResult : violatedpreconditionRules) { + print("- " + ruleResult.getResultDescription() + + " (" + ruleResult.getDefinitionHint() + ")"); + for (String violation : ruleResult.getViolationDescriptions()) { + print("--- " + violation); + } + print("--- RESOLUTION: " + ruleResult.getResolutionHint()); + } + } + } + + } else { + print("The item with handle " + itemHandle + " was not found."); + } + } + + private List filterRules(final List categoryResults) { + List output = new ArrayList(categoryResults.size()); + for (RuleComplianceResult result : categoryResults) { + if (result.isApplicable() || showAll) { + output.add(result); + } + } + return output; + } + +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/service/ComplianceCheckService.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/service/ComplianceCheckService.java new file mode 100644 index 0000000..9b1249c --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/service/ComplianceCheckService.java @@ -0,0 +1,15 @@ +package org.dspace.ref.compliance.service; + +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.ref.compliance.result.ComplianceResult; + +/** + * Service to check if an item is compliant with the defined validation rules + */ +public interface ComplianceCheckService { + + ComplianceResult checkCompliance(final Context context, final Item item); + + boolean blockOnWorkflow(String collectionHandle); +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/service/ComplianceCheckServiceBean.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/service/ComplianceCheckServiceBean.java new file mode 100644 index 0000000..190de36 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/service/ComplianceCheckServiceBean.java @@ -0,0 +1,204 @@ +package org.dspace.ref.compliance.service; + +import com.atmire.utils.EmbargoUtils; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.content.DCDate; +import org.dspace.content.Item; +import org.dspace.content.Metadatum; +import org.dspace.core.Context; +import org.dspace.ref.compliance.result.ComplianceResult; +import org.dspace.ref.compliance.rules.CompliancePolicy; +import org.dspace.ref.compliance.rules.exception.ValidationRuleDefinitionException; +import org.dspace.ref.compliance.rules.factory.ComplianceCategoryRulesFactory; +import org.dspace.util.subclasses.Metadata; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * Implementation of {@link ComplianceCheckService} + */ +public class ComplianceCheckServiceBean implements ComplianceCheckService { + + private static Logger log = Logger.getLogger(ComplianceCheckServiceBean.class); + + private Map fakeIfEmptyDuringValidation; + private Map fakeIfEmptyDuringUnarchivedValidation; + + private final String now = "now"; + private final String embargoOrNow = "embargoOrNow"; + private final String fullIso = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + + private final String blockWorkflowConfig = ".workflow.block.on.rule.violation."; + private final String defaultConfig = "default"; + + private String identifier; + + private ComplianceCategoryRulesFactory rulesFactory; + + public ComplianceResult checkCompliance(final Context context, final Item item) { + CompliancePolicy policy = getCompliancePolicy(); + + if (policy == null) { + return new ComplianceResult(); + + } else { + ComplianceResult complianceResult = null; + + // temporarily add fake values for empty fields so validation does not fail on these fields + List fakeFields = addFakeValues(context, item); + + try { + complianceResult = policy.validatePreconditionRules(context, item); + + if (complianceResult.isApplicable()) { + complianceResult = policy.validate(context, item, complianceResult); + + complianceResult.addEstimatedValues(fakeFields); + + } + + } catch(Exception ex) { + context.abort(); + log.warn(ex.getMessage(), ex); + + } finally { + // Always remove the temporary values + removeFakeValues(context, fakeFields, item); + } + + return complianceResult; + } + } + + @Override + public boolean blockOnWorkflow(String collectionHandle) { + String blockWorkflowOnViolation = org.dspace.core.ConfigurationManager.getProperty("item-compliance", + identifier + blockWorkflowConfig + collectionHandle); + + if (StringUtils.isBlank(blockWorkflowOnViolation)) { + blockWorkflowOnViolation = org.dspace.core.ConfigurationManager.getProperty("item-compliance", + identifier + blockWorkflowConfig + defaultConfig); + } + + if (StringUtils.isNotBlank(blockWorkflowOnViolation)) { + return Boolean.parseBoolean(blockWorkflowOnViolation); + } + + return false; + } + + private void removeFakeValues(Context context, List fakeFields, Item item) { + if(CollectionUtils.isNotEmpty(fakeFields) && context.isValid()) { + + for (Metadata fakeField : fakeFields) { + item.clearMetadata(fakeField.schema, fakeField.element, fakeField.qualifier, fakeField.language); + } + try { + item.update(); + context.commit(); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + + } + } + + private List addFakeValues(Context context, Item item) { + List fakeFields = addFakeValues(context, item, fakeIfEmptyDuringValidation); + + if (!item.isArchived()) { + fakeFields.addAll(addFakeValues(context, item, fakeIfEmptyDuringUnarchivedValidation)); + } + + return fakeFields; + } + + private List addFakeValues(Context context, Item item, Map fakeFieldMap) { + List fakeFields = new ArrayList(); + + if (MapUtils.isNotEmpty(fakeFieldMap)) { + for (String field : fakeFieldMap.keySet()) { + Metadatum[] dcValues = item.getMetadataByMetadataString(field); + + if (dcValues.length == 0) { + Metadata metadata = org.dspace.util.MetadataFieldString.encapsulate(field); + addFakeValue(context, item, metadata, fakeFieldMap.get(field)); + fakeFields.add(metadata); + } + } + } + + return fakeFields; + } + + private void addFakeValue(Context context, Item item, Metadata field, String value) { + String estimatedValue = estimateValue(context, item, value); + + item.addMetadata(field.schema, field.element, field.qualifier, field.language, estimatedValue); + field.value = estimatedValue; + + try { + item.update(); + context.commit(); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + private String estimateValue(Context context, Item item, String value) { + String estimatedValue = null; + if (now.equals(value)) { + estimatedValue = DCDate.getCurrent().toString(); + } else if (embargoOrNow.equals(value)) { + Date lastEmbargo = EmbargoUtils.getLastEmbargo(item, context); + + if (lastEmbargo == null) { + lastEmbargo = new Date(); + } + + estimatedValue = new DCDate(lastEmbargo).toString(); + } else { + Metadatum[] metadata = item.getMetadataByMetadataString(value); + + if (metadata.length > 0) { + estimatedValue = metadata[0].value; + } + } + return estimatedValue; + } + + private CompliancePolicy getCompliancePolicy() { + try { + return rulesFactory.createComplianceRulePolicy(); + } catch (ValidationRuleDefinitionException e) { + log.warn("Unable to load the validation rules: " + e.getMessage(), e); + } + return null; + } + + public void setRulesFactory(ComplianceCategoryRulesFactory rulesFactory) { + this.rulesFactory = rulesFactory; + } + + public void setFakeIfEmptyDuringValidation(Map fakeIfEmptyDuringValidation) { + this.fakeIfEmptyDuringValidation = fakeIfEmptyDuringValidation; + } + + public void setFakeIfEmptyDuringUnarchivedValidation(Map fakeIfEmptyDuringUnarchivedValidation) { + this.fakeIfEmptyDuringUnarchivedValidation = fakeIfEmptyDuringUnarchivedValidation; + } + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/submission/REFComplianceStep.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/submission/REFComplianceStep.java new file mode 100644 index 0000000..447627d --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/submission/REFComplianceStep.java @@ -0,0 +1,62 @@ +package org.dspace.ref.compliance.submission; + +import java.io.*; +import java.sql.*; +import javax.servlet.*; +import javax.servlet.http.*; +import org.apache.commons.lang.*; +import org.dspace.app.util.*; +import org.dspace.authorize.*; +import org.dspace.content.*; +import org.dspace.core.*; +import org.dspace.ref.compliance.result.*; +import org.dspace.ref.compliance.service.*; +import org.dspace.submit.*; +import org.dspace.utils.*; + +/** + * Created by Philip Vissenaekens (philip at atmire dot com) + * Date: 23/03/16 + * Time: 11:02 + */ +public class REFComplianceStep extends AbstractProcessingStep { + + public static final int STATUS_BLOCKED = 2; + + private static final String blockSubmissionConfig = "ref.submission.block.on.rule.violation."; + private static final String defaultConfig = "default"; + + private static ComplianceCheckService complianceCheckService = new DSpace().getServiceManager() + .getServiceByName("refComplianceCheckService", ComplianceCheckService.class); + + @Override + public int doProcessing(Context context, HttpServletRequest request, HttpServletResponse response, SubmissionInfo subInfo) throws ServletException, IOException, SQLException, AuthorizeException { + String buttonPressed = Util.getSubmitButton(request, NEXT_BUTTON); + + if(NEXT_BUTTON.equals(buttonPressed)) { + + Item item = subInfo.getSubmissionItem().getItem(); + + String blockSubmissionOnViolation = org.dspace.core.ConfigurationManager.getProperty("item-compliance", blockSubmissionConfig + subInfo.getCollectionHandle()); + + if(StringUtils.isBlank(blockSubmissionOnViolation)) { + blockSubmissionOnViolation = org.dspace.core.ConfigurationManager.getProperty("item-compliance", blockSubmissionConfig + defaultConfig); + } + + if (StringUtils.isNotBlank(blockSubmissionOnViolation) && Boolean.parseBoolean(blockSubmissionOnViolation)) { + ComplianceResult result = complianceCheckService.checkCompliance(context, item); + + if (!result.isCompliant()) { + return STATUS_BLOCKED; + } + } + } + + return STATUS_COMPLETE; + } + + @Override + public int getNumberOfPages(HttpServletRequest request, SubmissionInfo subInfo) throws ServletException { + return 1; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/submission/RIOXXComplianceStep.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/submission/RIOXXComplianceStep.java new file mode 100644 index 0000000..c2da4aa --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/submission/RIOXXComplianceStep.java @@ -0,0 +1,62 @@ +package org.dspace.ref.compliance.submission; + +import java.io.*; +import java.sql.*; +import javax.servlet.*; +import javax.servlet.http.*; +import org.apache.commons.lang.*; +import org.dspace.app.util.*; +import org.dspace.authorize.*; +import org.dspace.content.*; +import org.dspace.core.*; +import org.dspace.ref.compliance.result.*; +import org.dspace.ref.compliance.service.*; +import org.dspace.submit.*; +import org.dspace.utils.*; + +/** + * Created by Philip Vissenaekens (philip at atmire dot com) + * Date: 23/03/16 + * Time: 11:02 + */ +public class RIOXXComplianceStep extends AbstractProcessingStep { + + public static final int STATUS_BLOCKED = 2; + + private static final String blockSubmissionConfig = "rioxx.submission.block.on.rule.violation."; + private static final String defaultConfig = "default"; + + private static ComplianceCheckService complianceCheckService = new DSpace().getServiceManager() + .getServiceByName("rioxxComplianceCheckService", ComplianceCheckService.class); + + @Override + public int doProcessing(Context context, HttpServletRequest request, HttpServletResponse response, SubmissionInfo subInfo) throws ServletException, IOException, SQLException, AuthorizeException { + String buttonPressed = Util.getSubmitButton(request, NEXT_BUTTON); + + if(NEXT_BUTTON.equals(buttonPressed)) { + + Item item = subInfo.getSubmissionItem().getItem(); + + String blockSubmissionOnViolation = ConfigurationManager.getProperty("item-compliance", blockSubmissionConfig + subInfo.getCollectionHandle()); + + if(StringUtils.isBlank(blockSubmissionOnViolation)) { + blockSubmissionOnViolation = ConfigurationManager.getProperty("item-compliance", blockSubmissionConfig + defaultConfig); + } + + if (StringUtils.isNotBlank(blockSubmissionOnViolation) && Boolean.parseBoolean(blockSubmissionOnViolation)) { + ComplianceResult result = complianceCheckService.checkCompliance(context, item); + + if (!result.isCompliant()) { + return STATUS_BLOCKED; + } + } + } + + return STATUS_COMPLETE; + } + + @Override + public int getNumberOfPages(HttpServletRequest request, SubmissionInfo subInfo) throws ServletException { + return 1; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/workflow/REFComplianceAction.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/workflow/REFComplianceAction.java new file mode 100644 index 0000000..7eea2d9 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/workflow/REFComplianceAction.java @@ -0,0 +1,56 @@ +package org.dspace.ref.compliance.workflow; + +import java.io.*; +import java.sql.*; +import javax.servlet.http.*; +import org.dspace.authorize.*; +import org.dspace.core.*; +import org.dspace.ref.compliance.result.*; +import org.dspace.ref.compliance.service.*; +import org.dspace.utils.*; +import org.dspace.xmlworkflow.*; +import org.dspace.xmlworkflow.state.*; +import org.dspace.xmlworkflow.state.actions.*; +import org.dspace.xmlworkflow.state.actions.processingaction.*; +import org.dspace.xmlworkflow.storedcomponents.*; + +/** + * Created by Philip Vissenaekens (philip at atmire dot com) + * Date: 22/03/16 + * Time: 16:28 + */ +public class REFComplianceAction extends ProcessingAction { + + private static ComplianceCheckService complianceCheckService = new DSpace().getServiceManager() + .getServiceByName("refComplianceCheckService", ComplianceCheckService.class); + + @Override + public void activate(Context c, XmlWorkflowItem wf) throws SQLException, IOException, AuthorizeException, WorkflowException { + + } + + @Override + public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) throws SQLException, AuthorizeException, IOException, WorkflowException { + if(request.getParameter("submit_continue") != null && allowContinue(c, wfi)){ + return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); + } else if (request.getParameter("submit_return") != null){ + return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, 1); + } else { + return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); + } + } + + private boolean allowContinue(Context c, XmlWorkflowItem wfi){ + boolean blockWorkflow = complianceCheckService.blockOnWorkflow(wfi.getCollection().getHandle()); + + if (blockWorkflow) { + ComplianceResult result = complianceCheckService.checkCompliance(c, wfi.getItem()); + + if (!result.isCompliant()) { + return false; + } + } + + return true; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/workflow/RIOXXComplianceAction.java b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/workflow/RIOXXComplianceAction.java new file mode 100644 index 0000000..12adaf0 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/ref/compliance/workflow/RIOXXComplianceAction.java @@ -0,0 +1,56 @@ +package org.dspace.ref.compliance.workflow; + +import java.io.*; +import java.sql.*; +import javax.servlet.http.*; +import org.dspace.authorize.*; +import org.dspace.core.*; +import org.dspace.ref.compliance.result.*; +import org.dspace.ref.compliance.service.*; +import org.dspace.utils.*; +import org.dspace.xmlworkflow.*; +import org.dspace.xmlworkflow.state.*; +import org.dspace.xmlworkflow.state.actions.*; +import org.dspace.xmlworkflow.state.actions.processingaction.*; +import org.dspace.xmlworkflow.storedcomponents.*; + +/** + * Created by Philip Vissenaekens (philip at atmire dot com) + * Date: 22/03/16 + * Time: 16:28 + */ +public class RIOXXComplianceAction extends ProcessingAction { + + private static ComplianceCheckService complianceCheckService = new DSpace().getServiceManager() + .getServiceByName("rioxxComplianceCheckService", ComplianceCheckService.class); + + @Override + public void activate(Context c, XmlWorkflowItem wf) throws SQLException, IOException, AuthorizeException, WorkflowException { + + } + + @Override + public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) throws SQLException, AuthorizeException, IOException, WorkflowException { + if(request.getParameter("submit_continue") != null && allowContinue(c, wfi)){ + return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); + } else if (request.getParameter("submit_return") != null){ + return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, 1); + } else { + return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); + } + } + + private boolean allowContinue(Context c, XmlWorkflowItem wfi){ + boolean blockWorkflow = complianceCheckService.blockOnWorkflow(wfi.getCollection().getHandle()); + + if (blockWorkflow) { + ComplianceResult result = complianceCheckService.checkCompliance(c, wfi.getItem()); + + if (!result.isCompliant()) { + return false; + } + } + + return true; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/scripts/ContextScript.java b/dspace/modules/additions/src/main/java/org/dspace/scripts/ContextScript.java new file mode 100644 index 0000000..1398c68 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/scripts/ContextScript.java @@ -0,0 +1,53 @@ +package org.dspace.scripts; + +import org.apache.commons.cli.CommandLine; +import org.dspace.core.Context; + +import java.sql.SQLException; + +/** + * Created by: Antoine Snyers (antoine at atmire dot com) + * Date: 21 May 2014 + */ +public class ContextScript extends Script { + + protected Context context; + + public ContextScript(Context context) { + super(); + this.context = context; + } + + public ContextScript() { + } + + public static void main(String[] args) { + Script Script = new ContextScript(); + Script.mainImpl(args); + } + + public void mainImpl(String[] args) { + context = null; + try { + context = new Context(); + context.turnOffAuthorisationSystem(); + + super.mainImpl(args); + + } catch (SQLException e) { + printAndLogError(e); + } finally { + if (context != null) { + context.abort(); + } + } + } + + @Override + protected int processLine(CommandLine line) { + // no extra arguments + return 0; + } + + +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/scripts/ItemIteratorScript.java b/dspace/modules/additions/src/main/java/org/dspace/scripts/ItemIteratorScript.java new file mode 100644 index 0000000..8158d5e --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/scripts/ItemIteratorScript.java @@ -0,0 +1,168 @@ +package org.dspace.scripts; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Options; +import org.apache.commons.lang.ArrayUtils; +import org.dspace.content.*; +import org.dspace.core.Context; +import org.dspace.handle.HandleManager; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +/** + * Created by: Antoine Snyers (antoine at atmire dot com) + * Date: 21 May 2014 + */ +public class ItemIteratorScript extends ContextScript { + + + private String[] includes; + private String[] excludes; + + public ItemIteratorScript(Context context) { + super(context); + } + + public ItemIteratorScript() { + } + + public void run() throws Exception { + print("ItemIteratorScript initializing..."); + try { + ItemIterator itemIterator = getItemIterator(); + while (itemIterator.hasNext()) { + Item item = itemIterator.next(); + print(item.getHandle() + "\t\t" + item.getName()); + } + } catch (Exception e) { + printAndLogError(e); + } + print("ItemIteratorScript done."); + } + + public static void main(String[] args) { + Script Script = new ItemIteratorScript(); + Script.mainImpl(args); + } + + @Override + protected int processLine(CommandLine line) { + int status = super.processLine(line); + if (status == 0) { + // other arguments + if (line.hasOption('i')) { + setIncludes(line.getOptionValues('i')); + } + if (line.hasOption('o')) { + setExcludes(line.getOptionValues('o')); + } + } + return status; + } + + protected Options createCommandLineOptions() { + Options options = super.createCommandLineOptions(); + options.addOption("i", "item", true, "Items, collections, communities over which to iterate"); + options.addOption("o", "omit", true, "Items, collections, communities to exclude"); + return options; + } + + protected ItemIterator getItemIterator() throws SQLException { + ItemIterator items = null; + + if (ArrayUtils.isNotEmpty(includes)) { + Set ids = getItemIDs(includes); + items = new ItemIterator(context, new ArrayList(ids)); + } + + if (ArrayUtils.isEmpty(includes) || items == null || !items.hasNext()) { + items = Item.findAll(context); + } + + if (ArrayUtils.isNotEmpty(excludes)) { + Set excludedIDs = getItemIDs(excludes); + Set ids = new HashSet(); + while (items.hasNext()) { + Item item = items.next(); + int id = item.getID(); + if (excludedIDs == null || !excludedIDs.contains(id)) { + ids.add(id); + } + } + items = new ItemIterator(context, new ArrayList(ids)); + } + return items; + } + + /** + * Returns a set of all item IDs present in "includes" + * and all items from a collection or community present in "includes". + * + * @param includes An array of handles or item IDs + * @throws java.sql.SQLException + */ + protected Set getItemIDs(String[] includes) throws SQLException { + Set ids = new HashSet(); + if (includes != null) { + for (String handle : includes) { + if (handle.contains("/")) { + DSpaceObject dSpaceObject = HandleManager.resolveToObject(context, handle); + if (dSpaceObject instanceof Item) { + ids.add(dSpaceObject.getID()); + } else if (dSpaceObject instanceof Collection) { + Collection collection = (Collection) dSpaceObject; + addCollection(ids, collection); + } else if (dSpaceObject instanceof Community) { + Community community = (Community) dSpaceObject; + addCommunity(ids, community); + } else { + print(handle + " could not be resolved to an item, collection or community"); + } + } else { + // not a handle, maybe an internal ID + Item item = null; + try { + item = Item.find(context, Integer.valueOf(handle)); + } catch (NumberFormatException e) { + print(handle + " could not be resolved to an item"); + } + + if (item != null) { + ids.add(item.getID()); + } + } + } + } + return ids; + } + + protected void addCommunity(Set ids, Community community) throws SQLException { + Community[] subcommunities = community.getSubcommunities(); + for (Community subcommunity : subcommunities) { + addCommunity(ids, subcommunity); + } + Collection[] collections = community.getCollections(); + for (Collection collection : collections) { + addCollection(ids, collection); + } + } + + protected void addCollection(Set ids, Collection collection) throws SQLException { + ItemIterator collectionItems = collection.getItems(); + while (collectionItems.hasNext()) { + Item item = collectionItems.next(); + ids.add(item.getID()); + } + } + + public void setIncludes(String[] includes) { + this.includes = includes; + } + + public void setExcludes(String[] excludes) { + this.excludes = excludes; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/scripts/Script.java b/dspace/modules/additions/src/main/java/org/dspace/scripts/Script.java new file mode 100644 index 0000000..4aafc07 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/scripts/Script.java @@ -0,0 +1,133 @@ +package org.dspace.scripts; + +import org.apache.commons.cli.*; +import org.apache.log4j.Logger; + +import java.io.PrintWriter; + +/** + * Created by: Antoine Snyers (antoine at atmire dot com) + * Date: 21 May 2014 + */ +public class Script { + + /** + * log4j logger + */ + private static Logger log = Logger.getLogger(Script.class); + + protected PrintWriter print = null; + + protected boolean printToLog = false; + + public Script() { + print = new PrintWriter(System.out, true); + } + + public static void main(String[] args) { + + Script Script = new Script(); + Script.mainImpl(args); + } + + public void mainImpl(String[] args) { + try { + if (processArgs(args) == 0) { + System.exit(0); + } + run(); + + } catch (ParseException e) { + print(e.getMessage()); + printHelp(createCommandLineOptions()); + } catch (Exception e) { + printAndLogError(e); + } + } + + protected int processArgs(String[] args) throws ParseException { + CommandLineParser parser = new PosixParser(); + Options options = createCommandLineOptions(); + CommandLine line = parser.parse(options, args); + + // help + if (line.hasOption("h")) { + printHelp(options); + return 0; + } + + // other arguments + int status = processLine(line); + if (status != 0) { + return status; + } + + // print to std out + setPrinter(new PrintWriter(System.out, true)); + return 1; + } + + private void printHelp(Options options) { + HelpFormatter helpFormatter = new HelpFormatter(); + helpFormatter.printHelp("dsrun " + getClass().getCanonicalName(), options); + } + + /** + * Hook for subclasses + */ + protected int processLine(CommandLine line) { + return 0; + } + + protected Options createCommandLineOptions() { + Options options = new Options(); + options.addOption("h", "help", false, "Prints a helpful message about this script's usage"); + + /** Example - long + OptionBuilder.withDescription("File to import"); + OptionBuilder.withLongOpt("file"); + OptionBuilder.hasArg(true); + OptionBuilder.isRequired(true); + options.addOption(OptionBuilder.create("f")); + */ + return options; + } + + /** + * The actual functionality of the script. + */ + public void run() throws Exception { + print("Script initializing..."); + try { + print.println("Running wild !"); + } catch (Exception e) { + printAndLogError(e); + } + print("Script done."); + } + + + public void print(String line) { + if (printToLog) { + log.info(line); + } else { + print.println(line); + } + } + + public void printAndLogError(Exception error) { + if (print != null) { + print.println("Error in Script " + error.getClass()); + error.printStackTrace(print); + } + log.error("Error in Script", error); + } + + public void setPrinter(PrintWriter print) { + this.print = print; + } + + public void setPrintToLog(boolean printToLog) { + this.printToLog = printToLog; + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/storage/rdbms/DatabaseRegistryUpdater.java b/dspace/modules/additions/src/main/java/org/dspace/storage/rdbms/DatabaseRegistryUpdater.java new file mode 100644 index 0000000..167da03 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/storage/rdbms/DatabaseRegistryUpdater.java @@ -0,0 +1,227 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.storage.rdbms; + +import java.io.*; +import java.sql.*; +import org.dspace.administer.*; +import org.dspace.core.*; +import org.dspace.eperson.*; +import org.flywaydb.core.api.*; +import org.flywaydb.core.api.callback.*; +import org.slf4j.*; + +/** + * This is a FlywayCallback class which automatically updates the + * Metadata Schema Registry and Bitstream Formats Registries BEFORE + * any Database migration occurs. + *

+ * The reason this runs BEFORE a migration is to ensure that any new + * metadata fields are FIRST added to our registries, so that the + * migrations can make use of those new metadata fields, etc. + *

+ * However, there is one exception. If this is a "fresh install" of DSpace, + * we'll need to wait until the necessary database tables are created. In + * that scenario we will load registries AFTER the initial migration. + * + * @author Tim Donohue + */ +public class DatabaseRegistryUpdater implements FlywayCallback +{ + /** logging category */ + private static final Logger log = LoggerFactory.getLogger(DatabaseRegistryUpdater.class); + + // Whether or not this is a fresh install of DSpace + // This determines whether to update registries PRE or POST migration + private boolean freshInstall = false; + + /** + * Method to actually update our registries from latest configs + */ + private void updateRegistries() + { + Context context = null; + try + { + context = new Context(); + context.turnOffAuthorisationSystem(); + + String base = ConfigurationManager.getProperty("dspace.dir") + + File.separator + "config" + File.separator + + "registries" + File.separator; + + // Load updates to Bitstream format registry (if any) + log.info("Updating Bitstream Format Registry based on " + base + "bitstream-formats.xml"); + RegistryLoader.loadBitstreamFormats(context, base + "bitstream-formats.xml"); + + // Load updates to Metadata schema registries (if any) + log.info("Updating Metadata Registries based on metadata type configs in " + base); + MetadataImporter.loadRegistry(base + "dublin-core-types.xml", true); + MetadataImporter.loadRegistry(base + "dcterms-types.xml", true); + MetadataImporter.loadRegistry(base + "eperson-types.xml", true); + MetadataImporter.loadRegistry(base + "sword-metadata.xml", true); + MetadataImporter.loadRegistry(base + "rioxxterms-types.xml", true); + MetadataImporter.loadRegistry(base + "refterms-types.xml", true); + + // Check if XML Workflow is enabled in workflow.cfg + if (ConfigurationManager.getProperty("workflow", "workflow.framework").equals("xmlworkflow")) + { + // If so, load in the workflow metadata types as well + MetadataImporter.loadRegistry(base + "workflow-types.xml", true); + } + + context.restoreAuthSystemState(); + // Commit changes and close context + context.complete(); + log.info("All Bitstream Format Regitry and Metadata Registry updates were completed."); + } + catch(Exception e) + { + log.error("Error attempting to update Bitstream Format and/or Metadata Registries", e); + } + finally + { + // Clean up our context, if it still exists & it was never completed + if(context!=null && context.isValid()) + context.abort(); + } + } + + + @Override + public void afterClean(Connection connection) + { + // do nothing + } + + @Override + public void afterEachMigrate(Connection connection, MigrationInfo info) + { + // do nothing + } + + @Override + public void afterInfo(Connection connection) + { + if (!freshInstall) { + updateRegistries(); + } + } + + @Override + public void afterInit(Connection connection) + { + // do nothing + } + + @Override + public void afterMigrate(Connection connection) + { + // If this is a fresh install, we must update registries AFTER the + // initial migrations (since the registry tables won't exist until the + // initial migrations are performed) + if(freshInstall) + { + updateRegistries(); + freshInstall = false; + } + + // After every migrate, ensure default Groups are setup correctly. + Context context = null; + try + { + context = new Context(); + context.turnOffAuthorisationSystem(); + // While it's not really a formal "registry", we need to ensure the + // default, required Groups exist in the DSpace database + Group.initDefaultGroupNames(context); + context.restoreAuthSystemState(); + // Commit changes and close context + context.complete(); + } + catch(Exception e) + { + log.error("Error attempting to add/update default DSpace Groups", e); + } + finally + { + // Clean up our context, if it still exists & it was never completed + if(context!=null && context.isValid()) + context.abort(); + } + } + + @Override + public void afterRepair(Connection connection) + { + // do nothing + } + + @Override + public void afterValidate(Connection connection) + { + // do nothing + } + + @Override + public void beforeClean(Connection connection) + { + // do nothing + } + + @Override + public void beforeEachMigrate(Connection connection, MigrationInfo info) + { + // do nothing + } + + @Override + public void beforeInfo(Connection connection) + { + // do nothing + } + + @Override + public void beforeInit(Connection connection) + { + // do nothing + } + + @Override + public void beforeMigrate(Connection connection) + { + // Check if our MetadataSchemaRegistry table exists yet. + // If it does NOT, then this is a fresh install & we'll need to + // updateRegistries() AFTER migration + if(DatabaseUtils.tableExists(connection, "MetadataSchemaRegistry")) + { + // Ensure registries are updated BEFORE a database migration (upgrade) + // We need to ensure any new metadata fields are added before running + // migrations, just in case the migrations need to utilize those new fields + updateRegistries(); + } + else + { + // this is a fresh install, need to migrate first in order to create + // the registry tables. + freshInstall = true; + } + } + + @Override + public void beforeRepair(Connection connection) + { + // do nothing + } + + @Override + public void beforeValidate(Connection connection) + { + // do nothing + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/submit/step/DescribeStep.java b/dspace/modules/additions/src/main/java/org/dspace/submit/step/DescribeStep.java new file mode 100644 index 0000000..09b0f78 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/submit/step/DescribeStep.java @@ -0,0 +1,1068 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.submit.step; + +import java.io.*; +import java.sql.*; +import java.util.*; +import javax.servlet.*; +import javax.servlet.http.*; +import org.apache.commons.lang.*; +import org.apache.log4j.*; +import org.dspace.app.util.*; +import org.dspace.authorize.*; +import org.dspace.content.Collection; +import org.dspace.content.*; +import org.dspace.content.authority.*; +import org.dspace.core.*; +import org.dspace.submit.*; + +/** + * Describe step for DSpace submission process. Handles the gathering of + * descriptive information (i.e. metadata) for an item being submitted into + * DSpace. + *

+ * This class performs all the behind-the-scenes processing that + * this particular step requires. This class's methods are utilized + * by both the JSP-UI and the Manakin XML-UI + *

+ * + * @see org.dspace.app.util.SubmissionConfig + * @see org.dspace.app.util.SubmissionStepConfig + * @see AbstractProcessingStep + * + * @author Tim Donohue + * @version $Revision$ + */ +public class DescribeStep extends AbstractProcessingStep +{ + /** log4j logger */ + private static Logger log = Logger.getLogger(DescribeStep.class); + + /** hash of all submission forms details */ + private static DCInputsReader inputsReader = null; + + /*************************************************************************** + * STATUS / ERROR FLAGS (returned by doProcessing() if an error occurs or + * additional user interaction may be required) + * + * (Do NOT use status of 0, since it corresponds to STATUS_COMPLETE flag + * defined in the JSPStepManager class) + **************************************************************************/ + // user requested an extra input field to be displayed + public static final int STATUS_MORE_INPUT_REQUESTED = 1; + + // there were required fields that were not filled out + public static final int STATUS_MISSING_REQUIRED_FIELDS = 2; + + // the metadata language qualifier + public static final String LANGUAGE_QUALIFIER = getDefaultLanguageQualifier(); + + /** Constructor */ + public DescribeStep() throws ServletException + { + //load the DCInputsReader + getInputsReader(); + } + + + + /** + * Do any processing of the information input by the user, and/or perform + * step processing (if no user interaction required) + *

+ * It is this method's job to save any data to the underlying database, as + * necessary, and return error messages (if any) which can then be processed + * by the appropriate user interface (JSP-UI or XML-UI) + *

+ * NOTE: If this step is a non-interactive step (i.e. requires no UI), then + * it should perform *all* of its processing in this method! + * + * @param context + * current DSpace context + * @param request + * current servlet request object + * @param response + * current servlet response object + * @param subInfo + * submission info object + * @return Status or error flag which will be processed by + * doPostProcessing() below! (if STATUS_COMPLETE or 0 is returned, + * no errors occurred!) + */ + public int doProcessing(Context context, HttpServletRequest request, + HttpServletResponse response, SubmissionInfo subInfo) + throws ServletException, IOException, SQLException, + AuthorizeException + { + if(!request.getParameterNames().hasMoreElements()){ + //In case of an empty request do NOT just remove all metadata, just return to the submission page + return STATUS_MORE_INPUT_REQUESTED; + } + // check what submit button was pressed in User Interface + String buttonPressed = Util.getSubmitButton(request, NEXT_BUTTON); + + // get the item and current page + Item item = subInfo.getSubmissionItem().getItem(); + int currentPage = getCurrentPage(request); + + // lookup applicable inputs + Collection c = subInfo.getSubmissionItem().getCollection(); + DCInput[] inputs = null; + try + { + inputs = inputsReader.getInputs(c.getHandle()).getPageRows( + currentPage - 1, + subInfo.getSubmissionItem().hasMultipleTitles(), + subInfo.getSubmissionItem().isPublishedBefore()); + } + catch (DCInputsReaderException e) + { + throw new ServletException(e); + } + + // Fetch the document type (dc.type) + String typeField = org.dspace.core.ConfigurationManager.getProperty("submission", "type.bind.field"); + + String documentType = ""; + if(org.apache.commons.lang3.StringUtils.isNotBlank(typeField) && (item.getMetadata(typeField) != null) && (item.getMetadataByMetadataString(typeField).length >0) ) + { + documentType = item.getMetadataByMetadataString(typeField)[0].value; + } + + // Step 1: + // clear out all item metadata defined on this page + for (int i = 0; i < inputs.length; i++) + { + + // Allow the clearing out of the metadata defined for other document types, provided it can change anytime + + if (!inputs[i] + .isVisible(subInfo.isInWorkflow() ? DCInput.WORKFLOW_SCOPE + : DCInput.SUBMISSION_SCOPE)) + { + continue; + } + if (inputs[i].getInputType().equals("qualdrop_value")) + { + @SuppressWarnings("unchecked") // This cast is correct + List pairs = inputs[i].getPairs(); + for (int j = 0; j < pairs.size(); j += 2) + { + String qualifier = pairs.get(j+1); + item.clearMetadata(inputs[i].getSchema(), inputs[i].getElement(), qualifier, Item.ANY); + } + } + else + { + String qualifier = inputs[i].getQualifier(); + item.clearMetadata(inputs[i].getSchema(), inputs[i].getElement(), qualifier, Item.ANY); + } + } + + // Clear required-field errors first since missing authority + // values can add them too. + clearErrorFields(request); + + // Step 2: + // now update the item metadata. + String fieldName; + boolean moreInput = false; + for (int j = 0; j < inputs.length; j++) + { + // Omit fields not allowed for this document type + if(!inputs[j].isAllowedFor(documentType)) + { + continue; + } + + if (!inputs[j] + .isVisible(subInfo.isInWorkflow() ? DCInput.WORKFLOW_SCOPE + : DCInput.SUBMISSION_SCOPE)) + { + continue; + } + String element = inputs[j].getElement(); + String qualifier = inputs[j].getQualifier(); + String schema = inputs[j].getSchema(); + if (qualifier != null && !qualifier.equals(Item.ANY)) + { + fieldName = schema + "_" + element + '_' + qualifier; + } + else + { + fieldName = schema + "_" + element; + } + + String fieldKey = MetadataAuthorityManager.makeFieldKey(schema, element, qualifier); + ChoiceAuthorityManager cmgr = ChoiceAuthorityManager.getManager(); + String inputType = inputs[j].getInputType(); + if (inputType.equals("name")) + { + readNames(request, item, schema, element, qualifier, inputs[j] + .getRepeatable()); + } + else if (inputType.equals("date")) + { + readDate(request, item, schema, element, qualifier); + } + else if (inputType.equals("fulldate")) + { + readFullDate(request, item, schema, element, qualifier); + } + // choice-controlled input with "select" presentation type is + // always rendered as a dropdown menu + else if (inputType.equals("dropdown") || inputType.equals("list") || + (cmgr.isChoicesConfigured(fieldKey) && + "select".equals(cmgr.getPresentation(fieldKey)))) + { + String[] vals = request.getParameterValues(fieldName); + if (vals != null) + { + for (int z = 0; z < vals.length; z++) + { + if (!vals[z].equals("")) + { + item.addMetadata(schema, element, qualifier, LANGUAGE_QUALIFIER, + vals[z]); + } + } + } + } + else if (inputType.equals("series")) + { + readSeriesNumbers(request, item, schema, element, qualifier, + inputs[j].getRepeatable()); + } + else if (inputType.equals("qualdrop_value")) + { + List quals = getRepeatedParameter(request, schema + "_" + + element, schema + "_" + element + "_qualifier"); + List vals = getRepeatedParameter(request, schema + "_" + + element, schema + "_" + element + "_value"); + for (int z = 0; z < vals.size(); z++) + { + String thisQual = quals.get(z); + if ("".equals(thisQual)) + { + thisQual = null; + } + String thisVal = vals.get(z); + if (!buttonPressed.equals("submit_" + schema + "_" + + element + "_remove_" + z) + && !thisVal.equals("")) + { + item.addMetadata(schema, element, thisQual, null, + thisVal); + } + } + } + else if ((inputType.equals("onebox")) + || (inputType.equals("twobox")) + || (inputType.equals("textarea"))) + { + readText(request, item, schema, element, qualifier, inputs[j] + .getRepeatable(), LANGUAGE_QUALIFIER); + } + else + { + throw new ServletException("Field " + fieldName + + " has an unknown input type: " + inputType); + } + + // determine if more input fields were requested + if (!moreInput + && buttonPressed.equals("submit_" + fieldName + "_add")) + { + subInfo.setMoreBoxesFor(fieldName); + subInfo.setJumpToField(fieldName); + moreInput = true; + } + // was XMLUI's "remove" button pushed? + else if (buttonPressed.equals("submit_" + fieldName + "_delete")) + { + subInfo.setJumpToField(fieldName); + } + } + + // Step 3: + // Check to see if any fields are missing + // Only check for required fields if user clicked the "next", the "previous" or the "progress bar" button + if (buttonPressed.equals(NEXT_BUTTON) + || buttonPressed.startsWith(PROGRESS_BAR_PREFIX) + || buttonPressed.equals(PREVIOUS_BUTTON) + || buttonPressed.equals(CANCEL_BUTTON)) + { + for (int i = 0; i < inputs.length; i++) + { + // Do not check the required attribute if it is not visible or not allowed for the document type + String scope = subInfo.isInWorkflow() ? DCInput.WORKFLOW_SCOPE : DCInput.SUBMISSION_SCOPE; + if ( !( inputs[i].isVisible(scope) && inputs[i].isAllowedFor(documentType) ) ) + { + continue; + } + + String qualifier = inputs[i].getQualifier(); + if (qualifier == null + && inputs[i].getInputType().equals("qualdrop_value")) + { + qualifier = Item.ANY; + } + Metadatum[] values = item.getMetadata(inputs[i].getSchema(), + inputs[i].getElement(), qualifier, Item.ANY); + + if ((inputs[i].isRequired() && values.length == 0) && + inputs[i].isVisible(subInfo.isInWorkflow() ? DCInput.WORKFLOW_SCOPE : DCInput.SUBMISSION_SCOPE)) + { + // since this field is missing add to list of error fields + addErrorField(request, getFieldName(inputs[i])); + } + } + } + + // Step 4: + // Save changes to database + subInfo.getSubmissionItem().update(); + + // commit changes + context.commit(); + + // check for request for more input fields, first + if (moreInput) + { + return STATUS_MORE_INPUT_REQUESTED; + } + // if one or more fields errored out, return + else if (getErrorFields(request) != null && getErrorFields(request).size() > 0) + { + return STATUS_MISSING_REQUIRED_FIELDS; + } + + //Type-dependency can be based on metadata in this step so the config should be reloaded to accommodate for changes + subInfo.reloadSubmissionConfig(request); + // completed without errors + return STATUS_COMPLETE; + } + + + + /** + * Retrieves the number of pages that this "step" extends over. This method + * is used to build the progress bar. + *

+ * This method may just return 1 for most steps (since most steps consist of + * a single page). But, it should return a number greater than 1 for any + * "step" which spans across a number of HTML pages. For example, the + * configurable "Describe" step (configured using input-forms.xml) overrides + * this method to return the number of pages that are defined by its + * configuration file. + *

+ * Steps which are non-interactive (i.e. they do not display an interface to + * the user) should return a value of 1, so that they are only processed + * once! + * + * @param request + * The HTTP Request + * @param subInfo + * The current submission information object + * + * @return the number of pages in this step + */ + public int getNumberOfPages(HttpServletRequest request, + SubmissionInfo subInfo) throws ServletException + { + // by default, use the "default" collection handle + String collectionHandle = DCInputsReader.DEFAULT_COLLECTION; + + if (subInfo.getSubmissionItem() != null) + { + collectionHandle = subInfo.getSubmissionItem().getCollection() + .getHandle(); + } + + // get number of input pages (i.e. "Describe" pages) + try + { + return getInputsReader().getNumberInputPages(collectionHandle); + } + catch (DCInputsReaderException e) + { + throw new ServletException(e); + } + } + + /** + * + * @return the current DCInputsReader + */ + public static DCInputsReader getInputsReader() throws ServletException + { + // load inputsReader only the first time + if (inputsReader == null) + { + // read configurable submissions forms data + try + { + inputsReader = new DCInputsReader(); + } + catch (DCInputsReaderException e) + { + throw new ServletException(e); + } + } + + return inputsReader; + } + + /** + * @param filename + * file to get the input reader for + * @return the current DCInputsReader + */ + public static DCInputsReader getInputsReader(String filename) throws ServletException + { + try + { + inputsReader = new DCInputsReader(filename); + } + catch (DCInputsReaderException e) + { + throw new ServletException(e); + } + return inputsReader; + } + + /** + * @return the default language qualifier for metadata + */ + + public static String getDefaultLanguageQualifier() + { + String language = ""; + language = ConfigurationManager.getProperty("default.language"); + if (StringUtils.isEmpty(language)) + { + language = "en"; + } + return language; + } + + // **************************************************************** + // **************************************************************** + // METHODS FOR FILLING DC FIELDS FROM METADATA FORMS + // **************************************************************** + // **************************************************************** + + /** + * Set relevant metadata fields in an item from name values in the form. + * Some fields are repeatable in the form. If this is the case, and the + * field is "dc.contributor.author", the names in the request will be from + * the fields as follows: + * + * dc_contributor_author_last -> last name of first author + * dc_contributor_author_first -> first name(s) of first author + * dc_contributor_author_last_1 -> last name of second author + * dc_contributor_author_first_1 -> first name(s) of second author + * + * and so on. If the field is unqualified: + * + * dc_contributor_last -> last name of first contributor + * dc_contributor_first -> first name(s) of first contributor + * + * If the parameter "submit_dc_contributor_author_remove_n" is set, that + * value is removed. + * + * Otherwise the parameters are of the form: + * + * dc_contributor_author_last dc_contributor_author_first + * + * The values will be put in separate DCValues, in the form "last name, + * first name(s)", ordered as they appear in the list. These will replace + * any existing values. + * + * @param request + * the request object + * @param item + * the item to update + * @param schema + * the metadata schema + * @param element + * the metadata element + * @param qualifier + * the metadata qualifier, or null if unqualified + * @param repeated + * set to true if the field is repeatable on the form + */ + protected void readNames(HttpServletRequest request, Item item, + String schema, String element, String qualifier, boolean repeated) + { + String metadataField = MetadataField + .formKey(schema, element, qualifier); + + String fieldKey = MetadataAuthorityManager.makeFieldKey(schema, element, qualifier); + boolean isAuthorityControlled = MetadataAuthorityManager.getManager().isAuthorityControlled(fieldKey); + + // Names to add + List firsts = new LinkedList(); + List lasts = new LinkedList(); + List auths = new LinkedList(); + List confs = new LinkedList(); + + if (repeated) + { + firsts = getRepeatedParameter(request, metadataField, metadataField + + "_first"); + lasts = getRepeatedParameter(request, metadataField, metadataField + + "_last"); + + if(isAuthorityControlled) + { + auths = getRepeatedParameter(request, metadataField, metadataField + + "_authority"); + confs = getRepeatedParameter(request, metadataField, metadataField + + "_confidence"); + } + + // Find out if the relevant "remove" button was pressed + // TODO: These separate remove buttons are only relevant + // for DSpace JSP UI, and the code below can be removed + // once the DSpace JSP UI is obsolete! + String buttonPressed = Util.getSubmitButton(request, ""); + String removeButton = "submit_" + metadataField + "_remove_"; + + if (buttonPressed.startsWith(removeButton)) + { + int valToRemove = Integer.parseInt(buttonPressed + .substring(removeButton.length())); + + firsts.remove(valToRemove); + lasts.remove(valToRemove); + if(isAuthorityControlled) + { + if(valToRemove < auths.size()) + { + auths.remove(valToRemove); + confs.remove(valToRemove); + } + } + } + } + else + { + // Just a single name + String lastName = request.getParameter(metadataField + "_last"); + String firstNames = request.getParameter(metadataField + "_first"); + String authority = request.getParameter(metadataField + "_authority"); + String confidence = request.getParameter(metadataField + "_confidence"); + + if (lastName != null) + { + lasts.add(lastName); + } + if (firstNames != null) + { + firsts.add(firstNames); + } + auths.add(authority == null ? "" : authority); + confs.add(confidence == null ? "" : confidence); + } + + // Remove existing values, already done in doProcessing see also bug DS-203 + // item.clearMetadata(schema, element, qualifier, Item.ANY); + + // Put the names in the correct form + for (int i = 0; i < lasts.size(); i++) + { + String f = firsts.get(i); + String l = lasts.get(i); + + // only add if lastname is non-empty + if ((l != null) && !((l.trim()).equals(""))) + { + // Ensure first name non-null + if (f == null) + { + f = ""; + } + + // If there is a comma in the last name, we take everything + // after that comma, and add it to the right of the + // first name + int comma = l.indexOf(','); + + if (comma >= 0) + { + f = f + l.substring(comma + 1); + l = l.substring(0, comma); + + // Remove leading whitespace from first name + while (f.startsWith(" ")) + { + f = f.substring(1); + } + } + + // Add to the database -- unless required authority is missing + if (isAuthorityControlled) + { + String authKey = auths.size() > i ? auths.get(i) : null; + String sconf = (authKey != null && confs.size() > i) ? confs.get(i) : null; + if (MetadataAuthorityManager.getManager().isAuthorityRequired(fieldKey) && + (authKey == null || authKey.length() == 0)) + { + log.warn("Skipping value of "+metadataField+" because the required Authority key is missing or empty."); + addErrorField(request, metadataField); + } + else + { + item.addMetadata(schema, element, qualifier, null, + new DCPersonName(l, f).toString(), authKey, + (sconf != null && sconf.length() > 0) ? + Choices.getConfidenceValue(sconf) : Choices.CF_ACCEPTED); + } + } + else + { + item.addMetadata(schema, element, qualifier, null, + new DCPersonName(l, f).toString()); + } + } + } + } + + /** + * Fill out an item's metadata values from a plain standard text field. If + * the field isn't repeatable, the input field name is called: + * + * element_qualifier + * + * or for an unqualified element: + * + * element + * + * Repeated elements are appended with an underscore then an integer. e.g.: + * + * dc_title_alternative dc_title_alternative_1 + * + * The values will be put in separate DCValues, ordered as they appear in + * the list. These will replace any existing values. + * + * @param request + * the request object + * @param item + * the item to update + * @param schema + * the short schema name + * @param element + * the metadata element + * @param qualifier + * the metadata qualifier, or null if unqualified + * @param repeated + * set to true if the field is repeatable on the form + * @param lang + * language to set (ISO code) + */ + protected void readText(HttpServletRequest request, Item item, String schema, + String element, String qualifier, boolean repeated, String lang) + { + // FIXME: Of course, language should be part of form, or determined + // some other way + String metadataField = MetadataField + .formKey(schema, element, qualifier); + + String fieldKey = MetadataAuthorityManager.makeFieldKey(schema, element, qualifier); + boolean isAuthorityControlled = MetadataAuthorityManager.getManager().isAuthorityControlled(fieldKey); + + // Values to add + List vals = null; + List auths = null; + List confs = null; + + if (repeated) + { + vals = getRepeatedParameter(request, metadataField, metadataField); + if (isAuthorityControlled) + { + auths = getRepeatedParameter(request, metadataField, metadataField+"_authority"); + confs = getRepeatedParameter(request, metadataField, metadataField+"_confidence"); + } + + // Find out if the relevant "remove" button was pressed + // TODO: These separate remove buttons are only relevant + // for DSpace JSP UI, and the code below can be removed + // once the DSpace JSP UI is obsolete! + String buttonPressed = Util.getSubmitButton(request, ""); + String removeButton = "submit_" + metadataField + "_remove_"; + + if (buttonPressed.startsWith(removeButton)) + { + int valToRemove = Integer.parseInt(buttonPressed + .substring(removeButton.length())); + + vals.remove(valToRemove); + if(isAuthorityControlled) + { + auths.remove(valToRemove); + confs.remove(valToRemove); + } + } + } + else + { + // Just a single name + vals = new LinkedList(); + String value = request.getParameter(metadataField); + if (value != null) + { + vals.add(value.trim()); + } + if (isAuthorityControlled) + { + auths = new LinkedList(); + confs = new LinkedList(); + String av = request.getParameter(metadataField+"_authority"); + String cv = request.getParameter(metadataField+"_confidence"); + auths.add(av == null ? "":av.trim()); + confs.add(cv == null ? "":cv.trim()); + } + } + + // Remove existing values, already done in doProcessing see also bug DS-203 + // item.clearMetadata(schema, element, qualifier, Item.ANY); + + // Put the names in the correct form + for (int i = 0; i < vals.size(); i++) + { + // Add to the database if non-empty + String s = vals.get(i); + if ((s != null) && !s.equals("")) + { + if (isAuthorityControlled) + { + String authKey = auths.size() > i ? auths.get(i) : null; + String sconf = (authKey != null && confs.size() > i) ? confs.get(i) : null; + if (MetadataAuthorityManager.getManager().isAuthorityRequired(fieldKey) && + (authKey == null || authKey.length() == 0)) + { + log.warn("Skipping value of "+metadataField+" because the required Authority key is missing or empty."); + addErrorField(request, metadataField); + } + else + { + item.addMetadata(schema, element, qualifier, lang, s, + authKey, (sconf != null && sconf.length() > 0) ? + Choices.getConfidenceValue(sconf) : Choices.CF_ACCEPTED); + } + } + else + { + item.addMetadata(schema, element, qualifier, lang, s); + } + } + } + } + + /** + * Fill out a metadata date field with the value from a form. The date is + * taken from the three parameters: + * + * element_qualifier_year element_qualifier_month element_qualifier_day + * + * The granularity is determined by the values that are actually set. If the + * year isn't set (or is invalid) + * + * @param request + * the request object + * @param item + * the item to update + * @param schema + * the metadata schema + * @param element + * the metadata element + * @param qualifier + * the metadata qualifier, or null if unqualified + * @throws SQLException + */ + protected void readDate(HttpServletRequest request, Item item, String schema, + String element, String qualifier) throws SQLException + { + String metadataField = MetadataField + .formKey(schema, element, qualifier); + + int year = Util.getIntParameter(request, metadataField + "_year"); + int month = Util.getIntParameter(request, metadataField + "_month"); + int day = Util.getIntParameter(request, metadataField + "_day"); + + // FIXME: Probably should be some more validation + // Make a standard format date + DCDate d = new DCDate(year, month, day, -1, -1, -1); + + // already done in doProcessing see also bug DS-203 + // item.clearMetadata(schema, element, qualifier, Item.ANY); + + if (year > 0) + { + // Only put in date if there is one! + item.addMetadata(schema, element, qualifier, null, d.toString()); + } + } + + + + /** + * Fill out a metadata date field with the value from a form. The date is + * taken from the three parameters: + * + * element_qualifier_year element_qualifier_month element_qualifier_day + * + * If the + * year, month or day isn't set (or is invalid) + * + * @param request + * the request object + * @param item + * the item to update + * @param schema + * the metadata schema + * @param element + * the metadata element + * @param qualifier + * the metadata qualifier, or null if unqualified + * @throws SQLException + */ + protected void readFullDate(HttpServletRequest request, Item item, String schema, + String element, String qualifier) throws SQLException + { + String metadataField = MetadataField + .formKey(schema, element, qualifier); + + int year = Util.getIntParameter(request, metadataField + "_year"); + int month = Util.getIntParameter(request, metadataField + "_month"); + int day = Util.getIntParameter(request, metadataField + "_day"); + + // FIXME: Probably should be some more validation + // Make a standard format date + DCDate d = new DCDate(year, month, day, -1, -1, -1); + + // already done in doProcessing see also bug DS-203 + // item.clearMetadata(schema, element, qualifier, Item.ANY); + + if (year > 0 && month > 0 && day > 0) + { + // Only put in date if there is one! + item.addMetadata(schema, element, qualifier, null, d.toString()); + } + } + + + + /** + * Set relevant metadata fields in an item from series/number values in the + * form. Some fields are repeatable in the form. If this is the case, and + * the field is "relation.ispartof", the names in the request will be from + * the fields as follows: + * + * dc_relation_ispartof_series dc_relation_ispartof_number + * dc_relation_ispartof_series_1 dc_relation_ispartof_number_1 + * + * and so on. If the field is unqualified: + * + * dc_relation_series dc_relation_number + * + * Otherwise the parameters are of the form: + * + * dc_relation_ispartof_series dc_relation_ispartof_number + * + * The values will be put in separate DCValues, in the form "last name, + * first name(s)", ordered as they appear in the list. These will replace + * any existing values. + * + * @param request + * the request object + * @param item + * the item to update + * @param schema + * the metadata schema + * @param element + * the metadata element + * @param qualifier + * the metadata qualifier, or null if unqualified + * @param repeated + * set to true if the field is repeatable on the form + */ + protected void readSeriesNumbers(HttpServletRequest request, Item item, + String schema, String element, String qualifier, boolean repeated) + { + String metadataField = MetadataField + .formKey(schema, element, qualifier); + + // Names to add + List series = new LinkedList(); + List numbers = new LinkedList(); + + if (repeated) + { + series = getRepeatedParameter(request, metadataField, metadataField + + "_series"); + numbers = getRepeatedParameter(request, metadataField, + metadataField + "_number"); + + // Find out if the relevant "remove" button was pressed + String buttonPressed = Util.getSubmitButton(request, ""); + String removeButton = "submit_" + metadataField + "_remove_"; + + if (buttonPressed.startsWith(removeButton)) + { + int valToRemove = Integer.parseInt(buttonPressed + .substring(removeButton.length())); + + series.remove(valToRemove); + numbers.remove(valToRemove); + } + } + else + { + // Just a single name + String s = request.getParameter(metadataField + "_series"); + String n = request.getParameter(metadataField + "_number"); + + // Only put it in if there was a name present + if ((s != null) && !s.equals("")) + { + // if number is null, just set to a nullstring + if (n == null) + { + n = ""; + } + + series.add(s); + numbers.add(n); + } + } + + // Remove existing values, already done in doProcessing see also bug DS-203 + // item.clearMetadata(schema, element, qualifier, Item.ANY); + + // Put the names in the correct form + for (int i = 0; i < series.size(); i++) + { + String s = (series.get(i)).trim(); + String n = (numbers.get(i)).trim(); + + // Only add non-empty + if (!s.equals("") || !n.equals("")) + { + item.addMetadata(schema, element, qualifier, null, + new DCSeriesNumber(s, n).toString()); + } + } + } + + /** + * Get repeated values from a form. If "foo" is passed in as the parameter, + * values in the form of parameters "foo", "foo_1", "foo_2", etc. are + * returned. + *

+ * This method can also handle "composite fields" (metadata fields which may + * require multiple params, etc. a first name and last name). + * + * @param request + * the HTTP request containing the form information + * @param metadataField + * the metadata field which can store repeated values + * @param param + * the repeated parameter on the page (used to fill out the + * metadataField) + * + * @return a List of Strings + */ + protected List getRepeatedParameter(HttpServletRequest request, + String metadataField, String param) + { + List vals = new LinkedList(); + + int i = 1; //start index at the first of the previously entered values + boolean foundLast = false; + + // Iterate through the values in the form. + while (!foundLast) + { + String s = null; + + //First, add the previously entered values. + // This ensures we preserve the order that these values were entered + s = request.getParameter(param + "_" + i); + + // If there are no more previously entered values, + // see if there's a new value entered in textbox + if (s==null) + { + s = request.getParameter(param); + //this will be the last value added + foundLast = true; + } + + // We're only going to add non-null values + if (s != null) + { + boolean addValue = true; + + // Check to make sure that this value was not selected to be + // removed. + // (This is for the "remove multiple" option available in + // Manakin) + String[] selected = request.getParameterValues(metadataField + + "_selected"); + + if (selected != null) + { + for (int j = 0; j < selected.length; j++) + { + if (selected[j].equals(metadataField + "_" + i)) + { + addValue = false; + } + } + } + + if (addValue) + { + vals.add(s.trim()); + } + } + + i++; + } + + log.debug("getRepeatedParameter: metadataField=" + metadataField + + " param=" + metadataField + ", return count = "+vals.size()); + + return vals; + } + + /** + * Return the HTML / DRI field name for the given input. + * + * @param input + */ + public static String getFieldName(DCInput input) + { + String dcSchema = input.getSchema(); + String dcElement = input.getElement(); + String dcQualifier = input.getQualifier(); + if (dcQualifier != null && !dcQualifier.equals(Item.ANY)) + { + return dcSchema + "_" + dcElement + '_' + dcQualifier; + } + else + { + return dcSchema + "_" + dcElement; + } + + } +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/util/DcValueUtils.java b/dspace/modules/additions/src/main/java/org/dspace/util/DcValueUtils.java new file mode 100644 index 0000000..13ac166 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/util/DcValueUtils.java @@ -0,0 +1,78 @@ +package org.dspace.util; + +import org.apache.commons.lang3.*; +import org.dspace.content.*; + +/** + * Created by: Antoine Snyers (antoine at atmire dot com) + * Date: 06 Mar 2014 + */ +public class DcValueUtils { + + public static Metadatum copy(Metadatum source) { + Metadatum copy = new Metadatum(); + copy.value = source.value; + copy.authority = source.authority; + copy.confidence = source.confidence; + copy.element = source.element; + copy.language = source.language; + copy.qualifier = source.qualifier; + copy.schema = source.schema; + return copy; + } + + public static boolean equalField(Metadatum dc1, Metadatum dc2) { + if (dc1 == dc2) { + return true; + } + if (dc1.element != null ? !dc1.element.equals(dc2.element) : dc2.element != null) { + return false; + } + if (dc1.qualifier != null ? !dc1.qualifier.equals(dc2.qualifier) : dc2.qualifier != null) { + return false; + } + if (dc1.schema != null ? !dc1.schema.equals(dc2.schema) : dc2.schema != null) { + return false; + } + return true; + } + + public static boolean equal(Metadatum dc1, Metadatum dc2) { + if (dc1 == dc2) { + return true; + } + + if (dc1.confidence != dc2.confidence) { + return false; + } + if (dc1.authority != null ? !dc1.authority.equals(dc2.authority) : dc2.authority != null) { + return false; + } + if (dc1.element != null ? !dc1.element.equals(dc2.element) : dc2.element != null) { + return false; + } + if (dc1.language != null ? !dc1.language.equals(dc2.language) : dc2.language != null) { + return false; + } + if (dc1.qualifier != null ? !dc1.qualifier.equals(dc2.qualifier) : dc2.qualifier != null) { + return false; + } + if (dc1.schema != null ? !dc1.schema.equals(dc2.schema) : dc2.schema != null) { + return false; + } + if (dc1.value != null ? !dc1.value.equals(dc2.value) : dc2.value != null) { + return false; + } + + return true; + } + + public static boolean isBlank(Metadatum dcValue) { + return dcValue == null || StringUtils.isBlank(dcValue.value); + } + + public static boolean hasValue(Metadatum dcValue, String value) { + return dcValue != null && StringUtils.equals(dcValue.value, value); + } + +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/util/ItemUtils.java b/dspace/modules/additions/src/main/java/org/dspace/util/ItemUtils.java new file mode 100644 index 0000000..e8edf76 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/util/ItemUtils.java @@ -0,0 +1,251 @@ +package org.dspace.util; + +import java.io.*; +import java.sql.*; +import java.util.*; +import org.dspace.authorize.*; +import org.dspace.content.*; +import org.dspace.core.*; +import org.dspace.storage.rdbms.*; +import org.dspace.util.subclasses.*; + +/** + * Created by: Antoine Snyers (antoine at atmire dot com) + * Date: 06 Mar 2014 + */ +public class ItemUtils { + + public static void addAllMetadata(Item item, List values) { + for (Metadatum value : values) { + addMetadata(item, value); + } + } + + public static void addMetadata(Item item, Metadatum dcValue) { + item.addMetadata(dcValue.schema, dcValue.element, dcValue.qualifier, dcValue.language, dcValue.value, dcValue.authority, dcValue.confidence); + } + + public static void addMetadata(Item item, String mdField, String value) { + Metadata metadata = MetadataFieldString.encapsulate(mdField); + item.addMetadata(metadata.getSchema(), metadata.getElement(), metadata.getQualifier(), metadata.getLanguage(), value); + } + + public static void clearAllMetadata(Item item) { + item.clearMetadata(Item.ANY, Item.ANY, Item.ANY, Item.ANY); + } + + public static void clearMetadata(Item item, Metadatum dcValue) { + item.clearMetadata(dcValue.schema, dcValue.element, dcValue.qualifier, dcValue.language); + } + + public static void clearMetadata(Item item, String mdField) { + Metadata metadata = MetadataFieldString.encapsulate(mdField).withWildcards(); + item.clearMetadata(metadata.getSchema(), metadata.getElement(), metadata.getQualifier(), metadata.getLanguage()); + } + + public static void clearMetadata(Item item, String schema, String element, String qualifier, String lang, String authority) { + // We will build a list of values NOT matching the values to clear + List values = new ArrayList(); + for (Metadatum dcv : getMetadata(item)) { + if (!match(schema, element, qualifier, lang, authority, dcv)) { + values.add(dcv); + } + } + + // Now swap the old list of values for the new, unremoved values + clearAllMetadata(item); + addAllMetadata(item, values); + } + + public static void copyMetadata(Item item, Item item2) { + List metadata = getMetadata(item2); + clearAllMetadata(item); + addAllMetadata(item, metadata); + } + + public static ItemIterator findByMetadataFieldAuthority(Context context, String mdString, String authority) + throws SQLException, AuthorizeException, IOException { + Metadata encapsulate = MetadataFieldString.encapsulate(mdString); + return findByMetadataFieldAuthority(context, encapsulate.getSchema(), encapsulate.getElement(), encapsulate.getQualifier(), authority); + } + + public static ItemIterator findByMetadataFieldAuthority(Context context, String schema, String element, String qualifier, String authority) + throws SQLException, AuthorizeException, IOException { + MetadataSchema mds = MetadataSchema.find(context, schema); + if (mds == null) { + throw new IllegalArgumentException("No such metadata schema: " + schema); + } + MetadataField mdf = MetadataField.findByElement(context, mds.getSchemaID(), element, qualifier); + if (mdf == null) { + throw new IllegalArgumentException( + "No such metadata field: schema=" + schema + ", element=" + element + ", qualifier=" + qualifier); + } + + String query = "SELECT item.* FROM metadatavalue,item WHERE item.in_archive='1' " + + "AND item.item_id = metadatavalue.item_id AND metadata_field_id = ?"; + TableRowIterator rows = null; + if (Item.ANY.equals(authority)) { + rows = DatabaseManager.queryTable(context, "item", query, mdf.getFieldID()); + } else { + query += " AND metadatavalue.authority = ?"; + rows = DatabaseManager.queryTable(context, "item", query, mdf.getFieldID(), authority); + } + return new ItemIterator(context, rows); + } + + public static Set getBitstreams(Item item) throws SQLException { + Set bitstreams = new HashSet(); + for (Bundle bundle : item.getBundles()) { + for (Bitstream bitstream : bundle.getBitstreams()) { + bitstreams.add(bitstream); + } + } + return bitstreams; + } + + public static List getMetadata(Item item) { + return getMetadata(item, Item.ANY + "." + Item.ANY + "." + Item.ANY); + } + + public static List getMetadata(Item item, String mdString) { + Metadata elements = MetadataFieldString.encapsulate(mdString).withWildcards(); + Metadatum[] metadata = item.getMetadata(elements.getSchema(), elements.getElement(), elements.getQualifier(), elements.getLanguage()); + if(metadata == null || metadata.length == 0) { + return Collections.emptyList(); + } else { + return Arrays.asList(metadata); + } + } + + public static List getMetadata(Item item, String mdString, String authority) { + Metadata elements = MetadataFieldString.encapsulate(mdString).withWildcards(); + return getMetadata(item, elements.getSchema(), elements.getElement(), elements.getQualifier(), elements.getLanguage(), authority); + } + + public static List getMetadata(Item item, String schema, String element, String qualifier, String lang, String authority) { + Metadatum[] metadata = item.getMetadata(schema, element, qualifier, lang); + List dcValues = Arrays.asList(metadata); + if (!authority.equals(Item.ANY)) { + for (Metadatum dcValue : metadata) { + if (!authority.equals(dcValue.authority)) { + dcValues.remove(dcValue); + } + } + } + return dcValues; + } + + public static String getMetadataFirstValue(Item item, String fieldName) { + Metadata elements = MetadataFieldString.encapsulate(fieldName); // this is better not with wildcards + return getMetadataFirstValue(item, elements.getSchema(), elements.getElement(), elements.getQualifier(), elements.getLanguage()); + } + + public static String getMetadataFirstValue(Item item, String schema, String element, String qualifier, String language) { + Metadatum[] metadata = item.getMetadata(schema, element, qualifier, language); + String value = null; + if (metadata.length > 0) { + value = metadata[0].value; + } + return value; + } + + public static void replaceMetadataValue(Item item, Metadatum oldValue, Metadatum newValue) { + // check both dcvalues are for the same field + if (DcValueUtils.equalField(oldValue, newValue)) { + + String schema = oldValue.schema; + String element = oldValue.element; + String qualifier = oldValue.qualifier; + + // Save all metadata for this field + Metadatum[] dcvalues = item.getMetadata(schema, element, qualifier, Item.ANY); + item.clearMetadata(schema, element, qualifier, Item.ANY); + + for (Metadatum dcvalue : dcvalues) { + if (DcValueUtils.equal(dcvalue, oldValue)) { + item.addMetadata(schema, element, qualifier, newValue.language, newValue.value, newValue.authority, newValue.confidence); + } else { + item.addMetadata(schema, element, qualifier, dcvalue.language, dcvalue.value, dcvalue.authority, dcvalue.confidence); + } + } + } + } + + private static boolean match(String schema, String element, String qualifier, String language, String authority, Metadatum dcv) { + boolean match = match(schema, element, qualifier, language, dcv); + if (match) { + if (!authority.equals(Item.ANY)) { + if (authority != null) { + match = authority.equals(dcv.authority); + } else { + match = dcv.authority == null; + } + } + } + return match; + } + + /** + * (yes this is a copy of the original class) + *

+ * Utility method for pattern-matching metadata elements. This + * method will return true if the given schema, + * element, qualifier and language match the schema, element, + * qualifier and language of the DCValue object passed + * in. Any or all of the element, qualifier and language passed + * in can be the Item.ANY wildcard. + * + * @param schema the schema for the metadata field. Must match + * the name of an existing metadata schema. + * @param element the element to match, or Item.ANY + * @param qualifier the qualifier to match, or Item.ANY + * @param language the language to match, or Item.ANY + * @param dcv the Dublin Core value + * @return true if there is a match + */ + private static boolean match(String schema, String element, String qualifier, + String language, Metadatum dcv) { + // We will attempt to disprove a match - if we can't we have a match + if (!element.equals(Item.ANY) && !element.equals(dcv.element)) { + // Elements do not match, no wildcard + return false; + } + + if (qualifier == null) { + // Value must be unqualified + if (dcv.qualifier != null) { + // Value is qualified, so no match + return false; + } + } else if (!qualifier.equals(Item.ANY)) { + // Not a wildcard, so qualifier must match exactly + if (!qualifier.equals(dcv.qualifier)) { + return false; + } + } + + if (language == null) { + // Value must be null language to match + if (dcv.language != null) { + // Value is qualified, so no match + return false; + } + } else if (!language.equals(Item.ANY)) { + // Not a wildcard, so language must match exactly + if (!language.equals(dcv.language)) { + return false; + } + } + + if (!schema.equals(Item.ANY)) { + if (dcv.schema != null && !dcv.schema.equals(schema)) { + // The namespace doesn't match + return false; + } + } + + // If we get this far, we have a match + return true; + } + +} diff --git a/dspace/modules/additions/src/main/java/org/dspace/util/MetadataFieldString.java b/dspace/modules/additions/src/main/java/org/dspace/util/MetadataFieldString.java new file mode 100644 index 0000000..c05eb35 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/util/MetadataFieldString.java @@ -0,0 +1,157 @@ +package org.dspace.util; + +import java.util.*; +import org.apache.commons.lang.*; +import org.dspace.app.util.*; +import org.dspace.content.*; +import org.dspace.content.authority.*; +import org.dspace.util.subclasses.*; + +/** + * metadataFieldString = schema.element.qualifier[language]::authority::confidence + * qualifier is optional + * [language] is optional + * authority is optional + * confidence: not if no authority, otherwise optional + *

+ * Created by: Antoine Snyers (antoine at atmire dot com) + * Date: 19 Sep 2014 + */ +public class MetadataFieldString { + + private static enum Parts { + SCHEMA, ELEMENT, QUALIFIER, LANGUAGE, AUTHORITY, CONFIDENCE + } + + private static final String SIGNATURE_SEPARATOR = "."; + private static final String AUTHORITY_SEPARATOR = "::"; + + public static String getSchema(String metadataFieldString) { + return parse(metadataFieldString).get(Parts.SCHEMA.ordinal()); + } + + public static String getElement(String metadataFieldString) { + return parse(metadataFieldString).get(Parts.ELEMENT.ordinal()); + } + + public static String getQualifier(String metadataFieldString) { + return parse(metadataFieldString).get(Parts.QUALIFIER.ordinal()); + } + + public static String getLanguage(String metadataFieldString) { + return parse(metadataFieldString).get(Parts.LANGUAGE.ordinal()); + } + + public static String getAuthority(String metadataFieldString) { + return parse(metadataFieldString).get(Parts.AUTHORITY.ordinal()); + } + + public static int getConfidence(String metadataFieldString) { + int confidence; + try { + confidence = Integer.parseInt(parse(metadataFieldString).get(Parts.CONFIDENCE.ordinal())); + } catch (NumberFormatException n) { + confidence = Choices.CF_UNSET; + } + return confidence; + } + + private static List parse(String metadataFieldString) { + String schema, element, qualifier = null, language = null, authority = null, confidence = null; + + String[] tokens; + if (StringUtils.isBlank(metadataFieldString)) { + tokens = new String[]{}; + } else { + tokens = StringUtils.split(metadataFieldString, SIGNATURE_SEPARATOR); + } + + if (tokens.length < 2) { + throw new IllegalArgumentException("invalid metadataFieldString: too few tokens"); + } else if (tokens.length >= 4) { + throw new IllegalArgumentException("invalid metadataFieldString: too many tokens"); + } else { + schema = tokens[0]; + element = tokens[1]; + + if (tokens.length == 3) { + String rest = tokens[2]; + + String[] authoritySplit = StringUtils.split(rest, AUTHORITY_SEPARATOR); + if (authoritySplit.length > 1) { + authority = authoritySplit[1]; + } + if (authoritySplit.length > 2) { + confidence = authoritySplit[2]; + } + + rest = authoritySplit[0]; + if (rest.contains("[")) { + int open = rest.indexOf('['); + int close = rest.indexOf(']'); + qualifier = rest.substring(0, open); + language = rest.substring(open + 1, close); + } else { + qualifier = rest; + } + } + } + + ArrayList parsed = new ArrayList(); + Collections.addAll(parsed, new String[Parts.values().length]); + parsed.set(Parts.SCHEMA.ordinal(), schema); + parsed.set(Parts.ELEMENT.ordinal(), element); + parsed.set(Parts.QUALIFIER.ordinal(), qualifier); + parsed.set(Parts.LANGUAGE.ordinal(), language); + parsed.set(Parts.AUTHORITY.ordinal(), authority); + parsed.set(Parts.CONFIDENCE.ordinal(), confidence); + + return parsed; + } + + public static String representing(Metadatum dcValue) { + StringBuilder builder = new StringBuilder(); + builder.append(StringUtils.isNotBlank(dcValue.schema) ? dcValue.schema : Item.ANY) + .append(SIGNATURE_SEPARATOR) + .append(StringUtils.isNotBlank(dcValue.element) ? dcValue.element : Item.ANY); + if (StringUtils.isNotBlank(dcValue.qualifier)) { + builder.append(SIGNATURE_SEPARATOR).append(dcValue.qualifier); + } + if (StringUtils.isNotBlank(dcValue.language)) { + builder.append('[').append(dcValue.language).append(']'); + } + if (StringUtils.isNotBlank(dcValue.authority)) { + builder.append(AUTHORITY_SEPARATOR).append(dcValue.authority); + builder.append(AUTHORITY_SEPARATOR).append(dcValue.confidence); + } + return builder.toString(); + } + + public static String representing(DCInput input) { + StringBuilder builder = new StringBuilder(); + builder.append(StringUtils.isNotBlank(input.getSchema()) ? input.getSchema() : Item.ANY) + .append(SIGNATURE_SEPARATOR) + .append(StringUtils.isNotBlank(input.getElement()) ? input.getElement() : Item.ANY); + if (StringUtils.isNotBlank(input.getQualifier())) { + builder.append(SIGNATURE_SEPARATOR).append(input.getQualifier()); + } + return builder.toString(); + } + + public static Metadata encapsulate(String metadataFieldString, boolean noSchema) { + if (noSchema) { + metadataFieldString = "bogus" + SIGNATURE_SEPARATOR + metadataFieldString; + } + return encapsulate(metadataFieldString); + } + + public static Metadata encapsulate(String metadataFieldString) { + String schema = getSchema(metadataFieldString); + String element = getElement(metadataFieldString); + String qualifier = getQualifier(metadataFieldString); + String language = getLanguage(metadataFieldString); + String authority = getAuthority(metadataFieldString); + int confidence = getConfidence(metadataFieldString); + return new Metadata(schema, element, qualifier, language, null, authority, confidence); + } +} \ No newline at end of file diff --git a/dspace/modules/additions/src/main/java/org/dspace/util/XmlMarshaller.java b/dspace/modules/additions/src/main/java/org/dspace/util/XmlMarshaller.java new file mode 100644 index 0000000..1688385 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/util/XmlMarshaller.java @@ -0,0 +1,36 @@ +package org.dspace.util; + +import javax.xml.bind.*; +import javax.xml.namespace.QName; +import javax.xml.transform.stream.StreamSource; +import java.io.InputStream; +import java.io.StringWriter; + +/** + * Utility class to marshall or unmarshall object to/from XML + */ +public class XmlMarshaller { + + private Class clazz; + + public XmlMarshaller(final Class clazz) { + this.clazz = clazz; + } + + public T unmarshal(final InputStream xmlContent) throws JAXBException { + JAXBContext context = JAXBContext.newInstance(clazz); + Unmarshaller unmarshaller = context.createUnmarshaller(); + return unmarshaller.unmarshal(new StreamSource(xmlContent), clazz).getValue(); + } + + public String marshall(final T objectToTransform) + throws JAXBException { + JAXBContext context = JAXBContext.newInstance(clazz); + Marshaller marshaller = context.createMarshaller(); + StringWriter sw = new StringWriter(); + marshaller + .marshal(new JAXBElement(new QName(clazz.getClass().getSimpleName()), clazz, objectToTransform), sw); + return sw.toString(); + } +} + diff --git a/dspace/modules/additions/src/main/java/org/dspace/util/subclasses/Metadata.java b/dspace/modules/additions/src/main/java/org/dspace/util/subclasses/Metadata.java new file mode 100644 index 0000000..c461070 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/util/subclasses/Metadata.java @@ -0,0 +1,155 @@ +package org.dspace.util.subclasses; + +import org.apache.commons.lang3.*; +import org.dspace.content.*; +import org.dspace.util.*; + +/** + * Created by: Antoine Snyers (antoine at atmire dot com) + * Date: 19 Sep 2014 + */ +public class Metadata extends Metadatum { + + /** + * @param metadataFieldString schema.element.qualifier[language]::authority::confidence + */ + public Metadata(String metadataFieldString, String value) { + this(MetadataFieldString.encapsulate(metadataFieldString), value); + } + + public Metadata(Metadatum field, String value){ + this(field.schema, field.element, field.qualifier, field.language, value, field.authority, field.confidence); + } + + public Metadata(Metadatum dcValue) { + this(dcValue.schema, dcValue.element, dcValue.qualifier, dcValue.language, dcValue.value, dcValue.authority, dcValue.confidence); + } + + public Metadata(String schema, String element, String qualifier, String language, String value, String authority, int confidence) { + this.schema = schema; + this.element = element; + this.qualifier = qualifier; + this.language = language; + this.value = value; + this.authority = authority; + this.confidence = confidence; + } + + public Metadata() { + + } + + public String getSchema() { + return schema; + } + + + public void setSchema(String schema) { + this.schema = schema; + } + + public String getElement() { + return element; + } + + public void setElement(String element) { + this.element = element; + } + + public String getQualifier() { + return qualifier; + } + + public void setQualifier(String qualifier) { + this.qualifier = qualifier; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getAuthority() { + return authority; + } + + public void setAuthority(String authority) { + this.authority = authority; + } + + public int getConfidence() { + return confidence; + } + + public void setConfidence(int confidence) { + this.confidence = confidence; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Metadata metadata = (Metadata) o; + + if (confidence != metadata.confidence) { + return false; + } + if (authority != null ? !authority.equals(metadata.authority) : metadata.authority != null) { + return false; + } + if (element != null ? !element.equals(metadata.element) : metadata.element != null) { + return false; + } + if (language != null ? !language.equals(metadata.language) : metadata.language != null) { + return false; + } + if (qualifier != null ? !qualifier.equals(metadata.qualifier) : metadata.qualifier != null) { + return false; + } + if (schema != null ? !schema.equals(metadata.schema) : metadata.schema != null) { + return false; + } + if (value != null ? !value.equals(metadata.value) : metadata.value != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = schema != null ? schema.hashCode() : 0; + result = 31 * result + (element != null ? element.hashCode() : 0); + result = 31 * result + (qualifier != null ? qualifier.hashCode() : 0); + result = 31 * result + (language != null ? language.hashCode() : 0); + result = 31 * result + (value != null ? value.hashCode() : 0); + result = 31 * result + (authority != null ? authority.hashCode() : 0); + result = 31 * result + confidence; + return result; + } + + public Metadata withWildcards() { + String wilcard = Item.ANY; + String schema1 = StringUtils.isBlank(schema) ? wilcard : schema; + String element1 = StringUtils.isBlank(element) ? wilcard : element; + String qualifier1 = StringUtils.isBlank(qualifier) ? wilcard : qualifier; + String language1 = StringUtils.isBlank(language) ? wilcard : language; + return new Metadata(schema1, element1, qualifier1, language1, value, authority, confidence); + } +} \ No newline at end of file diff --git a/dspace/modules/additions/src/main/java/org/dspace/xmlworkflow/WorkflowRequirementsManager.java b/dspace/modules/additions/src/main/java/org/dspace/xmlworkflow/WorkflowRequirementsManager.java new file mode 100644 index 0000000..a1b8590 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/xmlworkflow/WorkflowRequirementsManager.java @@ -0,0 +1,141 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.xmlworkflow; + +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.xmlworkflow.state.Step; +import org.dspace.xmlworkflow.state.Workflow; +import org.dspace.xmlworkflow.storedcomponents.*; +import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; + +/** + * A class that contains utililty methods related to the workflow + * The adding/removing from claimed users and ensuring that + * if multiple users have to perform these steps that a count is kept + * so that no more then the allowed user count are allowed to perform their actions + * + * @author Bram De Schouwer (bram.deschouwer at dot com) + * @author Kevin Van de Velde (kevin at atmire dot com) + * @author Ben Bosman (ben at atmire dot com) + * @author Mark Diggory (markd at atmire dot com) + */ +public class WorkflowRequirementsManager { + + public static final String WORKFLOW_SCHEMA = "workflow"; + + + /** + * Adds a claimed user in the metadata + * if enough users have claimed this task (claimed or finished) to meet the required number + * the pooled tasks will be deleted + * @param c the dspace context + * @param wfi the workflow item + * @param step the step for which we are accepting + * @param user the current user + * @throws SQLException ... + * @throws AuthorizeException ... + * @throws IOException ... + */ + public static void addClaimedUser(Context c, XmlWorkflowItem wfi, Step step, EPerson user) throws SQLException, AuthorizeException, IOException { + + //Make sure we delete the pooled task for our current user if the task is not a group pooltask + PoolTask task = PoolTask.findByWorkflowIdAndEPerson(c,wfi.getID(),user.getID()); + if(task != null && task.getEpersonID() >= 0){ + XmlWorkflowManager.deletePooledTask(c, wfi, task); + } + + InProgressUser ipu = InProgressUser.create(c); + ipu.setWorkflowItemID(wfi.getID()); + ipu.setUserID(user.getID()); + ipu.setFinished(false); + ipu.update(); + int totalUsers = InProgressUser.getNumberOfInProgressUsers(c, wfi.getID()) + InProgressUser.getNumberOfFinishedUsers(c, wfi.getID()); + + if(totalUsers == step.getRequiredUsers()){ + //If enough users have claimed/finished this step then remove the tasks + XmlWorkflowManager.deleteAllPooledTasks(c, wfi); + } + + c.turnOffAuthorisationSystem(); + wfi.update(); + c.restoreAuthSystemState(); + } + + public static void removeClaimedUser(Context c, XmlWorkflowItem wfi, EPerson user, String stepID) throws SQLException, IOException, WorkflowConfigurationException, AuthorizeException { + //Check if we had reached our max number @ this moment + int totalUsers = InProgressUser.getNumberOfInProgressUsers(c, wfi.getID()) + InProgressUser.getNumberOfFinishedUsers(c, wfi.getID()); + + //Then remove the current user from the inProgressUsers + InProgressUser.findByWorkflowItemAndEPerson(c, wfi.getID(), user.getID()).delete(); + + Workflow workflow = WorkflowFactory.getWorkflow(wfi.getCollection()); + Step step = workflow.getStep(stepID); + +// WorkflowManager.deleteOwnedTask(c, user, wfi, step, step.getActionConfig()); + //We had reached our total user, so recreate tasks for the user who don't have one + if(totalUsers == step.getRequiredUsers()){ + + //Create a list of the users we are to ignore + List toIgnore = InProgressUser.findByWorkflowItem(c, wfi.getID()); + + //Remove the users to ignore + RoleMembers roleMembers = step.getRole().getMembers(c, wfi); + //Create a list out all the users we are to pool a task for + for (InProgressUser ipu: toIgnore) { + roleMembers.removeEperson(ipu.getUserID()); + } + step.getUserSelectionMethod().getProcessingAction().regenerateTasks(c, wfi, roleMembers); + + }else{ + //If the user previously had a personal PoolTask, this must be regenerated. Therefore we call the regeneration method + //with only one EPerson + RoleMembers role = step.getRole().getMembers(c, wfi); + List epersons = role.getEPersons(); + for(EPerson eperson: epersons){ + if(eperson.getID() == user.getID()){ + RoleMembers memberToRegenerateTasksFor = new RoleMembers(); + memberToRegenerateTasksFor.addEPerson(user); + step.getUserSelectionMethod().getProcessingAction().regenerateTasks(c, wfi, memberToRegenerateTasksFor); + break; + } + } + } + //Update our item + wfi.getItem().update(); + } + + /** + * Adds a finished user in the metadata + * this method will also remove the user from the inprogress metadata + * @param c the dspace context + * @param wfi the workflow item + * @param user the current user + * @throws AuthorizeException ... + * @throws SQLException ... + */ + public static void addFinishedUser(Context c, XmlWorkflowItem wfi, EPerson user) throws AuthorizeException, SQLException { + InProgressUser ipu = InProgressUser.findByWorkflowItemAndEPerson(c, wfi.getID(), user.getID()); + ipu.setFinished(true); + ipu.update(); + } + + + public static void clearInProgressUsers(Context c, XmlWorkflowItem wfi) throws AuthorizeException, SQLException { + List ipus = InProgressUser.findByWorkflowItem(c, wfi.getID()); + for(InProgressUser ipu: ipus){ + ipu.delete(); + } + } + +} diff --git a/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/result/ComplianceResultTest.java b/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/result/ComplianceResultTest.java new file mode 100644 index 0000000..95261e0 --- /dev/null +++ b/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/result/ComplianceResultTest.java @@ -0,0 +1,115 @@ +package org.dspace.ref.compliance.result; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ComplianceResultTest { + + @Test + public void testIsCompliantTrue() throws Exception { + ComplianceResult result = new ComplianceResult(); + + RuleComplianceResult rule1 = new RuleComplianceResult(); + rule1.setCompliant(true); + + CategoryComplianceResult categoryResult1 = new CategoryComplianceResult(); + categoryResult1.addRuleResult(rule1); + categoryResult1.setApplicable(true); + + RuleComplianceResult rule2 = new RuleComplianceResult(); + rule2.setCompliant(true); + + CategoryComplianceResult categoryResult2 = new CategoryComplianceResult(); + categoryResult2.addRuleResult(rule2); + categoryResult2.setApplicable(true); + + result.addCategoryResult(categoryResult2); + result.addCategoryResult(categoryResult2); + + assertEquals(true, result.isCompliant()); + } + + @Test + public void testIsCompliantFalse() throws Exception { + ComplianceResult result = new ComplianceResult(); + + RuleComplianceResult rule1 = new RuleComplianceResult(); + rule1.setCompliant(true); + + CategoryComplianceResult categoryResult1 = new CategoryComplianceResult(); + categoryResult1.addRuleResult(rule1); + categoryResult1.setApplicable(true); + + RuleComplianceResult rule2 = new RuleComplianceResult(); + rule2.setCompliant(false); + + CategoryComplianceResult categoryResult2 = new CategoryComplianceResult(); + categoryResult2.addRuleResult(rule2); + categoryResult2.setApplicable(true); + + result.addCategoryResult(categoryResult2); + result.addCategoryResult(categoryResult2); + + assertEquals(false, result.isCompliant()); + } + + @Test + public void testIsCompliantGlobalException() throws Exception { + ComplianceResult result = new ComplianceResult(); + + RuleComplianceResult rule1 = new RuleComplianceResult(); + rule1.setCompliant(true); + + CategoryComplianceResult categoryResult1 = new CategoryComplianceResult(); + categoryResult1.addRuleResult(rule1); + categoryResult1.setApplicable(true); + + RuleComplianceResult rule2 = new RuleComplianceResult(); + rule2.setCompliant(false); + + CategoryComplianceResult categoryResult2 = new CategoryComplianceResult(); + categoryResult2.addRuleResult(rule2); + categoryResult2.setApplicable(true); + + result.addCategoryResult(categoryResult2); + result.addCategoryResult(categoryResult2); + + RuleComplianceResult exception1 = new RuleComplianceResult(); + exception1.setApplicable(true); + exception1.setCompliant(true); + + result.addExceptionResult(exception1); + + assertEquals(true, result.isCompliant()); + } + + @Test + public void testIsCompliantCategoryException() throws Exception { + ComplianceResult result = new ComplianceResult(); + + RuleComplianceResult rule1 = new RuleComplianceResult(); + rule1.setCompliant(false); + + CategoryComplianceResult categoryResult1 = new CategoryComplianceResult(); + categoryResult1.addRuleResult(rule1); + categoryResult1.setApplicable(true); + + RuleComplianceResult exception1 = new RuleComplianceResult(); + exception1.setCompliant(true); + exception1.setApplicable(true); + categoryResult1.addExceptionResult(exception1); + + RuleComplianceResult rule2 = new RuleComplianceResult(); + rule2.setCompliant(false); + + CategoryComplianceResult categoryResult2 = new CategoryComplianceResult(); + categoryResult2.addRuleResult(rule2); + categoryResult2.setApplicable(false); + + result.addCategoryResult(categoryResult2); + result.addCategoryResult(categoryResult2); + + assertEquals(true, result.isCompliant()); + } +} \ No newline at end of file diff --git a/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/rules/CountGreaterThanRuleTest.java b/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/rules/CountGreaterThanRuleTest.java new file mode 100644 index 0000000..254a8f2 --- /dev/null +++ b/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/rules/CountGreaterThanRuleTest.java @@ -0,0 +1,77 @@ +package org.dspace.ref.compliance.rules; + +import org.dspace.content.Item; +import org.dspace.content.Metadatum; +import org.dspace.core.Context; +import org.dspace.ref.compliance.definition.model.Value; +import org.dspace.ref.compliance.result.RuleComplianceResult; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class CountGreaterThanRuleTest { + + @Mock + private Context context; + + @Mock + private Item item; + + private Value value; + + @Before + public void setUp() { + when(item.getMetadata(eq("test"), eq("case"), eq("valid"), anyString())).thenReturn(new Metadatum[] {new Metadatum(), new Metadatum(), new Metadatum()}); + when(item.getMetadata(eq("test"), eq("case"), eq("invalid"), anyString())).thenReturn(new Metadatum[] {}); + value = new Value(); + } + + @Test + public void testDoValidationValid() throws Exception { + value.setValue("2"); + CountGreaterThanRule rule = new CountGreaterThanRule("test field", "test.case.valid", Arrays.asList(value)); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(true, result.isCompliant()); + } + + @Test + public void testDoValidationInvalid() throws Exception { + value.setValue("2"); + CountGreaterThanRule rule = new CountGreaterThanRule("test field", "test.case.invalid", Arrays.asList(value)); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(false, result.isCompliant()); + } + + @Test + public void testDoValidationNoThreshold() throws Exception { + CountGreaterThanRule rule = new CountGreaterThanRule("test field", "test.case.invalid", null); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(false, result.isCompliant()); + } + + @Test + public void testDoValidationNoNumericThreshold() throws Exception { + value.setValue("foobar"); + CountGreaterThanRule rule = new CountGreaterThanRule("test field", "test.case.invalid", Arrays.asList(value)); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(false, result.isCompliant()); + } +} \ No newline at end of file diff --git a/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/rules/DateRangeSmallerThanRuleTest.java b/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/rules/DateRangeSmallerThanRuleTest.java new file mode 100644 index 0000000..9a4b6af --- /dev/null +++ b/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/rules/DateRangeSmallerThanRuleTest.java @@ -0,0 +1,121 @@ +package org.dspace.ref.compliance.rules; + +import org.dspace.content.Item; +import org.dspace.content.Metadatum; +import org.dspace.core.Context; +import org.dspace.ref.compliance.definition.model.Value; +import org.dspace.ref.compliance.result.RuleComplianceResult; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.when; + + +@RunWith(MockitoJUnitRunner.class) +public class DateRangeSmallerThanRuleTest { + + @Mock + private Context context; + + @Mock + private Item item; + + private Value value; + + @Before + public void setUp() { + Metadatum from = new Metadatum(); + from.value = "2016-01-01T11:21:13Z"; + when(item.getMetadata(eq("test"), eq("case"), eq("from"), anyString())).thenReturn(new Metadatum[] {from}); + + Metadatum to = new Metadatum(); + to.value = "2016-04-04T10:21:13Z"; + when(item.getMetadata(eq("test"), eq("case"), eq("to"), anyString())).thenReturn(new Metadatum[] {to}); + + when(item.getMetadata(eq("test"), eq("case"), eq("invalid"), anyString())).thenReturn(new Metadatum[] {}); + + Metadatum invalid2 = new Metadatum(); + invalid2.value = "2016-03-04TBARZ"; + when(item.getMetadata(eq("test"), eq("case"), eq("invalid2"), anyString())).thenReturn(new Metadatum[] {invalid2}); + + value = new Value(); + } + + + @Test + public void testDoValidationValid() throws Exception { + value.setValue("4"); + DateRangeSmallerThanRule rule = new DateRangeSmallerThanRule("test.case.from", "test.case.to", "test range", Arrays.asList(value)); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(true, result.isCompliant()); + } + + @Test + public void testDoValidationInvalid() throws Exception { + value.setValue("3"); + DateRangeSmallerThanRule rule = new DateRangeSmallerThanRule("test.case.from", "test.case.to", "test range", Arrays.asList(value)); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(false, result.isCompliant()); + } + + @Test + public void testDoValidationNoNumbericThreshold() throws Exception { + value.setValue("Foobar"); + DateRangeSmallerThanRule rule = new DateRangeSmallerThanRule("test.case.from", "test.case.to", "test range", Arrays.asList(value)); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(false, result.isCompliant()); + } + + @Test + public void testDoValidationNoThreshold() throws Exception { + DateRangeSmallerThanRule rule = new DateRangeSmallerThanRule("test.case.from", "test.case.to", "test range", null); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(false, result.isCompliant()); + } + + @Test + public void testDoValidationNoDateRange() throws Exception { + value.setValue("4"); + DateRangeSmallerThanRule rule = new DateRangeSmallerThanRule("test.case.from", "", "test range", Arrays.asList(value)); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(false, result.isCompliant()); + } + + @Test + public void testDoValidationInvalidDateRange() throws Exception { + value.setValue("4"); + DateRangeSmallerThanRule rule = new DateRangeSmallerThanRule("test.case.from", "test.case.invalid", "test range", Arrays.asList(value)); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(false, result.isCompliant()); + } + + @Test + public void testDoValidationInvalidDateRange2() throws Exception { + value.setValue("4"); + DateRangeSmallerThanRule rule = new DateRangeSmallerThanRule("test.case.from", "test.case.invalid2", "test range", Arrays.asList(value)); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(false, result.isCompliant()); + } +} \ No newline at end of file diff --git a/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/rules/DateSmallerThanRuleTest.java b/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/rules/DateSmallerThanRuleTest.java new file mode 100644 index 0000000..81c151c --- /dev/null +++ b/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/rules/DateSmallerThanRuleTest.java @@ -0,0 +1,89 @@ +package org.dspace.ref.compliance.rules; + +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.ref.compliance.definition.model.Value; +import org.dspace.ref.compliance.result.RuleComplianceResult; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class DateSmallerThanRuleTest { + + @Mock + private Context context; + + @Mock + private Item item; + + private Value value; + + @Before + public void setUp() { +// DCValue from = new DCValue(); +// from.value = "2016-05-01T11:21:13Z"; +// when(item.getMetadata(eq("test"), eq("case"), eq("date"), anyString())).thenReturn(new DCValue[]{from}); +// +// value = new Value(); + } + + @Test + public void testDoValidationValid() throws Exception { + value.setValue("2016-06-03"); + DateSmallerThanRule rule = new DateSmallerThanRule("test date", "test.case.date", Arrays.asList(value)); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(true, result.isCompliant()); + } + + @Test + public void testDoValidationInvalid() throws Exception { + value.setValue("2016-04-03"); + DateSmallerThanRule rule = new DateSmallerThanRule("test date", "test.case.date", Arrays.asList(value)); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(false, result.isCompliant()); + } + + @Test + public void testDoValidationNoValue() throws Exception { + value.setValue("2016-06-03"); + DateSmallerThanRule rule = new DateSmallerThanRule("test date", "test.case.nodate", Arrays.asList(value)); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(false, result.isCompliant()); + } + + @Test + public void testDoValidationNoThreshold() throws Exception { + value.setValue(null); + DateSmallerThanRule rule = new DateSmallerThanRule("test date", "test.case.date", Arrays.asList(value)); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(false, result.isCompliant()); + } + + @Test + public void testDoValidationNoThresholdList() throws Exception { + value.setValue(null); + DateSmallerThanRule rule = new DateSmallerThanRule("test date", "test.case.date", null); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(false, result.isCompliant()); + } +} \ No newline at end of file diff --git a/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/rules/FieldHasValueRuleTest.java b/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/rules/FieldHasValueRuleTest.java new file mode 100644 index 0000000..da15e4e --- /dev/null +++ b/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/rules/FieldHasValueRuleTest.java @@ -0,0 +1,92 @@ +package org.dspace.ref.compliance.rules; + +import org.dspace.content.Item; +import org.dspace.content.Metadatum; +import org.dspace.core.Context; +import org.dspace.ref.compliance.definition.model.Value; +import org.dspace.ref.compliance.result.RuleComplianceResult; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class FieldHasValueRuleTest { + + @Mock + private Context context; + + @Mock + private Item item; + + private Value value1; + private Value value2; + + @Before + public void setUp() { + Metadatum value = new Metadatum(); + value.value = "abc123"; + when(item.getMetadata(eq("test"), eq("case"), eq("value"), anyString())).thenReturn(new Metadatum[] {value}); + + when(item.getMetadata(eq("test"), eq("case"), eq("invalid"), anyString())).thenReturn(new Metadatum[] {}); + + Metadatum invalid2 = new Metadatum(); + invalid2.value = ""; + when(item.getMetadata(eq("test"), eq("case"), eq("invalid2"), anyString())).thenReturn(new Metadatum[] {invalid2}); + + value1 = new Value(); + value2 = new Value(); + } + + @Test + public void testValidation() { + value1.setValue("def567"); + value2.setValue("abc123"); + FieldHasValueRule rule = new FieldHasValueRule("test field", "test.case.value", Arrays.asList(value1, value2)); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(true, result.isCompliant()); + } + + @Test + public void testValidationValueNotInList() { + value1.setValue("def567"); + value2.setValue("ghi890"); + FieldHasValueRule rule = new FieldHasValueRule("test field", "test.case.value", Arrays.asList(value1, value2)); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(false, result.isCompliant()); + } + + @Test + public void testValidationValueInvalid() { + value1.setValue("def567"); + value2.setValue("abc123"); + FieldHasValueRule rule = new FieldHasValueRule("test field", "test.case.invalid", Arrays.asList(value1, value2)); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(false, result.isCompliant()); + } + + @Test + public void testValidationValueInvalid2() { + value1.setValue("def567"); + value2.setValue("abc123"); + FieldHasValueRule rule = new FieldHasValueRule("test field", "test.case.invalid2", Arrays.asList(value1, value2)); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(false, result.isCompliant()); + } +} \ No newline at end of file diff --git a/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/rules/FieldIsNotBlankRuleTest.java b/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/rules/FieldIsNotBlankRuleTest.java new file mode 100644 index 0000000..6e60b48 --- /dev/null +++ b/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/rules/FieldIsNotBlankRuleTest.java @@ -0,0 +1,67 @@ +package org.dspace.ref.compliance.rules; + +import org.dspace.content.Item; +import org.dspace.content.Metadatum; +import org.dspace.core.Context; +import org.dspace.ref.compliance.result.RuleComplianceResult; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class FieldIsNotBlankRuleTest { + + @Mock + private Context context; + + @Mock + private Item item; + + @Before + public void setUp() { + Metadatum value = new Metadatum(); + value.value = "abc123"; + when(item.getMetadata(eq("test"), eq("case"), eq("value"), anyString())).thenReturn(new Metadatum[] {value}); + + when(item.getMetadata(eq("test"), eq("case"), eq("invalid"), anyString())).thenReturn(new Metadatum[] {}); + + Metadatum invalid2 = new Metadatum(); + invalid2.value = ""; + when(item.getMetadata(eq("test"), eq("case"), eq("invalid2"), anyString())).thenReturn(new Metadatum[] {invalid2}); + } + + @Test + public void testValidation() { + FieldIsNotBlankRule rule = new FieldIsNotBlankRule("test field", "test.case.value"); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(true, result.isCompliant()); + } + + + @Test + public void testValidationValueInvalid() { + FieldIsNotBlankRule rule = new FieldIsNotBlankRule("test field", "test.case.invalid"); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(false, result.isCompliant()); + } + + @Test + public void testValidationValueInvalid2() { + FieldIsNotBlankRule rule = new FieldIsNotBlankRule("test field", "test.case.invalid2"); + + RuleComplianceResult result = rule.validate(context, item); + + assertEquals(false, result.isCompliant()); + } +} \ No newline at end of file diff --git a/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/rules/factory/CategorySetMarshallerTest.java b/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/rules/factory/CategorySetMarshallerTest.java new file mode 100644 index 0000000..0e94d3f --- /dev/null +++ b/dspace/modules/additions/src/test/java/org/dspace/ref/compliance/rules/factory/CategorySetMarshallerTest.java @@ -0,0 +1,43 @@ +package org.dspace.ref.compliance.rules.factory; + +import org.apache.commons.io.FileUtils; +import org.dspace.ref.compliance.definition.model.CategorySet; +import org.junit.Before; +import org.junit.Test; + +import javax.xml.bind.JAXBException; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class CategorySetMarshallerTest { + + private CategorySetMarshaller marshaller; + + @Before + public void setUp() { + marshaller = new CategorySetMarshaller(); + } + + @Test + public void testUnmarshall() throws IOException, JAXBException { + File file = FileUtils.toFile(CategorySetMarshaller.class.getResource("item-validation-rules.xml")); + FileInputStream inputStream = new FileInputStream(file); + + CategorySet response = marshaller.unmarshal(inputStream); + inputStream.close(); + + assertNotNull(response); + assertEquals(2, response.getCategory().size()); + assertEquals(2, response.getExceptions().getRule().size()); + assertEquals("notBlank", response.getCategory().get(0).getRules().getRule().get(0).getType()); + assertEquals("value", response.getCategory().get(0).getRules().getRule().get(1).getType()); + assertEquals("discoverable", response.getCategory().get(1).getRules().getRule().get(0).getType()); + assertEquals("countGreaterThan", response.getCategory().get(1).getRules().getRule().get(1).getType()); + assertEquals("dateRangeSmallerThan", response.getCategory().get(1).getRules().getRule().get(2).getType()); + } + +} \ No newline at end of file diff --git a/dspace/modules/additions/src/test/resources/org/dspace/ref/compliance/rules/factory/item-validation-rules.xml b/dspace/modules/additions/src/test/resources/org/dspace/ref/compliance/rules/factory/item-validation-rules.xml new file mode 100644 index 0000000..e92ba32 --- /dev/null +++ b/dspace/modules/additions/src/test/resources/org/dspace/ref/compliance/rules/factory/item-validation-rules.xml @@ -0,0 +1,68 @@ + + + + + + rioxxterms.type + output type + + + ref.exception.conferenceProceeding + Conference proceeding status + true + + + + + rioxxterms.version + version of deposited file + AM + P + VoR + CVoR + EVoR + + + + + + + + + archived + archived status + false + + + + + bitstreams + 0 + + + + + field.to.determine + specific field + foobar + + + dc.date.from + dc.date.to + embargo period + + + + + + archived + archived status + false + + + test + test + test + + + \ No newline at end of file diff --git a/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/bower.json b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/bower.json new file mode 100644 index 0000000..9260e5c --- /dev/null +++ b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/bower.json @@ -0,0 +1,18 @@ +{ + "name": "Mirage2", + "version": "0.1.1", + "dependencies": { + "bootstrap-sass-official": "3.3.0", + "modernizr": "2.8.3", + "jquery": "~1.10.2", + "jquery-ui": "1.10.3", + "jqueryuibootstrap": "4f3772cd37b898f456c0126c4b44178ce8d4aad7", + "handlebars": "2.0.0", + "respond": "1.4.2", + "html5shiv": "3.7.2", + "holderjs": "2.4.1", + "datatables": "1.10.3", + "fontawesome": "4.2.0" + }, + "devDependencies": {} +} diff --git a/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/scripts.xml b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/scripts.xml new file mode 100644 index 0000000..7fb3079 --- /dev/null +++ b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/scripts.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/scripts/item-compliance.js b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/scripts/item-compliance.js new file mode 100644 index 0000000..b46322c --- /dev/null +++ b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/scripts/item-compliance.js @@ -0,0 +1,50 @@ +(function ($) { + + $(document).ready(function() { + + var show_all = $("p[id$='p_rules_show_all']"); + var show_applicable = $("p[id$='p_rules_show_applicable']"); + + show_all.find("a").click(function() { + show_all.addClass('hidden'); + show_applicable.removeClass('hidden'); + $(".not-applicable-rule").removeClass('hidden'); + + return false; + }); + + show_applicable.find("a").click(function() { + show_applicable.addClass('hidden'); + show_all.removeClass('hidden'); + $(".not-applicable-rule").addClass('hidden'); + + return false; + }); + + $('.rule-tooltip').click(function(){ + return false; + }); + + var show = $("p[id$='p_rules_show']"); + var hide= $("p[id$='p_rules_hide']"); + + show.find("a").click(function() { + show.addClass('hidden'); + hide.removeClass('hidden'); + $(".item-not-applicable ul").removeClass('hidden'); + + return false; + }); + + hide.find("a").click(function() { + hide.addClass('hidden'); + show.removeClass('hidden'); + $(".item-not-applicable ul").addClass('hidden'); + + return false; + }); + + }); + + +})(jQuery); diff --git a/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/scripts/ref-exceptions.js b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/scripts/ref-exceptions.js new file mode 100644 index 0000000..ecdb28c --- /dev/null +++ b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/scripts/ref-exceptions.js @@ -0,0 +1,22 @@ +(function ($) { + + $(document).ready(function() { + + $('.subdivision').change(function () { + var value = $(this).val(); + $('.helpText').addClass("hidden"); + $('#aspect_submission_StepTransformer_p_' + value).removeClass("hidden"); + }); + $('input[name="exception-options"]').click(function () { + var value = $(this).val(); + $('.specificExceptions').addClass("hidden"); + $("#aspect_submission_StepTransformer_div_" + value).removeClass("hidden"); + $('.helpText').addClass("hidden"); + var helpText = $('select[name="' + value + '-dropdown"] option:selected').val(); + $('#aspect_submission_StepTransformer_p_' + helpText).removeClass("hidden"); + + }); + + }); + +})(jQuery); diff --git a/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/styles/_style.scss b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/styles/_style.scss index 91425db..e4b6463 100644 --- a/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/styles/_style.scss +++ b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/styles/_style.scss @@ -11,13 +11,257 @@ * It will be included in all color schemes */ -//example mixin to set the text-selection style -@mixin selection { - ::-moz-selection { @content; } - ::selection { @content; } +.related-data-table, +.related-data-table tr, +.related-data-table td, +.related-data-table th{ + background-color: #FFFFFF; + border: 1px solid #9CACBA; } -@include selection { +.related-data-table tr.ds-table-header-row, +.related-data-table tr.ds-table-header-row th +{ + background-color: #B6C8D9; + border: 1px solid #9CACBA; + +} + +#aspect_submission_StepTransformer_div_submit-describe select { + max-width: 500px; +} + +#com_atmire_xmlui_compliance_ItemComplianceMain_div_item-compliance h2, +#com_atmire_xmlui_compliance_ItemComplianceMain_div_item-compliance +{ + padding-top:10px; +} + +.compliance-return-link{ + padding-top:10px; + display: block; +} + +.compliance-section, .exceptions-section { + margin-bottom: 15px; +} + +.compliance-section ul { + margin-left: 0px; +} + +.compliance-section span::before { + width: 15px; + display: inline-block; +} + + +.compliance-section li { + list-style: none; + padding-top:5px; +} + +.compliance-section li.compliant { + color: #007800; +} + +.compliance-section li.violating, +.compliance-error{ + color: #e38d13; +} + +span.rule-description { + display: block; + margin: 5px 30px; +} + +span.rule-resolution { + display: block; + margin: 5px 60px; +} + +span.rule-description-text, .rule-resolution-text { + font-family: sans-serif; + margin-left: 5px; +} + +#aspect_xmlworkflow_WorkflowTransformer_div_item-compliance, +#aspect_submission_StepTransformer_list_item-compliance-list{ + padding-bottom: 20px; +} + +#aspect_submission_StepTransformer_list_submit-compliance-1 { + margin-bottom:0px; +} + +ol#aspect_submission_StepTransformer_list_ref-compliance-exceptions label { + display: inherit; +} +#aspect_submission_StepTransformer_list_ref-compliance-exceptions { + margin-bottom: 20px; +} +div#aspect_submission_StepTransformer_div_dropdownDivision { + margin-bottom: 10px; +} + +.news-div { + margin-bottom: 20px; + margin-top: 5px; +} + +.top-information-bar .top-information-bar-not-compliant, .resolution-summary-bar{ + background-image: linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%); + border-color: #e38d13; +} + +.top-information-bar .top-information-bar-compliant{ + background-image: linear-gradient(to bottom,#5cb85c 0,#419641 100%); + border: 1px solid #3e8f3e; + margin-bottom:20px; +} + +.top-information-bar .top-information-bar-not-applicable{ + background-image: linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%); + border: 1px solid #28a4c9; + margin-bottom:20px; +} + +.top-information-bar p { + padding: 10px; + font-size: 16px; + color:white; + font-weight: bold; +} + +.resolution-summary-bar { color: white; - background: hotpink; + padding: 10px; + margin-bottom:20px; +} + +.category-name, .exceptions-section-title { + font-size: 150%; + margin-bottom: 10px; + color: #1F3F5E; +} + +.related-data-title { + margin-bottom: 20px; + font-size: 21px; border-bottom: 1px solid #e5e5e5; +} + +.question-circle, .category-name { + display: inline-block; +} + +.fa-question-circle { + margin-left:5px; + text-decoration: none; +} + +.not-applicable-section { + color:gray; +} + +.rule-tooltip{ + display: inline; + position: relative; + z-index:100; + cursor: default; + text-decoration: none; + padding-right: 5px; +} + +.rule-tooltip:hover{ + display: inline; + position: relative; +} + +.rule-tooltip:hover:after{ + background: #333; + background: rgb(31, 63, 94); + border-radius: 5px; + bottom: 26px; + color: #fff; + content: attr(href); + left: -20px; + padding: 5px 15px; + position: absolute; + z-index: 98; + width: 220px; +} + +.rule-tooltip:hover:before{ + border: solid; + border-color: rgb(31, 63, 94) transparent; + border-width: 6px 6px 0 6px; + bottom: 20px; + content: ""; + left: 10px; + position: absolute; + z-index: 99; +} + +.all-rules a{ + float:right; + margin-top:-15px; +} + +.not-applicable-rule, .not-applicable-section p, .not-applicable-section span { + color:gray !important; +} + +.related-data-section { + margin-bottom:15px; +} + +#aspect_xmlworkflow_WorkflowTransformer_div_compliance-wrapper { + margin-top:20px; +} + +.not-applicable-text { + display: inline-block; +} + +div#aspect_submission_StepTransformer_div_help-section { + padding-top: 3px; +} + +.estimated-help-paragraph { + margin-top: 15px; +} + +#aspect_compliance_REFItemComplianceMain_div_item-compliance .top-information-bar, +#aspect_compliance_RIOXXItemComplianceMain_div_item-compliance .top-information-bar, +#aspect_xmlworkflow_WorkflowTransformer_div_compliance-wrapper .top-information-bar { + margin-top: 15px; +} + +#aspect_submission_StepTransformer_div_ref-compliance p, #aspect_submission_StepTransformer_div_ref-compliance ul { + font-weight: bold; +} + +#aspect_submission_StepTransformer_list_ref-compliance-exceptions { + list-style: none; + margin-left:-40px; +} + +span.rule-description { + display: block; + margin: 5px 30px; +} + +.bold { + font-weight:bold; +} + +.estimated-help-paragraph { + margin-top: 0px; + margin-bottom: 25px; +} + +#aspect_xmlworkflow_WorkflowTransformer_div_help-section, +#aspect_compliance_REFItemComplianceMain_div_help-section, +#aspect_compliance_RIOXXItemComplianceMain_div_help-section { + clear:both; } \ No newline at end of file diff --git a/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/styles/classic_mirage_color_scheme/_dspace_variables.scss b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/styles/classic_mirage_color_scheme/_dspace_variables.scss new file mode 100644 index 0000000..a5e5c15 --- /dev/null +++ b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/styles/classic_mirage_color_scheme/_dspace_variables.scss @@ -0,0 +1,16 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +// Extra DSpace variables +// ---------------------- +$fa-font-path: "../vendor/fontawesome/fonts"; +@import "../vendor/fontawesome/scss/font-awesome"; + +$almost-white: #f7f7f7 !default; +$center-section-padding-top: 20px; +$center-section-padding-bottom: 20px; +$artifact-title-font-size: ceil($font-size-base * 1.1666) !default; // ~14px diff --git a/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/xsl/custom/compliance/item-compliance.xsl b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/xsl/custom/compliance/item-compliance.xsl new file mode 100644 index 0000000..3e22928 --- /dev/null +++ b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/xsl/custom/compliance/item-compliance.xsl @@ -0,0 +1,48 @@ + + + + + + +

  • + + + + + + + + + +   + + +   + + +   + + + + + + ds-simple-list-item + + + +
  • + + + + + diff --git a/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/xsl/custom/core/forms.xsl b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/xsl/custom/core/forms.xsl new file mode 100644 index 0000000..de49e69 --- /dev/null +++ b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/xsl/custom/core/forms.xsl @@ -0,0 +1,44 @@ + + + + + + + + + + + +

    + +

    +
    +
    + +
    diff --git a/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/xsl/custom/core/page-structure.xsl b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/xsl/custom/core/page-structure.xsl new file mode 100644 index 0000000..e695ceb --- /dev/null +++ b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/xsl/custom/core/page-structure.xsl @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + images/favicon.ico + + + + + + images/apple-touch-icon.png + + + + + + DSpace + + + + + + + + + + + + + + + + + + + + + + + + + + + + + application/ + + + + + + + + + + + + + + :// + + : + + + / + + + + + + + + + + + + + <!--[if lt IE 9]> + <script src=""> </script> + <script src=""> </script> + <![endif]--> + + + + + + + + <xsl:choose> + <xsl:when test="starts-with($request-uri, 'page/about')"> + <i18n:text>xmlui.mirage2.page-structure.aboutThisRepository</i18n:text> + </xsl:when> + <xsl:when test="not($page_title)"> + <xsl:text> </xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:copy-of select="$page_title/node()" /> + </xsl:otherwise> + </xsl:choose> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/xsl/theme.xsl b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/xsl/theme.xsl new file mode 100644 index 0000000..ddd2b8c --- /dev/null +++ b/dspace/modules/xmlui-mirage2/src/main/webapp/themes/Mirage2/xsl/theme.xsl @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace/modules/xmlui/src/main/java/com/atmire/xmlui/compliance/submission/REFComplianceStep.java b/dspace/modules/xmlui/src/main/java/com/atmire/xmlui/compliance/submission/REFComplianceStep.java new file mode 100644 index 0000000..95cc54f --- /dev/null +++ b/dspace/modules/xmlui/src/main/java/com/atmire/xmlui/compliance/submission/REFComplianceStep.java @@ -0,0 +1,59 @@ +package com.atmire.xmlui.compliance.submission; + +import java.io.*; +import java.sql.*; +import org.apache.cocoon.*; +import org.dspace.app.xmlui.aspect.compliance.ComplianceUI; +import org.dspace.app.xmlui.aspect.submission.*; +import org.dspace.app.xmlui.utils.*; +import org.dspace.app.xmlui.wing.*; +import org.dspace.app.xmlui.wing.element.*; +import org.dspace.authorize.*; +import org.dspace.content.*; +import org.dspace.utils.*; +import org.xml.sax.*; + +/** + * Created by Philip Vissenaekens (philip at atmire dot com) + * Date: 23/03/16 + * Time: 10:57 + */ +public class REFComplianceStep extends AbstractSubmissionStep { + + protected static final Message T_error_blocked = + message("xmlui.compliance.submission.error_blocked"); + + protected static final Message T_head = + message("xmlui.compliance.submission.head"); + + private ComplianceUI complianceUI = new DSpace().getServiceManager().getServiceByName("refComplianceUI", ComplianceUI.class); + + @Override + public List addReviewSection(List reviewList) throws SAXException, WingException, UIException, SQLException, IOException, AuthorizeException { + return null; + } + + @Override + public void addBody(Body body) throws SAXException, WingException, UIException, SQLException, IOException, AuthorizeException, ProcessingException { + org.dspace.content.Item item = submission.getItem(); + Collection collection = submission.getCollection(); + String actionURL = contextPath + "/handle/"+collection.getHandle() + "/submit/" + knot.getId() + ".continue"; + + Division div = body.addInteractiveDivision("submit-describe",actionURL,Division.METHOD_POST,"primary submission"); + div.setHead(T_submission_head); + addSubmissionProgressList(div); + + div.addList("submit-compliance-1",List.TYPE_FORM).setHead(T_head.parameterize(complianceUI.getShortname())); + + complianceUI.addComplianceSections(div, item, context); + + if (this.errorFlag== org.dspace.ref.compliance.submission.REFComplianceStep.STATUS_BLOCKED) + { + div.addPara("compliance-error", "compliance-error").addContent(T_error_blocked); + } + + List form = div.addList("submit-compliance-2",List.TYPE_FORM); + + addControlButtons(form); + } +} diff --git a/dspace/modules/xmlui/src/main/java/com/atmire/xmlui/compliance/submission/REFExceptionStep.java b/dspace/modules/xmlui/src/main/java/com/atmire/xmlui/compliance/submission/REFExceptionStep.java new file mode 100644 index 0000000..0deee25 --- /dev/null +++ b/dspace/modules/xmlui/src/main/java/com/atmire/xmlui/compliance/submission/REFExceptionStep.java @@ -0,0 +1,245 @@ +package com.atmire.xmlui.compliance.submission; + +import com.atmire.ref.compliance.submission.ExceptionInformation; +import org.apache.cocoon.ProcessingException; +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.xmlui.aspect.compliance.ComplianceUI; +import org.dspace.app.xmlui.aspect.submission.AbstractSubmissionStep; +import org.dspace.app.xmlui.wing.Message; +import org.dspace.app.xmlui.wing.WingException; +import org.dspace.app.xmlui.wing.element.*; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Collection; +import org.dspace.content.Metadatum; +import org.dspace.ref.compliance.result.CategoryComplianceResult; +import org.dspace.ref.compliance.result.ComplianceResult; +import org.dspace.ref.compliance.service.ComplianceCheckService; +import org.dspace.utils.DSpace; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * Created by jonas - jonas@atmire.com on 08/04/16. + */ +public class REFExceptionStep extends AbstractSubmissionStep { + + protected static final Message T_head = message("xmlui.compliance.submission.ExceptionStep.head"); + protected static final Message T_info = message("xmlui.compliance.submission.ExceptionStep.info"); + protected static final Message T_none = message("xmlui.compliance.submission.ExceptionStep.exception.none"); + private static final String TITLE_1_BASE = "xmlui.compliance.submission.ExceptionStep.specificException.title1."; + private static final String PARA_1_BASE = "xmlui.compliance.submission.ExceptionStep.specificException.para1."; + private static final String TITLE_2_BASE = "xmlui.compliance.submission.ExceptionStep.specificException.title2."; + private static final String PARA_2_BASE = "xmlui.compliance.submission.ExceptionStep.specificException.para2."; + private static final String EXCEPTION_RADIOBUTTON_BASE = "xmlui.compliance.submission.ExceptionStep.exception."; + + private static final Message T_compliant = message("xmlui.compliance.submission.ExceptionStep.compliant"); + private static final Message T_not_compliant = message("xmlui.compliance.submission.ExceptionStep.not_compliant"); + + private static ComplianceCheckService complianceCheckService = new DSpace().getServiceManager() + .getServiceByName("refComplianceCheckService", ComplianceCheckService.class); + + public void addPageMeta(PageMeta pageMeta) throws SAXException, WingException, SQLException, IOException, AuthorizeException { + super.addPageMeta(pageMeta); + pageMeta.addMetadata("javascript", null, "person-lookup", true).addContent("../../static/js/ref-exceptions.js"); + + } + + @Override + public List addReviewSection(List list) throws SAXException, WingException, SQLException, IOException, AuthorizeException { + return null; + } + + @Override + public void addBody(Body body) throws SAXException, WingException, SQLException, IOException, AuthorizeException, ProcessingException { + Collection collection = submission.getCollection(); + String actionURL = contextPath + "/handle/" + collection.getHandle() + "/submit/" + knot.getId() + ".continue"; + + Division div = body.addInteractiveDivision("submit-describe", actionURL, Division.METHOD_POST, "primary submission"); + addSubmissionProgressList(div); + + div.setHead(T_head); + + org.dspace.content.Item item = submission.getItem(); + ComplianceResult complianceResult = complianceCheckService.checkCompliance(context, item); + + //Only show exception select options when the item is applicable for REF. + if(complianceResult.isApplicable()) { + div.addPara(T_info); + + String[] specifiedExceptions = new DSpace().getServiceManager().getServiceByName("configuredExceptions", String[].class); + + List form = div.addList("ref-compliance-exceptions", List.TYPE_ORDERED); + + Item item1 = form.addItem("exceptions-list", null); + Radio exceptions = item1.addRadio("exception-options"); + exceptions.addOption(true, "none", T_none); + + addBaseExceptionSelection(specifiedExceptions, exceptions, item); + + for (int i = 0; i < specifiedExceptions.length; i++) { + String specifiedException = specifiedExceptions[i]; + addSpecifiedExceptionPart(div, item, specifiedException); + } + + Division complianceDiv = div.addDivision("ref-compliance", "ref-compliance"); + + if (complianceResult.isCompliant()) { + complianceDiv.addPara().addContent(T_compliant); + } else { + complianceDiv.addPara().addContent(T_not_compliant); + List list = complianceDiv.addList("requirements-list"); + + for (CategoryComplianceResult categoryComplianceResult : complianceResult.getOrderedCategoryResults()) { + if (!categoryComplianceResult.isCompliant()) { + list.addItem().addContent(categoryComplianceResult.getCategoryName()); + } + } + } + + } else { + //Inform the user that his item is not applicable and that he doesn't need to select an exception. + ComplianceUI complianceUI = new ComplianceUI(); + complianceUI.setIdentifier("ref"); + complianceUI.addItemNotApplicableSection(div, complianceResult); + } + + List submit = div.addList("submit-exception", List.TYPE_FORM); + addControlButtons(submit); + } + + /** + * Add configured exceptions as options to radiobutton + * @param specifiedExceptions The exceptions configured in refterms-exceptions.xml + * @param exceptions A Radio object to add the exceptions to + * @param item The item to check metadata against + * @throws WingException + */ + private void addBaseExceptionSelection(String[] specifiedExceptions, Radio exceptions, org.dspace.content.Item item) throws WingException { + // Loop over configured exceptions to create the select box + for(int i = 0; i