Java tutorial
/* * Copyright (C) 2015-2015 The Helenus Driver Project Authors. * * 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 com.github.helenusdriver.driver.impl; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import com.github.helenusdriver.driver.Clause; import com.github.helenusdriver.driver.ObjectSet; import com.github.helenusdriver.driver.ObjectSetFuture; import com.github.helenusdriver.driver.Ordering; import com.github.helenusdriver.driver.Select; import com.github.helenusdriver.driver.StatementBridge; import com.github.helenusdriver.driver.impl.Utils.CName; /** * The <code>SelectImpl</code> class extends the functionality of Cassandra's * {@link com.datastax.driver.core.querybuilder.Select} class to provide support * for POJOs. * * @copyright 2015-2015 The Helenus Driver Project Authors * * @author The Helenus Driver Project Authors * @version 1 - Jan 19, 2015 - paouelle - Creation * * @param <T> The type of POJO associated with this statement. * * @since 1.0 */ public class SelectImpl<T> extends StatementImpl<ObjectSet<T>, ObjectSetFuture<T>, T> implements Select<T> { /** * Holds a special list of column names used when we are counting instead of * querying. * * @author paouelle */ static final List<Object> COUNT_ALL = Collections .<Object>singletonList(new Utils.FCall("count", new Utils.RawString("*"))); /** * Holds the table info for this statement. * * @author paouelle */ final TableInfoImpl<T> table; /** * Holds the where statement part. * * @author paouelle */ private final WhereImpl<T> where; /** * Holds the registered ordering statement parts. * * @author paouelle */ private List<OrderingImpl> orderings; /** * Holds the limit associated with this statement. * * @author paouelle */ private int limit = -1; /** * Flag indicating if filtering is allowed. * * @author paouelle */ private boolean allowFiltering; /** * List of columns added. * * @author paouelle */ protected List<Object> columnNames; /** * Flag to keep track of whether or not the special COUNT() or * has been * selected. * * @author paouelle */ protected boolean countOrAllSelected = false; /** * Instantiates a new <code>SelectImpl</code> object. * * @author paouelle * * @param context the non-<code>null</code> class info context for the POJO * associated with this statement * @param table the non-<code>null</code> table to select from * @param columnNames the selected columns * @param countOrAllSelected <code>true</code> if the special COUNT() or * * has been selected * @param mgr the non-<code>null</code> statement manager * @param bridge the non-<code>null</code> statement bridge * @throws NullPointerException if <code>context</code> is <code>null</code> * @throws IllegalArgumentException if the table or any of the referenced * columns are not defined by the POJO */ @SuppressWarnings({ "cast", "unchecked", "rawtypes" }) SelectImpl(ClassInfoImpl<T>.Context context, String table, List<Object> columnNames, boolean countOrAllSelected, StatementManagerImpl mgr, StatementBridge bridge) { super((Class<ObjectSet<T>>) (Class) ObjectSet.class, context, mgr, bridge); this.table = (TableInfoImpl<T>) context.getClassInfo().getTable(table); this.columnNames = columnNames; this.countOrAllSelected = countOrAllSelected; this.where = new WhereImpl<>(this); org.apache.commons.lang3.Validate.isTrue(!(!countOrAllSelected && CollectionUtils.isEmpty(columnNames)), "must select at least one column"); if (columnNames != null) { this.table.validateColumns(columnNames); } } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.impl.StatementImpl#buildQueryStrings() */ @Override protected StringBuilder[] buildQueryStrings() { return new StringBuilder[] { buildQueryString() }; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.impl.StatementImpl#buildQueryString() */ @Override @SuppressWarnings("synthetic-access") protected StringBuilder buildQueryString() { final StringBuilder builder = new StringBuilder(); builder.append("SELECT "); if (columnNames == null) { builder.append("*"); } else { if (countOrAllSelected) { Utils.joinAndAppendNames(builder, ",", columnNames); } else { // prepend all mandatory and primary key columns if it is not the special COUNT() final Set<String> set = new HashSet<>(25); for (final Object name : columnNames) { if (name instanceof String) { set.add((String) name); } else if (name instanceof CName) { set.add(((CName) name).getName()); } // else - ????? } final List<Object> names = new ArrayList<>(); for (final String name : table.getMandatoryAndPrimaryKeyColumns()) { if (!set.contains(name)) { names.add(name); } } names.addAll(columnNames); Utils.joinAndAppendNames(builder, ",", names); } } builder.append(" FROM "); if (getKeyspace() != null) { Utils.appendName(getKeyspace(), builder).append("."); } Utils.appendName(table.getName(), builder); if (!where.clauses.isEmpty()) { builder.append(" WHERE "); Utils.joinAndAppend(table, builder, " AND ", where.getClauses(table)); } if (orderings != null) { builder.append(" ORDER BY "); Utils.joinAndAppend(table, builder, ",", orderings); } if (limit > 0) { builder.append(" LIMIT ").append(limit); } if (allowFiltering) { builder.append(" ALLOW FILTERING"); } return builder; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.Select#where(com.github.helenusdriver.driver.Clause) */ @Override public Where<T> where(Clause clause) { return where.and(clause); } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.Select#where() */ @Override public Where<T> where() { return where; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.Select#orderBy(com.github.helenusdriver.driver.Ordering[]) */ @Override public Select<T> orderBy(Ordering... orderings) { org.apache.commons.lang3.Validate.validState(this.orderings == null, "an ORDER BY clause has already been provided"); this.orderings = new ArrayList<>(orderings.length); for (final Ordering o : orderings) { org.apache.commons.lang3.Validate.isTrue(o instanceof OrderingImpl, "unsupported class of orderings: %s", o.getClass().getName()); final OrderingImpl oi = (OrderingImpl) o; oi.validate(table); // check the referenced column is a multi-key in which case we actually // need to treat this ordering request as if it was for the special // multi-key column instead if (table.isMultiKey(oi.getColumnName())) { this.orderings .add(new OrderingImpl(StatementImpl.MK_PREFIX + oi.getColumnName(), oi.isDescending())); } else { this.orderings.add(oi); } } setDirty(); return this; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.Select#limit(int) */ @Override public Select<T> limit(int limit) { org.apache.commons.lang3.Validate.validState(this.limit <= 0, "a LIMIT value has already been provided"); org.apache.commons.lang3.Validate.isTrue(limit > 0, "invalid negative LIMIT: %s", limit); this.limit = limit; setDirty(); return this; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.Select#allowFiltering() */ @Override public Select<T> allowFiltering() { this.allowFiltering = true; return this; } /** * The <code>WhereImpl</code> class defines a WHERE clause for a SELECT * statement. * * @copyright 2015-2015 The Helenus Driver Project Authors * * @author The Helenus Driver Project Authors * @version 1 - Jan 19, 2015 - paouelle - Creation * * @param <T> The type of POJO associated with the statement. * * @since 1.0 */ public static class WhereImpl<T> extends ForwardingStatementImpl<ObjectSet<T>, ObjectSetFuture<T>, T, SelectImpl<T>> implements Where<T> { /** * Holds the list of clauses for this statement * * @author paouelle */ private final List<ClauseImpl> clauses = new ArrayList<>(10); /** * Instantiates a new <code>WhereImpl</code> object. * * @author paouelle * * @param statement the encapsulated statement */ WhereImpl(SelectImpl<T> statement) { super(statement); } /** * Gets the "where" clauses while adding missing final partition keys. * * @author paouelle * * @param table the table for which to get the "where" clauses * @return the "where" clauses */ private List<ClauseImpl> getClauses(TableInfoImpl<T> table) { // check if the table defines any final primary keys // in which case we want to make sure to add clauses for them too // and also check for multi-keys as we need to convert the provided value // (if a set of size 1) into an EQ final List<ClauseImpl> clauses = new ArrayList<>(this.clauses); if (!table.getMultiKeys().isEmpty()) { // need to see if we have multi-keys in the list and if // so, we need to convert its value (if a set) into an "EQ" for (final ListIterator<ClauseImpl> i = clauses.listIterator(); i.hasNext();) { final ClauseImpl clause = i.next(); if (table.isMultiKey(clause.getColumnName())) { final Set<Object> in = new LinkedHashSet<>(8); // preserve order for (final Object v : clause.values()) { if (v instanceof Set<?>) { for (final Object sv : (Set<?>) v) { in.add(sv); } } else { in.add(v); } } // NOTE: Cassandra doesn't support using an 'IN' for a primary key // if as part of the columns selected, one is a collection. In our // case, the multi-key column is itself a collection as such, we will // never be able to support the 'IN' clause for that column if (in.size() == 1) { i.set(new ClauseImpl.EqClauseImpl(StatementImpl.MK_PREFIX + clause.getColumnName(), in.iterator().next())); } else { throw new IllegalArgumentException( "unsupported selection of multiple values for multi-key column '" + clause.getColumnName() + "'"); } } } } for (final Map.Entry<String, Object> e : table.getFinalPrimaryKeyValues().entrySet()) { final String name = e.getKey(); // check if we already have a clause for that column boolean found = false; for (final ClauseImpl c : this.clauses) { if (name.equals(c.getColumnName().toString())) { found = true; break; } } if (!found) { clauses.add(new ClauseImpl.EqClauseImpl(name, e.getValue())); } } return clauses; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.Select.Where#and(com.github.helenusdriver.driver.Clause) */ @Override public Where<T> and(Clause clause) { org.apache.commons.lang3.Validate.notNull(clause, "invalid null clause"); org.apache.commons.lang3.Validate.isTrue(clause instanceof ClauseImpl, "unsupported class of clauses: %s", clause.getClass().getName()); org.apache.commons.lang3.Validate.isTrue(!(clause instanceof ClauseImpl.DelayedWithObject), "unsupported clause '%s' for a SELECT statement", clause); if (clause instanceof ClauseImpl.Delayed) { for (final Clause c : ((ClauseImpl.Delayed) clause).processWith(statement.table)) { and(c); // recurse to add the processed clause } } else { final ClauseImpl c = (ClauseImpl) clause; boolean add = true; if (c instanceof Clause.Equality) { statement.table.validateSuffixKeyOrPrimaryKeyOrIndexColumn(c.getColumnName()); if (statement.getContext().getClassInfo().isSuffixKey(c.getColumnName().toString())) { statement.getContext().addSuffix(c.getColumnName().toString(), c.firstValue()); // only add if it is a column too add = statement.table.hasColumn(c.getColumnName()); } } else { statement.table.validatePrimaryKeyOrIndexColumn(c.getColumnName()); } c.validate(statement.table); if (add) { clauses.add(c); setDirty(); } } return this; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.Select.Where#orderBy(com.github.helenusdriver.driver.Ordering[]) */ @Override public Select<T> orderBy(Ordering... orderings) { return statement.orderBy(orderings); } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.Select.Where#limit(int) */ @Override public Select<T> limit(int limit) { return statement.limit(limit); } } /** * The <code>BuilderImpl</code> class defines an in-construction SELECT statement. * * @copyright 2015-2015 The Helenus Driver Project Authors * * @author The Helenus Driver Project Authors * @version 1 - Jan 19, 2015 - paouelle - Creation * * @param <T> The type of POJO associated with the statement. * * @since 1.0 */ public static class BuilderImpl<T> implements Builder<T> { /** * Holds the statement manager. * * @author paouelle */ protected final StatementManagerImpl mgr; /** * Holds the statement bridge. * * @author paouelle */ protected final StatementBridge bridge; /** * Holds the context associated with the POJO class for this statement. * * @author paouelle */ protected final ClassInfoImpl<T>.Context context; /** * List of columns added. * * @author paouelle */ protected List<Object> columnNames; /** * Flag to keep track of whether or not the special COUNT() or * has been * selected. * * @author paouelle */ protected boolean countOrAllSelected = false; /** * Instantiates a new <code>BuilderImpl</code> object. * * @author paouelle * * @param context the non-<code>null</code> class info context for the POJO * associated with this statement * @param mgr the non-<code>null</code> statement manager * @param bridge the non-<code>null</code> statement bridge */ BuilderImpl(ClassInfoImpl<T>.Context context, StatementManagerImpl mgr, StatementBridge bridge) { this.mgr = mgr; this.bridge = bridge; this.context = context; } /** * Instantiates a new <code>BuilderImpl</code> object. * * @author paouelle * * @param context the non-<code>null</code> class info context for the POJO * associated with this statement * @param columnNames the column names * @param mgr the non-<code>null</code> statement manager * @param bridge the non-<code>null</code> statement bridge * @throws NullPointerException if <code>context</code> is <code>null</code> * @throws IllegalArgumentException if any of the specified columns are not * defined by the POJO */ BuilderImpl(ClassInfoImpl<T>.Context context, List<Object> columnNames, StatementManagerImpl mgr, StatementBridge bridge) { this(context, mgr, bridge); context.getClassInfo().validateColumns(columnNames); this.columnNames = columnNames; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.Select.Builder#from(java.lang.String) */ @Override public Select<T> from(String table) { return new SelectImpl<>(context, table, columnNames, countOrAllSelected, mgr, bridge); } } /** * The <code>SelectionImpl</code> class defines a selection clause for an * in-construction SELECT statement. * * @copyright 2015-2015 The Helenus Driver Project Authors * * @author The Helenus Driver Project Authors * @version 1 - Jan 19, 2015 - paouelle - Creation * * @param <T> The type of POJO associated with the response from this statement. * * @since 1.0 */ public static class SelectionImpl<T> extends BuilderImpl<T> implements Selection<T> { /** * Instantiates a new <code>SelectionImpl</code> object. * * @author paouelle * * @param context the non-<code>null</code> class info context for the POJO * associated with this statement * @param mgr the non-<code>null</code> statement manager * @param bridge the non-<code>null</code> statement bridge */ SelectionImpl(ClassInfoImpl<T>.Context context, StatementManagerImpl mgr, StatementBridge bridge) { super(context, mgr, bridge); } /** * Adds the specified column name. * * @author paouelle * * @param name the non-<code>null</code> column name to be added * @return this for chaining * @throws NullPointerException if <code>name</code> is <code>null</code> * @throws IllegalArgumentException if any of the specified columns are not defined * by the POJO */ private Selection<T> addName(Object name) { context.getClassInfo().validateColumn(name); if (columnNames == null) { super.columnNames = new ArrayList<>(25); } columnNames.add(name); return this; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.Select.Selection#all() */ @Override public Builder<T> all() { org.apache.commons.lang3.Validate.validState(columnNames == null, "some columns (%s) have already been selected", StringUtils.join(columnNames, ", ")); super.countOrAllSelected = true; return this; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.Select.Selection#countAll() */ @Override public Builder<T> countAll() { org.apache.commons.lang3.Validate.validState(columnNames == null, "some columns (%s) have already been selected", StringUtils.join(columnNames, ", ")); super.columnNames = SelectImpl.COUNT_ALL; super.countOrAllSelected = true; return this; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.Select.Selection#column(java.lang.Object) */ @Override public Selection<T> column(Object name) { return addName(name); } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.Select.Selection#writeTime(java.lang.String) */ @Override public Selection<T> writeTime(String name) { return addName(new Utils.FCall("writetime", new Utils.CName(name))); } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.Select.Selection#ttl(java.lang.String) */ @Override public Selection<T> ttl(String name) { return addName(new Utils.FCall("ttl", new Utils.CName(name))); } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.Select.Selection#fcall(java.lang.String, java.lang.Object[]) */ @Override public Selection<T> fcall(String name, Object... parameters) { return addName(new Utils.FCall(name, parameters)); } } }