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.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import com.github.helenusdriver.commons.lang3.reflect.ReflectionUtils; import com.github.helenusdriver.driver.Clause; import com.github.helenusdriver.driver.CreateSchemas; import com.github.helenusdriver.driver.StatementBridge; import com.github.helenusdriver.driver.VoidFuture; import com.github.helenusdriver.driver.info.ClassInfo; import com.github.helenusdriver.persistence.Keyspace; import org.reflections.Reflections; /** * The <code>CreateSchemasImpl</code> class provides support for a statement * which will create all the required elements (keyspace, tables, and indexes) * to support the schema for a given package of POJOs. It will take care of * creating the required keyspace, tables, and indexes. * * @copyright 2015-2015 The Helenus Driver Project Authors * * @author The Helenus Driver Project Authors * @version 1 - Jan 19, 2015 - paouelle - Creation * * @since 1.0 */ public class CreateSchemasImpl extends SequenceStatementImpl<Void, VoidFuture, Void> implements CreateSchemas { /** * Holds the package for all POJO classes for which to create schemas. * * @author paouelle */ private final String pkg; /** * Flag indicating if only POJOs with keyspace names that can be computed * based on exactly the set of suffixes provided should be considered. * * @author paouelle */ private final boolean matching; /** * Set of POJO class infos with their keyspace to be created. * * @author paouelle */ private final Map<Keyspace, List<ClassInfoImpl<?>>> keyspaces; /** * Holds the cache of contexts for POJOs that will have schemas created. * * @author paouelle */ private volatile List<ClassInfoImpl<?>.Context> contexts; /** * Flag indicating if the "IF NOT EXIST" option has been selected. * * @author paouelle */ private volatile boolean ifNotExists; /** * Holds the where statement part. * * @author paouelle */ private final WhereImpl where; /** * Instantiates a new <code>CreateSchemaImpl</code> object. * * @author paouelle * * @param pkg the package where to find all POJO classes to create schemas for * associated with this statement * @param matching <code>true</code> to only consider POJOs with keyspace names * that can be computed with exactly the set of suffixes provided * @param mgr the non-<code>null</code> statement manager * @param bridge the non-<code>null</code> statement bridge * @throws NullPointerException if <code>pkg</code> is <code>null</code> * @throws IllegalArgumentException if an @Entity or @RootEntity annotated * class is missing the @Keyspace annotation or two entities defines * the same keyspace with different options or an entity class doesn't * represent a valid POJO class or if no entities are found */ public CreateSchemasImpl(String pkg, boolean matching, StatementManagerImpl mgr, StatementBridge bridge) { super(Void.class, (String) null, mgr, bridge); org.apache.commons.lang3.Validate.notNull(pkg, "invalid null package"); this.pkg = pkg; this.matching = matching; this.keyspaces = findKeyspaces(); this.where = new WhereImpl(this); } /** * Find all keyspaces. * * @author paouelle * * @return a map of all the keyspaces along with the list of POJO class info * associated with them * @throws IllegalArgumentException if an @Entity annotated class is missing the * Keyspace annotation or two entities defines the same keyspace with * different options or an @Entitiy annotated class doesn't represent * a valid POJO class or if no entities are found */ private Map<Keyspace, List<ClassInfoImpl<?>>> findKeyspaces() { final Map<String, Keyspace> keyspaces = new HashMap<>(25); final Map<Keyspace, List<ClassInfoImpl<?>>> cinfos = new HashMap<>(25); final Reflections reflections = new Reflections(pkg); // search for all POJO annotated classes with @Entity for (final Class<?> clazz : reflections .getTypesAnnotatedWith(com.github.helenusdriver.persistence.Entity.class, true)) { // skip abstract POJO classes if (Modifier.isAbstract(clazz.getModifiers())) { continue; } final ClassInfoImpl<?> cinfo = mgr.getClassInfoImpl(clazz); final Keyspace k = cinfo.getKeyspace(); final Keyspace old = keyspaces.put(k.name(), k); List<ClassInfoImpl<?>> cs = cinfos.get(k); if (cs == null) { cs = new ArrayList<>(25); cinfos.put(k, cs); } cs.add(cinfo); if ((old != null) && !k.equals(old)) { // duplicate annotation found with different attribute throw new IllegalArgumentException("two different @Keyspace annotations found with class '" + clazz.getName() + "': " + old + " and: " + k); } } // search for all POJO annotated classes with @RootEntity for (final Class<?> clazz : reflections .getTypesAnnotatedWith(com.github.helenusdriver.persistence.RootEntity.class, true)) { // skip classes that are not directly annotated if (ReflectionUtils.findFirstClassAnnotatedWith(clazz, com.github.helenusdriver.persistence.RootEntity.class) != clazz) { continue; } final ClassInfoImpl<?> cinfo = mgr.getClassInfoImpl(clazz); final Keyspace k = cinfo.getKeyspace(); final Keyspace old = keyspaces.put(k.name(), k); List<ClassInfoImpl<?>> cs = cinfos.get(k); if (cs == null) { cs = new ArrayList<>(25); cinfos.put(k, cs); } cs.add(cinfo); if ((old != null) && !k.equals(old)) { // duplicate annotation found with different attribute throw new IllegalArgumentException("two different @Keyspace annotations found with class '" + clazz.getName() + "': " + old + " and: " + k); } } // search for all POJO annotated classes with @UDTEntity for (final Class<?> clazz : reflections .getTypesAnnotatedWith(com.github.helenusdriver.persistence.UDTEntity.class, true)) { // skip abstract POJO classes if (Modifier.isAbstract(clazz.getModifiers())) { continue; } final ClassInfoImpl<?> cinfo = mgr.getClassInfoImpl(clazz); final Keyspace k = cinfo.getKeyspace(); final Keyspace old = keyspaces.put(k.name(), k); List<ClassInfoImpl<?>> cs = cinfos.get(k); if (cs == null) { cs = new ArrayList<>(25); cinfos.put(k, cs); } cs.add(cinfo); if ((old != null) && !k.equals(old)) { // duplicate annotation found with different attribute throw new IllegalArgumentException("two different @Keyspace annotations found with class '" + clazz.getName() + "': " + old + " and: " + k); } } org.apache.commons.lang3.Validate.isTrue(!cinfos.isEmpty(), "no classes annotated with @Entity, @RootEntity, or @UDTEntity found in package: %s", pkg); return cinfos; } /** * Gets the contexts for all POJO classes for which we should create schemas. * * @author paouelle * * @return the list of contexts for all POJO for which we are creating schemas * @throws IllegalArgumentException if the value for a provided suffix doesn't * match the POJO's definition for that suffix */ @SuppressWarnings({ "synthetic-access", "unchecked", "rawtypes" }) private List<ClassInfoImpl<?>.Context> getContexts() { if (contexts == null) { this.contexts = new ArrayList<>(25); next_keyspace: for (final List<ClassInfoImpl<?>> cinfos : keyspaces.values()) { // create contexts for all the classes associated with the keyspace for (final ClassInfoImpl<?> cinfo : cinfos) { final ClassInfoImpl<?>.Context context = cinfo.newContext(); IllegalArgumentException iae = null; int found = 0; // populate the required suffixes for (final Map.Entry<String, FieldInfoImpl<?>> e : (Set<Map.Entry<String, FieldInfoImpl<?>>>) (Set) cinfo .getSuffixTypes().entrySet()) { final String type = e.getKey(); final FieldInfoImpl<?> finfo = e.getValue(); if (!where.suffixes.containsKey(type)) { // we are missing a required suffix for this POJO and since all pojos // references the same @Keyspace, all of them will have the same problem // -- so continue with next keyspace and ignore any errors that might // occurred for suffixes before that continue next_keyspace; } found++; // don't forget to convert the type into the suffix key name for the // associated POJO try { context.addSuffix(finfo.getSuffixKey().name(), where.suffixes.get(type)); } catch (IllegalArgumentException ee) { if (iae == null) { // keep only first one iae = ee; } } } // if we get here then we must have found all required suffixes in the // where clause so whether or not we consider it depends on whether or // not we required the exact same number of suffixes as defined if we // are matching or if we are not matching (and there would potentially // be more suffixes defined than we needed) if ((found == where.suffixes.size()) || !matching) { // if we got an error on one of the suffix, throw it back if (iae != null) { throw iae; } contexts.add(context); } } } } return contexts; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.impl.StatementImpl#setDirty() */ @Override protected void setDirty() { super.setDirty(); this.contexts = null; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.impl.StatementImpl#buildQueryStrings() */ @SuppressWarnings({ "unchecked", "rawtypes" }) @Override protected StringBuilder[] buildQueryStrings() { // we do not want to create the same keyspace so many times for nothing final List<ClassInfoImpl<?>.Context> contexts = getContexts(); final List<StringBuilder> builders = new ArrayList<>(contexts.size() * 2); final Set<Keyspace> keyspaces = new HashSet<>(contexts.size()); for (final ClassInfoImpl<?>.Context context : contexts) { final CreateSchemaImpl cs = new CreateSchemaImpl(context, mgr, bridge); if (ifNotExists) { cs.ifNotExists(); } final StringBuilder[] cbuilders = cs.buildQueryStrings(keyspaces); if (cbuilders != null) { for (final StringBuilder builder : cbuilders) { if (builder != null) { builders.add(builder); } } } } if (builders.isEmpty()) { return null; } return builders.toArray(new StringBuilder[builders.size()]); } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.impl.StatementImpl#appendGroupSubType(java.lang.StringBuilder) */ @Override protected void appendGroupSubType(StringBuilder builder) { builder.append(" CREATE"); } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.impl.SequenceStatementImpl#appendGroupType(java.lang.StringBuilder) */ @Override protected void appendGroupType(StringBuilder builder) { builder.append("SCHEMAS"); } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.CreateSchemas#getObjectClasses() */ @Override public Set<Class<?>> getObjectClasses() { return getContexts().stream().flatMap(c -> c.getClassInfo().objectClasses()).collect(Collectors.toSet()); } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.CreateSchemas#getClassInfos() */ @Override public Set<ClassInfo<?>> getClassInfos() { return getContexts().stream().flatMap(c -> c.getClassInfo().classInfos()).collect(Collectors.toSet()); } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.CreateSchemas#getDefinedClassInfos() */ @Override public Set<ClassInfo<?>> getDefinedClassInfos() { return keyspaces.values().stream().flatMap(cl -> cl.stream()).flatMap(cl -> cl.classInfos()) .collect(Collectors.toSet()); } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.CreateSchemas#ifNotExists() */ @Override public CreateSchemas ifNotExists() { this.ifNotExists = true; setDirty(); return this; } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.CreateSchemas#where(com.github.helenusdriver.driver.Clause) */ @Override public Where where(Clause clause) { return where.and(clause); } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.CreateSchemas#where() */ @Override public Where where() { return where; } /** * The <code>WhereImpl</code> class defines a WHERE clause for the CREATE * SCHEMAS statement which can be used to specify suffix types used for * keyspace names. * * @copyright 2015-2015 The Helenus Driver Project Authors * * @author The Helenus Driver Project Authors * @version 1 - Jan 19, 2015 - paouelle - Creation * * @since 1.0 */ public static class WhereImpl extends ForwardingStatementImpl<Void, VoidFuture, Void, CreateSchemasImpl> implements Where { /** * Holds the suffixes with their values. * * @author paouelle */ private final Map<String, Object> suffixes = new HashMap<>(8); /** * Instantiates a new <code>WhereImpl</code> object. * * @author paouelle * * @param statement the encapsulated statement */ WhereImpl(CreateSchemasImpl statement) { super(statement); } /** * {@inheritDoc} * * @author paouelle * * @see com.github.helenusdriver.driver.CreateTable.Where#and(com.github.helenusdriver.driver.Clause) */ @Override public Where 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 CREATE SCHEMA statement", clause); if (clause instanceof ClauseImpl.Delayed) { for (final Clause c : ((ClauseImpl.Delayed) clause) .processWith(statement.getContext().getClassInfo())) { and(c); // recurse to add the processed clause } } else { final ClauseImpl c = (ClauseImpl) clause; org.apache.commons.lang3.Validate.isTrue(clause instanceof Clause.Equality, "unsupported class of clauses: %s", clause.getClass().getName()); suffixes.put(c.getColumnName().toString(), c.firstValue()); setDirty(); } return this; } } }