Skip to content

Commit

Permalink
#381: Support pagination and sorting in ITI-58 requests
Browse files Browse the repository at this point in the history
  • Loading branch information
unixoid committed Apr 11, 2022
1 parent 79d676d commit 3359e1c
Show file tree
Hide file tree
Showing 36 changed files with 2,155 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import static java.util.Objects.requireNonNull;

/**
* @author Dmytro Ruds
* @author Dmytro Rud
* @author Christian Ohr
*/
public class EhcacheHl7v3ContinuationStorage implements Hl7v3ContinuationStorage {
Expand Down
15 changes: 15 additions & 0 deletions commons/ihe/hpd/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,21 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>

<!-- Dependencies for test -->
<dependency>
<groupId>org.openehealth.ipf.commons</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2022 the original author or authors.
*
* 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.openehealth.ipf.commons.ihe.hpd;

import lombok.experimental.UtilityClass;
import org.apache.commons.lang3.NotImplementedException;
import org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2.ErrorResponse;
import org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2.LDAPResult;
import org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2.ObjectFactory;
import org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2.SearchResponse;

import javax.xml.bind.JAXBElement;

/**
* @author Dmytro Rud
* @since 4.3
*/
@UtilityClass
public class HpdUtils {

public static final ObjectFactory DSMLV2_OBJECT_FACTORY = new ObjectFactory();

public static JAXBElement<ErrorResponse> errorResponse(Exception exception, String requestId) {
ErrorResponse error = DSMLV2_OBJECT_FACTORY.createErrorResponse();
error.setMessage(exception.getMessage());
error.setRequestID(requestId);
ErrorResponse.ErrorType errorType = (exception instanceof HpdException) ? ((HpdException) exception).getType() : ErrorResponse.ErrorType.OTHER;
error.setType(errorType);
return DSMLV2_OBJECT_FACTORY.createBatchResponseErrorResponse(error);
}

public static String extractResponseRequestId(Object dsmlResponse) {
if (dsmlResponse instanceof SearchResponse) {
return ((SearchResponse) dsmlResponse).getRequestID();
} else if (dsmlResponse instanceof LDAPResult) {
return ((LDAPResult) dsmlResponse).getRequestID();
} else if (dsmlResponse instanceof ErrorResponse) {
return ((ErrorResponse) dsmlResponse).getRequestID();
} else {
throw new NotImplementedException("Cannot handle HPD response type " + dsmlResponse.getClass() + ", please submit a bug report");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,14 @@
import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.util.JAXBSource;
import javax.xml.transform.Source;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import java.util.regex.Pattern;

/**
* @author Dmytro Rud
Expand All @@ -51,6 +55,8 @@ public class HpdValidator {

private static final XsdValidator XSD_VALIDATOR = new XsdValidator();

private static final Pattern DIGITS_PATTERN = Pattern.compile("\\d+");

private static void check(boolean condition, String message) {
if (! condition) {
throw new HpdException(message, ErrorType.MALFORMED_REQUEST);
Expand All @@ -68,23 +74,31 @@ private static void validateWithXsd(Object object, String schemaName) {
}
}

private static boolean isUniqueRequestId(String id, Set<String> knownIds) {
return (id != null) && DIGITS_PATTERN.matcher(id).matches() && knownIds.add(id);
}

private static void validateBatchRequest(
BatchRequest batch,
BatchRequest batchRequest,
Class<? extends DsmlMessage>[] allowedElementTypes,
Consumer<DsmlMessage> requestValidator)
{
check(batch.getBatchRequests() != null, "Batch is null");
check(!batch.getBatchRequests().isEmpty(), "Batch is empty");
for(var dsml : batch.getBatchRequests()) {
check(dsml != null, "Batch element is null");
check(ArrayUtils.contains(allowedElementTypes, dsml.getClass()), "Bad batch element type " + ClassUtils.getSimpleName(dsml));
check(batchRequest.getBatchRequests() != null, "Batch request is null");
check(!batchRequest.getBatchRequests().isEmpty(), "Batch request is empty");
Set<String> requestIds = new HashSet<>();
check(isUniqueRequestId(batchRequest.getRequestID(), requestIds), "Batch request ID must be a non-negative number");

for (DsmlMessage dsml : batchRequest.getBatchRequests()) {
check(dsml != null, "Batch request element is null");
check(ArrayUtils.contains(allowedElementTypes, dsml.getClass()), "Bad batch request element type " + ClassUtils.getSimpleName(dsml));
check(isUniqueRequestId(dsml.getRequestID(), requestIds), "Request ID must be a non-negative number unique in the batch request");
requestValidator.accept(dsml);
}
}

private static void validateSearchRequest(SearchRequest request, String dc, String o, String c) {
private static void validateSearchRequest(SearchRequest searchRequest, String dc, String o, String c) {
try {
var ldapName = new LdapName(request.getDn());
var ldapName = new LdapName(searchRequest.getDn());
for (var rdn : ldapName.getRdns()) {
var value = (String) rdn.getValue();
switch (rdn.getType().toLowerCase()) {
Expand Down Expand Up @@ -121,6 +135,26 @@ public static void validateIti58Request(BatchRequest batchRequest) {

public static void validateIti58Response(BatchResponse batchResponse) {
validateWithXsd(batchResponse, "/schema/DSMLv2.xsd");
Set<String> requestIds = new HashSet<>();
check(isUniqueRequestId(batchResponse.getRequestID(), requestIds), "Batch request ID must be a non-negative number");

for (JAXBElement<?> jaxbElement : batchResponse.getBatchResponses()) {
check(jaxbElement != null, "Batch response element is null");
Object value = jaxbElement.getValue();
check(value != null, "Batch response element is null");

String requestId;
if (value instanceof LDAPResult) {
requestId = ((LDAPResult) value).getRequestID();
} else if (value instanceof SearchResponse) {
requestId = ((SearchResponse) value).getRequestID();
} else if (value instanceof ErrorResponse) {
requestId = ((ErrorResponse) value).getRequestID();
} else {
throw new HpdException("Wrong response element type " + value.getClass().getSimpleName(), ErrorType.MALFORMED_REQUEST);
}
check(isUniqueRequestId(requestId, requestIds), "Request ID must be a non-negative number unique in the batch response");
}
}

public static void validateIti59Request(BatchRequest batchRequest) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2022 the original author or authors.
*
* 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.openehealth.ipf.commons.ihe.hpd.controls;

import org.openehealth.ipf.commons.ihe.hpd.controls.handlers.ConsumerHandler;
import org.openehealth.ipf.commons.ihe.hpd.controls.handlers.ConsumerHandlerBase;
import org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2.BatchRequest;
import org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2.BatchResponse;

import javax.xml.bind.JAXBElement;

/**
* @author Dmytro Rud
* @since 4.3
*/
abstract public class ConsumerHpdHandler extends ConsumerHandlerBase<BatchRequest, BatchResponse> {

public ConsumerHpdHandler(ConsumerHandler<BatchRequest, BatchResponse> wrappedHandler) {
super(wrappedHandler);
}

/**
* Combines the entries from the batch response with the ones computed locally.
*/
protected static BatchResponse aggregateResponse(BatchRequest batchRequest, BatchResponse batchResponse, JAXBElement<?>[] localResponses) {
batchResponse.setRequestID(batchRequest.getRequestID());
for (int i = 0; i < localResponses.length; ++i) {
if (localResponses[i] != null) {
int position = Math.min(i, batchResponse.getBatchResponses().size());
batchResponse.getBatchResponses().add(position, localResponses[i]);
}
}
return batchResponse;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2022 the original author or authors.
*
* 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.openehealth.ipf.commons.ihe.hpd.controls;

import lombok.experimental.UtilityClass;
import org.apache.commons.lang3.NotImplementedException;
import org.openehealth.ipf.commons.ihe.hpd.controls.sorting.SortControl2;
import org.openehealth.ipf.commons.ihe.hpd.controls.sorting.SortResponseControl2;
import org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2.Control;
import org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2.DsmlMessage;
import org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2.SearchResponse;

import javax.naming.ldap.*;
import java.io.IOException;
import java.util.List;

/**
* Methods for mapping of Controls to and from DSMLv2 and Strings.
*
* @author Dmytro Rud
* @since 4.3
*/
@UtilityClass
public class ControlUtils {

public static <T extends BasicControl> T extractControl(byte[] berBytes, String type, boolean criticality) throws IOException {
switch (type) {
case PagedResultsControl.OID:
return (T) new PagedResultsResponseControl(PagedResultsResponseControl.OID, criticality, berBytes);
case SortControl.OID:
return (T) new SortControl2(berBytes, criticality);
case SortResponseControl.OID:
return (T) new SortResponseControl2(berBytes, criticality);
default:
throw new NotImplementedException("Cannot handle control type " + type);
}
}

public static PagedResultsResponseControl convert(PagedResultsControl control) throws IOException {
byte[] berBytes = control.getEncodedValue();
return new PagedResultsResponseControl(PagedResultsResponseControl.OID, control.isCritical(), berBytes);
}

public static <T extends BasicControl> T extractControl(List<Control> controls, String type) throws IOException {
for (Control control : controls) {
if (type.equals(control.getType())) {
return extractControl((byte[]) control.getControlValue(), type, control.isCriticality());
}
}
return null;
}

public static <T extends BasicControl> T extractControl(SearchResponse searchResponse, String type) throws IOException {
return ((searchResponse != null) && (searchResponse.getSearchResultDone() != null))
? extractControl(searchResponse.getSearchResultDone().getControl(), type)
: null;
}

public static <T extends BasicControl> T extractControl(DsmlMessage dsmlMessage, String type) throws IOException {
return (dsmlMessage != null)
? extractControl(dsmlMessage.getControl(), type)
: null;
}

public static Control toDsmlv2(BasicControl bc) {
Control control = new Control();
control.setType(bc.getID());
control.setCriticality(bc.isCritical());
control.setControlValue(bc.getEncodedValue());
return control;
}

public static void setControl(SearchResponse response, BasicControl bc) {
setControl(response.getSearchResultDone(), bc);
}

public static void setControl(DsmlMessage dsmlMessage, BasicControl bc) {
List<Control> controls = dsmlMessage.getControl();
for (int i = 0; i < controls.size(); ++i) {
if (bc.getID().equals(controls.get(i).getType())) {
controls.set(i, toDsmlv2(bc));
return;
}
}
controls.add(toDsmlv2(bc));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2022 the original author or authors.
*
* 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.openehealth.ipf.commons.ihe.hpd.controls.handlers;

/**
* @author Dmytro Rud
* @since 4.3
*/
public interface ConsumerHandler<RequestType, ResponseType> {

/**
* @return Handler which is one step nearer to the Camel route.
*/
ConsumerHandler<RequestType, ResponseType> getWrappedHandler();

/**
* @return Response to the given request.
*/
ResponseType handle(RequestType request);

}
Loading

0 comments on commit 3359e1c

Please sign in to comment.