001/* 002 * The contents of this file are subject to the license and copyright 003 * detailed in the LICENSE and NOTICE files at the root of the source 004 * tree. 005 */ 006package org.fcrepo.search.api; 007 008import java.util.regex.Pattern; 009 010/** 011 * A data structure representing a search condition. 012 * 013 * @author dbernstein 014 */ 015public class Condition { 016 public enum Operator { 017 LTE("<="), 018 GTE(">="), 019 EQ("="), 020 GT(">"), 021 LT("<"); 022 023 private String value; 024 025 Operator(final String value) { 026 this.value = value; 027 } 028 029 public String getStringValue() { 030 return this.value; 031 } 032 033 public static Operator fromString(final String str) { 034 for (final Operator o : Operator.values()) { 035 if (o.value.equals(str)) { 036 return o; 037 } 038 } 039 040 throw new IllegalArgumentException("Value " + str + " not recognized."); 041 } 042 043 } 044 045 public enum Field { 046 FEDORA_ID, 047 MODIFIED, 048 CREATED, 049 CONTENT_SIZE, 050 MIME_TYPE, 051 RDF_TYPE; 052 053 @Override 054 public String toString() { 055 return super.toString().toLowerCase(); 056 } 057 058 public static Field fromString(final String fieldStr) throws InvalidConditionExpressionException { 059 for (final var field : values()) { 060 if (field.toString().equalsIgnoreCase(fieldStr)) { 061 return field; 062 } 063 } 064 065 throw new InvalidConditionExpressionException(fieldStr + " is not a valid search field"); 066 } 067 } 068 069 /* A regex for parsing the value of a "condition" query parameter which follows the format 070 * [field_name][operation][object] 071 * The field name is composed of at least one character and can contain alpha number characters and underscores. 072 * The operation can equal "=", "<", ">", "<=" or ">=" 073 * The object can be anything but cannot start with >, <, and =. 074 */ 075 final static Pattern CONDITION_REGEX = Pattern.compile("([a-zA-Z0-9_]+)([><=]|<=|>=)([^><=].*)"); 076 077 078 private Field field; 079 private Operator operator; 080 private String object; 081 082 /** 083 * Internal constructor 084 * 085 * @param field The search field (condition subject) 086 * @param operator The operator (condition predicate) 087 * @param object The object (condition object) 088 */ 089 private Condition(final Field field, final Operator operator, final String object) { 090 this.field = field; 091 this.operator = operator; 092 this.object = object; 093 } 094 095 /** 096 * Field accessor 097 * 098 * @return the field 099 */ 100 public Field getField() { 101 return field; 102 } 103 104 /** 105 * Operator accessor 106 * @return the operator 107 */ 108 public Operator getOperator() { 109 return operator; 110 } 111 112 /** 113 * @return the object portion of the condition 114 */ 115 public String getObject() { 116 return object; 117 } 118 119 @Override 120 public String toString() { 121 return this.field.toString().toLowerCase() + operator + object; 122 } 123 124 /** 125 * Parses a string expression into a Condition object. 126 * @param expression The condition as a string expression. 127 * @return The condition 128 * @throws InvalidConditionExpressionException if we can't parse the string into a Condition. 129 */ 130 public static Condition fromExpression(final String expression) throws InvalidConditionExpressionException { 131 final var m = CONDITION_REGEX.matcher(expression); 132 if (m.matches()) { 133 final var field = Field.fromString(m.group(1)); 134 final var operation = Operator.fromString(m.group(2)); 135 final var object = m.group(3); 136 return fromEnums(field, operation, object); 137 } 138 139 throw new InvalidConditionExpressionException(expression); 140 } 141 142 public static Condition fromEnums(final Field field, final Operator operator, final String expression) { 143 return new Condition(field, operator, expression); 144 } 145}