View Javadoc
1   /*
2    * Copyright 2017 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.openehealth.ipf.commons.ihe.hpd.iti59;
17  
18  import lombok.extern.slf4j.Slf4j;
19  import org.apache.commons.lang3.StringUtils;
20  import org.openehealth.ipf.commons.audit.AuditContext;
21  import org.openehealth.ipf.commons.audit.codes.EventActionCode;
22  import org.openehealth.ipf.commons.audit.codes.EventOutcomeIndicator;
23  import org.openehealth.ipf.commons.audit.codes.ParticipantObjectTypeCode;
24  import org.openehealth.ipf.commons.audit.model.AuditMessage;
25  import org.openehealth.ipf.commons.ihe.core.atna.AuditStrategySupport;
26  import org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2.*;
27  
28  import javax.naming.InvalidNameException;
29  import javax.naming.ldap.LdapName;
30  import javax.naming.ldap.Rdn;
31  import java.util.HashMap;
32  import java.util.Map;
33  import java.util.stream.Stream;
34  
35  import static org.apache.commons.lang3.ClassUtils.getShortCanonicalName;
36  import static org.apache.commons.lang3.StringUtils.isEmpty;
37  import static org.apache.commons.lang3.StringUtils.trimToNull;
38  
39  /**
40   * Audit strategy for the ITI-59 transaction.
41   *
42   * @author Dmytro Rud
43   */
44  @Slf4j
45  abstract class Iti59AuditStrategy extends AuditStrategySupport<Iti59AuditDataset> {
46  
47      private static final Map<String, ParticipantObjectTypeCode> PARTICIPANT_OBJECT_CODE_MAP;
48      static {
49          PARTICIPANT_OBJECT_CODE_MAP = new HashMap<>();
50          PARTICIPANT_OBJECT_CODE_MAP.put("HCProfessional".toLowerCase(), ParticipantObjectTypeCode.Person);
51          PARTICIPANT_OBJECT_CODE_MAP.put("HCRegulatedOrganization".toLowerCase(), ParticipantObjectTypeCode.Organization);
52      }
53  
54      protected Iti59AuditStrategy(boolean serverSide) {
55          super(serverSide);
56      }
57  
58      @Override
59      public Iti59AuditDataset createAuditDataset() {
60          return new Iti59AuditDataset(isServerSide());
61      }
62  
63      /**
64       * Enriches the given audit item with UID and participant object type code which correspond to the given DN.
65       * @param item      audit item to be enriched
66       * @param dn        current (old) LDAP DN value
67       */
68      private static void enrichAuditItem(Iti59AuditDataset.RequestItem item, String dn) {
69          try {
70              for (Rdn rdn : new LdapName(dn).getRdns()) {
71                  String value = (String) rdn.getValue();
72                  switch (rdn.getType().toLowerCase()) {
73                      case "uid":
74                          item.setUid(value);
75                          break;
76                      case "ou":
77                          item.setParticipantObjectTypeCode(PARTICIPANT_OBJECT_CODE_MAP.get(value.toLowerCase()));
78                          break;
79                  }
80              }
81          } catch (InvalidNameException e) {
82              log.debug("Cannot parse DN", e);
83          }
84      }
85  
86      @Override
87      public Iti59AuditDataset enrichAuditDatasetFromRequest(Iti59AuditDataset auditDataset, Object requestObject, Map<String, Object> parameters) {
88          BatchRequest batchRequest = (BatchRequest) requestObject;
89          if ((batchRequest == null) ||
90                  (batchRequest.getBatchRequests() == null) ||
91                  batchRequest.getBatchRequests().isEmpty()) {
92              log.debug("Empty batch request");
93              return auditDataset;
94          }
95  
96          Iti59AuditDataset.RequestItem[] requestItems = new Iti59AuditDataset.RequestItem[batchRequest.getBatchRequests().size()];
97  
98          for (int i = 0; i < batchRequest.getBatchRequests().size(); ++i) {
99              DsmlMessage dsmlMessage = batchRequest.getBatchRequests().get(i);
100 
101             if (dsmlMessage instanceof AddRequest) {
102                 AddRequest addRequest = (AddRequest) dsmlMessage;
103                 requestItems[i] = new Iti59AuditDataset.RequestItem(trimToNull(addRequest.getRequestID()), EventActionCode.Create);
104                 enrichAuditItem(requestItems[i], addRequest.getDn());
105 
106             } else if (dsmlMessage instanceof ModifyRequest) {
107                 ModifyRequest modifyRequest = (ModifyRequest) dsmlMessage;
108                 requestItems[i] = new Iti59AuditDataset.RequestItem(trimToNull(modifyRequest.getRequestID()), EventActionCode.Update);
109                 enrichAuditItem(requestItems[i], modifyRequest.getDn());
110 
111             } else if (dsmlMessage instanceof ModifyDNRequest) {
112                 ModifyDNRequest modifyDNRequest = (ModifyDNRequest) dsmlMessage;
113                 requestItems[i] = new Iti59AuditDataset.RequestItem(trimToNull(modifyDNRequest.getRequestID()), EventActionCode.Execute);
114                 enrichAuditItem(requestItems[i], modifyDNRequest.getDn());
115                 try {
116                     requestItems[i].setNewUid(new LdapName(modifyDNRequest.getNewrdn()).getRdns().stream()
117                             .filter(rdn -> "uid".equalsIgnoreCase(rdn.getType()))
118                             .map(rdn -> (String) rdn.getValue())
119                             .findAny()
120                             .orElse(null));
121                 } catch (Exception e) {
122                     log.debug("Cannot parse new Rdn", e);
123                 }
124 
125             } else if (dsmlMessage instanceof DelRequest) {
126                 DelRequest delRequest = (DelRequest) dsmlMessage;
127                 requestItems[i] = new Iti59AuditDataset.RequestItem(trimToNull(delRequest.getRequestID()), EventActionCode.Delete);
128                 enrichAuditItem(requestItems[i], delRequest.getDn());
129 
130             } else {
131                 log.debug("Cannot handle ITI-59 request of type {}", getShortCanonicalName(dsmlMessage, "<null>"));
132             }
133         }
134 
135         auditDataset.setRequestItems(requestItems);
136         return auditDataset;
137     }
138 
139     @Override
140     public boolean enrichAuditDatasetFromResponse(Iti59AuditDataset auditDataset, Object responseObject, AuditContext auditContext) {
141         // check whether there is any need to analyse the response object
142         if (auditDataset.getRequestItems() == null) {
143             log.debug("The request was empty, nothing to audit");
144             return true;
145         }
146 
147         BatchResponse batchResponse = (BatchResponse) responseObject;
148 
149         // if there are no response fragments at all -- set outcome codes of all requests to failure
150         if ((batchResponse == null) || (batchResponse.getBatchResponses() == null)) {
151             for (Iti59AuditDataset.RequestItem requestItem : auditDataset.getRequestItems()) {
152                 if (requestItem != null) {
153                     requestItem.setOutcomeCode(EventOutcomeIndicator.SeriousFailure);
154                 }
155             }
156             return false;
157         }
158 
159         // prepare to pairing
160         Map<String, Object> byRequestId = new HashMap<>();
161         Object[] byNumber = new Object[batchResponse.getBatchResponses().size()];
162 
163         for (int i = 0; i < batchResponse.getBatchResponses().size(); ++i) {
164             Object value = batchResponse.getBatchResponses().get(i).getValue();
165             if (value instanceof LDAPResult) {
166                 LDAPResult ldapResult = (LDAPResult) value;
167                 if (isEmpty(ldapResult.getRequestID())) {
168                     byNumber[i] = ldapResult;
169                 } else {
170                     byRequestId.put(ldapResult.getRequestID(), ldapResult);
171                 }
172             } else if (value instanceof ErrorResponse) {
173                 ErrorResponse errorResponse = (ErrorResponse) value;
174                 if (isEmpty(errorResponse.getRequestID())) {
175                     byNumber[i] = errorResponse;
176                 } else {
177                     byRequestId.put(errorResponse.getRequestID(), errorResponse);
178                 }
179             }
180         }
181 
182         // try to pair requests with responses
183         for (int i = 0; i < auditDataset.getRequestItems().length; ++i) {
184             Iti59AuditDataset.RequestItem requestItem = auditDataset.getRequestItems()[i];
185 
186             if (requestItem != null) {
187                 if (isEmpty(requestItem.getRequestId())) {
188                     setOutcomeCode(
189                             requestItem,
190                             (i < byNumber.length) ? byNumber[i] : null,
191                             "Could not find response for the ID-less ITI-59 request number {}: either too few responses, or wrong type, or has a request ID",
192                             i);
193                 } else {
194                     setOutcomeCode(
195                             requestItem,
196                             byRequestId.get(requestItem.getRequestId()),
197                             "Could not find response for the ITI-59 sub-request with ID '{}': either no ID match, or wrong type",
198                             requestItem.getRequestId());
199                 }
200             }
201         }
202 
203         return true;
204     }
205 
206     private static void setOutcomeCode(Iti59AuditDataset.RequestItem requestItem, Object value, String failureLogMessage, Object... failureLogArgs) {
207         if (value instanceof LDAPResult) {
208             LDAPResult ldapResult = (LDAPResult) value;
209             requestItem.setOutcomeCode((ldapResult.getResultCode() != null) && (ldapResult.getResultCode().getCode() == 0)
210                     ? EventOutcomeIndicator.Success
211                     : EventOutcomeIndicator.SeriousFailure);
212             requestItem.setOutcomeDescription(ldapResult.getErrorMessage());
213         } else if (value instanceof ErrorResponse) {
214             requestItem.setOutcomeCode(EventOutcomeIndicator.SeriousFailure);
215             requestItem.setOutcomeDescription(((ErrorResponse)value).getMessage());
216         } else {
217             requestItem.setOutcomeCode(EventOutcomeIndicator.MajorFailure);
218             log.debug(failureLogMessage, failureLogArgs);
219         }
220     }
221 
222     @Override
223     public EventOutcomeIndicator getEventOutcomeIndicator(Object response) {
224         // is not used because individual outcome codes are determined for each sub-request
225         return null;
226     }
227 
228     @Override
229     public AuditMessage[] makeAuditMessage(AuditContext auditContext, Iti59AuditDataset auditDataset) {
230         // TODO: consider grouping multiple items per ATNA message based on a combination of action code and outcome code
231         return Stream.of(auditDataset.getRequestItems())
232                 .filter(requestItem -> StringUtils.isNotBlank(requestItem.getUid()))
233                 .map(requestItem -> makeAuditMessage(auditContext, auditDataset, requestItem))
234                 .toArray(AuditMessage[]::new);
235     }
236 
237     protected abstract AuditMessage makeAuditMessage(AuditContext auditContext, Iti59AuditDataset auditDataset, Iti59AuditDataset.RequestItem requestItem);
238 
239 }