org.fryske_akademy.jpa.Param Maven / Gradle / Ivy
The newest version!
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.fryske_akademy.jpa;
/*-
* #%L
* ejbCrudApi
* %%
* Copyright (C) 2018 Fryske Akademy
* %%
* 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.
* #L%
*/
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
/**
* Holder for parameter info that can be used when {@link JpqlBuilder building}
* a jpql query. Uses {@link Builder#Builder() } with
* syntax support by default.
*
* @author eduard
*/
public class Param {
/**
* call {@link #one(java.lang.String, java.lang.Object, boolean, org.fryske_akademy.ejb.Param.Builder.WildcardMapping)
* } with true, {@link Builder#DEFAULT_MAPPING} and false
*
* @param key
* @param value
* @return
*/
public static List one(String key, Object value) {
return one(key, value, true, Builder.DEFAULT_MAPPING, false);
}
/**
* @see Builder#Builder(boolean,
* org.fryske_akademy.ejb.Param.Builder.WildcardMapping, boolean)
* @param key
* @param value
* @param syntaxSupport
* @param wildcardMapping
* @param caseInsensitive
* @return
*/
public static List one(String key, Object value, boolean syntaxSupport, Builder.WildcardMapping wildcardMapping, boolean caseInsensitive) {
return new Builder(syntaxSupport, wildcardMapping, caseInsensitive).add(key, value).build();
}
public enum AndOr {
AND, OR;
private static AndOr fromBool(boolean or) {
return or ? OR : AND;
}
@Override
public String toString() {
return " " + name() + " ";
}
}
private final String propertyPath;
private final String paramKey;
private final String operator;
private final Object paramValue;
private final String not;
private final AndOr andOr;
private final boolean skipSetValue;
private final boolean caseInsensitive;
/**
* Bottleneck constructor
*
* @param propertyPath the path to the property in an entity i.e.
* lemma.soart.id, or id
* @param paramKey the key of the parameter i.e. :id
* @param operator the operator i.e. like or =
* @param paramValue may not be null
* @param not when true use negation
* @param or when true use or otherwise and
* @param caseInsensitive when true query case insensitive
* must be non null, when both set classes must be the same
*/
private Param(String propertyPath, String paramKey, String operator, Object paramValue, boolean not, boolean or, boolean caseInsensitive) {
if (paramValue == null) {
throw new IllegalArgumentException("param value may not be null");
}
this.propertyPath = propertyPath + " ";
this.skipSetValue = Builder.nullComp(operator) || Builder.emptyComp(operator);
this.paramKey = paramKey;
this.operator = " " + operator + " ";
this.paramValue = paramValue;
this.not = not ? " NOT " : " ";
this.andOr = AndOr.fromBool(or);
this.caseInsensitive = caseInsensitive;
}
public String getPropertyPath() {
return propertyPath;
}
/**
* for native queries this key should be a numeric positional parameter
*
* @return
*/
public String getParamKey() {
return paramKey;
}
public String getOperator() {
return operator;
}
public Object getParamValue() {
return paramValue;
}
public String getNot() {
return not;
}
public AndOr getAndOr() {
return andOr;
}
public boolean isSkipSetValue() {
return skipSetValue;
}
public Class getParamType() {
return paramValue.getClass();
}
public boolean isCaseInsensitive() {
return caseInsensitive;
}
@Override
public String toString() {
return "Param{" + "propertyPath=" + propertyPath + ", paramKey=" + paramKey + ", operator=" + operator + ", paramValue=" + paramValue + ", not=" + not + ", andOr=" + andOr + ", skipSetValue=" + skipSetValue + '}';
}
@Override
public int hashCode() {
int hash = 7;
hash = 47 * hash + Objects.hashCode(this.paramKey);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Param other = (Param) obj;
if (!Objects.equals(this.paramKey, other.paramKey)) {
return false;
}
return true;
}
/**
* helps building a list of Param, by default supports a bit of syntax in
* String values: "is [not] null/empty" and ! at the beginning of a value.
* You can also use {@link #Builder(boolean, WildcardMapping) wildcards}
*/
public static class Builder {
/**
* translation table a wildcard for more characters and a wildcard for
* one character
*/
public interface WildcardMapping {
char getMoreIn();
char getMoreOut();
char getOneIn();
char getOneOut();
}
/**
* * => %, ? => _
*/
public static class DefaultWildcardMapping implements WildcardMapping {
@Override
public char getMoreIn() {
return '*';
}
@Override
public char getMoreOut() {
return '%';
}
@Override
public char getOneIn() {
return '?';
}
@Override
public char getOneOut() {
return '_';
}
}
private final List params = new ArrayList<>(3);
public static final WildcardMapping DEFAULT_MAPPING = new DefaultWildcardMapping();
public static final String ISNULL = "is null";
public static final String ISNOTNULL = "is not null";
public static final String ISEMPTY = "is empty";
public static final String ISNOTEMPTY = "is not empty";
public static final char NEGATION = '!';
private final boolean syntaxInValue;
private final WildcardMapping wildcardMapping;
private final boolean caseInsensitive;
public Builder(boolean syntaxInValue, WildcardMapping wildcardMapping, boolean caseInsensitive) {
this.syntaxInValue = syntaxInValue;
this.wildcardMapping = wildcardMapping;
this.caseInsensitive = caseInsensitive;
}
/**
* Builder with syntax support in value (!, is [not] null). Calls
* {@link #Builder(boolean, WildcardMapping, boolean)
* } with true, null and false.
*/
public Builder() {
this(true, null, false);
}
/**
* Calls {@link #Builder(boolean, org.fryske_akademy.ejb.Param.Builder.WildcardMapping, boolean)
* } with true, null and the caseInsensitive argument.
*
* @param caseInsensitive
*/
public Builder(boolean caseInsensitive) {
this(true, null, caseInsensitive);
}
/**
* When paramValue is a String call {@link #add(java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean)
* } with false otherwise call {@link #add(java.lang.String, java.lang.String, java.lang.String, java.lang.Object, boolean, boolean)
* } with false, false.
*
* @param propertyPath
* @param paramKey
* @param operator
* @param paramValue
* @return
*/
public Builder add(String propertyPath, String paramKey, String operator, Object paramValue) {
if (paramValue instanceof String) {
return add(propertyPath, paramKey, operator, (String)paramValue, false);
} else {
return add(propertyPath, paramKey, operator, paramValue, false, false);
}
}
/**
* When paramValue starts with a ! {@link #getNot() negation} is used,
* when trimmed paramValue is "is [not] null", operator is set to value
* and value will be ignored.
*
* @param propertyPath
* @param paramKey
* @param operator
* @param paramValue
* @param or
* @return
*/
public Builder add(String propertyPath, String paramKey, String operator, String paramValue, boolean or) {
return add(propertyPath, paramKey, determineOperator(operator, paramValue), replaceWildcards(stripNegation(paramValue)), isNegation(paramValue), or);
}
/**
* Bottleneck function, adds a new Param
*
* @param propertyPath
* @param paramKey
* @param operator
* @param paramValue
* @param not
* @param or
* @throws IllegalArgumentException when paramKey is already present or when value is null
* @return
*/
public Builder add(String propertyPath, String paramKey, String operator, Object paramValue, boolean not, boolean or) {
if (params.stream().anyMatch((p) -> {
return p.paramKey.equals(paramKey);
})) {
throw new IllegalArgumentException(String.format("builder already contains %s", paramKey));
}
params.add(new Param(propertyPath, paramKey, operator, paramValue, not, or, caseInsensitive));
return this;
}
public Builder remove(String paramKey) {
if (params.stream().anyMatch((p) -> {
return p.paramKey.equals(paramKey);
})) {
for (Iterator iterator = params.iterator(); iterator.hasNext();) {
Param next = iterator.next();
if (next.paramKey.equals(paramKey)) {
iterator.remove();
break;
}
}
}
return this;
}
/**
* check if a value indicates a negation
*
* @param value
* @return
*/
public boolean isNegation(String value) {
return syntaxInValue && value != null && value.indexOf(NEGATION) == 0;
}
private String stripNegation(String value) {
return isNegation(value) ? value.substring(1) : value;
}
private String replaceWildcards(String value) {
return wildcardMapping == null ? value : value
.replace(wildcardMapping.getMoreIn(), wildcardMapping.getMoreOut())
.replace(wildcardMapping.getOneIn(), wildcardMapping.getOneOut());
}
private String determineOperator(String operator, String value) {
if (syntaxInValue && nullComp(value)) {
return value;
}
return operator;
}
/**
* check if a value is a null comparison
*
* @param s
* @return
*/
public static boolean nullComp(String s) {
if (s == null) {
return false;
}
String t = s.trim().toLowerCase();
return t.equals(ISNULL) || t.equals(ISNOTNULL);
}
/**
* check if a value is a empty comparison
*
* @param s
* @return
*/
public static boolean emptyComp(String s) {
if (s == null) {
return false;
}
String t = s.trim().toLowerCase();
return t.equals(ISEMPTY) || t.equals(ISNOTEMPTY);
}
/**
* Calls {@link #add(java.lang.String, java.lang.Object, boolean) } with
* false
*
* @param paramKey
* @param paramValue
* @return
*/
public Builder add(String paramKey, Object paramValue) {
return add(paramKey, paramValue, false);
}
/**
* When paramValue is a String use like as operator otherwise = and call
* {@link #add(java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean) },
* otherwise call {@link #add(java.lang.String, java.lang.String, java.lang.String, java.lang.Object, boolean, boolean)
* }
* with false for not.
*
* @param paramKey
* @param paramValue
* @param or when true use or in queries
* @return
*/
public Builder add(String paramKey, Object paramValue, boolean or) {
if (paramValue instanceof String) {
return add(paramKey, paramKey, "like", (String) paramValue, or);
} else {
return add(paramKey, paramKey, "=", paramValue, false, or);
}
}
public List build() {
return params;
}
public boolean isSyntaxInValue() {
return syntaxInValue;
}
public boolean containsKey(String key) {
return params.stream().anyMatch((t) -> {
return t.getParamKey().equals(key);
});
}
}
}