diff --git a/pom.xml b/pom.xml index 652018abc..7fbfd9786 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ uk.gov.moj.cpp.sjp sjp-parent - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT pom @@ -40,9 +40,9 @@ 6.0.0 4.1.4-ATCM - 17.0.57 + 17.0.85 17.0.1 - 17.104.137 + 17.104.140 17.104.52 17.0.11 0.1.49 diff --git a/sjp-command/pom.xml b/sjp-command/pom.xml index 3fb7981f8..550b11de0 100644 --- a/sjp-command/pom.xml +++ b/sjp-command/pom.xml @@ -3,7 +3,7 @@ sjp-parent uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0 diff --git a/sjp-command/sjp-command-api/pom.xml b/sjp-command/sjp-command-api/pom.xml index dadb0c5bc..9f95f5c5f 100644 --- a/sjp-command/sjp-command-api/pom.xml +++ b/sjp-command/sjp-command-api/pom.xml @@ -3,7 +3,7 @@ sjp-command uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0 diff --git a/sjp-command/sjp-command-controller/pom.xml b/sjp-command/sjp-command-controller/pom.xml index 41ed1c96c..9a8f5a4f6 100644 --- a/sjp-command/sjp-command-controller/pom.xml +++ b/sjp-command/sjp-command-controller/pom.xml @@ -3,7 +3,7 @@ sjp-command uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0 diff --git a/sjp-command/sjp-command-handler/pom.xml b/sjp-command/sjp-command-handler/pom.xml index f2f299dd5..13fec93a9 100644 --- a/sjp-command/sjp-command-handler/pom.xml +++ b/sjp-command/sjp-command-handler/pom.xml @@ -3,7 +3,7 @@ sjp-command uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0 diff --git a/sjp-domain/pom.xml b/sjp-domain/pom.xml index c3732ae46..761e5e1fa 100644 --- a/sjp-domain/pom.xml +++ b/sjp-domain/pom.xml @@ -5,7 +5,7 @@ uk.gov.moj.cpp.sjp sjp-parent - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT sjp-domain diff --git a/sjp-domain/sjp-domain-aggregate/pom.xml b/sjp-domain/sjp-domain-aggregate/pom.xml index 424fec42f..3b558e3cd 100644 --- a/sjp-domain/sjp-domain-aggregate/pom.xml +++ b/sjp-domain/sjp-domain-aggregate/pom.xml @@ -5,7 +5,7 @@ uk.gov.moj.cpp.sjp sjp-domain - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT sjp-domain-aggregate diff --git a/sjp-domain/sjp-domain-common/pom.xml b/sjp-domain/sjp-domain-common/pom.xml index 83352fdb0..0f7aba924 100644 --- a/sjp-domain/sjp-domain-common/pom.xml +++ b/sjp-domain/sjp-domain-common/pom.xml @@ -5,7 +5,7 @@ uk.gov.moj.cpp.sjp sjp-domain - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT diff --git a/sjp-domain/sjp-domain-event-processor/pom.xml b/sjp-domain/sjp-domain-event-processor/pom.xml index 3a22aa980..65fd560dc 100644 --- a/sjp-domain/sjp-domain-event-processor/pom.xml +++ b/sjp-domain/sjp-domain-event-processor/pom.xml @@ -3,7 +3,7 @@ sjp-domain uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0 diff --git a/sjp-domain/sjp-domain-event/pom.xml b/sjp-domain/sjp-domain-event/pom.xml index b804fbee0..3fdf88f09 100644 --- a/sjp-domain/sjp-domain-event/pom.xml +++ b/sjp-domain/sjp-domain-event/pom.xml @@ -5,7 +5,7 @@ uk.gov.moj.cpp.sjp sjp-domain - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT sjp-domain-event diff --git a/sjp-domain/sjp-domain-prosecution/pom.xml b/sjp-domain/sjp-domain-prosecution/pom.xml index 49ed5c5d5..bafd5a14c 100644 --- a/sjp-domain/sjp-domain-prosecution/pom.xml +++ b/sjp-domain/sjp-domain-prosecution/pom.xml @@ -4,7 +4,7 @@ sjp-domain uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT sjp-domain-prosecution diff --git a/sjp-domain/sjp-domain-transformation/pom.xml b/sjp-domain/sjp-domain-transformation/pom.xml index ca55ce3a7..f6aabba6e 100644 --- a/sjp-domain/sjp-domain-transformation/pom.xml +++ b/sjp-domain/sjp-domain-transformation/pom.xml @@ -3,7 +3,7 @@ sjp-domain uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0 diff --git a/sjp-domain/sjp-domain-transformation/sjp-domain-transformation-anonymise/pom.xml b/sjp-domain/sjp-domain-transformation/sjp-domain-transformation-anonymise/pom.xml index d75ad2744..b51b85aa0 100644 --- a/sjp-domain/sjp-domain-transformation/sjp-domain-transformation-anonymise/pom.xml +++ b/sjp-domain/sjp-domain-transformation/sjp-domain-transformation-anonymise/pom.xml @@ -3,7 +3,7 @@ sjp-domain-transformation uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0 diff --git a/sjp-domain/sjp-domain-transformation/sjp-domain-transformation-postcode/pom.xml b/sjp-domain/sjp-domain-transformation/sjp-domain-transformation-postcode/pom.xml index 655863a63..ca677eb2b 100644 --- a/sjp-domain/sjp-domain-transformation/sjp-domain-transformation-postcode/pom.xml +++ b/sjp-domain/sjp-domain-transformation/sjp-domain-transformation-postcode/pom.xml @@ -3,7 +3,7 @@ sjp-domain-transformation uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0 diff --git a/sjp-event-sources/pom.xml b/sjp-event-sources/pom.xml index fdf178715..3b1dbda35 100644 --- a/sjp-event-sources/pom.xml +++ b/sjp-event-sources/pom.xml @@ -3,7 +3,7 @@ sjp-parent uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT ../pom.xml 4.0.0 diff --git a/sjp-event/pom.xml b/sjp-event/pom.xml index d224b3378..1a5f7cb6f 100644 --- a/sjp-event/pom.xml +++ b/sjp-event/pom.xml @@ -3,7 +3,7 @@ sjp-parent uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0 diff --git a/sjp-event/sjp-event-indexer/pom.xml b/sjp-event/sjp-event-indexer/pom.xml index 6c6f91b2e..903bec337 100644 --- a/sjp-event/sjp-event-indexer/pom.xml +++ b/sjp-event/sjp-event-indexer/pom.xml @@ -3,7 +3,7 @@ sjp-event uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0 diff --git a/sjp-event/sjp-event-listener/pom.xml b/sjp-event/sjp-event-listener/pom.xml index b11f0a92a..3cb566975 100644 --- a/sjp-event/sjp-event-listener/pom.xml +++ b/sjp-event/sjp-event-listener/pom.xml @@ -3,7 +3,7 @@ sjp-event uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0 diff --git a/sjp-event/sjp-event-processor/pom.xml b/sjp-event/sjp-event-processor/pom.xml index 484355009..73156ef02 100644 --- a/sjp-event/sjp-event-processor/pom.xml +++ b/sjp-event/sjp-event-processor/pom.xml @@ -3,7 +3,7 @@ sjp-event uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0 diff --git a/sjp-healthchecks/pom.xml b/sjp-healthchecks/pom.xml index 5f142a8cd..c40d89acb 100644 --- a/sjp-healthchecks/pom.xml +++ b/sjp-healthchecks/pom.xml @@ -3,7 +3,7 @@ sjp-parent uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0 diff --git a/sjp-integration-test/pom.xml b/sjp-integration-test/pom.xml index 038bb5d42..ee1d456ee 100644 --- a/sjp-integration-test/pom.xml +++ b/sjp-integration-test/pom.xml @@ -4,7 +4,7 @@ uk.gov.moj.cpp.sjp sjp-parent - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0 diff --git a/sjp-json/pom.xml b/sjp-json/pom.xml index 69c4af04f..e18bb3aa8 100644 --- a/sjp-json/pom.xml +++ b/sjp-json/pom.xml @@ -2,7 +2,7 @@ sjp-parent uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0 diff --git a/sjp-query/pom.xml b/sjp-query/pom.xml index 4725d6246..a3f0f22d0 100644 --- a/sjp-query/pom.xml +++ b/sjp-query/pom.xml @@ -3,7 +3,7 @@ sjp-parent uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0 diff --git a/sjp-query/sjp-query-api/pom.xml b/sjp-query/sjp-query-api/pom.xml index e4bef0920..377f419a4 100644 --- a/sjp-query/sjp-query-api/pom.xml +++ b/sjp-query/sjp-query-api/pom.xml @@ -3,7 +3,7 @@ sjp-query uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0 @@ -89,17 +89,6 @@ sjp-json ${project.version} - - uk.gov.moj.cpp.material - material-client - ${material.version} - - - uk.gov.justice.services - test-utils-core - - - javax javaee-api diff --git a/sjp-query/sjp-query-api/src/main/java/uk/gov/justice/api/resource/DefaultQueryApiCasesCaseIdDocumentsDocumentIdContentResource.java b/sjp-query/sjp-query-api/src/main/java/uk/gov/justice/api/resource/DefaultQueryApiCasesCaseIdDocumentsDocumentIdContentResource.java index 2fec7bb1b..8242edbb3 100644 --- a/sjp-query/sjp-query-api/src/main/java/uk/gov/justice/api/resource/DefaultQueryApiCasesCaseIdDocumentsDocumentIdContentResource.java +++ b/sjp-query/sjp-query-api/src/main/java/uk/gov/justice/api/resource/DefaultQueryApiCasesCaseIdDocumentsDocumentIdContentResource.java @@ -16,7 +16,7 @@ import uk.gov.justice.services.core.annotation.Component; import uk.gov.justice.services.core.interceptor.InterceptorChainProcessor; import uk.gov.justice.services.messaging.JsonEnvelope; -import uk.gov.moj.cpp.material.client.MaterialClient; +import uk.gov.moj.cpp.sjp.material.client.MaterialClient; import uk.gov.moj.cpp.systemusers.ServiceContextSystemUserProvider; import java.util.UUID; diff --git a/sjp-query/sjp-query-api/src/main/java/uk/gov/moj/cpp/sjp/material/client/MaterialClient.java b/sjp-query/sjp-query-api/src/main/java/uk/gov/moj/cpp/sjp/material/client/MaterialClient.java new file mode 100644 index 000000000..10d9d5ea8 --- /dev/null +++ b/sjp-query/sjp-query-api/src/main/java/uk/gov/moj/cpp/sjp/material/client/MaterialClient.java @@ -0,0 +1,62 @@ +package uk.gov.moj.cpp.sjp.material.client; + +import static javax.ws.rs.client.ClientBuilder.newClient; +import static uk.gov.justice.services.common.http.HeaderConstants.USER_ID; + +import java.util.UUID; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.Invocation; +import javax.ws.rs.core.Response; + +import org.slf4j.Logger; + +/** + * SJP-owned copy of material's HTTP adapter (decouples sjp from the material-client JAR - PEG-3447). + * Trimmed to the single method sjp uses; runtime behaviour is unchanged - sjp still calls material + * over HTTP exactly as before. + */ +@ApplicationScoped +public class MaterialClient { + + public static final String REQUEST_PARAM_ADD_INLINE_CONTENT_DISPOSITION_HEADER = "addInlineContentDispositionHeader"; + + @Inject + Logger logger; + + private static final String BASE_URI = "http://localhost:8080/material-query-api/query/api/rest/material"; + private static final String MATERIAL_REQUEST_PATH = "/material/"; + private static final String GET_MATERIAL_AS_PDF = "application/vnd.material.query.material+json"; + private static final String REQUEST_PARAM_STREAM = "stream"; + private static final String REQUEST_PARAM_REQUEST_PDF = "requestPdf"; + + public Response getMaterialWithHeader(final UUID materialId, final UUID userId) { + return getMaterial(materialId.toString(), userId.toString(), true, false, true); + } + + private Response getMaterial(final String materialId, final String userId, final boolean asStream, final boolean asPdf, final boolean addInlineContentDispositionHeader) { + + final Invocation.Builder builder = getClient() + .target(BASE_URI) + .path(MATERIAL_REQUEST_PATH + materialId) + .queryParam(REQUEST_PARAM_STREAM, asStream) + .queryParam(REQUEST_PARAM_REQUEST_PDF, asPdf) + .queryParam(REQUEST_PARAM_ADD_INLINE_CONTENT_DISPOSITION_HEADER, addInlineContentDispositionHeader) + .request() + .header(USER_ID, userId) + .accept(GET_MATERIAL_AS_PDF); + + logger.info("Invoking call to material context"); + return builder.get(); + } + + /** + * Returns the JAX-RS client. Extracted so the client can be mocked in unit tests, since the + * static {@code newClient()} call is otherwise hard to stub. + */ + Client getClient() { + return newClient(); + } +} diff --git a/sjp-query/sjp-query-api/src/test/java/uk/gov/justice/api/resource/DefaultQueryApiCasesCaseIdDocumentsDocumentIdContentResourceTest.java b/sjp-query/sjp-query-api/src/test/java/uk/gov/justice/api/resource/DefaultQueryApiCasesCaseIdDocumentsDocumentIdContentResourceTest.java index 1433e0d53..b47f95d80 100644 --- a/sjp-query/sjp-query-api/src/test/java/uk/gov/justice/api/resource/DefaultQueryApiCasesCaseIdDocumentsDocumentIdContentResourceTest.java +++ b/sjp-query/sjp-query-api/src/test/java/uk/gov/justice/api/resource/DefaultQueryApiCasesCaseIdDocumentsDocumentIdContentResourceTest.java @@ -27,7 +27,7 @@ import uk.gov.justice.services.core.interceptor.InterceptorChainProcessor; import uk.gov.justice.services.core.interceptor.InterceptorContext; import uk.gov.justice.services.messaging.JsonEnvelope; -import uk.gov.moj.cpp.material.client.MaterialClient; +import uk.gov.moj.cpp.sjp.material.client.MaterialClient; import uk.gov.moj.cpp.systemusers.ServiceContextSystemUserProvider; import java.util.Optional; @@ -116,7 +116,7 @@ public void shouldRunNotFoundStatusWhenDocumentNotFound() { verifyInterceptorChainExecution(); - verify(materialClient, never()).getMaterial(argThat(any(UUID.class)), argThat(any(UUID.class))); + verify(materialClient, never()).getMaterialWithHeader(argThat(any(UUID.class)), argThat(any(UUID.class))); } @Test @@ -150,7 +150,7 @@ public void shouldRethrowAnyInterceptorException() { } verifyInterceptorChainExecution(); - verify(materialClient, never()).getMaterial(argThat(any(UUID.class)), argThat(any(UUID.class))); + verify(materialClient, never()).getMaterialWithHeader(argThat(any(UUID.class)), argThat(any(UUID.class))); } @Test diff --git a/sjp-query/sjp-query-api/src/test/java/uk/gov/moj/cpp/sjp/material/client/MaterialClientTest.java b/sjp-query/sjp-query-api/src/test/java/uk/gov/moj/cpp/sjp/material/client/MaterialClientTest.java new file mode 100644 index 000000000..1f0ab4bb7 --- /dev/null +++ b/sjp-query/sjp-query-api/src/test/java/uk/gov/moj/cpp/sjp/material/client/MaterialClientTest.java @@ -0,0 +1,91 @@ +package uk.gov.moj.cpp.sjp.material.client; + +import static java.util.UUID.fromString; +import static java.util.UUID.randomUUID; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static uk.gov.justice.services.common.http.HeaderConstants.USER_ID; +import static uk.gov.moj.cpp.sjp.material.client.MaterialClient.REQUEST_PARAM_ADD_INLINE_CONTENT_DISPOSITION_HEADER; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Invocation; +import javax.ws.rs.client.WebTarget; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.slf4j.Logger; + +@ExtendWith(MockitoExtension.class) +public class MaterialClientTest { + + private static final String BASE_URI = "http://localhost:8080/material-query-api/query/api/rest/material"; + private static final String MATERIAL_REQUEST_PATH = "/material/"; + private static final String GET_MATERIAL_AS_PDF = "application/vnd.material.query.material+json"; + private static final String REQUEST_PARAM_STREAM = "stream"; + private static final String REQUEST_PARAM_REQUEST_PDF = "requestPdf"; + private static final String MATERIAL_ID_VALUE = randomUUID().toString(); + private static final String USER_ID_VALUE = randomUUID().toString(); + private static final String LOG_MATERIAL_CALL = "Invoking call to material context"; + + @Mock + private Logger logger; + + private final Client client = mock(Client.class); + private final WebTarget webTarget = mock(WebTarget.class); + private final Invocation.Builder builder = mock(Invocation.Builder.class); + + private final MaterialClient materialClient = new MaterialClient() { + @Override + Client getClient() { + return client; + } + }; + + @BeforeEach + public void init() { + materialClient.logger = logger; + when(client.target(anyString())).thenReturn(webTarget); + when(webTarget.path(anyString())).thenReturn(webTarget); + when(webTarget.queryParam(anyString(), any())).thenReturn(webTarget); + when(webTarget.request()).thenReturn(builder); + when(builder.header(anyString(), anyString())).thenReturn(builder); + when(builder.accept(anyString())).thenReturn(builder); + } + + @Test + public void shouldGetMaterialWithHeader() { + materialClient.getMaterialWithHeader(fromString(MATERIAL_ID_VALUE), fromString(USER_ID_VALUE)); + + verify(client).target(BASE_URI); + verify(webTarget).path(MATERIAL_REQUEST_PATH + MATERIAL_ID_VALUE); + verify(webTarget).queryParam(REQUEST_PARAM_STREAM, true); + verify(webTarget).queryParam(REQUEST_PARAM_REQUEST_PDF, false); + verify(webTarget).queryParam(REQUEST_PARAM_ADD_INLINE_CONTENT_DISPOSITION_HEADER, true); + verify(webTarget).request(); + verify(builder).header(eq(USER_ID), anyString()); + verify(builder).accept(GET_MATERIAL_AS_PDF); + verify(builder).get(); + verify(logger).info(LOG_MATERIAL_CALL); + } + + @Test + public void shouldReturnTheJaxRsClientFromGetClient() { + try (final MockedStatic clientBuilder = mockStatic(ClientBuilder.class)) { + clientBuilder.when(ClientBuilder::newClient).thenReturn(client); + + assertThat(new MaterialClient().getClient(), is(client)); + } + } +} diff --git a/sjp-query/sjp-query-view/pom.xml b/sjp-query/sjp-query-view/pom.xml index a36630940..7c7707443 100644 --- a/sjp-query/sjp-query-view/pom.xml +++ b/sjp-query/sjp-query-view/pom.xml @@ -3,7 +3,7 @@ sjp-query uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0 diff --git a/sjp-service/pom.xml b/sjp-service/pom.xml index 7370eac27..778da14ba 100644 --- a/sjp-service/pom.xml +++ b/sjp-service/pom.xml @@ -5,7 +5,7 @@ uk.gov.moj.cpp.sjp sjp-parent - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT sjp-service war diff --git a/sjp-viewstore/pom.xml b/sjp-viewstore/pom.xml index 1363d57a6..56a567f09 100644 --- a/sjp-viewstore/pom.xml +++ b/sjp-viewstore/pom.xml @@ -3,7 +3,7 @@ sjp-parent uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0 sjp-viewstore diff --git a/sjp-viewstore/sjp-viewstore-liquibase/pom.xml b/sjp-viewstore/sjp-viewstore-liquibase/pom.xml index 531a10733..c9d2122f1 100644 --- a/sjp-viewstore/sjp-viewstore-liquibase/pom.xml +++ b/sjp-viewstore/sjp-viewstore-liquibase/pom.xml @@ -5,7 +5,7 @@ uk.gov.moj.cpp.sjp sjp-viewstore - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT sjp-viewstore-liquibase diff --git a/sjp-viewstore/sjp-viewstore-persistence/pom.xml b/sjp-viewstore/sjp-viewstore-persistence/pom.xml index 418bd4ef8..ed8b15290 100644 --- a/sjp-viewstore/sjp-viewstore-persistence/pom.xml +++ b/sjp-viewstore/sjp-viewstore-persistence/pom.xml @@ -3,7 +3,7 @@ sjp-viewstore uk.gov.moj.cpp.sjp - 17.104.194-SNAPSHOT + 17.104.194-CHD-2845-SNAPSHOT 4.0.0