/* * 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. * * Contributions from 2013-2017 where performed either by US government * employees, or under US Veterans Health Administration contracts. * * US Veterans Health Administration contributions by government employees * are work of the U.S. Government and are not subject to copyright * protection in the United States. Portions contributed by government * employees are USGovWork (17USC ยง105). Not subject to copyright. * * Contribution by contractors to the US Veterans Health Administration * during this period are contractually contributed under the * Apache License, Version 2.0. * * See: https://www.usa.gov/government-works * * Contributions prior to 2013: * * Copyright (C) International Health Terminology Standards Development Organisation. * Licensed under the Apache License, Version 2.0. * */ package sh.isaac.api.component.sememe.version.dynamicSememe; //~--- JDK imports ------------------------------------------------------------ import java.security.InvalidParameterException; import java.util.Locale; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; //~--- non-JDK imports -------------------------------------------------------- import org.apache.commons.lang3.StringUtils; import sh.isaac.api.Get; import sh.isaac.api.LookupService; import sh.isaac.api.chronicle.ObjectChronologyType; import sh.isaac.api.component.sememe.SememeChronology; import sh.isaac.api.component.sememe.SememeType; import sh.isaac.api.component.sememe.version.SememeVersion; import sh.isaac.api.component.sememe.version.dynamicSememe.dataTypes.DynamicSememeArray; import sh.isaac.api.component.sememe.version.dynamicSememe.dataTypes.DynamicSememeDouble; import sh.isaac.api.component.sememe.version.dynamicSememe.dataTypes.DynamicSememeFloat; import sh.isaac.api.component.sememe.version.dynamicSememe.dataTypes.DynamicSememeInteger; import sh.isaac.api.component.sememe.version.dynamicSememe.dataTypes.DynamicSememeLong; import sh.isaac.api.component.sememe.version.dynamicSememe.dataTypes.DynamicSememeNid; import sh.isaac.api.component.sememe.version.dynamicSememe.dataTypes.DynamicSememeSequence; import sh.isaac.api.component.sememe.version.dynamicSememe.dataTypes.DynamicSememeString; import sh.isaac.api.component.sememe.version.dynamicSememe.dataTypes.DynamicSememeUUID; import sh.isaac.api.coordinate.StampCoordinate; import sh.isaac.api.coordinate.TaxonomyCoordinate; import sh.isaac.api.util.Interval; import sh.isaac.api.util.NumericUtils; //~--- enums ------------------------------------------------------------------ /** * {@link DynamicSememeValidatorType} * * The acceptable validatorDefinitionData object type(s) for the following fields: * {@link DynamicSememeValidatorType#LESS_THAN} * {@link DynamicSememeValidatorType#GREATER_THAN} * {@link DynamicSememeValidatorType#LESS_THAN_OR_EQUAL} * {@link DynamicSememeValidatorType#GREATER_THAN_OR_EQUAL} * * are one of ( {@link DynamicSememeInteger}, {@link DynamicSememeLong}, {@link DynamicSememeFloat}, {@link DynamicSememeDouble}) * * {@link DynamicSememeValidatorType#INTERVAL} - Should be a {@link DynamicSememeString} with valid interval notation - such as "[4,6)" * * {@link DynamicSememeValidatorType#REGEXP} - Should be a {@link DynamicSememeString} with valid regular expression, per * http://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html * * And for the following two: * {@link DynamicSememeValidatorType#IS_CHILD_OF} * {@link DynamicSememeValidatorType#IS_KIND_OF} * The validatorDefinitionData should be either an {@link DynamicSememeNid} or {@link DynamicSememeSequence} or a {@link DynamicSememeUUID}. * * For {@link DynamicSememeValidatorType#COMPONENT_TYPE} the validator definition data should be a {@link DynamicSememeArray <DynamicSememeString>} * where position 0 is a string constant parseable by {@link ObjectChronologyType#parse(String)}. Postion 1 is optional, and is only applicable when * position 0 is {@link ObjectChronologyType#SEMEME} - in which case - the value should be parsable by {@link SememeType#parse(String)} * * For {@link DynamicSememeValidatorType#EXTERNAL} the validatorDefinitionData should be a {@link DynamicSememeArray <DynamicSememeString>} * which contains (in the first position of the array) the name of an HK2 named service which implements {@link DynamicSememeExternalValidator} * the name that you provide should be the value of the '@Name' annotation within the class which implements the ExternalValidatorBI class. * This code will request that implementation (by name) and pass the validation call to it. * * Optionally, the validatorDefinitionData more that one {@link DynamicSememeString} in the array - only the first position of the array * will be considered as the '@Name' to be used for the HK2 lookup. All following data is ignored, and may be used by the external validator * implementation to store other data. For example, if the validatorDefinitionData {@link DynamicSememeArray <DynamicSememeString>} * contains an array of strings such as new String[]{"mySuperRefexValidator", "somespecialmappingdata", "some other mapping data"} * then the following HK2 call will be made to locate the validator implementation (and validate): * <pre> * ExternalValidatorBI validator = LookupService.get().getService(ExternalValidatorBI.class, "mySuperRefexValidator"); * return validator.validate(userData, validatorDefinitionData, viewCoordinate); * </pre> * * @author <a href="mailto:daniel.armbrust.list@gmail.com">Dan Armbrust</a> */ public enum DynamicSememeValidatorType { /** The less than. */ LESS_THAN("<"), /** The greater than. */ GREATER_THAN(">"), /** The less than or equal. */ LESS_THAN_OR_EQUAL("<="), /** The greater than or equal. */ GREATER_THAN_OR_EQUAL(">="), /** The interval. */ // Standard math stuff INTERVAL("Interval"), /** The regexp. */ // math interval notation - such as [5,10) REGEXP("Regular Expression"), /** The external. */ // http://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html EXTERNAL("External"), /** The is child of. */ // see class docs above - implemented by an ExternalValidatorBI IS_CHILD_OF("Is Child Of"), /** The is kind of. */ // OTF is child of - which only includes immediate (not recursive) children on the 'Is A' relationship. IS_KIND_OF("Is Kind Of"), /** The component type. */ // OTF kind of - which is child of - but recursive, and self (heart disease is a kind-of heart disease); COMPONENT_TYPE("Component Type Restriction"), /** The unknown. */ // specify which type of nid can be put into a UUID or nid column UNKNOWN( "Unknown"); // Not a real validator, only exists to allow GUI convenience, or potentially store other validator data that we don't support in OTF // but we may need to store / retreive /** The logger. */ private static final Logger logger = Logger.getLogger(DynamicSememeValidatorType.class.getName()); //~--- fields -------------------------------------------------------------- /** The display name. */ private final String displayName; //~--- constructors -------------------------------------------------------- /** * Instantiates a new dynamic sememe validator type. * * @param displayName the display name */ private DynamicSememeValidatorType(String displayName) { this.displayName = displayName; } //~--- methods ------------------------------------------------------------- /** * Parses the. * * @param nameOrEnumId the name or enum id * @param exceptionOnParseFail the exception on parse fail * @return the dynamic sememe validator type */ public static DynamicSememeValidatorType parse(String nameOrEnumId, boolean exceptionOnParseFail) { if (nameOrEnumId == null) { return null; } final String clean = nameOrEnumId.toLowerCase(Locale.ENGLISH) .trim(); if (StringUtils.isBlank(clean)) { return null; } try { final int i = Integer.parseInt(clean); // enumId return DynamicSememeValidatorType.values()[i]; } catch (final NumberFormatException e) { for (final DynamicSememeValidatorType x: DynamicSememeValidatorType.values()) { if (x.displayName.equalsIgnoreCase(clean) || x.name().toLowerCase().equals(clean)) { return x; } } } if (exceptionOnParseFail) { throw new InvalidParameterException("The value " + nameOrEnumId + " could not be parsed as a DynamicSememeValidatorType"); } else { return UNKNOWN; } } /** * Parses the. * * @param nameOrEnumId the name or enum id * @param exceptionOnParseFail the exception on parse fail * @return the dynamic sememe validator type[] */ public static DynamicSememeValidatorType[] parse(String[] nameOrEnumId, boolean exceptionOnParseFail) { if (nameOrEnumId == null) { return null; } final DynamicSememeValidatorType[] temp = new DynamicSememeValidatorType[nameOrEnumId.length]; { for (int i = 0; i < nameOrEnumId.length; i++) { temp[i] = parse(nameOrEnumId[i], exceptionOnParseFail); } } return temp; } /** * These are all defined from the perspective of the userData - so for passesValidator to return true - * userData must be LESS_THAN validatorDefinitionData, for example. * * @param userData the user data * @param validatorDefinitionData the validator definition data * @param sc The Stamp Coordinate - not needed for some types of validations. Null allowed when unneeded (for math based tests, for example) * {@link IllegalArgumentException} will be thrown if the coordinate was required for the validator (but it wasn't supplied) * @param tc The Taxonomy Coordinate - not needed for some types of validations. Null allowed when unneeded (for math based tests, for example) * {@link IllegalArgumentException} will be thrown if the coordinate was required for the validator (but it wasn't supplied) * @return true, if successful * @throws IllegalArgumentException the illegal argument exception */ @SuppressWarnings("unchecked") public boolean passesValidator(DynamicSememeData userData, DynamicSememeData validatorDefinitionData, StampCoordinate sc, TaxonomyCoordinate tc) throws IllegalArgumentException { if (validatorDefinitionData == null) { throw new RuntimeException("The validator definition data is required"); } if (userData instanceof DynamicSememeArray) { // If the user data is an array, unwrap, and validate each. for (final DynamicSememeData userDataItem: ((DynamicSememeArray<?>) userData).getDataArray()) { if (!passesValidator(userDataItem, validatorDefinitionData, sc, tc)) { return false; } } return true; } if (this == DynamicSememeValidatorType.EXTERNAL) { DynamicSememeExternalValidator validator = null; DynamicSememeString[] valNameInfo = null; DynamicSememeArray<DynamicSememeString> stringValidatorDefData = null; String valName = null; if (validatorDefinitionData != null) { stringValidatorDefData = (DynamicSememeArray<DynamicSememeString>) validatorDefinitionData; valNameInfo = stringValidatorDefData.getDataArray(); } if ((valNameInfo != null) && (valNameInfo.length > 0)) { valName = valNameInfo[0].getDataString(); logger.fine("Looking for an ExternalValidatorBI with the name of '" + valName + "'"); validator = LookupService.get() .getService(DynamicSememeExternalValidator.class, valName); } else { logger.severe( "An external validator type was specified, but no DynamicSememeExternalValidatorBI 'name' was provided. API misuse!"); } if (validator == null) { throw new RuntimeException( "Could not locate an implementation of DynamicSememeExternalValidatorBI with the requested name of '" + valName + "'"); } return validator.validate(userData, stringValidatorDefData, sc, tc); } else if (this == DynamicSememeValidatorType.REGEXP) { try { if (userData == null) { return false; } return Pattern.matches(((DynamicSememeString) validatorDefinitionData).getDataString(), userData.getDataObject() .toString()); } catch (final Exception e) { throw new RuntimeException("The specified validator data object was not a valid regular expression: " + e.getMessage()); } } else if ((this == DynamicSememeValidatorType.IS_CHILD_OF) || (this == DynamicSememeValidatorType.IS_KIND_OF)) { try { int childId; int parentId; if (userData instanceof DynamicSememeUUID) { childId = Get.identifierService() .getNidForUuids(((DynamicSememeUUID) userData).getDataUUID()); } else if (userData instanceof DynamicSememeNid) { childId = ((DynamicSememeNid) userData).getDataNid(); } else if (userData instanceof DynamicSememeSequence) { childId = ((DynamicSememeSequence) userData).getDataSequence(); } else { throw new RuntimeException("Userdata is invalid for a IS_CHILD_OF or IS_KIND_OF comparison"); } if (validatorDefinitionData instanceof DynamicSememeUUID) { parentId = Get.identifierService() .getNidForUuids(((DynamicSememeUUID) validatorDefinitionData).getDataUUID()); } else if (validatorDefinitionData instanceof DynamicSememeNid) { parentId = ((DynamicSememeNid) validatorDefinitionData).getDataNid(); } else if (userData instanceof DynamicSememeSequence) { parentId = ((DynamicSememeSequence) validatorDefinitionData).getDataSequence(); } else { throw new RuntimeException( "Validator DefinitionData is invalid for a IS_CHILD_OF or IS_KIND_OF comparison"); } if (this == DynamicSememeValidatorType.IS_CHILD_OF) { if (tc == null) { throw new IllegalArgumentException("A taxonomy coordinate must be provided to evaluate IS_CHILD_OF"); } return Get.taxonomyService() .isChildOf(childId, parentId, tc); } else { if (tc == null) { return Get.taxonomyService() .wasEverKindOf(childId, parentId); } else { return Get.taxonomyService() .isKindOf(childId, parentId, tc); } } } catch (final IllegalArgumentException e) { throw e; } catch (final Exception e) { logger.log(Level.WARNING, "Failure executing validator", e); throw new RuntimeException("Failure executing validator", e); } } else if (this == DynamicSememeValidatorType.COMPONENT_TYPE) { try { int nid; if (userData instanceof DynamicSememeUUID) { final DynamicSememeUUID uuid = (DynamicSememeUUID) userData; if (!Get.identifierService() .hasUuid(uuid.getDataUUID())) { throw new RuntimeException( "The specified UUID can not be found in the database, so the validator cannot execute"); } else { nid = Get.identifierService() .getNidForUuids(uuid.getDataUUID()); } } else if (userData instanceof DynamicSememeNid) { nid = ((DynamicSememeNid) userData).getDataNid(); } else { throw new RuntimeException("Userdata is invalid for a COMPONENT_TYPE comparison"); } // Position 0 tells us the ObjectChronologyType. When the type is Sememe, position 2 tells us the (optional) SememeType of the assemblage restriction final DynamicSememeString[] valData = ((DynamicSememeArray<DynamicSememeString>) validatorDefinitionData).getDataArray(); final ObjectChronologyType expectedCT = ObjectChronologyType.parse(valData[0].getDataString(), false); final ObjectChronologyType component = Get.identifierService() .getChronologyTypeForNid(nid); if (expectedCT == ObjectChronologyType.UNKNOWN_NID) { throw new RuntimeException("Couldn't determine validator type from validator data '" + valData + "'"); } if (component != expectedCT) { throw new RuntimeException("The specified component must be of type " + expectedCT.toString() + ", not " + component); } if ((expectedCT == ObjectChronologyType.SEMEME) && (valData.length == 2)) { // they specified a specific sememe type. Verify. final SememeType st = SememeType.parse(valData[1].getDataString(), false); final SememeChronology<? extends SememeVersion<?>> sememe = Get.sememeService() .getSememe(nid); if (sememe.getSememeType() != st) { throw new RuntimeException("The specified component must be of type " + st.toString() + ", not " + sememe.getSememeType().toString()); } } return true; } catch (final RuntimeException e) { throw e; } catch (final Exception e) { logger.log(Level.WARNING, "Failure executing validator", e); throw new RuntimeException("Failure executing validator", e); } } else { final Number userDataNumber = NumericUtils.readNumber(userData); Number validatorDefinitionDataNumber; if (this == DynamicSememeValidatorType.INTERVAL) { final String s = validatorDefinitionData.getDataObject() .toString() .trim(); final Interval interval = new Interval(s); if (interval.getLeft() != null) { final int compareLeft = NumericUtils.compare(userDataNumber, interval.getLeft()); if ((!interval.isLeftInclusive() && (compareLeft == 0)) || (compareLeft < 0)) { return false; } } if (interval.getRight() != null) { final int compareRight = NumericUtils.compare(userDataNumber, interval.getRight()); if ((!interval.isRightInclusive() && (compareRight == 0)) || (compareRight > 0)) { return false; } } return true; } else { validatorDefinitionDataNumber = NumericUtils.readNumber(validatorDefinitionData); final int compareResult = NumericUtils.compare(userDataNumber, validatorDefinitionDataNumber); switch (this) { case LESS_THAN: return compareResult < 0; case GREATER_THAN: return compareResult > 0; case GREATER_THAN_OR_EQUAL: return compareResult >= 0; case LESS_THAN_OR_EQUAL: return compareResult <= 0; default: throw new RuntimeException("oops"); } } } } /** * A convenience wrapper of {@link #passesValidator(DynamicSememeDataBI, DynamicSememeDataBI, ViewCoordinate)} that just returns a string - never * throws an error * * These are all defined from the perspective of the userData - so for passesValidator to return true - * userData must be LESS_THAN validatorDefinitionData, for example. * * @param userData the user data * @param validatorDefinitionData the validator definition data * @param sc - The Stamp Coordinate - not needed for some types of validations. Null allowed when unneeded (for math based tests, for example) * @param tc - The Taxonomy Coordinate - not needed for some types of validations. Null allowed when unneeded (for math based tests, for example) * @return - empty string if valid, an error message otherwise. */ public String passesValidatorStringReturn(DynamicSememeData userData, DynamicSememeData validatorDefinitionData, StampCoordinate sc, TaxonomyCoordinate tc) { try { if (passesValidator(userData, validatorDefinitionData, sc, tc)) { return ""; } else { return "The value does not pass the validator"; } } catch (final Exception e) { return e.getMessage(); } } /** * Validator supports type. * * @param type the type * @return true, if successful */ public boolean validatorSupportsType(DynamicSememeDataType type) { // These are supported by all types - external specifies itself, what it supports, and we always include UNKNOWN. if ((this == UNKNOWN) || (this == EXTERNAL)) { return true; } switch (type) { case BOOLEAN: case POLYMORPHIC: { // technically, regexp would work here... but makes no sense. return false; } case DOUBLE: case FLOAT: case INTEGER: case LONG: { if ((this == GREATER_THAN) || (this == GREATER_THAN_OR_EQUAL) || (this == LESS_THAN) || (this == LESS_THAN_OR_EQUAL) || (this == INTERVAL) || (this == REGEXP)) { return true; } else { return false; } } case NID: case UUID: { if ((this == IS_CHILD_OF) || (this == IS_KIND_OF) || (this == REGEXP) || (this == COMPONENT_TYPE)) { return true; } else { return false; } } case SEQUENCE: // can't support component type with sequence, because we don't know how to look it up { if ((this == IS_CHILD_OF) || (this == IS_KIND_OF) || (this == REGEXP)) { return true; } else { return false; } } case STRING: case BYTEARRAY: { if (this == REGEXP) { return true; } else { return false; } } default: { logger.warning("Unexpected case!"); return false; } } } //~--- get methods --------------------------------------------------------- /** * Gets the display name. * * @return the display name */ public String getDisplayName() { return this.displayName; } }