/* * ModeShape (http://www.modeshape.org) * * 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. */ package org.modeshape.jcr.query.model; import java.math.BigDecimal; import java.net.URI; import java.text.CharacterIterator; import java.text.StringCharacterIterator; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Set; import javax.jcr.Binary; import javax.jcr.PropertyType; import javax.jcr.Value; import org.modeshape.common.util.CheckArg; import org.modeshape.jcr.ExecutionContext; import org.modeshape.jcr.api.value.DateTime; import org.modeshape.jcr.value.Name; import org.modeshape.jcr.value.NamespaceRegistry; import org.modeshape.jcr.value.Path; import org.modeshape.jcr.value.Reference; import org.modeshape.jcr.value.ValueFactories; import org.modeshape.jcr.value.ValueFactory; /** * A set of common visitors that can be reused or extended, and methods that provide easy construction and calling of visitors. */ public class Visitors { protected static final char OPEN_SQUARE = '['; protected static final char CLOSE_SQUARE = ']'; protected static final char DOUBLE_QUOTE = '"'; protected static final char SINGLE_QUOTE = '\''; private static final ExecutionContext DEFAULT_CONTEXT = new ExecutionContext(); /** * Visit all objects in the supplied {@link Visitable object} using a {@link NavigationVisitor} (specifically a * {@link WalkAllVisitor}), and with each of these visited objects calling the appropriate {@code visit(...)} method on the * supplied {@link Visitor}. * * @param <StrategyVisitor> the type of strategy visitor * @param visitable the top-level object to be visited * @param strategyVisitor the visitor that is to be called for each visited objects, but that does <i>not</i> call * {@link Visitable#accept(Visitor)} * @return the strategy visitor, allowing the caller to easily invoke operations on the visitor after visitation has completed */ public static <StrategyVisitor extends Visitor> StrategyVisitor visitAll( Visitable visitable, StrategyVisitor strategyVisitor ) { if (visitable != null) visitable.accept(new WalkAllVisitor(strategyVisitor)); return strategyVisitor; } /** * Visit all objects in the supplied {@link Visitable object} using a {@link NavigationVisitor} (specifically a * {@link WalkAllVisitor}), and with each of these visited objects calling the appropriate {@code visit(...)} method on the * supplied {@link Visitor}. * * @param <StrategyVisitor> the type of strategy visitor * @param visitables the top-level collection of visitable objects to be visited * @param strategyVisitor the visitor that is to be called for each visited objects, but that does <i>not</i> call * {@link Visitable#accept(Visitor)} * @return the strategy visitor, allowing the caller to easily invoke operations on the visitor after visitation has completed */ public static <StrategyVisitor extends Visitor> StrategyVisitor visitAll( Iterable<? extends Visitable> visitables, StrategyVisitor strategyVisitor ) { if (visitables != null) { for (Visitable visitable : visitables) { if (visitable != null) { visitable.accept(new WalkAllVisitor(strategyVisitor)); } } } return strategyVisitor; } /** * Visit the supplied {@link Visitable object} using the supplied {@link Visitor}, which must be responsible for navigation as * well as any business logic. * * @param <GeneralVisitor> the type of visitor * @param visitable the top-level object to be visited * @param visitor the visitor that is to be used * @return the visitor, allowing the caller to easily invoke operations on the visitor after visitation has completed */ public static <GeneralVisitor extends Visitor> GeneralVisitor visit( Visitable visitable, GeneralVisitor visitor ) { if (visitable != null) visitable.accept(visitor); return visitor; } /** * Using a visitor, obtain the readable string representation of the supplied {@link Visitable object} * * @param visitable the visitable * @return the string representation */ public static String readable( Visitable visitable ) { // return visit(visitable, new ReadableVisitor()).getString(); return visit(visitable, new JcrSql2Writer(DEFAULT_CONTEXT)).getString(); } /** * Using a visitor, obtain the readable string representation of the supplied {@link Visitable object} * * @param visitable the visitable * @param context the execution context in which the visitable should be converted to a string * @return the string representation */ public static String readable( Visitable visitable, ExecutionContext context ) { // return visit(visitable, new ReadableVisitor()).getString(); return visit(visitable, new JcrSql2Writer(context)).getString(); } public static String readable( Object[] tuple ) { return readable(tuple, DEFAULT_CONTEXT); } public static String readable( Object[] tuple, ExecutionContext context ) { if (tuple.length == 0) { return "||"; } ValueFactory<String> stringFactory = context.getValueFactories().getStringFactory(); StringBuilder sb = new StringBuilder(); sb.append("| "); for (Object value : tuple) { if (value != null) { sb.append(' '); if (value instanceof Object[]) { Object[] array = (Object[])value; int len = array.length; for (int i = 0; i != len; ++i) { if (i != 0) sb.append(", "); sb.append(stringFactory.create(array[i])); } } else { sb.append(stringFactory.create(value)); } } sb.append(" |"); } return sb.toString(); } /** * Using a visitor, obtain the {@link Subquery} objects that are contained within the supplied {@link Visitable object}. This * method does find Subquery objets nested in other Subquery objects. * * @param visitable the visitable * @param includeNestedSubqueries true if any Subquery objects within other Subquery objects should be included, or false if * only the top-level Subquery objects should be included * @return the collection of subqueries; never null but possibly empty if no subqueries were found */ public static Collection<Subquery> subqueries( Visitable visitable, final boolean includeNestedSubqueries ) { final Collection<Subquery> subqueries = new LinkedList<Subquery>(); Visitors.visitAll(visitable, new Visitors.AbstractVisitor() { @Override public void visit( Subquery subquery ) { subqueries.add(subquery); if (includeNestedSubqueries) { // Now look for any subqueries in the subquery ... subquery.getQuery().accept(this); } } }); return subqueries; } /** * Get a map of the selector names keyed by their aliases. * * @param visitable the object to be visited * @return the map from the aliases to the aliased selector name; never null but possibly empty */ public static Map<SelectorName, SelectorName> getSelectorNamesByAlias( Visitable visitable ) { // Find all of the selectors that have aliases ... final Map<SelectorName, SelectorName> result = new HashMap<SelectorName, SelectorName>(); Visitors.visitAll(visitable, new Visitors.AbstractVisitor() { @Override public void visit( AllNodes allNodes ) { if (allNodes.hasAlias()) { result.put(allNodes.alias(), allNodes.name()); } } @Override public void visit( NamedSelector selector ) { if (selector.hasAlias()) { result.put(selector.alias(), selector.name()); } } }); return result; } /** * Get a map of the selector aliases keyed by their names. * * @param visitable the object to be visited * @return the map from the selector names to their alias (or name if there is no alias); never null but possibly empty */ public static Map<SelectorName, SelectorName> getSelectorAliasesByName( Visitable visitable ) { // Find all of the selectors that have aliases ... final Map<SelectorName, SelectorName> result = new HashMap<SelectorName, SelectorName>(); Visitors.visitAll(visitable, new Visitors.AbstractVisitor() { @Override public void visit( AllNodes allNodes ) { if (allNodes.hasAlias()) { result.put(allNodes.name(), allNodes.aliasOrName()); } } @Override public void visit( NamedSelector selector ) { if (selector.hasAlias()) { result.put(selector.name(), selector.aliasOrName()); } } }); return result; } /** * Get the names of the selectors referenced by the visitable object. * * @param visitable the object to be visited * @return the set of selector names referenced in some way by the visitable; never null */ public static Set<SelectorName> getSelectorsReferencedBy( Visitable visitable ) { final Set<SelectorName> symbols = new HashSet<SelectorName>(); // Walk the entire structure, so only supply a StrategyVisitor (that does no navigation) ... visitAll(visitable, new AbstractVisitor() { @Override public void visit( AllNodes allNodes ) { if (allNodes.hasAlias()) { symbols.add(allNodes.alias()); } else { symbols.add(allNodes.name()); } } @Override public void visit( ChildNode childNode ) { symbols.add(childNode.selectorName()); } @Override public void visit( ChildNodeJoinCondition joinCondition ) { symbols.add(joinCondition.childSelectorName()); symbols.add(joinCondition.parentSelectorName()); } @Override public void visit( Column column ) { symbols.add(column.selectorName()); } @Override public void visit( DescendantNode descendant ) { symbols.add(descendant.selectorName()); } @Override public void visit( DescendantNodeJoinCondition joinCondition ) { symbols.add(joinCondition.ancestorSelectorName()); symbols.add(joinCondition.descendantSelectorName()); } @Override public void visit( EquiJoinCondition joinCondition ) { symbols.add(joinCondition.selector1Name()); symbols.add(joinCondition.selector2Name()); } @Override public void visit( FullTextSearch fullTextSearch ) { symbols.add(fullTextSearch.selectorName()); } @Override public void visit( FullTextSearchScore fullTextSearchScore ) { symbols.add(fullTextSearchScore.selectorName()); } @Override public void visit( Length length ) { symbols.add(length.selectorName()); } @Override public void visit( NodeDepth depth ) { symbols.add(depth.selectorName()); } @Override public void visit( NodePath path ) { symbols.add(path.selectorName()); } @Override public void visit( NodeLocalName node ) { symbols.add(node.selectorName()); } @Override public void visit( NodeName node ) { symbols.add(node.selectorName()); } @Override public void visit( NamedSelector node ) { if (node.hasAlias()) { symbols.add(node.alias()); } else { symbols.add(node.name()); } } @Override public void visit( PropertyExistence prop ) { symbols.add(prop.selectorName()); } @Override public void visit( PropertyValue prop ) { symbols.add(prop.selectorName()); } @Override public void visit( Subquery obj ) { // do nothing ... } @Override public void visit( ReferenceValue ref ) { symbols.add(ref.selectorName()); } @Override public void visit( SameNode node ) { symbols.add(node.selectorName()); } @Override public void visit( SameNodeJoinCondition joinCondition ) { symbols.add(joinCondition.selector1Name()); symbols.add(joinCondition.selector2Name()); } }); return symbols; } /** * A common base class for all visitors, which provides no-op implementations for all {@code visit(...)} methods. Visitor * implementations can subclass and implement only those methods that they need to implement. * <p> * This is often an excellent base class for <i>strategy visitors</i>, which simply are {@link Visitor} implementations that * are responsible only for visiting the supplied object but that never call {@link Visitable#accept(Visitor)}. Such strategy * visitors can be used in conjunction with separate <i>{@link NavigationVisitor navigation visitors}</i> that do the job of * navigating the Visitable objects and, for each, delegating to the strategy visitor. See * {@link Visitors#visitAll(Visitable, Visitor)} for an example. * </p> */ public static class AbstractVisitor implements Visitor { @Override public void visit( AllNodes obj ) { } @Override public void visit( And obj ) { } @Override public void visit( ArithmeticOperand obj ) { } @Override public void visit( Between obj ) { } @Override public void visit( BindVariableName obj ) { } @Override public void visit( ChildCount obj ) { } @Override public void visit( ChildNode obj ) { } @Override public void visit( ChildNodeJoinCondition obj ) { } @Override public void visit( Column obj ) { } @Override public void visit( Comparison obj ) { } @Override public void visit( DescendantNode obj ) { } @Override public void visit( DescendantNodeJoinCondition obj ) { } @Override public void visit( EquiJoinCondition obj ) { } @Override public void visit( FullTextSearch obj ) { } @Override public void visit( FullTextSearchScore obj ) { } @Override public void visit( Join obj ) { } @Override public void visit( Length obj ) { } @Override public void visit( Limit limit ) { } @Override public void visit( Literal obj ) { } @Override public void visit( LowerCase obj ) { } @Override public void visit( NodeId obj ) { } @Override public void visit( NodeDepth obj ) { } @Override public void visit( NodePath obj ) { } @Override public void visit( NodeName obj ) { } @Override public void visit( NodeLocalName obj ) { } @Override public void visit( NamedSelector obj ) { } @Override public void visit( Not obj ) { } @Override public void visit( Or obj ) { } @Override public void visit( Ordering obj ) { } @Override public void visit( PropertyExistence obj ) { } @Override public void visit( PropertyValue obj ) { } @Override public void visit( Query obj ) { } @Override public void visit( Subquery obj ) { } @Override public void visit( ReferenceValue obj ) { } @Override public void visit( SameNode obj ) { } @Override public void visit( SameNodeJoinCondition obj ) { } @Override public void visit( SetCriteria obj ) { } @Override public void visit( SetQuery obj ) { } @Override public void visit( UpperCase obj ) { } @Override public void visit( Relike obj ) { } @Override public void visit( Cast cast ) { } } /** * An abstract visitor implementation that performs navigation of the query object. * <p> * Subclasses should always implement the {@code visit(T object)} methods by performing the following actions: * <ol> * <li>Call <code>strategy.visit(object);</code></li> * <li>Add any children of {@code object} that are to be visited using {@link #enqueue(Visitable)}</li> * <li>Call {@link #visitNext()}</code></li> * </ol> * </p> */ public static abstract class NavigationVisitor implements Visitor { protected final Visitor strategy; private final LinkedList<? super Visitable> itemQueue = new LinkedList<Visitable>(); /** * Create a visitor that walks all query objects. * * @param strategy the visitor that should be called at every node. */ protected NavigationVisitor( Visitor strategy ) { assert strategy != null; this.strategy = strategy; } protected void enqueue( Visitable objectToBeVisited ) { if (objectToBeVisited != null) { itemQueue.add(objectToBeVisited); } } protected void enqueue( Iterable<? extends Visitable> objectsToBeVisited ) { for (Visitable objectToBeVisited : objectsToBeVisited) { enqueue(objectToBeVisited); } } protected final void visitNext() { if (!itemQueue.isEmpty()) { Visitable first = (Visitable)itemQueue.removeFirst(); assert first != null; first.accept(this); } } } /** * A visitor implementation that walks the entire query object tree and delegates to another supplied visitor to do the actual * work. */ public static class WalkAllVisitor extends NavigationVisitor { /** * Create a visitor that walks all query objects. * * @param strategy the visitor that should be called at every node. */ public WalkAllVisitor( Visitor strategy ) { super(strategy); } @Override public void visit( AllNodes allNodes ) { strategy.visit(allNodes); visitNext(); } @Override public void visit( And and ) { strategy.visit(and); enqueue(and.left()); enqueue(and.right()); visitNext(); } @Override public void visit( ArithmeticOperand arithmeticOperation ) { strategy.visit(arithmeticOperation); enqueue(arithmeticOperation.getLeft()); enqueue(arithmeticOperation.getRight()); visitNext(); } @Override public void visit( Between between ) { strategy.visit(between); enqueue(between.getOperand()); enqueue(between.getLowerBound()); enqueue(between.getUpperBound()); visitNext(); } @Override public void visit( BindVariableName variableName ) { strategy.visit(variableName); visitNext(); } @Override public void visit( ChildCount childCount ) { strategy.visit(childCount); visitNext(); } @Override public void visit( ChildNode child ) { strategy.visit(child); visitNext(); } @Override public void visit( ChildNodeJoinCondition joinCondition ) { strategy.visit(joinCondition); visitNext(); } @Override public void visit( Column column ) { strategy.visit(column); visitNext(); } @Override public void visit( Comparison comparison ) { strategy.visit(comparison); enqueue(comparison.getOperand1()); enqueue(comparison.getOperand2()); visitNext(); } @Override public void visit( Relike relike ) { strategy.visit(relike); enqueue(relike.getOperand1()); enqueue(relike.getOperand2()); visitNext(); } @Override public void visit( DescendantNode descendant ) { strategy.visit(descendant); visitNext(); } @Override public void visit( DescendantNodeJoinCondition condition ) { strategy.visit(condition); visitNext(); } @Override public void visit( EquiJoinCondition condition ) { strategy.visit(condition); visitNext(); } @Override public void visit( FullTextSearch fullTextSearch ) { strategy.visit(fullTextSearch); enqueue(fullTextSearch.getFullTextSearchExpression()); visitNext(); } @Override public void visit( FullTextSearchScore score ) { strategy.visit(score); visitNext(); } @Override public void visit( Join join ) { strategy.visit(join); enqueue(join.getLeft()); enqueue(join.getJoinCondition()); enqueue(join.getRight()); visitNext(); } @Override public void visit( Length length ) { strategy.visit(length); visitNext(); } @Override public void visit( Limit limit ) { strategy.visit(limit); visitNext(); } @Override public void visit( Literal literal ) { strategy.visit(literal); visitNext(); } @Override public void visit( LowerCase lowerCase ) { strategy.visit(lowerCase); enqueue(lowerCase.getOperand()); visitNext(); } @Override public void visit( NodeDepth depth ) { strategy.visit(depth); visitNext(); } @Override public void visit( NodeId id ) { strategy.visit(id); visitNext(); } @Override public void visit( NodePath path ) { strategy.visit(path); visitNext(); } @Override public void visit( NodeName nodeName ) { strategy.visit(nodeName); visitNext(); } @Override public void visit( NodeLocalName nodeLocalName ) { strategy.visit(nodeLocalName); visitNext(); } @Override public void visit( NamedSelector selector ) { strategy.visit(selector); visitNext(); } @Override public void visit( Not not ) { strategy.visit(not); enqueue(not.getConstraint()); visitNext(); } @Override public void visit( Or or ) { strategy.visit(or); enqueue(or.left()); enqueue(or.right()); visitNext(); } @Override public void visit( Ordering ordering ) { strategy.visit(ordering); enqueue(ordering.getOperand()); visitNext(); } @Override public void visit( PropertyExistence existence ) { strategy.visit(existence); visitNext(); } @Override public void visit( PropertyValue propertyValue ) { strategy.visit(propertyValue); visitNext(); } @Override public void visit( Query query ) { strategy.visit(query); enqueue(query.source()); enqueue(query.columns()); enqueue(query.constraint()); enqueue(query.orderings()); visitNext(); } @Override public void visit( Subquery subquery ) { strategy.visit(subquery); enqueue(subquery.getQuery()); visitNext(); } @Override public void visit( ReferenceValue referenceValue ) { strategy.visit(referenceValue); visitNext(); } @Override public void visit( SameNode sameNode ) { strategy.visit(sameNode); visitNext(); } @Override public void visit( SameNodeJoinCondition condition ) { strategy.visit(condition); visitNext(); } @Override public void visit( SetCriteria setCriteria ) { strategy.visit(setCriteria); enqueue(setCriteria.leftOperand()); for (StaticOperand right : setCriteria.rightOperands()) { enqueue(right); } visitNext(); } @Override public void visit( SetQuery setQuery ) { strategy.visit(setQuery); enqueue(setQuery.getLeft()); enqueue(setQuery.getRight()); visitNext(); } @Override public void visit( UpperCase upperCase ) { strategy.visit(upperCase); enqueue(upperCase.getOperand()); visitNext(); } @Override public void visit( Cast cast ) { strategy.visit(cast); enqueue(cast.getOperand()); visitNext(); } } public static class ReadableVisitor implements Visitor { protected final StringBuilder sb = new StringBuilder(); protected final ExecutionContext context; protected final NamespaceRegistry registry; public ReadableVisitor( ExecutionContext context ) { CheckArg.isNotNull(context, "context"); this.context = context; this.registry = context == null ? null : context.getNamespaceRegistry(); } protected ReadableVisitor appendAlias( String columnName ) { append(columnName); return this; } protected ReadableVisitor appendColumnName( String columnName ) { append(columnName); return this; } protected ReadableVisitor appendPropertyName( String columnName ) { append(columnName); return this; } protected ReadableVisitor append( String string ) { sb.append(string); return this; } protected ReadableVisitor append( char character ) { sb.append(character); return this; } protected ReadableVisitor append( int value ) { sb.append(value); return this; } protected ReadableVisitor append( SelectorName name ) { sb.append(name.getString()); return this; } protected ReadableVisitor append( Name name ) { append(SINGLE_QUOTE); append(name.getString(registry, null, null)); append(SINGLE_QUOTE); return this; } protected ReadableVisitor append( Path path ) { sb.append(SINGLE_QUOTE); sb.append(path.getString(registry)); sb.append(SINGLE_QUOTE); return this; } /** * @return context */ public final ExecutionContext getContext() { return context; } /** * Get the string representation of the visited objects. * * @return the string representation */ public final String getString() { return sb.toString(); } @Override public String toString() { return sb.toString(); } @Override public void visit( AllNodes allNodes ) { append(allNodes.name()); if (allNodes.hasAlias()) { append(" AS ").append(allNodes.alias()); } } @Override public void visit( And and ) { append('('); and.left().accept(this); append(" AND "); and.right().accept(this); append(')'); } @Override public void visit( ArithmeticOperand arithmeticOperand ) { append('('); arithmeticOperand.getLeft().accept(this); append(' '); append(arithmeticOperand.operator().symbol()); append(' '); arithmeticOperand.getRight().accept(this); append(')'); } @Override public void visit( Between between ) { between.getOperand().accept(this); append(" BETWEEN "); between.getLowerBound().accept(this); if (!between.isLowerBoundIncluded()) append(" EXCLUSIVE"); append(" AND "); between.getUpperBound().accept(this); if (!between.isUpperBoundIncluded()) append(" EXCLUSIVE"); } @Override public void visit( BindVariableName variable ) { append('$').append(variable.getBindVariableName()); } @Override public void visit( ChildCount childCount ) { append("CHILDCOUNT(").append(childCount.selectorName()).append(')'); } @Override public void visit( ChildNode child ) { append("ISCHILDNODE("); append(child.selectorName()); append(','); append(SINGLE_QUOTE); append(child.getParentPath()); append(SINGLE_QUOTE); append(')'); } @Override public void visit( ChildNodeJoinCondition condition ) { append("ISCHILDNODE("); append(condition.childSelectorName()); append(','); append(condition.parentSelectorName()); append(')'); } @Override public void visit( Column column ) { append(column.selectorName()); if (column.getPropertyName() == null) { append(".*"); } else { String propertyName = column.getPropertyName(); append('.').appendPropertyName(propertyName); if (!propertyName.equals(column.getColumnName()) && !propertyName.equals(column.getColumnName()) && !(column.selectorName() + "." + propertyName).equals(column.getColumnName())) { append(" AS ").appendAlias(column.getColumnName()); } } } @Override public void visit( Comparison comparison ) { comparison.getOperand1().accept(this); append(' ').append(comparison.operator().symbol()).append(' '); comparison.getOperand2().accept(this); } @Override public void visit( Relike relike ) { append("RELIKE("); relike.getOperand1().accept(this); append(','); relike.getOperand2().accept(this); append(')'); } @Override public void visit( DescendantNode descendant ) { append("ISDESCENDANTNODE("); append(descendant.selectorName()); append(','); append(SINGLE_QUOTE); append(descendant.getAncestorPath()); append(SINGLE_QUOTE); append(')'); } @Override public void visit( DescendantNodeJoinCondition condition ) { append("ISDESCENDANTNODE("); append(condition.descendantSelectorName()); append(','); append(condition.ancestorSelectorName()); append(')'); } @Override public void visit( EquiJoinCondition condition ) { append(condition.selector1Name()).append('.').appendPropertyName(condition.getProperty1Name()); append(" = "); append(condition.selector2Name()).append('.').appendPropertyName(condition.getProperty2Name()); } @Override public void visit( FullTextSearch fullText ) { append("CONTAINS(").append(fullText.selectorName()); if (fullText.getPropertyName() != null) { append('.').appendPropertyName(fullText.getPropertyName()); } sb.append(",'").append(fullText.fullTextSearchExpression()).append("')"); } @Override public void visit( FullTextSearchScore score ) { append("SCORE(").append(score.selectorName()).append(')'); } @Override public void visit( Join join ) { join.getLeft().accept(this); // if (join.getType() != JoinType.INNER) { sb.append(' ').append(join.type().symbol()); // } else { // sb.append(','); // } append(' '); join.getRight().accept(this); append(" ON "); join.getJoinCondition().accept(this); } @Override public void visit( Length length ) { append("LENGTH("); length.getPropertyValue().accept(this); append(')'); } @Override public void visit( Limit limit ) { append("LIMIT ").append(limit.getRowLimit()); if (limit.getOffset() != 0) { append(" OFFSET ").append(limit.getOffset()); } } @Override public void visit( Literal literal ) { if (literal instanceof LiteralValue) { LiteralValue literalValue = (LiteralValue)literal; Value value = literalValue.getLiteralValue(); String typeName = null; ValueFactories factories = context.getValueFactories(); switch (value.getType()) { case PropertyType.UNDEFINED: case PropertyType.STRING: append(SINGLE_QUOTE); String str = factories.getStringFactory().create(literalValue.value()); append(str); append(SINGLE_QUOTE); return; case PropertyType.PATH: append("CAST("); append(factories.getPathFactory().create(literalValue.value())); append(" AS ").append(PropertyType.TYPENAME_PATH.toUpperCase()).append(')'); return; case PropertyType.NAME: append("CAST("); append(factories.getNameFactory().create(literalValue.value())); append(" AS ").append(PropertyType.TYPENAME_NAME.toUpperCase()).append(')'); return; case PropertyType.REFERENCE: typeName = PropertyType.TYPENAME_REFERENCE; break; case PropertyType.WEAKREFERENCE: typeName = PropertyType.TYPENAME_WEAKREFERENCE; break; case org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE: typeName = org.modeshape.jcr.api.PropertyType.TYPENAME_SIMPLE_REFERENCE; break; case PropertyType.BINARY: typeName = PropertyType.TYPENAME_BINARY; break; case PropertyType.BOOLEAN: typeName = PropertyType.TYPENAME_BOOLEAN; break; case PropertyType.DATE: typeName = PropertyType.TYPENAME_DATE; break; case PropertyType.DECIMAL: typeName = PropertyType.TYPENAME_DECIMAL; break; case PropertyType.DOUBLE: typeName = PropertyType.TYPENAME_DOUBLE; break; case PropertyType.LONG: typeName = PropertyType.TYPENAME_LONG; break; case PropertyType.URI: typeName = PropertyType.TYPENAME_URI; break; } assert typeName != null; String str = factories.getStringFactory().create(literalValue.value()); append("CAST('").append(str).append("' AS ").append(typeName.toUpperCase()).append(')'); } else { Object value = literal.value(); String typeName = null; ValueFactories factories = context.getValueFactories(); if (value instanceof String || value instanceof Character) { append(SINGLE_QUOTE); String str = factories.getStringFactory().create(value); append(str); append(SINGLE_QUOTE); return; } if (value instanceof Path) { append("CAST("); append(factories.getPathFactory().create(value)); append(" AS ").append(PropertyType.TYPENAME_PATH.toUpperCase()).append(')'); return; } if (value instanceof Name) { append("CAST("); append(factories.getNameFactory().create(value)); append(" AS ").append(PropertyType.TYPENAME_NAME.toUpperCase()).append(')'); return; } if (value instanceof Reference) { typeName = ((Reference)value).isWeak() ? PropertyType.TYPENAME_WEAKREFERENCE.toUpperCase() : PropertyType.TYPENAME_REFERENCE.toUpperCase(); } else if (value instanceof Binary) { typeName = PropertyType.TYPENAME_BINARY.toUpperCase(); } else if (value instanceof Boolean) { typeName = PropertyType.TYPENAME_BOOLEAN.toUpperCase(); } else if (value instanceof DateTime) { typeName = PropertyType.TYPENAME_DATE.toUpperCase(); } else if (value instanceof BigDecimal) { typeName = PropertyType.TYPENAME_DECIMAL.toUpperCase(); } else if (value instanceof Double || value instanceof Float) { typeName = PropertyType.TYPENAME_DOUBLE.toUpperCase(); } else if (value instanceof Long || value instanceof Integer || value instanceof Short) { typeName = PropertyType.TYPENAME_LONG.toUpperCase(); } else if (value instanceof URI) { typeName = PropertyType.TYPENAME_URI.toUpperCase(); } assert typeName != null; String str = factories.getStringFactory().create(value); append("CAST('").append(str).append("' AS ").append(typeName.toUpperCase()).append(')'); } } @Override public void visit( LowerCase lowerCase ) { append("LOWER("); lowerCase.getOperand().accept(this); append(')'); } @Override public void visit( NodeDepth depth ) { append("DEPTH(").append(depth.selectorName()).append(')'); } @Override public void visit( NodeId id ) { append("ID(").append(id.selectorName()).append(')'); } @Override public void visit( NodePath path ) { append("PATH(").append(path.selectorName()).append(')'); } @Override public void visit( NodeLocalName name ) { append("LOCALNAME(").append(name.selectorName()).append(')'); } @Override public void visit( NodeName name ) { append("NAME(").append(name.selectorName()).append(')'); } @Override public void visit( NamedSelector selector ) { append(selector.name()); if (selector.hasAlias()) { append(" AS ").append(selector.alias()); } } @Override public void visit( Not not ) { append("NOT "); append('('); not.getConstraint().accept(this); append(')'); } @Override public void visit( Or or ) { append('('); or.left().accept(this); append(" OR "); or.right().accept(this); append(')'); } @Override public void visit( Ordering ordering ) { ordering.getOperand().accept(this); append(' ').append(ordering.order().symbol()); } @Override public void visit( PropertyExistence existence ) { append(existence.selectorName()).append('.').appendPropertyName(existence.getPropertyName()).append(" IS NOT NULL"); } @Override public void visit( PropertyValue value ) { append(value.selectorName()).append('.').appendPropertyName(value.getPropertyName()); } @Override public void visit( ReferenceValue value ) { append("REFERENCE("); append(value.selectorName()); if (value.getPropertyName() != null) { append('.').appendPropertyName(value.getPropertyName()); } append(")"); } @Override public void visit( Query query ) { append("SELECT "); if (query.isDistinct()) append("DISTINCT "); if (query.columns().isEmpty()) { append('*'); } else { boolean isFirst = true; for (Column column : query.columns()) { if (isFirst) isFirst = false; else append(", "); column.accept(this); } } append(" FROM "); query.source().accept(this); if (query.constraint() != null) { append(" WHERE "); query.constraint().accept(this); } if (!query.orderings().isEmpty()) { append(" ORDER BY "); boolean isFirst = true; for (Ordering ordering : query.orderings()) { if (isFirst) isFirst = false; else append(", "); ordering.accept(this); } } if (!query.getLimits().isUnlimited()) { append(' '); query.getLimits().accept(this); } } @Override public void visit( Subquery subquery ) { append('('); subquery.getQuery().accept(this); append(')'); } @Override public void visit( SameNode sameNode ) { append("ISSAMENODE(").append(sameNode.selectorName()).append(",'").append(sameNode.getPath()).append("')"); } @Override public void visit( SameNodeJoinCondition condition ) { append("ISSAMENODE(").append(condition.selector1Name()).append(',').append(condition.selector2Name()); if (condition.getSelector2Path() != null) { append(",'").append(condition.getSelector2Path()).append('\''); } append(')'); } @Override public void visit( SetCriteria criteria ) { criteria.leftOperand().accept(this); append(" IN ("); Iterator<? extends StaticOperand> iter = criteria.rightOperands().iterator(); if (iter.hasNext()) { iter.next().accept(this); while (iter.hasNext()) { append(','); iter.next().accept(this); } } append(')'); } @Override public void visit( SetQuery query ) { query.getLeft().accept(this); append(' ').append(query.operation().getSymbol()).append(' '); if (query.isAll()) append("ALL "); query.getRight().accept(this); } @Override public void visit( UpperCase upperCase ) { append("UPPER("); upperCase.getOperand().accept(this); append(')'); } @Override public void visit( Cast cast ) { append("CAST("); cast.getOperand().accept(this); append(" AS "); append(cast.getDesiredTypeName()); append(")"); } } public static class JcrSql2Writer extends ReadableVisitor { public JcrSql2Writer( ExecutionContext context ) { super(context); } protected final boolean needsQuotes( String str ) { CharacterIterator iter = new StringCharacterIterator(str); for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) { if (!Character.isLetterOrDigit(c)) return true; } return false; } protected void appendQuoted( char openQuote, String name, char closeQuote ) { // If the name contains any non-alphanumeric characters, then we'll quote. // It's okay (and safer) to quote more often than necessary. if (needsQuotes(name)) { append(OPEN_SQUARE); append(name); append(CLOSE_SQUARE); } else { append(name); } } @Override protected ReadableVisitor append( String string ) { return super.append(string); } @Override protected ReadableVisitor append( char character ) { return super.append(character); } @Override protected ReadableVisitor append( int value ) { return super.append(value); } @Override protected ReadableVisitor appendColumnName( String columnName ) { appendQuoted(OPEN_SQUARE, columnName, CLOSE_SQUARE); return this; } @Override protected ReadableVisitor appendPropertyName( String propertyName ) { appendQuoted(OPEN_SQUARE, propertyName, CLOSE_SQUARE); return this; } @Override protected ReadableVisitor appendAlias( String alias ) { appendQuoted(OPEN_SQUARE, alias, CLOSE_SQUARE); return this; } @Override protected ReadableVisitor append( SelectorName name ) { appendQuoted(OPEN_SQUARE, name.name(), CLOSE_SQUARE); return this; } @Override public void visit( Ordering ordering ) { ordering.getOperand().accept(this); append(' ').append(ordering.order().symbol()); append(' ').append(ordering.nullOrder().symbol()); } } }