/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2014-2015 ForgeRock AS. All rights reserved. * * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the License at * http://forgerock.org/license/CDDLv1.0.html * See the License for the specific language governing * permission and limitations under the License. * * When distributing Covered Code, include this CDDL * Header Notice in each file and include the License file * at http://forgerock.org/license/CDDLv1.0.html * If applicable, add the following below the CDDL Header, * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" */ package org.forgerock.openidm.repo.util; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.forgerock.guava.common.base.Function; import org.forgerock.guava.common.collect.FluentIterable; import org.forgerock.json.JsonPointer; import org.forgerock.util.query.QueryFilter; import org.forgerock.util.query.QueryFilterVisitor; /** * An abstract {@link QueryFilterVisitor} to produce SQL using an {@link StringSQLRenderer}. * Includes patterns for the standard * * <ul> * <li>AND</li> * <li>OR</li> * <li>NOT</li> * <li>>=</li> * <li>></li> * <li>=</li> * <li><</li> * <li><=</li> * </ul> * operators, along with the following implementations for {@link QueryFilter}'s * <ul> * <li>contains : field LIKE '%value%'</li> * <li>startsWith : field LIKE 'value%'</li> * <li>literal true : 1 = 1</li> * <li>literal false : 1 <> 1</li> * </ul> * <p> * This implementation does not support extended-match. * <p> * The implementer is responsible for implementing {@link #visitValueAssertion(Object, String, org.forgerock.json.JsonPointer, Object)} * which handles the value assertions - x operand y for the standard operands. The implementer is also responsible for * implementing {@link #visitPresentFilter(Object, org.forgerock.json.JsonPointer)} as "field present" can vary * by database implementation (though typically "field IS NOT NULL" is chosen). */ public abstract class StringSQLQueryFilterVisitor<P> extends AbstractSQLQueryFilterVisitor<StringSQLRenderer, P> { /** * A templating method that will generate the actual value assertion. * <p> * Example: * <pre><blockquote> * ?_queryFilter=email+eq+"someone@example.com" * </blockquote></pre> * is an QueryFilter stating the value assertion "email" equals "someone@example.com". The correct SQL for that * may vary depending on database variant and schema definition. This method will be invoked as * <pre><blockquote> * return visitValueAssertion(parameters, "=", JsonPointer(/email), "someone@example.com"); * </blockquote></pre> * A possible implementation for the above example may be * <pre><blockquote> * return getDatabaseColumnFor("email") + "=" + ":email"; * </blockquote></pre> * The parameters argument is implementation-dependent as a way to store placeholder mapping throughout the query-filter visiting. * * @param parameters storage of parameter-substitutions for the value of the assertion * @param operand the operand used to compare * @param field the object field as a JsonPointer - implementations need to map this to an appropriate database column * @param valueAssertion the value in the assertion * @return a query expression or clause */ public abstract StringSQLRenderer visitValueAssertion(P parameters, String operand, JsonPointer field, Object valueAssertion); public StringSQLRenderer visitCompositeFilter(final P parameters, List<QueryFilter<JsonPointer>> subFilters, String operand) { final String operandDelimiter = new StringBuilder(" ").append(operand).append(" ").toString(); return new StringSQLRenderer("(") .append(StringUtils.join( FluentIterable.from(subFilters) .transform(new Function<QueryFilter<JsonPointer>, String>() { @Override public String apply(QueryFilter<JsonPointer> filter) { return filter.accept(StringSQLQueryFilterVisitor.this, parameters).toSQL(); } }), operandDelimiter)) .append(")"); } @Override public StringSQLRenderer visitAndFilter(P parameters, List<QueryFilter<JsonPointer>> subFilters) { return visitCompositeFilter(parameters, subFilters, "AND"); } @Override public StringSQLRenderer visitOrFilter(P parameters, List<QueryFilter<JsonPointer>> subFilters) { return visitCompositeFilter(parameters, subFilters, "OR"); } @Override public StringSQLRenderer visitBooleanLiteralFilter(P parameters, boolean value) { return new StringSQLRenderer(value ? "1 = 1" : "1 <> 1"); } @Override public StringSQLRenderer visitNotFilter(P parameters, QueryFilter<JsonPointer> subFilter) { return new StringSQLRenderer("NOT ") .append(subFilter.accept(this, parameters).toSQL()); } @Override public abstract StringSQLRenderer visitPresentFilter(P parameters, JsonPointer field); }