diff --git a/NEWS.md b/NEWS.md
index 657f995ea..fb7de0dc0 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -3,8 +3,8 @@
* Migrate "publicationPeriod" data to the Dates object and remove it from the Instance schema ([MODINVSTOR-1232](https://folio-org.atlassian.net/browse/MODINVSTOR-1232))
### New APIs versions
-* Provides `instance-storage 11.0`
-* Provides `instance-storage-batch 3.0`
+* Provides `instance-storage 11.1`
+* Provides `instance-storage-batch 3.1`
* Provides `instance-storage-batch-sync 3.0`
* Provides `instance-storage-batch-sync-unsafe 3.0`
* Provides `inventory-view 3.0`
@@ -16,6 +16,7 @@
### Features
* Add module descriptor validator plugin and fix the permission names ([MODINVSTOR-1247](https://folio-org.atlassian.net/browse/MODINVSTOR-1247))
+* Implement publication period migration on big dataset, create new InstanceWithoutPubPeriod schema only for input request ([MODINVSTOR-1271](https://folio-org.atlassian.net/browse/MODINVSTOR-1271))
### Tech Dept
* Upgrade localstack from 0.11.3 to s3-latest (=3.8.0) ([MODINVSTOR-1272](https://folio-org.atlassian.net/browse/MODINVSTOR-1272))
diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json
index 52f9afc72..ccf822302 100755
--- a/descriptors/ModuleDescriptor-template.json
+++ b/descriptors/ModuleDescriptor-template.json
@@ -167,7 +167,7 @@
},
{
"id": "instance-storage",
- "version": "11.0",
+ "version": "11.1",
"handlers": [
{
"methods": ["GET"],
@@ -274,7 +274,7 @@
},
{
"id": "instance-storage-batch",
- "version": "3.0",
+ "version": "3.1",
"handlers": [
{
"methods": ["POST"],
diff --git a/pom.xml b/pom.xml
index 63661b75a..1e4432ec7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,6 +23,7 @@
3.17.0
2.24.1
2.2.0-SNAPSHOT
+ 1.9.4
6.0.3
5.11.2
@@ -138,6 +139,11 @@
folio-s3-client
${folio-s3-client.version}
+
+ commons-beanutils
+ commons-beanutils
+ ${commons-beanutils.version}
+
diff --git a/ramls/examples/instance_get.json b/ramls/examples/instance_get.json
index 086356896..a4c007c2c 100644
--- a/ramls/examples/instance_get.json
+++ b/ramls/examples/instance_get.json
@@ -19,6 +19,10 @@
"value": "1"
}
],
+ "publicationPeriod": {
+ "start": 1999,
+ "end": 2001
+ },
"instanceTypeId": "2b94c631-fca9-4892-a730-03ee529ffe2c",
"tags" : {
"tagList" : [
diff --git a/ramls/examples/instances_get.json b/ramls/examples/instances_get.json
index 1d948b250..27c0c9a07 100644
--- a/ramls/examples/instances_get.json
+++ b/ramls/examples/instances_get.json
@@ -21,6 +21,10 @@
"value": "1"
}
],
+ "publicationPeriod": {
+ "start": 1999,
+ "end": 2001
+ },
"instanceTypeId": "2b94c631-fca9-4892-a730-03ee529ffe2c",
"tags" : {
"tagList" : [
diff --git a/ramls/examples/instanceswithoutpubperiod_get.json b/ramls/examples/instanceswithoutpubperiod_get.json
new file mode 100644
index 000000000..1d948b250
--- /dev/null
+++ b/ramls/examples/instanceswithoutpubperiod_get.json
@@ -0,0 +1,56 @@
+{
+ "instances": [
+ {
+ "id": "601a8dc4-dee7-48eb-b03f-d02fdf0debd0",
+ "title": "ADVANCING LIBRARY EDUCATION: TECHNOLOGICAL INNOVATION AND INSTRUCTIONAL DESIGN",
+ "source": "Local: MARC",
+ "contributors": [
+ {
+ "name": "Sigal, Ari",
+ "contributorNameTypeId": "2b94c631-fca9-4892-a730-03ee529ffe2a",
+ "primary": true
+ }
+ ],
+ "identifiers": [
+ {
+ "identifierTypeId": "2e48e713-17f3-4c13-a9f8-23845bb210af",
+ "value": "9781466636897"
+ },
+ {
+ "identifierTypeId": "6051f95c-028e-4c6a-8a9e-ee689dd51453",
+ "value": "1"
+ }
+ ],
+ "instanceTypeId": "2b94c631-fca9-4892-a730-03ee529ffe2c",
+ "tags" : {
+ "tagList" : [
+ "important"
+ ]
+ }
+ },
+ {
+ "id": "f31a36de-fcf8-44f9-87ef-a55d06ad21ae",
+ "title": "ADVANCING RESEARCH METHODS WITH NEW TECHNOLOGIES.",
+ "source": "Local: MARC",
+ "contributors": [
+ {
+ "name": "Sappleton, Natalie",
+ "contributorNameTypeId": "2b94c631-fca9-4892-a730-03ee529ffe2a",
+ "primary": true
+ }
+ ],
+ "identifiers": [
+ {
+ "identifierTypeId": "2e48e713-17f3-4c13-a9f8-23845bb210af",
+ "value": "9781466639195"
+ },
+ {
+ "identifierTypeId": "6051f95c-028e-4c6a-8a9e-ee689dd51453",
+ "value": "2"
+ }
+ ],
+ "instanceTypeId": "2b94c631-fca9-4892-a730-03ee529ffe2c"
+ }
+ ],
+ "totalRecords": 2
+}
diff --git a/ramls/examples/instancewithoutpubperiod_get.json b/ramls/examples/instancewithoutpubperiod_get.json
new file mode 100644
index 000000000..086356896
--- /dev/null
+++ b/ramls/examples/instancewithoutpubperiod_get.json
@@ -0,0 +1,28 @@
+{
+ "id": "601a8dc4-dee7-48eb-b03f-d02fdf0debd0",
+ "source": "Local: MARC",
+ "title": "ADVANCING LIBRARY EDUCATION: TECHNOLOGICAL INNOVATION AND INSTRUCTIONAL DESIGN",
+ "contributors": [
+ {
+ "name": "Sigal, Ari",
+ "contributorNameTypeId": "2b94c631-fca9-4892-a730-03ee529ffe2a",
+ "primary": true
+ }
+ ],
+ "identifiers": [
+ {
+ "identifierTypeId": "2e48e713-17f3-4c13-a9f8-23845bb210af",
+ "value": "9781466636897"
+ },
+ {
+ "identifierTypeId": "6051f95c-028e-4c6a-8a9e-ee689dd51453",
+ "value": "1"
+ }
+ ],
+ "instanceTypeId": "2b94c631-fca9-4892-a730-03ee529ffe2c",
+ "tags" : {
+ "tagList" : [
+ "important"
+ ]
+ }
+}
diff --git a/ramls/instance-storage-batch.raml b/ramls/instance-storage-batch.raml
index b28f3fc2a..6d637e8bd 100644
--- a/ramls/instance-storage-batch.raml
+++ b/ramls/instance-storage-batch.raml
@@ -11,6 +11,7 @@ documentation:
types:
errors: !include raml-util/schemas/errors.schema
instances: !include instances.json
+ instancesWithoutPubPeriod: !include instances-without-pub-period.json
instancesBatchResponse: !include instances-batch-response.json
/instance-storage/batch/instances:
@@ -19,7 +20,9 @@ types:
description: "Create collection of instances in one request - deprecated, use /instance-storage/sync instead"
body:
application/json:
- type: instances
+ type: instancesWithoutPubPeriod
+ example:
+ value: !include examples/instanceswithoutpubperiod_get.json
responses:
201:
description: "At least one Instance from the list was created"
diff --git a/ramls/instance-storage.raml b/ramls/instance-storage.raml
index 373a5785b..d7e97d287 100644
--- a/ramls/instance-storage.raml
+++ b/ramls/instance-storage.raml
@@ -9,6 +9,8 @@ documentation:
content: Storage for instances in the inventory
types:
+ instanceWithoutPubPeriod: !include instance-without-pub-period.json
+ instancesWithoutPubPeriod: !include instances-without-pub-period.json
instance: !include instance.json
instances: !include instances.json
marcJson: !include marc.json
@@ -60,16 +62,26 @@ resourceTypes:
displayName: Instances
type:
collection:
- exampleCollection: !include examples/instances_get.json
- schemaCollection: instances
- schemaItem: instance
- exampleItem: !include examples/instance_get.json
+ exampleCollection: !include examples/instanceswithoutpubperiod_get.json
+ schemaCollection: instancesWithoutPubPeriod
+ schemaItem: instanceWithoutPubPeriod
+ exampleItem: !include examples/instancewithoutpubperiod_get.json
get:
is: [pageable,
searchable: {description: "by title (using CQL)",
example: "title=\"*uproot*\""},
]
+ responses:
+ 200:
+ body:
+ application/json:
+ type: instances
post:
+ responses:
+ 200:
+ body:
+ application/json:
+ type: instance
delete:
is: [searchable: { description: "CQL to select instances to delete, use cql.allRecords=1 to delete all. Deletes connected marc source records.",
example: "hrid==\"in123-0*\"" } ]
@@ -89,8 +101,8 @@ resourceTypes:
/{instanceId}:
type:
collection-item:
- exampleItem: !include examples/instance_get.json
- schema: instance
+ exampleItem: !include examples/instancewithoutpubperiod_get.json
+ schema: instanceWithoutPubPeriod
get:
responses:
200:
diff --git a/ramls/instance-without-pub-period.json b/ramls/instance-without-pub-period.json
new file mode 100644
index 000000000..b6542c778
--- /dev/null
+++ b/ramls/instance-without-pub-period.json
@@ -0,0 +1,508 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "An instance record without publicationPeriod",
+ "type": "object",
+ "javaType": "org.folio.rest.jaxrs.model.InstanceWithoutPubPeriod",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "The unique ID of the instance record; a UUID",
+ "$ref": "uuid.json"
+ },
+ "_version": {
+ "type": "integer",
+ "description": "Record version for optimistic locking"
+ },
+ "hrid": {
+ "type": "string",
+ "description": "The human readable ID, also called eye readable ID. A system-assigned sequential ID which maps to the Instance ID"
+ },
+ "matchKey": {
+ "type": "string",
+ "description" : "A unique instance identifier matching a client-side bibliographic record identification scheme, in particular for a scenario where multiple separate catalogs with no shared record identifiers contribute to the same Instance in Inventory. A match key is typically generated from select, normalized pieces of metadata in bibliographic records"
+ },
+ "source": {
+ "type": "string",
+ "description": "The metadata source and its format of the underlying record to the instance record. (e.g. FOLIO if it's a record created in Inventory; MARC if it's a MARC record created in MARCcat or EPKB if it's a record coming from eHoldings; CONSORTIUM-MARC or CONSORTIUM-FOLIO for sharing Instances)."
+ },
+ "title": {
+ "type": "string",
+ "description": "The primary title (or label) associated with the resource"
+ },
+ "indexTitle": {
+ "type": "string",
+ "description": "Title normalized for browsing and searching; based on the title with articles removed"
+ },
+ "alternativeTitles": {
+ "type": "array",
+ "description": "List of alternative titles for the resource (e.g. original language version title of a movie)",
+ "items": {
+ "type": "object",
+ "properties": {
+ "alternativeTitleTypeId": {
+ "type": "string",
+ "description": "UUID for an alternative title qualifier",
+ "$ref": "uuid.json"
+ },
+ "alternativeTitle": {
+ "type": "string",
+ "description": "An alternative title for the resource"
+ },
+ "authorityId": {
+ "type": "string",
+ "description": "UUID of authority record that controls an alternative title",
+ "$ref": "uuid.json"
+ }
+ }
+ },
+ "uniqueItems": true
+ },
+ "editions": {
+ "type": "array",
+ "description": "The edition statement, imprint and other publication source information",
+ "items": {
+ "type": "string"
+ },
+ "uniqueItems": true
+ },
+ "series": {
+ "type": "array",
+ "description": "List of series titles associated with the resource (e.g. Harry Potter)",
+ "items": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "type": "string",
+ "description": "Series title value"
+ },
+ "authorityId": {
+ "type": "string",
+ "description": "UUID of authority record that controls an series title",
+ "$ref": "uuid.json"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "value"
+ ]
+ },
+ "uniqueItems": true
+ },
+ "identifiers": {
+ "type": "array",
+ "description": "An extensible set of name-value pairs of identifiers associated with the resource",
+ "minItems": 0,
+ "items": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "type": "string",
+ "description": "Resource identifier value"
+ },
+ "identifierTypeId": {
+ "type": "string",
+ "description": "UUID of resource identifier type (e.g. ISBN, ISSN, LCCN, CODEN, Locally defined identifiers)",
+ "$ref": "uuid.json"
+ },
+ "identifierTypeObject": {
+ "type": "object",
+ "description": "Information about identifier type, looked up from identifierTypeId",
+ "folio:$ref": "illpolicy.json",
+ "readonly": true,
+ "folio:isVirtual": true,
+ "folio:linkBase": "identifier-types",
+ "folio:linkFromField": "identifierTypeId",
+ "folio:linkToField": "id",
+ "folio:includedElement": "identifierTypes.0"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "value",
+ "identifierTypeId"
+ ]
+ }
+ },
+ "contributors": {
+ "type": "array",
+ "description": "List of contributors",
+ "minItems": 0,
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Personal name, corporate name, meeting name"
+ },
+ "contributorTypeId": {
+ "type": "string",
+ "description": "UUID for the contributor type term defined in controlled vocabulary",
+ "$ref": "uuid.json"
+ },
+ "contributorTypeText": {
+ "type": "string",
+ "description": "Free text element for adding contributor type terms other that defined by the MARC code list for relators"
+ },
+ "contributorNameTypeId": {
+ "type": "string",
+ "description": "UUID of contributor name type term defined by the MARC code list for relators",
+ "$ref": "uuid.json"
+ },
+ "authorityId": {
+ "type": "string",
+ "description": "UUID of authority record that controls the contributor",
+ "$ref": "uuid.json"
+ },
+ "contributorNameType": {
+ "type": "object",
+ "description": "Dereferenced contributor-name type",
+ "javaType": "org.folio.rest.jaxrs.model.contributorNameTypeVirtual",
+ "folio:$ref": "contributornametype.json",
+ "readonly": true,
+ "folio:isVirtual": true,
+ "folio:linkBase": "contributor-name-types",
+ "folio:linkFromField": "contributorNameTypeId",
+ "folio:linkToField": "id",
+ "folio:includedElement": "contributorNameTypes.0"
+ },
+ "primary": {
+ "type": "boolean",
+ "description": "Whether this is the primary contributor"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "name",
+ "contributorNameTypeId"
+ ]
+ }
+ },
+ "subjects": {
+ "type": "array",
+ "description": "List of subject headings",
+ "items": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "type": "string",
+ "description": "Subject heading value"
+ },
+ "authorityId": {
+ "type": "string",
+ "description": "UUID of authority record that controls a subject heading",
+ "$ref": "uuid.json"
+ },
+ "sourceId": {
+ "type": "string",
+ "description": "UUID of subject source",
+ "$ref": "uuid.json"
+ },
+ "typeId": {
+ "type": "string",
+ "description": "UUID of subject type",
+ "$ref": "uuid.json"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "value"
+ ]
+ },
+ "uniqueItems": true
+ },
+ "classifications": {
+ "type": "array",
+ "description": "List of classifications",
+ "minItems": 0,
+ "items": {
+ "type": "object",
+ "properties": {
+ "classificationNumber": {
+ "type": "string",
+ "description": "Classification (e.g. classification scheme, classification schedule)"
+ },
+ "classificationTypeId": {
+ "type": "string",
+ "description": "UUID of classification schema (e.g. LC, Canadian Classification, NLM, National Agricultural Library, UDC, and Dewey)",
+ "$ref": "uuid.json"
+ },
+ "classificationType": {
+ "type": "object",
+ "description": "Dereferenced classification schema",
+ "javaType": "org.folio.rest.jaxrs.model.classificationTypeVirtual",
+ "folio:$ref": "classificationtype.json",
+ "readonly": true,
+ "folio:isVirtual": true,
+ "folio:linkBase": "classification-types",
+ "folio:linkFromField": "classificationTypeId",
+ "folio:linkToField": "id",
+ "folio:includedElement": "classificationTypes.0"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "classificationNumber",
+ "classificationTypeId"
+ ]
+ }
+ },
+ "publication": {
+ "type": "array",
+ "description": "List of publication items",
+ "items": {
+ "type": "object",
+ "properties": {
+ "publisher": {
+ "type": "string",
+ "description": "Name of publisher, distributor, etc."
+ },
+ "place": {
+ "type": "string",
+ "description": "Place of publication, distribution, etc."
+ },
+ "dateOfPublication": {
+ "type": "string",
+ "description": "Date (year YYYY) of publication, distribution, etc."
+ },
+ "role": {
+ "type": "string",
+ "description": "The role of the publisher, distributor, etc."
+ }
+ }
+ }
+ },
+ "publicationFrequency": {
+ "type": "array",
+ "description": "List of intervals at which a serial appears (e.g. daily, weekly, monthly, quarterly, etc.)",
+ "items": {
+ "type": "string"
+ },
+ "uniqueItems": true
+ },
+ "publicationRange": {
+ "type": "array",
+ "description": "The range of sequential designation/chronology of publication, or date range",
+ "items": {
+ "type": "string"
+ },
+ "uniqueItems": true
+ },
+ "electronicAccess": {
+ "type": "array",
+ "description": "List of electronic access items",
+ "items": {
+ "type": "object",
+ "properties": {
+ "uri": {
+ "type": "string",
+ "description": "uniform resource identifier (URI) is a string of characters designed for unambiguous identification of resources"
+ },
+ "linkText": {
+ "type": "string",
+ "description": "The value of the MARC tag field 856 2nd indicator, where the values are: no information provided, resource, version of resource, related resource, no display constant generated"
+ },
+ "materialsSpecification": {
+ "type": "string",
+ "description": "Materials specified is used to specify to what portion or aspect of the resource the electronic location and access information applies (e.g. a portion or subset of the item is electronic, or a related electronic resource is being linked to the record)"
+ },
+ "publicNote": {
+ "type": "string",
+ "description": "URL public note to be displayed in the discovery"
+ },
+ "relationshipId": {
+ "type": "string",
+ "description": "UUID for the type of relationship between the electronic resource at the location identified and the item described in the record as a whole",
+ "$ref": "uuid.json"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "uri"
+ ]
+ }
+ },
+ "dates": {
+ "type": "object",
+ "description": "Instance Dates",
+ "properties": {
+ "dateTypeId": {
+ "type": "string",
+ "description": "Date type ID",
+ "$ref": "uuid.json"
+ },
+ "date1": {
+ "type": "string",
+ "description": "Date 1",
+ "maxLength": 4
+ },
+ "date2": {
+ "type": "string",
+ "description": "Date 2",
+ "maxLength": 4
+ }
+ },
+ "additionalProperties": false
+ },
+ "instanceTypeId": {
+ "type": "string",
+ "description": "UUID of the unique term for the resource type whether it's from the RDA content term list of locally defined",
+ "$ref": "uuid.json"
+ },
+ "instanceFormatIds": {
+ "type": "array",
+ "description": "UUIDs for the unique terms for the format whether it's from the RDA carrier term list of locally defined",
+ "items": {
+ "type": "string",
+ "$ref": "uuid.json"
+ }
+ },
+ "instanceFormats": {
+ "type": "array",
+ "description": "List of dereferenced instance formats",
+ "items": {
+ "type": "object",
+ "$ref": "instanceformat.json"
+ },
+ "readonly": true,
+ "folio:isVirtual": true,
+ "folio:linkBase": "instance-formats",
+ "folio:linkFromField": "instanceFormatIds",
+ "folio:linkToField": "id",
+ "folio:includedElement": "instanceFormats"
+ },
+ "physicalDescriptions": {
+ "type": "array",
+ "description": "Physical description of the described resource, including its extent, dimensions, and such other physical details as a description of any accompanying materials and unit type and size",
+ "items": {
+ "type": "string"
+ }
+ },
+ "languages": {
+ "type": "array",
+ "description": "The set of languages used by the resource",
+ "minItems": 0,
+ "items": {
+ "type": "string"
+ }
+ },
+ "notes": {
+ "type": "array",
+ "description": "Bibliographic notes (e.g. general notes, specialized notes)",
+ "items": {
+ "type": "object",
+ "javaType": "org.folio.rest.jaxrs.model.InstanceNote",
+ "additionalProperties": false,
+ "properties": {
+ "instanceNoteTypeId": {
+ "description": "ID of the type of note",
+ "$ref": "uuid.json"
+ },
+ "note": {
+ "type": "string",
+ "description": "Text content of the note"
+ },
+ "staffOnly": {
+ "type": "boolean",
+ "description": "If true, determines that the note should not be visible for others than staff",
+ "default": false
+ }
+ }
+ }
+ },
+ "administrativeNotes":{
+ "type": "array",
+ "description": "Administrative notes",
+ "minItems": 0,
+ "items": {
+ "type": "string"
+ }
+ },
+ "modeOfIssuanceId": {
+ "type": "string",
+ "description": "UUID of the RDA mode of issuance, a categorization reflecting whether a resource is issued in one or more parts, the way it is updated, and whether its termination is predetermined or not (e.g. monograph, sequential monograph, serial; integrating Resource, other)",
+ "$ref": "uuid.json"
+ },
+ "catalogedDate": {
+ "type": "string",
+ "description": "Date or timestamp on an instance for when is was considered cataloged"
+ },
+ "previouslyHeld": {
+ "type": "boolean",
+ "description": "Records the fact that the resource was previously held by the library for things like Hathi access, etc.",
+ "default": false
+ },
+ "staffSuppress": {
+ "type": "boolean",
+ "description": "Records the fact that the record should not be displayed for others than catalogers"
+ },
+ "discoverySuppress": {
+ "type": "boolean",
+ "description": "Records the fact that the record should not be displayed in a discovery system",
+ "default": false
+ },
+ "statisticalCodeIds": {
+ "type": "array",
+ "description": "List of statistical code IDs",
+ "items": {
+ "type": "string"
+ },
+ "uniqueItems": true
+ },
+ "sourceRecordFormat": {
+ "type": "string",
+ "description": "Format of the instance source record, if a source record exists (e.g. FOLIO if it's a record created in Inventory, MARC if it's a MARC record created in MARCcat or EPKB if it's a record coming from eHoldings)",
+ "enum": ["MARC-JSON"],
+ "readonly": true
+ },
+ "statusId": {
+ "type": "string",
+ "description": "UUID for the Instance status term (e.g. cataloged, uncatalogued, batch loaded, temporary, other, not yet assigned)",
+ "$ref": "uuid.json"
+ },
+ "statusUpdatedDate": {
+ "type": "string",
+ "description": "Date [or timestamp] for when the instance status was updated"
+ },
+ "tags": {
+ "description": "arbitrary tags associated with this instance",
+ "id": "tags",
+ "type": "object",
+ "$ref": "raml-util/schemas/tags.schema"
+ },
+ "metadata": {
+ "type": "object",
+ "$ref": "raml-util/schemas/metadata.schema",
+ "readonly": true
+ },
+ "holdingsRecords2": {
+ "type": "array",
+ "description": "List of holdings records",
+ "items": {
+ "type": "object",
+ "$ref": "holdings-storage/holdingsRecord.json"
+ },
+ "readonly": true,
+ "folio:isVirtual": true,
+ "folio:linkBase": "holdings-storage/holdings",
+ "folio:linkFromField": "id",
+ "folio:linkToField": "instanceId",
+ "folio:includedElement": "holdingsRecords"
+ },
+ "natureOfContentTermIds": {
+ "type": "array",
+ "description": "Array of UUID for the Instance nature of content (e.g. bibliography, biography, exhibition catalogue, festschrift, newspaper, proceedings, research report, thesis or website)",
+ "uniqueItems": true,
+ "items": {
+ "type": "string",
+ "description": "Single UUID for the Instance nature of content",
+ "$ref": "uuid.json"
+ }
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "source",
+ "title",
+ "instanceTypeId"
+ ]
+}
diff --git a/ramls/instances-without-pub-period.json b/ramls/instances-without-pub-period.json
new file mode 100644
index 000000000..88a0ba224
--- /dev/null
+++ b/ramls/instances-without-pub-period.json
@@ -0,0 +1,29 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "A collection of instance records without publicationPeriod",
+ "type": "object",
+ "properties": {
+ "instances": {
+ "description": "List of instance records without publication period",
+ "id": "instances",
+ "type": "array",
+ "items": {
+ "$ref": "instance-without-pub-period.json",
+ "type" : "object"
+ }
+ },
+ "totalRecords": {
+ "description": "Estimated or exact total number of records",
+ "type": "integer"
+ },
+ "resultInfo": {
+ "$ref": "raml-util/schemas/resultInfo.schema",
+ "readonly": true
+ }
+
+ },
+ "required": [
+ "instances",
+ "totalRecords"
+ ]
+}
diff --git a/ramls/instances_post.json b/ramls/instances_post.json
index bb857e514..1eb06e415 100644
--- a/ramls/instances_post.json
+++ b/ramls/instances_post.json
@@ -1,15 +1,15 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
- "description": "A collection of instance records",
+ "description": "A collection of instance records without the publicationPeriod",
"type": "object",
"properties": {
"instances": {
- "description": "List of instance records",
+ "description": "List of instance records without the publicationPeriod",
"id": "instances",
"type": "array",
"items": {
- "type": "object",
- "$ref": "instance.json"
+ "$ref": "instance-without-pub-period.json",
+ "type" : "object"
}
}
},
diff --git a/src/main/java/org/folio/rest/impl/InstanceBatchSyncApi.java b/src/main/java/org/folio/rest/impl/InstanceBatchSyncApi.java
index fc0e56ebe..20aa9b352 100644
--- a/src/main/java/org/folio/rest/impl/InstanceBatchSyncApi.java
+++ b/src/main/java/org/folio/rest/impl/InstanceBatchSyncApi.java
@@ -11,6 +11,7 @@
import org.folio.rest.jaxrs.model.InstancesPost;
import org.folio.rest.jaxrs.resource.InstanceStorageBatchSynchronous;
import org.folio.services.instance.InstanceService;
+import org.folio.utils.InstanceUtils;
public class InstanceBatchSyncApi implements InstanceStorageBatchSynchronous {
@Validate
@@ -20,8 +21,10 @@ public void postInstanceStorageBatchSynchronous(boolean upsert, InstancesPost en
Handler> asyncResultHandler,
Context vertxContext) {
+ var instances = InstanceUtils.copyPropertiesToInstances(entity.getInstances());
+
new InstanceService(vertxContext, okapiHeaders)
- .createInstances(entity.getInstances(), upsert, true)
+ .createInstances(instances.getInstances(), upsert, true)
.otherwise(cause -> respond500WithTextPlain(cause.getMessage()))
.onComplete(asyncResultHandler);
}
diff --git a/src/main/java/org/folio/rest/impl/InstanceBatchSyncUnsafeApi.java b/src/main/java/org/folio/rest/impl/InstanceBatchSyncUnsafeApi.java
index 565ebacd5..166465658 100644
--- a/src/main/java/org/folio/rest/impl/InstanceBatchSyncUnsafeApi.java
+++ b/src/main/java/org/folio/rest/impl/InstanceBatchSyncUnsafeApi.java
@@ -11,6 +11,7 @@
import org.folio.rest.jaxrs.model.InstancesPost;
import org.folio.rest.jaxrs.resource.InstanceStorageBatchSynchronousUnsafe;
import org.folio.services.instance.InstanceService;
+import org.folio.utils.InstanceUtils;
public class InstanceBatchSyncUnsafeApi implements InstanceStorageBatchSynchronousUnsafe {
@Validate
@@ -19,8 +20,10 @@ public void postInstanceStorageBatchSynchronousUnsafe(InstancesPost entity, Map<
Handler> asyncResultHandler,
Context vertxContext) {
+ var instances = InstanceUtils.copyPropertiesToInstances(entity.getInstances());
+
new InstanceService(vertxContext, okapiHeaders)
- .createInstances(entity.getInstances(), true, false)
+ .createInstances(instances.getInstances(), true, false)
.otherwise(cause -> respond500WithTextPlain(cause.getMessage()))
.onComplete(asyncResultHandler);
}
diff --git a/src/main/java/org/folio/rest/impl/InstanceStorageApi.java b/src/main/java/org/folio/rest/impl/InstanceStorageApi.java
index ed78f7684..2b304d691 100644
--- a/src/main/java/org/folio/rest/impl/InstanceStorageApi.java
+++ b/src/main/java/org/folio/rest/impl/InstanceStorageApi.java
@@ -23,6 +23,7 @@
import org.folio.rest.jaxrs.model.Instance;
import org.folio.rest.jaxrs.model.InstanceRelationship;
import org.folio.rest.jaxrs.model.InstanceRelationships;
+import org.folio.rest.jaxrs.model.InstanceWithoutPubPeriod;
import org.folio.rest.jaxrs.model.Instances;
import org.folio.rest.jaxrs.model.MarcJson;
import org.folio.rest.jaxrs.model.RetrieveDto;
@@ -38,6 +39,7 @@
import org.folio.rest.tools.messages.Messages;
import org.folio.rest.tools.utils.TenantTool;
import org.folio.services.instance.InstanceService;
+import org.folio.utils.InstanceUtils;
public class InstanceStorageApi implements InstanceStorage {
private static final Logger log = LogManager.getLogger();
@@ -209,13 +211,15 @@ public void getInstanceStorageInstances(String totalRecords, int offset, int lim
@Override
public void postInstanceStorageInstances(
- Instance entity,
+ InstanceWithoutPubPeriod entity,
RoutingContext routingContext, Map okapiHeaders,
Handler> asyncResultHandler,
Context vertxContext) {
+ var instance = InstanceUtils.copyPropertiesToInstance(entity);
+
new InstanceService(vertxContext, okapiHeaders)
- .createInstance(entity)
+ .createInstance(instance)
.onSuccess(response -> asyncResultHandler.handle(succeededFuture(response)))
.onFailure(handleFailure(asyncResultHandler));
}
@@ -267,13 +271,15 @@ public void deleteInstanceStorageInstancesByInstanceId(
public void putInstanceStorageInstancesByInstanceId(
String instanceId,
- Instance entity,
+ InstanceWithoutPubPeriod entity,
Map okapiHeaders,
Handler> asyncResultHandler,
Context vertxContext) {
+ var instance = InstanceUtils.copyPropertiesToInstance(entity);
+
new InstanceService(vertxContext, okapiHeaders)
- .updateInstance(instanceId, entity)
+ .updateInstance(instanceId, instance)
.onSuccess(response -> asyncResultHandler.handle(succeededFuture(response)))
.onFailure(handleFailure(asyncResultHandler));
}
diff --git a/src/main/java/org/folio/rest/impl/InstanceStorageBatchApi.java b/src/main/java/org/folio/rest/impl/InstanceStorageBatchApi.java
index 51594a4c3..dda94586c 100644
--- a/src/main/java/org/folio/rest/impl/InstanceStorageBatchApi.java
+++ b/src/main/java/org/folio/rest/impl/InstanceStorageBatchApi.java
@@ -22,14 +22,15 @@
import org.apache.logging.log4j.Logger;
import org.folio.rest.annotations.Validate;
import org.folio.rest.jaxrs.model.Instance;
-import org.folio.rest.jaxrs.model.Instances;
import org.folio.rest.jaxrs.model.InstancesBatchResponse;
+import org.folio.rest.jaxrs.model.InstancesWithoutPubPeriod;
import org.folio.rest.jaxrs.resource.InstanceStorageBatchInstances;
import org.folio.rest.persist.PgUtil;
import org.folio.rest.persist.PostgresClient;
import org.folio.rest.support.HridManager;
import org.folio.rest.tools.utils.MetadataUtil;
import org.folio.services.domainevent.InstanceDomainEventPublisher;
+import org.folio.utils.InstanceUtils;
@SuppressWarnings("rawtypes")
public class InstanceStorageBatchApi implements InstanceStorageBatchInstances {
@@ -44,13 +45,15 @@ public class InstanceStorageBatchApi implements InstanceStorageBatchInstances {
@Validate
@Override
- public void postInstanceStorageBatchInstances(Instances entity,
+ public void postInstanceStorageBatchInstances(InstancesWithoutPubPeriod entity,
Map okapiHeaders,
Handler> asyncResultHandler,
Context vertxContext) {
+ var instances = InstanceUtils.copyPropertiesToInstances(entity.getInstances());
+
final String statusUpdatedDate = generateStatusUpdatedDate();
- for (Instance instance : entity.getInstances()) {
+ for (Instance instance : instances.getInstances()) {
instance.setStatusUpdatedDate(statusUpdatedDate);
}
@@ -60,9 +63,9 @@ public void postInstanceStorageBatchInstances(Instances entity,
final InstanceDomainEventPublisher instanceDomainEventPublisher =
new InstanceDomainEventPublisher(vertxContext, okapiHeaders);
- MetadataUtil.populateMetadata(entity.getInstances(), okapiHeaders);
- executeInBatch(entity.getInstances(),
- instances -> saveInstances(instances, postgresClient))
+ MetadataUtil.populateMetadata(instances.getInstances(), okapiHeaders);
+ executeInBatch(instances.getInstances(),
+ instanceList -> saveInstances(instanceList, postgresClient))
.onComplete(ar -> {
InstancesBatchResponse response = constructResponse(ar.result());
diff --git a/src/main/java/org/folio/services/migration/async/AsyncMigrationJobService.java b/src/main/java/org/folio/services/migration/async/AsyncMigrationJobService.java
index 44d5c5c83..d5ed2a3a9 100644
--- a/src/main/java/org/folio/services/migration/async/AsyncMigrationJobService.java
+++ b/src/main/java/org/folio/services/migration/async/AsyncMigrationJobService.java
@@ -31,7 +31,9 @@
public final class AsyncMigrationJobService {
private static final List MIGRATION_JOB_RUNNERS = List
- .of(new ShelvingOrderMigrationJobRunner(), new SubjectSeriesMigrationJobRunner());
+ .of(new ShelvingOrderMigrationJobRunner(),
+ new SubjectSeriesMigrationJobRunner(),
+ new PublicationPeriodMigrationJobRunner());
private static final List ACCEPTABLE_STATUSES = List
.of(AsyncMigrationJob.JobStatus.IN_PROGRESS, IDS_PUBLISHED);
diff --git a/src/main/java/org/folio/services/migration/async/AsyncMigrationsConsumerUtils.java b/src/main/java/org/folio/services/migration/async/AsyncMigrationsConsumerUtils.java
index 4d398840d..107792b17 100644
--- a/src/main/java/org/folio/services/migration/async/AsyncMigrationsConsumerUtils.java
+++ b/src/main/java/org/folio/services/migration/async/AsyncMigrationsConsumerUtils.java
@@ -42,7 +42,8 @@ public static Handler> pollAsyncMigrati
var availableMigrations = Set.of(
new ShelvingOrderAsyncMigrationService(vertxContext, headers),
- new SubjectSeriesMigrationService(vertxContext, headers));
+ new SubjectSeriesMigrationService(vertxContext, headers),
+ new PublicationPeriodMigrationService(vertxContext, headers));
var jobService = new AsyncMigrationJobService(vertxContext, headers);
var migrationEvents = buildIdsForMigrations(v.getValue());
diff --git a/src/main/java/org/folio/services/migration/async/PublicationPeriodMigrationJobRunner.java b/src/main/java/org/folio/services/migration/async/PublicationPeriodMigrationJobRunner.java
new file mode 100644
index 000000000..2463b22e4
--- /dev/null
+++ b/src/main/java/org/folio/services/migration/async/PublicationPeriodMigrationJobRunner.java
@@ -0,0 +1,36 @@
+package org.folio.services.migration.async;
+
+import static java.lang.String.format;
+import static org.folio.persist.InstanceRepository.INSTANCE_TABLE;
+
+import io.vertx.core.Future;
+import io.vertx.sqlclient.Row;
+import io.vertx.sqlclient.RowStream;
+import java.util.Collections;
+import java.util.List;
+import org.folio.rest.jaxrs.model.AffectedEntity;
+import org.folio.rest.persist.PostgresClientFuturized;
+import org.folio.rest.persist.SQLConnection;
+
+public class PublicationPeriodMigrationJobRunner extends AbstractAsyncMigrationJobRunner {
+
+ private static final String SELECT_SQL = """
+ SELECT id FROM %s
+ WHERE jsonb -> 'publicationPeriod' IS NOT NULL
+ """;
+
+ @Override
+ protected Future> openStream(PostgresClientFuturized postgresClient, SQLConnection connection) {
+ return postgresClient.selectStream(connection, format(SELECT_SQL, postgresClient.getFullTableName(INSTANCE_TABLE)));
+ }
+
+ @Override
+ public String getMigrationName() {
+ return "publicationPeriodMigration";
+ }
+
+ @Override
+ public List getAffectedEntities() {
+ return Collections.singletonList(AffectedEntity.INSTANCE);
+ }
+}
diff --git a/src/main/java/org/folio/services/migration/async/PublicationPeriodMigrationService.java b/src/main/java/org/folio/services/migration/async/PublicationPeriodMigrationService.java
new file mode 100644
index 000000000..aec94e1df
--- /dev/null
+++ b/src/main/java/org/folio/services/migration/async/PublicationPeriodMigrationService.java
@@ -0,0 +1,77 @@
+package org.folio.services.migration.async;
+
+import static org.folio.persist.InstanceRepository.INSTANCE_TABLE;
+
+import io.vertx.core.Context;
+import io.vertx.core.Future;
+import io.vertx.sqlclient.Row;
+import io.vertx.sqlclient.RowStream;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.folio.persist.InstanceRepository;
+import org.folio.rest.jaxrs.model.Instance;
+import org.folio.rest.persist.PgUtil;
+import org.folio.rest.persist.PostgresClientFuturized;
+import org.folio.rest.persist.SQLConnection;
+
+public class PublicationPeriodMigrationService extends AsyncBaseMigrationService {
+
+ private static final String SELECT_SQL = """
+ SELECT migrate_publication_period(jsonb) as jsonb
+ FROM %s
+ WHERE %s FOR UPDATE
+ """;
+ private static final String WHERE_CONDITION = "id in (%s)";
+
+ private final PostgresClientFuturized postgresClient;
+ private final InstanceRepository instanceRepository;
+
+
+ public PublicationPeriodMigrationService(Context context, Map okapiHeaders) {
+ this(new PostgresClientFuturized(PgUtil.postgresClient(context, okapiHeaders)),
+ new InstanceRepository(context, okapiHeaders));
+ }
+
+ public PublicationPeriodMigrationService(PostgresClientFuturized postgresClient,
+ InstanceRepository instanceRepository) {
+
+ super("28.0.0", postgresClient);
+ this.postgresClient = postgresClient;
+ this.instanceRepository = instanceRepository;
+ }
+
+ @Override
+ protected Future> openStream(SQLConnection connection) {
+ return postgresClient.selectStream(connection, selectSql());
+ }
+
+ @Override
+ protected Future updateBatch(List batch, SQLConnection connection) {
+ var instances = batch.stream()
+ .map(row -> row.getJsonObject("jsonb"))
+ .map(json -> json.mapTo(Instance.class))
+ .toList();
+ return instanceRepository.updateBatch(instances, connection)
+ .map(notUsed -> instances.size());
+ }
+
+ @Override
+ public String getMigrationName() {
+ return "publicationPeriodMigration";
+ }
+
+ private String selectSql() {
+ var idsForMigration = getIdsForMigration();
+ var whereCondition = "false";
+
+ if (!idsForMigration.isEmpty()) {
+ var ids = idsForMigration.stream()
+ .map(id -> "'" + id + "'")
+ .collect(Collectors.joining(", "));
+
+ whereCondition = String.format(WHERE_CONDITION, ids);
+ }
+ return String.format(SELECT_SQL, postgresClient.getFullTableName(INSTANCE_TABLE), whereCondition);
+ }
+}
diff --git a/src/main/java/org/folio/utils/InstanceUtils.java b/src/main/java/org/folio/utils/InstanceUtils.java
new file mode 100644
index 000000000..21a9fa8f4
--- /dev/null
+++ b/src/main/java/org/folio/utils/InstanceUtils.java
@@ -0,0 +1,40 @@
+package org.folio.utils;
+
+import java.util.List;
+import org.apache.commons.beanutils.BeanUtils;
+import org.folio.rest.jaxrs.model.Instance;
+import org.folio.rest.jaxrs.model.InstanceWithoutPubPeriod;
+import org.folio.rest.jaxrs.model.Instances;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class InstanceUtils {
+
+ private static final Logger log = LoggerFactory.getLogger(InstanceUtils.class);
+
+ private InstanceUtils() {
+ throw new UnsupportedOperationException("Utility class");
+ }
+
+ public static Instances copyPropertiesToInstances(List instancesWithoutPubPeriod) {
+ var instances = new Instances();
+ instances.setInstances(instancesWithoutPubPeriod
+ .stream()
+ .map(InstanceUtils::copyPropertiesToInstance)
+ .toList());
+ return instances;
+ }
+
+ public static Instance copyPropertiesToInstance(InstanceWithoutPubPeriod instanceWithoutPubPeriod) {
+ var instance = new Instance();
+ try {
+ log.debug("copyPropertiesToInstance:: Copy all fields from InstanceWithoutPubPeriod to Instance, id: '{}'",
+ instanceWithoutPubPeriod.getId());
+ BeanUtils.copyProperties(instance, instanceWithoutPubPeriod);
+ return instance;
+ } catch (Exception e) {
+ log.error("Failed to copy properties from InstanceWithoutPubPeriod to Instance object: {}", e.getMessage(), e);
+ throw new IllegalArgumentException(e.getMessage(), e);
+ }
+ }
+}
diff --git a/src/main/resources/templates/db_scripts/publication-period/migratePublicationPeriod.sql b/src/main/resources/templates/db_scripts/publication-period/migratePublicationPeriod.sql
index 582795944..a2f1943f0 100644
--- a/src/main/resources/templates/db_scripts/publication-period/migratePublicationPeriod.sql
+++ b/src/main/resources/templates/db_scripts/publication-period/migratePublicationPeriod.sql
@@ -41,89 +41,3 @@ BEGIN
RETURN jsonb_data;
END;
$$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE STRICT;
-
-
--- Migration Script
-DO
-$$
-DECLARE
- trigger VARCHAR;
- triggers VARCHAR[] DEFAULT ARRAY[
- 'audit_instance',
- 'check_subject_references_on_insert_or_update',
- 'instance_check_statistical_code_references_on_insert',
- 'instance_check_statistical_code_references_on_update',
- 'set_id_in_jsonb',
- 'set_instance_md_json_trigger',
- 'set_instance_md_trigger',
- 'set_instance_ol_version_trigger',
- 'set_instance_sourcerecordformat',
- 'set_instance_status_updated_date',
- 'update_instance_references',
- 'updatecompleteupdateddate_instance'];
- arr UUID[] DEFAULT ARRAY[
- '10000000-0000-0000-0000-000000000000',
- '20000000-0000-0000-0000-000000000000',
- '30000000-0000-0000-0000-000000000000',
- '40000000-0000-0000-0000-000000000000',
- '50000000-0000-0000-0000-000000000000',
- '60000000-0000-0000-0000-000000000000',
- '70000000-0000-0000-0000-000000000000',
- '80000000-0000-0000-0000-000000000000',
- '90000000-0000-0000-0000-000000000000',
- 'a0000000-0000-0000-0000-000000000000',
- 'b0000000-0000-0000-0000-000000000000',
- 'c0000000-0000-0000-0000-000000000000',
- 'd0000000-0000-0000-0000-000000000000',
- 'e0000000-0000-0000-0000-000000000000',
- 'f0000000-0000-0000-0000-000000000000',
- 'ffffffff-ffff-ffff-ffff-ffffffffffff'
- ];
- lower UUID;
- cur UUID;
- rowcount BIGINT;
- need_migration BOOLEAN;
-BEGIN
- -- STEP 0: Check if migration is required
- SELECT EXISTS (
- SELECT 1
- FROM ${myuniversity}_${mymodule}.instance
- WHERE jsonb ? 'publicationPeriod'
- LIMIT 1
- ) INTO need_migration;
-
- IF need_migration THEN
- -- STEP 1: Disable triggers
- FOREACH trigger IN ARRAY triggers LOOP
- EXECUTE 'ALTER TABLE ${myuniversity}_${mymodule}.instance DISABLE TRIGGER '
- || trigger;
- END LOOP;
-
- -- STEP 2: Do updates
- lower := '00000000-0000-0000-0000-000000000000';
- FOREACH cur IN ARRAY arr LOOP
- RAISE INFO 'range: % - %', lower, cur;
- -- Update scripts
- EXECUTE format($q$
- UPDATE ${myuniversity}_${mymodule}.instance
- SET jsonb = ${myuniversity}_${mymodule}.migrate_publication_period(jsonb)
- WHERE (jsonb -> 'publicationPeriod' IS NOT NULL)
- AND (id > %L AND id <= %L);
- $q$, lower, cur);
-
- GET DIAGNOSTICS rowcount = ROW_COUNT;
- RAISE INFO 'updated % instances', rowcount;
-
- lower := cur;
- END LOOP;
-
- -- STEP 3: Enable triggers
- FOREACH trigger IN ARRAY triggers LOOP
- EXECUTE 'ALTER TABLE ${myuniversity}_${mymodule}.instance ENABLE TRIGGER '
- || trigger;
- END LOOP;
- END IF;
-END;
-$$ LANGUAGE 'plpgsql';
-
-DROP FUNCTION IF EXISTS ${myuniversity}_${mymodule}.migrate_publication_period(jsonb);
diff --git a/src/main/resources/templates/db_scripts/schema.json b/src/main/resources/templates/db_scripts/schema.json
index e3cd3a41b..e2c76f3e1 100644
--- a/src/main/resources/templates/db_scripts/schema.json
+++ b/src/main/resources/templates/db_scripts/schema.json
@@ -1227,6 +1227,11 @@
"run": "after",
"snippetPath": "subjectIdsReferenceCheckTrigger.sql",
"fromModuleVersion": "27.2.0"
+ },
+ {
+ "run": "after",
+ "snippetPath": "publication-period/migratePublicationPeriod.sql",
+ "fromModuleVersion": "28.0.0"
}
]
}
diff --git a/src/test/java/org/folio/rest/api/AsyncMigrationTest.java b/src/test/java/org/folio/rest/api/AsyncMigrationTest.java
index e681a12c5..5cf304727 100644
--- a/src/test/java/org/folio/rest/api/AsyncMigrationTest.java
+++ b/src/test/java/org/folio/rest/api/AsyncMigrationTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
@@ -26,14 +27,18 @@
import io.vertx.core.Context;
import io.vertx.core.json.JsonObject;
+import io.vertx.sqlclient.Row;
+import io.vertx.sqlclient.RowSet;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import junitparams.JUnitParamsRunner;
+import lombok.SneakyThrows;
import org.apache.commons.collections4.map.CaseInsensitiveMap;
import org.folio.persist.AsyncMigrationJobRepository;
import org.folio.rest.jaxrs.model.AsyncMigrationJob;
@@ -43,18 +48,36 @@
import org.folio.rest.jaxrs.model.EffectiveCallNumberComponents;
import org.folio.rest.jaxrs.model.Processed;
import org.folio.rest.jaxrs.model.Published;
+import org.folio.rest.persist.PostgresClient;
import org.folio.rest.persist.PostgresClientFuturized;
import org.folio.rest.support.sql.TestRowStream;
import org.folio.services.migration.async.AsyncMigrationContext;
import org.folio.services.migration.async.ShelvingOrderMigrationJobRunner;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(JUnitParamsRunner.class)
public class AsyncMigrationTest extends TestBaseWithInventoryUtil {
+ private static final String UPDATE_JSONB_WITH_PUB_PERIOD = """
+ UPDATE %s_mod_inventory_storage.instance
+ SET jsonb = jsonb || jsonb_set(jsonb, '{publicationPeriod}', jsonb_build_object('start', '1999', 'end', '2001'))
+ RETURNING id::text;
+ """;
+ private static final String SELECT_JSONB =
+ "SELECT jsonb FROM %s_mod_inventory_storage.instance";
+
private final AsyncMigrationJobRepository repository = getRepository();
+ @SneakyThrows
+ @Before
+ public void beforeEach() {
+ StorageTestSuite.deleteAll(itemsStorageUrl(""));
+ StorageTestSuite.deleteAll(holdingsStorageUrl(""));
+ StorageTestSuite.deleteAll(instancesStorageUrl(""));
+ }
+
private static Map okapiHeaders() {
return new CaseInsensitiveMap<>(Map.of(TENANT.toLowerCase(), TENANT_ID));
}
@@ -138,11 +161,66 @@ public void canMigrateInstanceSubjectsAndSeries() {
assertThat(job.getSubmittedDate(), notNullValue());
}
+ @Test
+ public void canMigrateInstancePublicationPeriod() {
+ var numberOfRecords = 10;
+
+ IntStream.range(0, numberOfRecords).parallel().forEach(v ->
+ instancesClient.create(new JsonObject()
+ .put("title", "test" + v)
+ .put("source", "MARC")
+ .put("instanceTypeId", "30fffe0e-e985-4144-b2e2-1e8179bdb41f")));
+
+ var countDownLatch = new CountDownLatch(1);
+ var query = String.format(UPDATE_JSONB_WITH_PUB_PERIOD, TENANT_ID);
+ postgresClient(getContext(), okapiHeaders()).execute(query)
+ .onSuccess(event -> countDownLatch.countDown());
+ // check jsonb contains 'publicationPeriod' data
+ RowSet selectResult = runSql(String.format(SELECT_JSONB, TENANT_ID));
+
+ assertEquals(10, selectResult.rowCount());
+ JsonObject jsonbData = selectResult.iterator().next().toJson().getJsonObject("jsonb");
+ assertNull(jsonbData.getJsonObject("dates"));
+ assertNotNull(jsonbData.getJsonObject("publicationPeriod"));
+
+
+ await().atMost(5, SECONDS).until(() -> countDownLatch.getCount() == 0L);
+
+ var migrationJob = asyncMigration.postMigrationJob(new AsyncMigrationJobRequest()
+ .withMigrations(List.of("publicationPeriodMigration")));
+
+ await().atMost(25, SECONDS).until(() -> asyncMigration.getMigrationJob(migrationJob.getId())
+ .getJobStatus() == AsyncMigrationJob.JobStatus.COMPLETED);
+
+ var job = asyncMigration.getMigrationJob(migrationJob.getId());
+
+ assertThat(job.getPublished().stream().map(Published::getCount)
+ .mapToInt(Integer::intValue).sum(), is(numberOfRecords));
+ assertThat(job.getProcessed().stream().map(Processed::getCount)
+ .mapToInt(Integer::intValue).sum(), is(numberOfRecords));
+ assertThat(job.getJobStatus(), is(AsyncMigrationJob.JobStatus.COMPLETED));
+ assertThat(job.getSubmittedDate(), notNullValue());
+
+ // check that the 'publicationPeriod' data has been migrated to the 'dates' data
+ var selectQuery = String.format(SELECT_JSONB, TENANT_ID);
+ RowSet result = runSql(selectQuery);
+
+ assertEquals(10, result.rowCount());
+ JsonObject entry = result.iterator().next().toJson();
+ JsonObject jsonb = entry.getJsonObject("jsonb");
+ JsonObject dates = jsonb.getJsonObject("dates");
+ assertNotNull(dates);
+ assertNull(jsonb.getString("publicationPeriod"));
+ assertEquals("1999", dates.getString("date1"));
+ assertEquals("2001", dates.getString("date2"));
+ assertEquals("8fa6d067-41ff-4362-96a0-96b16ddce267", dates.getString("dateTypeId"));
+ }
+
@Test
public void canGetAvailableMigrations() {
AsyncMigrations migrations = asyncMigration.getMigrations();
assertNotNull(migrations);
- assertEquals(Integer.valueOf(2), migrations.getTotalRecords());
+ assertEquals(Integer.valueOf(3), migrations.getTotalRecords());
assertEquals("itemShelvingOrderMigration", migrations.getAsyncMigrations().get(0).getMigrations().get(0));
}
@@ -192,4 +270,13 @@ private ShelvingOrderMigrationJobRunner jobRunner() {
private AsyncMigrationJobRepository getRepository() {
return new AsyncMigrationJobRepository(getContext(), okapiHeaders());
}
+
+ @SneakyThrows
+ private RowSet runSql(String sql) {
+ return PostgresClient.getInstance(getVertx())
+ .execute(sql)
+ .toCompletionStage()
+ .toCompletableFuture()
+ .get(TIMEOUT, TimeUnit.SECONDS);
+ }
}
diff --git a/src/test/java/org/folio/rest/api/PublicationPeriodMigrationTest.java b/src/test/java/org/folio/rest/api/PublicationPeriodMigrationTest.java
index f66d4b087..ce7889134 100644
--- a/src/test/java/org/folio/rest/api/PublicationPeriodMigrationTest.java
+++ b/src/test/java/org/folio/rest/api/PublicationPeriodMigrationTest.java
@@ -18,11 +18,8 @@
import org.folio.rest.persist.PostgresClient;
import org.junit.Before;
import org.junit.Test;
-import org.junit.jupiter.api.Disabled;
-@Disabled
public class PublicationPeriodMigrationTest extends MigrationTestBase {
- private static final String MIGRATION_SCRIPT = loadScript("publication-period/migratePublicationPeriod.sql");
private static final String TAG_VALUE = "test-tag";
private static final String START_DATE = "1877";
private static final String END_DATE = "1880";
@@ -40,6 +37,11 @@ public class PublicationPeriodMigrationTest extends MigrationTestBase {
SET jsonb = jsonb_set(jsonb, '{publicationPeriod}', jsonb_build_object('start', $1))
WHERE id = $2
""";
+ private static final String UPDATE_JSONB_WITH_PUB_PERIOD_MIGRATION= """
+ UPDATE %s_mod_inventory_storage.instance
+ SET jsonb = %s_mod_inventory_storage.migrate_publication_period(jsonb)
+ WHERE id = $1 AND jsonb -> 'publicationPeriod' IS NOT NULL
+ """;
@SneakyThrows
@Before
@@ -49,14 +51,15 @@ public void beforeEach() {
}
@Test
- public void canMigratePublicationPeriodToMultipleDates() throws Exception {
+ public void canMigratePublicationPeriodToMultipleDates() {
var instanceId = createInstance();
// add "publicationPeriod" object to jsonb
addPublicationPeriodToJsonb(instanceId, END_DATE);
//migrate "publicationPeriod" to Dates object
- executeMultipleSqlStatements(MIGRATION_SCRIPT);
+ var updateQuery = String.format(UPDATE_JSONB_WITH_PUB_PERIOD_MIGRATION, TENANT_ID, TENANT_ID);
+ runSql(updateQuery, Tuple.of(instanceId));
var query = String.format(SELECT_JSONB_BY_ID, TENANT_ID);
RowSet result = runSql(query, Tuple.of(instanceId));
@@ -71,14 +74,15 @@ public void canMigratePublicationPeriodToMultipleDates() throws Exception {
}
@Test
- public void canMigratePublicationPeriodToSingleDates() throws Exception {
+ public void canMigratePublicationPeriodToSingleDates() {
var instanceId = createInstance();
// add "publicationPeriod" object to jsonb
addPublicationPeriodToJsonb(instanceId, null);
//migrate "publicationPeriod" to Dates object
- executeMultipleSqlStatements(MIGRATION_SCRIPT);
+ var updateQuery = String.format(UPDATE_JSONB_WITH_PUB_PERIOD_MIGRATION, TENANT_ID, TENANT_ID);
+ runSql(updateQuery, Tuple.of(instanceId));
var query = String.format(SELECT_JSONB_BY_ID, TENANT_ID);
RowSet result = runSql(query, Tuple.of(instanceId));
@@ -93,11 +97,11 @@ public void canMigratePublicationPeriodToSingleDates() throws Exception {
}
@Test
- public void canNotMigrateWhenPublicationPeriodIsNull() throws Exception {
+ public void canNotMigrateWhenPublicationPeriodIsNull() {
var instanceId = createInstance();
- //migrate "publicationPeriod" to Dates object
- executeMultipleSqlStatements(MIGRATION_SCRIPT);
+ var updateQuery = String.format(UPDATE_JSONB_WITH_PUB_PERIOD_MIGRATION, TENANT_ID, TENANT_ID);
+ runSql(updateQuery, Tuple.of(instanceId));
var query = String.format(SELECT_JSONB_BY_ID, TENANT_ID);
RowSet result = runSql(query, Tuple.of(instanceId));
diff --git a/src/test/java/org/folio/services/InstanceUtilsTest.java b/src/test/java/org/folio/services/InstanceUtilsTest.java
new file mode 100644
index 000000000..a719b7911
--- /dev/null
+++ b/src/test/java/org/folio/services/InstanceUtilsTest.java
@@ -0,0 +1,51 @@
+package org.folio.services;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.util.List;
+import lombok.SneakyThrows;
+import org.folio.rest.jaxrs.model.InstanceWithoutPubPeriod;
+import org.folio.utils.InstanceUtils;
+import org.junit.jupiter.api.Test;
+
+class InstanceUtilsTest {
+
+ private static final String TITLE = "title";
+ private static final String ID = "123456789";
+
+ @Test
+ void shouldCopyPropertiesToInstances() {
+ var instanceWithoutPubPeriod = new InstanceWithoutPubPeriod();
+ instanceWithoutPubPeriod.setId(ID);
+ instanceWithoutPubPeriod.setTitle(TITLE);
+
+ var result = InstanceUtils.copyPropertiesToInstances(List.of(instanceWithoutPubPeriod));
+ var instances = result.getInstances();
+
+ assertNotNull(instances);
+ assertEquals(1, instances.size());
+ assertEquals(instanceWithoutPubPeriod.getId(), instances.get(0).getId());
+ assertEquals(instanceWithoutPubPeriod.getTitle(), instances.get(0).getTitle());
+ }
+
+ @Test
+ void shouldCopyPropertiesToInstance() {
+ var instanceWithoutPubPeriod = new InstanceWithoutPubPeriod();
+ instanceWithoutPubPeriod.setId(ID);
+ instanceWithoutPubPeriod.setTitle(TITLE);
+
+ var result = InstanceUtils.copyPropertiesToInstance(instanceWithoutPubPeriod);
+
+ assertNotNull(result);
+ assertEquals(instanceWithoutPubPeriod.getId(), result.getId());
+ assertEquals(instanceWithoutPubPeriod.getTitle(), result.getTitle());
+ }
+
+ @Test
+ @SneakyThrows
+ void shouldThrowIllegalArgumentExceptionWhenCannotCopyPropertiesToInstance() {
+ assertThrows(IllegalArgumentException.class, () -> InstanceUtils.copyPropertiesToInstance(null));
+ }
+}