View Javadoc
1   /*
2    * Copyright 2009 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.xds.core.validate.requests;
17  
18  import org.apache.commons.lang3.StringUtils;
19  import org.openehealth.ipf.commons.core.modules.api.Validator;
20  import org.openehealth.ipf.commons.ihe.xds.*;
21  import org.openehealth.ipf.commons.ihe.xds.core.ebxml.*;
22  import org.openehealth.ipf.commons.ihe.xds.core.metadata.*;
23  import org.openehealth.ipf.commons.ihe.xds.core.validate.*;
24  
25  import java.util.*;
26  import java.util.stream.Collectors;
27  
28  import static org.apache.commons.lang3.Validate.notNull;
29  import static org.openehealth.ipf.commons.ihe.xds.core.metadata.Vocabulary.*;
30  import static org.openehealth.ipf.commons.ihe.xds.core.metadata.Vocabulary.DisplayNameUsage.OPTIONAL;
31  import static org.openehealth.ipf.commons.ihe.xds.core.metadata.Vocabulary.DisplayNameUsage.REQUIRED;
32  import static org.openehealth.ipf.commons.ihe.xds.core.validate.ValidationMessage.*;
33  import static org.openehealth.ipf.commons.ihe.xds.core.validate.ValidatorAssertions.metaDataAssert;
34  
35  /**
36   * Validation of an ebXML object container.
37   * @author Jens Riemschneider
38   */
39  public class ObjectContainerValidator implements Validator<EbXMLObjectContainer, ValidationProfile> {
40  
41      private final SlotLengthAndNameUniquenessValidator slotLengthAndNameUniquenessValidator =
42              new SlotLengthAndNameUniquenessValidator();
43      private final OIDValidator oidValidator = new OIDValidator();
44      private final TimeValidator timeValidator = new TimeValidator();
45      private final TimeValidator timeValidatorSec = new TimeValidator(14);
46      private final XCNValidator xcnValidator = new XCNValidator();
47      private final XONValidator xonValidator = new XONValidator();
48      private final HashValidator hashValidator = new HashValidator();
49      private final NopValidator nopValidator = new NopValidator();
50      private final LanguageCodeValidator languageCodeValidator = new LanguageCodeValidator();
51      private final PositiveNumberValidator positiveNumberValidator = new PositiveNumberValidator();
52      private final PidValidator pidValidator = new PidValidator();
53      private final UriValidator uriValidator = new UriValidator();
54      private final RecipientListValidator recipientListValidator = new RecipientListValidator();
55      private final CXValidator cxValidatorRequiredAA = new CXValidator(true);
56      private final CXValidator cxValidatorOptionalAA = new CXValidator(false);
57      private final XTNValidator xtnValidator = new XTNValidator();
58      private final CXiValidator cxiValidator = new CXiValidator();
59      private final UUIDValidator uuidValidator = new UUIDValidator();
60  
61      private final SlotValueValidation[] authorValidations = new SlotValueValidation[] {
62          new SlotValueValidation(SLOT_NAME_AUTHOR_PERSON, xcnValidator, 0, 1),
63          new SlotValueValidation(SLOT_NAME_AUTHOR_INSTITUTION, xonValidator, 0, Integer.MAX_VALUE),
64          new SlotValueValidation(SLOT_NAME_AUTHOR_ROLE, cxValidatorOptionalAA, 0, Integer.MAX_VALUE),
65          new SlotValueValidation(SLOT_NAME_AUTHOR_SPECIALTY, cxValidatorOptionalAA, 0, Integer.MAX_VALUE),
66          new SlotValueValidation(SLOT_NAME_AUTHOR_TELECOM, xtnValidator, 0, Integer.MAX_VALUE)};
67      
68      private final SlotValueValidation[] codingSchemeValidations = new SlotValueValidation[] {
69          new SlotValueValidation(SLOT_NAME_CODING_SCHEME, nopValidator)};
70  
71  
72      private List<RegistryObjectValidator> documentEntrySlotValidators(ValidationProfile profile, boolean onDemandProvided, boolean limitedMetadata) {
73          List<RegistryObjectValidator> validators = new ArrayList<>();
74          boolean isContinuaHRN = (profile == CONTINUA_HRN.Interactions.ITI_41);
75          DisplayNameUsage requiredOnlyForContinuaHRN = isContinuaHRN ? REQUIRED : OPTIONAL;
76  
77          boolean isOnDemand    = (profile == XDS.Interactions.ITI_61) ||
78                                  (profile.isQuery() && onDemandProvided);
79  
80          boolean needHashAndSize = (! isOnDemand) &&
81                  (isContinuaHRN || profile.isQuery()
82                          || (profile == XDS.Interactions.ITI_42)
83                          || (profile == XDM.Interactions.ITI_41)
84                  );
85  
86          Collections.addAll(validators,
87              new SlotValueValidation(SLOT_NAME_CREATION_TIME, timeValidator,
88                      (limitedMetadata || isOnDemand) ? 0 : 1,
89                      isOnDemand ? 0 : 1),
90              new SlotValueValidation(SLOT_NAME_SERVICE_START_TIME, timeValidator, 0, 1),
91              new SlotValueValidation(SLOT_NAME_SERVICE_STOP_TIME, timeValidator, 0, 1),
92              new SlotValueValidation(SLOT_NAME_HASH, hashValidator,
93                      needHashAndSize ? 1 : 0,
94                      isOnDemand ? 0 : 1),
95              new SlotValueValidation(SLOT_NAME_LANGUAGE_CODE, languageCodeValidator, limitedMetadata ? 0 : 1, 1),
96              new SlotValueValidation(SLOT_NAME_LEGAL_AUTHENTICATOR, xcnValidator, 0,
97                      (isOnDemand || isContinuaHRN) ? 0 : 1),
98              new SlotValueValidation(SLOT_NAME_SIZE, positiveNumberValidator,
99                      needHashAndSize ? 1 : 0,
100                     isOnDemand ? 0 : 1),
101             new SlotValueValidation(SLOT_NAME_SOURCE_PATIENT_ID, cxValidatorRequiredAA,
102                     (profile.isEbXml30Based() && (! limitedMetadata)) ? 1 : 0, 1),
103             new SlotValueValidation(SLOT_NAME_SOURCE_PATIENT_INFO, pidValidator,
104                     isContinuaHRN ? 1 : 0, Integer.MAX_VALUE),
105             new SlotValueValidation(SLOT_NAME_REFERENCE_ID_LIST, cxiValidator, 0, Integer.MAX_VALUE),
106             new SlotValueValidation(SLOT_NAME_URI, uriValidator, 0, 1),
107             new AuthorClassificationValidation(DOC_ENTRY_AUTHOR_CLASS_SCHEME, authorValidations),
108             new ClassificationValidation(DOC_ENTRY_CLASS_CODE_CLASS_SCHEME,
109                     limitedMetadata ? 0 : 1, 1, requiredOnlyForContinuaHRN, codingSchemeValidations),
110             new ClassificationValidation(DOC_ENTRY_CONFIDENTIALITY_CODE_CLASS_SCHEME,
111                     limitedMetadata ? 0 : 1, Integer.MAX_VALUE,
112                     requiredOnlyForContinuaHRN, codingSchemeValidations),
113             new ClassificationValidation(DOC_ENTRY_EVENT_CODE_CLASS_SCHEME, 0, Integer.MAX_VALUE,
114                     requiredOnlyForContinuaHRN, codingSchemeValidations),
115             new ClassificationValidation(DOC_ENTRY_FORMAT_CODE_CLASS_SCHEME,
116                     limitedMetadata ? 0 : 1, 1, OPTIONAL, codingSchemeValidations),
117             new ClassificationValidation(DOC_ENTRY_HEALTHCARE_FACILITY_TYPE_CODE_CLASS_SCHEME,
118                     limitedMetadata ? 0 : 1, 1, requiredOnlyForContinuaHRN, codingSchemeValidations),
119             new ClassificationValidation(DOC_ENTRY_PRACTICE_SETTING_CODE_CLASS_SCHEME,
120                     limitedMetadata ? 0 : 1, 1, requiredOnlyForContinuaHRN, codingSchemeValidations),
121             new ClassificationValidation(DOC_ENTRY_TYPE_CODE_CLASS_SCHEME,
122                     limitedMetadata ? 0 : 1, 1, requiredOnlyForContinuaHRN, codingSchemeValidations));
123 
124         if (! limitedMetadata) {
125             validators.add(new ExternalIdentifierValidation(DOC_ENTRY_PATIENT_ID_EXTERNAL_ID, cxValidatorRequiredAA));
126         }
127 
128         if ((profile.getInteractionId() == XDS.Interactions.ITI_42) || isOnDemand || profile.isQuery()) {
129             validators.add(new SlotValueValidation(SLOT_NAME_REPOSITORY_UNIQUE_ID, oidValidator));
130         }
131         return validators;
132     }
133 
134     private List<RegistryObjectValidator> getSubmissionSetSlotValidations(ValidationProfile profile, boolean limitedMetadata) {
135         boolean isContinuaHRN = (profile == CONTINUA_HRN.Interactions.ITI_41);
136         DisplayNameUsage requiredOnlyForContinuaHRN = isContinuaHRN ? REQUIRED : OPTIONAL;
137 
138         List<RegistryObjectValidator> validators = new ArrayList<>();
139         Collections.addAll(validators,
140                 new SlotValidation(SLOT_NAME_INTENDED_RECIPIENT, recipientListValidator),
141                 new SlotValueValidation(SLOT_NAME_SUBMISSION_TIME, timeValidator),
142                 new AuthorClassificationValidation(SUBMISSION_SET_AUTHOR_CLASS_SCHEME, authorValidations),
143                 new ClassificationValidation(SUBMISSION_SET_CONTENT_TYPE_CODE_CLASS_SCHEME,
144                         limitedMetadata ? 0 : 1, 1, requiredOnlyForContinuaHRN, codingSchemeValidations),
145                 new ExternalIdentifierValidation(SUBMISSION_SET_SOURCE_ID_EXTERNAL_ID, oidValidator));
146         if (! limitedMetadata) {
147             validators.add(new ExternalIdentifierValidation(SUBMISSION_SET_PATIENT_ID_EXTERNAL_ID, cxValidatorRequiredAA));
148         }
149         return validators;
150     }
151 
152 
153     private List<RegistryObjectValidator> getFolderSlotValidations(boolean limitedMetadata) {
154         List<RegistryObjectValidator> validators = new ArrayList<>();
155         Collections.addAll(validators,
156                 new SlotValueValidation(SLOT_NAME_LAST_UPDATE_TIME, timeValidatorSec, 0, 1),
157                 new ClassificationValidation(FOLDER_CODE_LIST_CLASS_SCHEME,
158                         limitedMetadata ? 0 : 1, Integer.MAX_VALUE, OPTIONAL, codingSchemeValidations));
159         if (! limitedMetadata) {
160             validators.add(new ExternalIdentifierValidation(FOLDER_PATIENT_ID_EXTERNAL_ID, cxValidatorRequiredAA));
161         }
162         return validators;
163     }
164 
165     private boolean checkLimitedMetadata(EbXMLRegistryObject object, String limitedMetadataClassScheme, ValidationProfile profile) {
166         boolean limitedMetadata = ! object.getClassifications(limitedMetadataClassScheme).isEmpty();
167         if (limitedMetadata) {
168             metaDataAssert((profile == XDM.Interactions.ITI_41) || (profile == XDR.Interactions.ITI_41),
169                     ValidationMessage.LIMITED_METADATA_PROHIBITED, object.getId());
170         } else {
171             metaDataAssert(profile != XDM.Interactions.ITI_41,
172                     ValidationMessage.LIMITED_METADATA_REQUIRED, object.getId());
173         }
174         return limitedMetadata;
175     }
176 
177 
178     @Override
179     public void validate(EbXMLObjectContainer container, ValidationProfile profile) {
180         notNull(container, "container cannot be null");
181         notNull(profile, "profile must be set");
182 
183         slotLengthAndNameUniquenessValidator.validateContainer(container);
184     
185         // Note: The order of these checks is important!        
186         validateSubmissionSet(container, profile);
187         if (!profile.isQuery()) {
188             validateUniquenessOfUUIDs(container);
189             validateUniqueIds(container);
190         }
191         validateAssociations(container, profile);
192         validateDocumentEntries(container, profile);
193         validateFolders(container, profile);
194         if (!profile.isQuery()) {
195             validatePatientIdsAreIdentical(container);
196         }
197     }
198 
199     private void validateFolders(EbXMLObjectContainer container, ValidationProfile profile) throws XDSMetaDataException {
200         Set<String> logicalIds = new HashSet<>();
201         for (EbXMLRegistryPackage folder : container.getRegistryPackages(FOLDER_CLASS_NODE)) {
202             boolean limitedMetadata = checkLimitedMetadata(folder, FOLDER_LIMITED_METADATA_CLASS_SCHEME, profile);
203             runValidations(folder, getFolderSlotValidations(limitedMetadata));
204 
205             AvailabilityStatus status = folder.getStatus();
206             if (profile.isQuery() || status != null) {
207                 metaDataAssert(status == AvailabilityStatus.APPROVED,
208                         FOLDER_INVALID_AVAILABILITY_STATUS, status);
209             }
210 
211             metaDataAssert(StringUtils.isBlank(folder.getLid()) || logicalIds.add(folder.getLid()),
212                     LOGICAL_ID_SAME, folder.getLid());
213 
214             LocalizedString name = folder.getName();
215             metaDataAssert(limitedMetadata || ((name != null) && (name.getValue() != null)),
216                     MISSING_FOLDER_NAME, folder.getId());
217 
218             if ((profile == XDS.Interactions.ITI_57) || (profile == XCMU.Interactions.CH_XCMU)) {
219                 validateUpdateObject(folder, container);
220             }
221         }
222     }
223 
224     private void validateSubmissionSet(EbXMLObjectContainer container, ValidationProfile profile) throws XDSMetaDataException {
225         List<EbXMLRegistryPackage> submissionSets = container.getRegistryPackages(SUBMISSION_SET_CLASS_NODE);
226         if (!profile.isQuery()) {
227             metaDataAssert(submissionSets.size() == 1, EXACTLY_ONE_SUBMISSION_SET_MUST_EXIST);
228         }
229     
230         for (EbXMLRegistryPackage submissionSet : submissionSets) {
231             boolean limitedMetadata = checkLimitedMetadata(submissionSet, SUBMISSION_SET_LIMITED_METADATA_CLASS_SCHEME, profile);
232             runValidations(submissionSet, getSubmissionSetSlotValidations(profile, limitedMetadata));
233 
234             AvailabilityStatus status = submissionSet.getStatus();
235             if (profile.isQuery() || (status != null)) {
236                 metaDataAssert(status == AvailabilityStatus.APPROVED,
237                         SUBMISSION_SET_INVALID_AVAILABILITY_STATUS, status);
238             }
239         }
240     }
241 
242     private void validateDocumentEntries(EbXMLObjectContainer container, ValidationProfile profile) throws XDSMetaDataException {
243         Set<String> logicalIds = new HashSet<>();
244         for (EbXMLExtrinsicObject docEntry : container.getExtrinsicObjects(DocumentEntryType.STABLE_OR_ON_DEMAND)) {
245             boolean limitedMetadata = checkLimitedMetadata(docEntry, DOC_ENTRY_LIMITED_METADATA_CLASS_SCHEME, profile);
246 
247             boolean onDemandExpected = (profile == XDS.Interactions.ITI_61);
248             boolean onDemandProvided = DocumentEntryType.ON_DEMAND.getUuid().equals(docEntry.getObjectType());
249             metaDataAssert(profile.isQuery() || (onDemandExpected == onDemandProvided),
250                     WRONG_DOCUMENT_ENTRY_TYPE, docEntry.getObjectType());
251 
252             runValidations(docEntry, documentEntrySlotValidators(profile, onDemandProvided, limitedMetadata));
253 
254             if (profile.isQuery()) {
255                 AvailabilityStatus status = docEntry.getStatus();
256                 metaDataAssert(status == AvailabilityStatus.APPROVED || status == AvailabilityStatus.DEPRECATED,
257                         DOC_ENTRY_INVALID_AVAILABILITY_STATUS, status);
258             }
259 
260             LocalizedString name = docEntry.getName();
261             if (name != null && name.getValue() != null) {
262                 metaDataAssert("UTF8".equals(name.getCharset()) || "UTF-8".equals(name.getCharset()),
263                         INVALID_TITLE_ENCODING, name.getCharset());
264                 
265                 metaDataAssert(name.getValue().length() <= 128,
266                         TITLE_TOO_LONG, name.getValue());
267             }
268 
269             boolean attachmentExpected = (profile.getInteractionId() == XCF.Interactions.ITI_63);
270             boolean attachmentProvided = (docEntry.getDataHandler() != null);
271             metaDataAssert(attachmentProvided == attachmentExpected,
272                     attachmentExpected ? MISSING_DOCUMENT_FOR_DOC_ENTRY : DOCUMENT_NOT_ALLOWED_IN_DOC_ENTRY,
273                     docEntry.getId());
274 
275             metaDataAssert(profile.isQuery() || StringUtils.isBlank(docEntry.getLid()) || logicalIds.add(docEntry.getLid()),
276                     LOGICAL_ID_SAME, docEntry.getLid());
277 
278             if ((profile == XDS.Interactions.ITI_57) || (profile == XCMU.Interactions.CH_XCMU)) {
279                 validateUpdateObject(docEntry, container);
280             }
281         }
282     }
283 
284     private void runValidations(EbXMLRegistryObject obj, List<RegistryObjectValidator> validations) throws XDSMetaDataException {
285         for (RegistryObjectValidator validation : validations) {
286             validation.validate(obj);
287         }
288     }
289 
290     private void validateUniqueIds(EbXMLObjectContainer container) throws XDSMetaDataException {
291         validateUniqueIds(container.getExtrinsicObjects(DocumentEntryType.STABLE_OR_ON_DEMAND), DOC_ENTRY_UNIQUE_ID_EXTERNAL_ID);
292         validateUniqueIds(container.getRegistryPackages(FOLDER_CLASS_NODE), FOLDER_UNIQUE_ID_EXTERNAL_ID);
293         validateUniqueIds(container.getRegistryPackages(SUBMISSION_SET_CLASS_NODE), SUBMISSION_SET_UNIQUE_ID_EXTERNAL_ID);
294     }
295 
296     private void validateUniqueIds(List<? extends EbXMLRegistryObject> objects, String scheme) throws XDSMetaDataException {
297         for (EbXMLRegistryObject obj : objects) {
298             String uniqueId = obj.getExternalIdentifierValue(scheme);
299             metaDataAssert(uniqueId != null, UNIQUE_ID_MISSING);
300             metaDataAssert(uniqueId.length() <= 128, UNIQUE_ID_TOO_LONG);
301         }
302     }
303 
304     private void validateUniquenessOfUUIDs(EbXMLObjectContainer container) throws XDSMetaDataException {
305         Set<String> uuids = new HashSet<>();
306         addUUIDs(container.getAssociations(), uuids);
307         addUUIDs(container.getExtrinsicObjects(), uuids);
308         addUUIDs(container.getRegistryPackages(), uuids);
309     }
310 
311     private void addUUIDs(List<? extends EbXMLRegistryObject> objects, Set<String> uuids) throws XDSMetaDataException {
312         for (EbXMLRegistryObject obj : objects) {
313             String uuid = obj.getId();
314             if (uuid != null) {
315                 metaDataAssert(!uuids.contains(uuid), UUID_NOT_UNIQUE);
316                 uuids.add(uuid);
317             }
318         }
319     }
320 
321     private void validatePatientIdsAreIdentical(EbXMLObjectContainer container) throws XDSMetaDataException {
322         List<EbXMLRegistryPackage> submissionSets = container.getRegistryPackages(SUBMISSION_SET_CLASS_NODE);
323         EbXMLRegistryPackage submissionSet = submissionSets.get(0);
324     
325         String patientId = submissionSet.getExternalIdentifierValue(SUBMISSION_SET_PATIENT_ID_EXTERNAL_ID);
326     
327         for (EbXMLExtrinsicObject docEntry : container.getExtrinsicObjects(DocumentEntryType.STABLE_OR_ON_DEMAND)) {
328             String patientIdDocEntry = docEntry.getExternalIdentifierValue(DOC_ENTRY_PATIENT_ID_EXTERNAL_ID);
329             metaDataAssert(StringUtils.equals(patientId, patientIdDocEntry), DOC_ENTRY_PATIENT_ID_WRONG);
330         }
331         
332         for (EbXMLRegistryPackage folder : container.getRegistryPackages(FOLDER_CLASS_NODE)) {
333             String patientIdFolder = folder.getExternalIdentifierValue(FOLDER_PATIENT_ID_EXTERNAL_ID);
334             metaDataAssert(StringUtils.equals(patientId, patientIdFolder), FOLDER_PATIENT_ID_WRONG);
335         }
336     }
337 
338     private void validateAssociations(EbXMLObjectContainer container, ValidationProfile profile) throws XDSMetaDataException {
339         Set<String> logicalIds = new HashSet<>();
340         Set<String> docEntryIds = container.getExtrinsicObjects(DocumentEntryType.STABLE_OR_ON_DEMAND).stream()
341                 .filter(docEntry -> docEntry.getId() != null)
342                 .map(EbXMLRegistryObject::getId)
343                 .collect(Collectors.toSet());
344         Set<String> submissionSetIds = container.getRegistryPackages(SUBMISSION_SET_CLASS_NODE).stream()
345                 .map(EbXMLRegistryObject::getId)
346                 .collect(Collectors.toSet());
347         Set<String> associationIds = new HashSet<>();
348         boolean hasSubmitAssociationType = false;
349         for (EbXMLAssociation association : container.getAssociations()) {
350             //metaDataAssert(StringUtils.isNotEmpty(association.getId()), ASSOCIATION_ID_MISSING);
351             associationIds.add(association.getId());
352 
353             AssociationType type = association.getAssociationType();
354             metaDataAssert(type != null, INVALID_ASSOCIATION_TYPE);
355             hasSubmitAssociationType = hasSubmitAssociationType
356                     || (type == AssociationType.SUBMIT_ASSOCIATION)
357                     || (type == AssociationType.UPDATE_AVAILABILITY_STATUS);
358 
359             metaDataAssert(StringUtils.isBlank(association.getLid()) || logicalIds.add(association.getLid()),
360                     LOGICAL_ID_SAME, association.getLid());
361         }
362 
363         for (EbXMLAssociation association : container.getAssociations()) {
364             switch (association.getAssociationType()) {
365                 case HAS_MEMBER:
366                     boolean isSubmissionSetToDocEntry =
367                             submissionSetIds.contains(association.getSource()) && docEntryIds.contains(association.getTarget());
368                     validateAssociation(association, docEntryIds, profile, isSubmissionSetToDocEntry);
369                     break;
370 
371                 case IS_SNAPSHOT_OF:
372                     if (!profile.isQuery()) {
373                         EbXMLExtrinsicObject sourceDocEntry = getExtrinsicObject(
374                                 container, association.getSource(), DocumentEntryType.STABLE.getUuid());
375                         metaDataAssert(sourceDocEntry != null, MISSING_SNAPSHOT_ASSOCIATION, "sourceObject", association.getSource());
376                         metaDataAssert(hasSubmitAssociationType || docEntryIds.contains(association.getSource()), SOURCE_UUID_NOT_FOUND);
377                     }
378                     break;
379 
380                 case UPDATE_AVAILABILITY_STATUS:
381                     if (!profile.isQuery()) {
382                         metaDataAssert(submissionSetIds.contains(association.getSource()), MISSING_SUBMISSION_SET, association.getSource());
383                         metaDataAssert(association.getOriginalStatus() != null, MISSING_ORIGINAL_STATUS);
384                         metaDataAssert(association.getNewStatus() != null, MISSING_NEW_STATUS);
385                     }
386                     break;
387 
388                 case SUBMIT_ASSOCIATION:
389                     if (!profile.isQuery()) {
390                         metaDataAssert(submissionSetIds.contains(association.getSource()), MISSING_SUBMISSION_SET, association.getSource());
391                         metaDataAssert(associationIds.contains(association.getTarget()), MISSING_ASSOCIATION, association.getTarget());
392                     }
393                     break;
394 
395                 default:
396                     metaDataAssert(profile.isQuery() || hasSubmitAssociationType || docEntryIds.contains(association.getSource()), SOURCE_UUID_NOT_FOUND);
397             }
398         }
399     }
400 
401     private void validateAssociation(EbXMLAssociation association, Set<String> docEntryIds,
402                                      ValidationProfile profile, boolean isSubmissionSetToDocEntry) throws XDSMetaDataException {
403         metaDataAssert(association.getSingleClassification(Vocabulary.ASSOCIATION_DOC_CODE_CLASS_SCHEME) == null,
404                 DOC_CODE_NOT_ALLOWED_ON_HAS_MEMBER);
405 
406         List<String> slotValues = association.getSlotValues(SLOT_NAME_SUBMISSION_SET_STATUS);
407         if (isSubmissionSetToDocEntry){
408             metaDataAssert(!slotValues.isEmpty(), SUBMISSION_SET_STATUS_MANDATORY);
409         }
410         if (!slotValues.isEmpty()) {
411             metaDataAssert(slotValues.size() == 1, TOO_MANY_SUBMISSION_SET_STATES);
412 
413             AssociationLabel status = AssociationLabel.fromOpcode(slotValues.get(0));
414             metaDataAssert(status != null, INVALID_SUBMISSION_SET_STATUS);
415 
416             if (status == AssociationLabel.ORIGINAL && !profile.isQuery()) {
417                 metaDataAssert(docEntryIds.contains(association.getTarget()), MISSING_ORIGINAL);
418             }
419         }
420     }
421 
422     private EbXMLExtrinsicObject getExtrinsicObject(EbXMLObjectContainer container, String docEntryId, String... objectTypes) {
423         for (EbXMLExtrinsicObject docEntry : container.getExtrinsicObjects(objectTypes)) {
424             if (docEntry.getId() != null && docEntry.getId().equals(docEntryId)) {
425                 return docEntry;
426             }
427         }
428         return null;
429     }
430 
431     private EbXMLRegistryPackage getRegistryPackage(EbXMLObjectContainer container, String submissionSetId, String classificationNode) {
432         for (EbXMLRegistryPackage registryPackage : container.getRegistryPackages(classificationNode)) {
433             if (registryPackage.getId() != null && registryPackage.getId().equals(submissionSetId)) {
434                 return registryPackage;
435             }
436         }
437         return null;
438     }
439 
440     private void validateUpdateObject(EbXMLRegistryObject registryObject, EbXMLObjectContainer container) {
441         metaDataAssert(registryObject.getLid() != null, LOGICAL_ID_MISSING);
442         uuidValidator.validate(registryObject.getLid());
443         metaDataAssert(!registryObject.getLid().equals(registryObject.getId()), LOGICAL_ID_EQUALS_ENTRY_UUID,
444                 registryObject.getLid(), registryObject.getId());
445 
446         boolean foundHasMemberAssociation = false;
447         for (EbXMLAssociation association : container.getAssociations()) {
448             if (association.getAssociationType() == AssociationType.HAS_MEMBER
449                 && association.getTarget().equals(registryObject.getId())
450                 && (getRegistryPackage(container, association.getSource(), SUBMISSION_SET_CLASS_NODE) != null))
451             {
452                 metaDataAssert(association.getPreviousVersion() != null, MISSING_PREVIOUS_VERSION);
453                 foundHasMemberAssociation = true;
454             }
455         }
456         metaDataAssert(foundHasMemberAssociation, MISSING_HAS_MEMBER_ASSOCIATION, registryObject.getId());
457     }
458 }