diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/error/GlobalExceptionMapperCore.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/error/GlobalExceptionMapperCore.java index 6e546e14cf7197a480bf84de084a3cf628373ad0..b6323ac4554024cfc82171b8ea74924812498d3d 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/error/GlobalExceptionMapperCore.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/error/GlobalExceptionMapperCore.java @@ -83,6 +83,12 @@ public class GlobalExceptionMapperCore extends ResponseEntityExceptionHandler { this.logger.warning(exceptionMsg, e); } - return new ResponseEntity<Object>(e.getError(), HttpStatus.resolve(e.getError().getCode())); + // Support for non standard HttpStatus Codes + HttpStatus httpStatus = HttpStatus.resolve(e.getError().getCode()); + if (httpStatus == null) { + return ResponseEntity.status(e.getError().getCode()).body(e); + } else { + return new ResponseEntity<>(e.getError(), httpStatus); + } } } \ No newline at end of file diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/schema/converter/PropertiesProcessor.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/schema/converter/PropertiesProcessor.java index 04a3211fda1a6a91a71fc7cea639951b41b90642..458e60452df5dda07118c26cca06d5702a440f1b 100644 --- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/schema/converter/PropertiesProcessor.java +++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/schema/converter/PropertiesProcessor.java @@ -14,6 +14,15 @@ package org.opengroup.osdu.indexer.schema.converter; +import org.apache.http.HttpStatus; +import org.opengroup.osdu.core.common.Constants; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.core.common.search.Preconditions; +import org.opengroup.osdu.indexer.schema.converter.config.SchemaConverterConfig; +import org.opengroup.osdu.indexer.schema.converter.config.SchemaConverterPropertiesConfig; +import org.opengroup.osdu.indexer.schema.converter.exeption.SchemaProcessingException; +import org.opengroup.osdu.indexer.schema.converter.tags.*; + import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -24,18 +33,6 @@ import java.util.Optional; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.apache.http.HttpStatus; -import org.opengroup.osdu.core.common.Constants; -import org.opengroup.osdu.core.common.model.http.AppException; -import org.opengroup.osdu.core.common.search.Preconditions; -import org.opengroup.osdu.indexer.schema.converter.config.SchemaConverterConfig; -import org.opengroup.osdu.indexer.schema.converter.config.SchemaConverterPropertiesConfig; -import org.opengroup.osdu.indexer.schema.converter.exeption.SchemaProcessingException; -import org.opengroup.osdu.indexer.schema.converter.tags.AllOfItem; -import org.opengroup.osdu.indexer.schema.converter.tags.Definition; -import org.opengroup.osdu.indexer.schema.converter.tags.Definitions; -import org.opengroup.osdu.indexer.schema.converter.tags.Items; -import org.opengroup.osdu.indexer.schema.converter.tags.TypeProperty; public class PropertiesProcessor { @@ -185,9 +182,14 @@ public class PropertiesProcessor { } if ("array".equals(entry.getValue().getType())) { - Items items = entry.getValue().getItems(); - if(Objects.nonNull(items) && items.isComplexTypeItems()){ + + if (items == null) { + errors.add(String.format("Invalid array attribute: '%s', missing or null 'items'", entry.getKey())); + return Stream.empty(); + } + + if (Objects.nonNull(items) && items.isComplexTypeItems()) { return processComplexTypeItems(entry, items); } @@ -215,7 +217,7 @@ public class PropertiesProcessor { if (Objects.nonNull(entry.getValue().getRef())) { PropertiesProcessor propertiesProcessor = new PropertiesProcessor(definitions , pathPrefixWithDot + entry.getKey(), new SchemaConverterPropertiesConfig()); - Stream<Map<String, Object>> refResult = propertiesProcessor.processRef(entry.getValue().getRef()); + Stream<Map<String, Object>> refResult = propertiesProcessor.processRef(entry.getValue().getRef()); errors.addAll(propertiesProcessor.getErrors()); return refResult; } @@ -232,28 +234,28 @@ public class PropertiesProcessor { Map<String, String> indexHint = entry.getValue().getIndexHint(); String indexingType = Objects.isNull(indexHint) ? - schemaConverterConfig.getDefaultObjectArraysType() : - indexHint.getOrDefault(TYPE_KEY,schemaConverterConfig.getDefaultObjectArraysType()); + schemaConverterConfig.getDefaultObjectArraysType() : + indexHint.getOrDefault(TYPE_KEY, schemaConverterConfig.getDefaultObjectArraysType()); - if(schemaConverterConfig.getProcessedArraysTypes().contains(indexingType)){ + if (schemaConverterConfig.getProcessedArraysTypes().contains(indexingType)) { PropertiesProcessor propertiesProcessor = new PropertiesProcessor(definitions, new SchemaConverterPropertiesConfig()); Stream<Map<String, Object>> propertiesStream = Stream.empty(); - if(Objects.nonNull(items.getProperties())){ + if (Objects.nonNull(items.getProperties())) { propertiesStream = items.getProperties().entrySet().stream().flatMap(propertiesProcessor::processPropertyEntry); } - if (Objects.nonNull(items.getRef())){ + if (Objects.nonNull(items.getRef())) { propertiesStream = Stream.concat(propertiesStream, propertiesProcessor.processRef(items.getRef())); } - if(Objects.nonNull(items.getAllOf())){ + if (Objects.nonNull(items.getAllOf())) { propertiesStream = Stream.concat(propertiesStream, items.getAllOf().stream().flatMap(propertiesProcessor::processItem)); } return storageSchemaObjectArrayEntry( - indexingType, - entry.getKey(), - propertiesStream); - }else { + indexingType, + entry.getKey(), + propertiesStream); + } else { return storageSchemaEntry(indexingType, pathPrefixWithDot + entry.getKey()); } } @@ -300,14 +302,14 @@ public class PropertiesProcessor { return Stream.of(map); } - private Stream<Map<String, Object>> storageSchemaObjectArrayEntry(String kind, String path,Stream<Map<String, Object>> mapStream) { + private Stream<Map<String, Object>> storageSchemaObjectArrayEntry(String kind, String path, Stream<Map<String, Object>> mapStream) { Preconditions.checkNotNullOrEmpty(kind, "kind cannot be null or empty"); Preconditions.checkNotNullOrEmpty(path, "path cannot be null or empty"); Map<String, Object> map = new HashMap<>(); map.put("kind", kind); map.put("path", path); - map.put(Constants.PROPERTIES,mapStream.collect(Collectors.toList())); + map.put(Constants.PROPERTIES, mapStream.collect(Collectors.toList())); return Stream.of(map); } diff --git a/indexer-core/src/test/java/org/opengroup/osdu/indexer/error/GlobalExceptionMapperCoreTest.java b/indexer-core/src/test/java/org/opengroup/osdu/indexer/error/GlobalExceptionMapperCoreTest.java new file mode 100644 index 0000000000000000000000000000000000000000..aa53ea967f6a2ddfcc7324d46caabdffd82edf5f --- /dev/null +++ b/indexer-core/src/test/java/org/opengroup/osdu/indexer/error/GlobalExceptionMapperCoreTest.java @@ -0,0 +1,93 @@ +// Copyright © Schlumberger +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.indexer.error; + +import javassist.NotFoundException; +import org.apache.http.HttpStatus; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.core.common.model.http.RequestStatus; +import org.powermock.modules.junit4.PowerMockRunner; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; + +import javax.validation.ValidationException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@RunWith(PowerMockRunner.class) +public class GlobalExceptionMapperCoreTest { + + @Mock + private JaxRsDpsLog log; + @InjectMocks + private GlobalExceptionMapperCore sut; + + @Test + public void should_useValuesInAppExceptionInResponse_When_AppExceptionIsHandledByGlobalExceptionMapper() { + AppException exception = new AppException(409, "any reason", "any message"); + + ResponseEntity<Object> response = sut.handleAppException(exception); + assertEquals(409, response.getStatusCodeValue()); + assertEquals(exception.getError(), response.getBody()); + } + + @Test + public void should_use404ValueInResponse_When_NotFoundExceptionIsHandledByGlobalExceptionMapper() { + NotFoundException exception = new NotFoundException("any message"); + + ResponseEntity<Object> response = sut.handleNotFoundException(exception); + assertEquals(404, response.getStatusCodeValue()); + assertTrue(response.getBody().toString().contains("any message")); + } + + @Test + public void should_useGenericValuesInResponse_When_ExceptionIsHandledByGlobalExceptionMapper() { + Exception exception = new Exception("any message"); + + ResponseEntity<Object> response = sut.handleGeneralException(exception); + assertEquals(500, response.getStatusCodeValue()); + assertEquals("AppError(code=500, reason=Server error., message=An unknown error has occurred., errors=null, debuggingInfo=null, originalException=java.lang.Exception: any message)", response.getBody().toString()); + } + + @Test + public void should_useBadRequestInResponse_When_handleValidationExceptionIsHandledByGlobalExceptionMapper() { + ValidationException exception = new ValidationException(); + + ResponseEntity<Object> response = sut.handleValidationException(exception); + assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCodeValue()); + } + + @Test + public void should_useBadRequestInResponse_When_handleAccessDeniedExceptionIsHandledByGlobalExceptionMapper() { + AccessDeniedException exception = new AccessDeniedException("Access is denied."); + + ResponseEntity<Object> response = sut.handleAccessDeniedException(exception); + assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCodeValue()); + } + + @Test + public void should_useCustomErrorCodeInResponse_When_AppExceptionIsHandledByGlobalExceptionMapper() { + AppException exception = new AppException(RequestStatus.INVALID_RECORD, "Invalid request", "Successful Storage service response with wrong json"); + + ResponseEntity<Object> response = sut.handleAppException(exception); + assertEquals(RequestStatus.INVALID_RECORD, response.getStatusCodeValue()); + } +} \ No newline at end of file diff --git a/indexer-core/src/test/java/org/opengroup/osdu/indexer/schema/converter/SchemaToStorageFormatImplTest.java b/indexer-core/src/test/java/org/opengroup/osdu/indexer/schema/converter/SchemaToStorageFormatImplTest.java index 7947615ebcaac6ce5850a29d87fd4299fe6d2dfb..5bb87c7ea0bf73f398fd5d150f53f2c34b6b56f6 100644 --- a/indexer-core/src/test/java/org/opengroup/osdu/indexer/schema/converter/SchemaToStorageFormatImplTest.java +++ b/indexer-core/src/test/java/org/opengroup/osdu/indexer/schema/converter/SchemaToStorageFormatImplTest.java @@ -60,6 +60,11 @@ public class SchemaToStorageFormatImplTest { testSingleFile("/converter/bad-schema/wrong-definitions-and-missed-type.json", KIND); } + @Test(expected = SchemaProcessingException.class) + public void wrongArrayDefinitions() { + testSingleFile("/converter/bad-schema/wrong-array.json", KIND); + } + @Test public void firstSchemaPassed() { testSingleFile("/converter/basic/schema.json", "osdu:osdu:Wellbore:1.0.0"); diff --git a/indexer-core/src/test/resources/converter/bad-schema/wrong-array.json b/indexer-core/src/test/resources/converter/bad-schema/wrong-array.json new file mode 100644 index 0000000000000000000000000000000000000000..741c9a424b8b1e44c0b5b6a7dbe1b04c1ff113e3 --- /dev/null +++ b/indexer-core/src/test/resources/converter/bad-schema/wrong-array.json @@ -0,0 +1,312 @@ +{ + "license": "Copyright 2017-2020, Schlumberger\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "The wellbore schema. Used to capture the general information about a wellbore. This information is sometimes called a \"wellbore header\". A wellbore represents the path from surface to a unique bottomhole location. The wellbore object is uniquely identified within the context of one well object.", + "title": "Wellbore", + "type": "object", + "definitions": { + "legal": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "legal", + "type": "object" + }, + "metaItem": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "metaItem", + "type": "object" + }, + "tagDictionary": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "tagDictionary", + "type": "object" + }, + "linkList": { + "type": "object", + "properties": { + "name": { + "link": "string" + } + } + }, + "wellboreData": { + "description": "The domain specific data container for a wellbore.", + "title": "Wellbore Data", + "type": "object", + "properties": { + "WB_NAME": { + "description": "TBD", + "title": "Wellbore Name", + "type": "string", + "example": "SMP G09995 001S0B1" + }, + "SPUD_DATE": { + "format": "date", + "description": "The date and time when activities to drill the borehole begin to create a hole in the earth. For a sidetrack, this is the date kickoff operations began. The format follows ISO 8601 YYYY-MM-DD extended format", + "title": "Spud Date", + "type": "string", + "example": "2013-03-22" + }, + "TVD": { + "description": "TBD", + "title": "True Vertical Depth", + "type": "string", + "example": [ + 20711, + "TBD" + ] + }, + "PERMIT_NUMBER": { + "description": "Ther permit number for the wellbore", + "title": "Permit Number", + "type": "string", + "example": "SMP-09995" + }, + "CRS": { + "description": "Wellbore location CRS", + "title": "CRS", + "type": "string", + "example": "World Geodetic System 1984" + }, + "LONGUITUDE": { + "description": "TBD", + "title": "Longuitude", + "type": "number", + "example": [ + -119.2, + "TBD" + ] + }, + "STATE": { + "description": "The state, in which the wellbore is located.", + "title": "State", + "type": "string", + "example": [ + "Texas" + ] + }, + "CLASS": { + "description": "The current class of the wellbore", + "title": "class", + "type": "string", + "example": "NEW FIELD WILDCAT" + }, + "WELLBORE_SHAPE": { + "description": "The shape of the wellbore", + "title": "Wellbore Shape", + "type": "string", + "example": [ + "DIRECTIONAL", + "VERTICAL" + ] + }, + "FORMATION_AT_TD": { + "description": "The formation name at the wellbore total depth", + "title": "Formation at TD", + "type": "string", + "example": "MIOCENE LOWER" + }, + "PERMIT_DATE": { + "format": "date", + "description": "The date and time when the wellbore permit was issued. The format follows ISO 8601 YYYY-MM-DD extended format", + "title": "Permit Date", + "type": "string", + "example": "2013-03-22" + }, + "STATUS": { + "description": "The current status of the wellbore", + "title": "Status", + "type": "string", + "example": "DRY & ABANDONED" + }, + "COUNTRY": { + "description": "The country, in which the wellbore is located. The country name follows the convention in ISO 3166-1 'English short country name', see https://en.wikipedia.org/wiki/ISO_3166-1", + "title": "Country", + "type": "string", + "example": [ + "United States of America" + ] + }, + "WB_NUMBER": { + "description": "TBD", + "title": "Wellbore Number", + "type": "string", + "example": "001S0B1" + }, + "MD": { + "description": "TBD", + "title": "Measured Depth", + "type": "string", + "example": "12.20" + }, + "ORIGINAL_OPERATOR": { + "description": "The original operator of the wellbore.", + "title": "Original Operator", + "type": "string", + "example": "Anadarko Petroleum" + }, + "BASIN": { + "description": "The basin name, to which the wellbore belongs.", + "title": "Basin", + "type": "string", + "example": "ATWATER" + }, + "EPSG_CODE": { + "description": "EPSG code of the CRS", + "title": "EPSG Code", + "type": "string", + "example": "4326" + }, + "COUNTY": { + "description": "The county, in which the wellbore is located.", + "title": "County", + "type": "string", + "example": [ + "ATWATER VALLEY" + ] + }, + "UNIT_SYSTEM": { + "description": "Unit system used for the wellbore measurements", + "title": "Unit Sustem", + "type": "string", + "example": "English" + }, + "UWI": { + "description": "The unique wellbore identifier, aka. API number, US well number or UBHI. Codes can have 10, 12 or 14 digits depending on the availability of directional sidetrack (2 digits) and event sequence codes (2 digits).", + "title": "Unique Wellbore Identifier", + "type": "string", + "x-osdu-natural-key": 1, + "example": [ + "SP435844921288", + "42-501-20130-01-02" + ] + }, + "FIELD": { + "description": "The field name, to which the wellbore belongs.", + "title": "Field", + "type": "string", + "example": "ATWATER VLLY B 8" + }, + "INITIAL_COMPLETION_DATE": { + "format": "date", + "description": "The date and time of the initial completion of the wellbore. The format follows ISO 8601 YYYY-MM-DD extended format", + "title": "Initial Completion Date", + "type": "string", + "example": "2013-03-22" + }, + "ELEVATION": { + "description": "TBD", + "title": "Elevation", + "type": "string", + "example": [ + 84, + "TBD" + ] + }, + "STATUS_DATE": { + "format": "date", + "description": "The date and time of the current status of the wellbore. The format follows ISO 8601 YYYY-MM-DD extended format", + "title": "Status Date", + "type": "string", + "example": "2013-03-22" + }, + "OPERATOR": { + "description": "The operator of the wellbore.", + "title": "Operator", + "type": "string", + "example": "Anadarko Petroleum" + }, + "LEASE": { + "description": "The lease name, to which the wellbore belongs.", + "title": "LEASE", + "type": "string", + "example": "SMP G09995" + }, + "API": { + "description": "Second parameter used for relationship tests", + "title": "Api relationship test", + "type": "string", + "example": "test-wellbore-api" + }, + "LATITUDE": { + "description": "TBD", + "title": "Latitude", + "type": "number", + "example": [ + 60.2, + "TBD" + ] + }, + "ELEVATION_REF": { + "description": "Elevation reference used for the measurements", + "title": "Elevation reference", + "type": "string", + "example": "MSL" + }, + "Wellbores": { + "pattern": ".*U1A1NjA3MDM2Mzk5MzUy:.*", + "description": "The Well ID reference.", + "x-osdu-relationship": [ + { + "EntityType": "Wellbore", + "GroupType": "master-data" + } + ], + "type": "array" + } + }, + "$id": "definitions/wellboreData" + } + }, + "properties": { + "ancestry": { + "description": "The links to data, which constitute the inputs.", + "title": "Ancestry", + "$ref": "#/definitions/linkList" + }, + "data": { + "description": "Wellbore data container", + "title": "Wellbore Data", + "$ref": "#/definitions/wellboreData" + }, + "kind": { + "description": "OSDU demo wellbore kind specification", + "title": "Wellbore Kind", + "type": "string" + }, + "meta": { + "description": "The meta data section linking the 'unitKey', 'crsKey' to self-contained definitions (persistableReference)", + "title": "Frame of Reference Meta Data", + "type": "array", + "items": { + "$ref": "#/definitions/metaItem" + } + }, + "legal": { + "description": "The geological interpretation's legal tags", + "title": "Legal Tags", + "$ref": "#/definitions/legal" + }, + "acl": { + "description": "The access control tags associated with this entity.", + "title": "Access Control List", + "$ref": "#/definitions/tagDictionary" + }, + "id": { + "description": "The unique identifier of the wellbore", + "title": "Wellbore ID", + "type": "string" + }, + "type": { + "description": "The reference entity type as declared in common:metadata:entity:*.", + "title": "Entity Type", + "type": "string" + }, + "version": { + "format": "int64", + "description": "The version number of this wellbore; set by the framework.", + "title": "Entity Version Number", + "type": "number", + "example": "1040815391631285" + } + } +} \ No newline at end of file