All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.helenusdriver.driver.impl.RootClassInfoImpl Maven / Gradle / Ivy

Go to download

JPA-like syntax for annotating POJO classes for persistence via Cassandra's Java driver - Implementation

The newest version!
/*
 * 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.Array;
import java.lang.reflect.Modifier;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.datastax.driver.core.ColumnDefinitions;
import com.datastax.driver.core.Row;

import com.github.helenusdriver.driver.ObjectConversionException;
import com.github.helenusdriver.persistence.RootEntity;

/**
 * The RootClassInfoImpl class provides information about a
 * particular root element POJO class.
 *
 * @copyright 2015-2015 The Helenus Driver Project Authors
 *
 * @author  The Helenus Driver Project Authors
 * @version 1 - Jan 19, 2015 - paouelle - Creation
 *
 * @param  The type of POJO represented by this root class
 *
 * @since 2.0
 */
@lombok.ToString(callSuper=true, of={"ntypes"})
@lombok.EqualsAndHashCode(callSuper=true)
public class RootClassInfoImpl extends ClassInfoImpl {
  /**
   * Finds the class info for all type classes for this root element class.
   *
   * @author paouelle
   *
   * @param  The type of POJO represented by this root element class
   *
   * @param  mgr the non-null statement manager
   * @param  clazz the root POJO class
   * @return the non-null map of class info for all types
   * @throws NullPointerException if clazz is null
   * @throws IllegalArgumentException if any of the type classes are invalid
   */
  private static  Map, TypeClassInfoImpl> findTypeInfos(
    StatementManagerImpl mgr, Class clazz
  ) {
    org.apache.commons.lang3.Validate.notNull(clazz, "invalid null root POJO class");
    final RootEntity re = clazz.getAnnotation(RootEntity.class);
    final int hsize = re.types().length * 3 / 2;
    final Map, TypeClassInfoImpl> types
      = new HashMap<>(hsize);
    final Set names = new HashSet<>(hsize);

    for (final Class type: re.types()) {
      org.apache.commons.lang3.Validate.isTrue(
        clazz.isAssignableFrom(type),
        "type class '%s' must extends root element class: %s",
        type.getName(), clazz.getName()
      );
      @SuppressWarnings("unchecked") // tested above!
      final TypeClassInfoImpl tcinfo
        = new TypeClassInfoImpl<>(mgr, clazz, (Class)type);

      org.apache.commons.lang3.Validate.isTrue(
        types.put(tcinfo.getObjectClass(), tcinfo) == null,
        "duplicate type element class '%s' defined for root element class '%s'",
        type.getSimpleName(), clazz.getSimpleName()
      );
      org.apache.commons.lang3.Validate.isTrue(
        names.add(tcinfo.getType()),
        "duplicate type name '%s' defined by class '%s' for root element class '%s'",
        tcinfo.getType(), type.getSimpleName(), clazz.getSimpleName()
      );
    }
    return types;
  }

  /**
   * The Context class extends the {@link ClassInfoImpl.Context}.
   *
   * @copyright 2015-2015 The Helenus Driver Project Authors
   *
   * @author  The Helenus Driver Project Authors
   * @version 1 - Jan 19, 2015 - paouelle - Creation
   *
   * @since 2.0
   */
  public class Context extends ClassInfoImpl.Context {
    /**
     * Holds the contexts for all defined types.
     *
     * @author paouelle
     */
    private final Map, TypeClassInfoImpl.Context> contexts;

    /**
     * Instantiates a new Context object.
     *
     * @author paouelle
     */
    @SuppressWarnings("synthetic-access")
    Context() {
      this.contexts = ctypes.values().stream()
        .collect(
          Collectors.toMap(
            tcinfo -> tcinfo.getObjectClass(),
            tcinfo -> tcinfo.newContext()
          )
        );
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.Context#getInitialObjects()
     */
    @Override
    @SuppressWarnings("unchecked")
    public T[] getInitialObjects() {
      return contexts.values().stream()
      .flatMap(
        tc -> {
          final T[] ts = tc.getInitialObjects();

          return (ts != null) ? Arrays.stream(ts) : Stream.empty();
        }
      ).toArray(
        sz -> (T[])Array.newInstance(clazz, sz)
      );
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.Context#addSuffix(java.lang.String, java.lang.Object)
     */
    @Override
    public void addSuffix(String suffix, Object value) {
      super.addSuffix(suffix, value);
      contexts.values().forEach(
        tc -> tc.addSuffix(suffix, value)
      );
    }
  }

  /**
   * The POJOContext class extends the
   * {@link ClassInfoImpl.POJOContext}.
   *
   * @copyright 2015-2015 The Helenus Driver Project Authors
   *
   * @author  The Helenus Driver Project Authors
   * @version 1 - Jan 19, 2015 - paouelle - Creation
   *
   * @since 2.0
   */
  public class POJOContext extends ClassInfoImpl.POJOContext {
    /**
     * Holds the corresponding type context for the POJO.
     *
     * @author paouelle
     */
    private final TypeClassInfoImpl.POJOContext tcontext;

    /**
     * Instantiates a new POJORootContext object.
     *
     * @author paouelle
     *
     * @param  object the POJO object
     * @throws NullPointerException if object is null
     * @throws IllegalArgumentException if object is not of the
     *         appropriate class
     */
    @SuppressWarnings("synthetic-access")
    public POJOContext(T object) {
      super(object);
      final TypeClassInfoImpl tcinfo = ctypes.get(object.getClass());

      org.apache.commons.lang3.Validate.isTrue(
        tcinfo != null,
        "invalid POJO class '%s'; expecting one of %s",
        object.getClass().getName(), ctypes.keySet()
      );
      this.tcontext = tcinfo.newContextFromRoot(object);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getColumnValues(java.lang.String)
     */
    @Override
    public Map getColumnValues(String tname) {
      return tcontext.getColumnValues(tname);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getPartitionKeyColumnValues(java.lang.String)
     */
    @Override
    public Map getPartitionKeyColumnValues(String tname) {
      return tcontext.getPartitionKeyColumnValues(tname);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getSuffixAndPartitionKeyColumnValues(java.lang.String)
     */
    @Override
    public Map getSuffixAndPartitionKeyColumnValues(String tname) {
      return tcontext.getSuffixAndPartitionKeyColumnValues(tname);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getPrimaryKeyColumnValues(java.lang.String)
     */
    @Override
    public Map getPrimaryKeyColumnValues(String tname) {
      return tcontext.getPrimaryKeyColumnValues(tname);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getSuffixKeyValues()
     */
    @Override
    public Map getSuffixKeyValues() {
      return tcontext.getSuffixKeyValues();
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getSuffixAndPrimaryKeyColumnValues(java.lang.String)
     */
    @Override
    public Map getSuffixAndPrimaryKeyColumnValues(String tname) {
      return tcontext.getSuffixAndPrimaryKeyColumnValues(tname);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getMandatoryAndPrimaryKeyColumnValues(java.lang.String)
     */
    @Override
    public Map getMandatoryAndPrimaryKeyColumnValues(
      String tname
    ) {
      return tcontext.getMandatoryAndPrimaryKeyColumnValues(tname);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getNonPrimaryKeyColumnValues(java.lang.String)
     */
    @Override
    public Map getNonPrimaryKeyColumnValues(String tname) {
      return tcontext.getNonPrimaryKeyColumnValues(tname);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getColumnValue(java.lang.String, java.lang.CharSequence)
     */
    @Override
    public Object getColumnValue(String tname, CharSequence name) {
      return tcontext.getColumnValue(tname, name);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getColumnValues(java.lang.String, java.lang.Iterable)
     */
    @Override
    public Map getColumnValues(
      String tname, Iterable names
    ) {
      return tcontext.getColumnValues(tname, names);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.POJOContext#getColumnValues(java.lang.String, java.lang.CharSequence[])
     */
    @Override
    public Map getColumnValues(
      String tname, CharSequence... names
    ) {
      return tcontext.getColumnValues(tname, names);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.Context#getKeyspace()
     */
    @Override
    public String getKeyspace() {
      return tcontext.getKeyspace();
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.Context#addSuffix(java.lang.String, java.lang.Object)
     */
    @Override
    public void addSuffix(String suffix, Object value) {
      tcontext.addSuffix(suffix, value);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.Context#getObject(com.datastax.driver.core.Row)
     */
    @Override
    public T getObject(Row row) {
      return tcontext.getObject(row);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.driver.impl.ClassInfoImpl.Context#getInitialObjects()
     */
    @Override
    public T[] getInitialObjects() {
      return tcontext.getInitialObjects();
    }
  }

  /**
   * Holds all the type element subclasses for this root element base class
   * keyed by their type class.
   *
   * @author paouelle
   */
  private final Map, TypeClassInfoImpl> ctypes;

  /**
   * Holds all the type element subclasses for this root element base class
   * keyed by their type names.
   *
   * @author paouelle
   */
  private final Map> ntypes;

  /**
   * Instantiates a new RootClassInfoImpl object.
   *
   * @author paouelle
   *
   * @param  mgr the non-null statement manager
   * @param  clazz the root class of POJO for which to get a class info object for
   * @param  types the non-null map of all type class infos
   * @throws NullPointerException if clazz is null
   * @throws IllegalArgumentException if clazz or any of its type
   *         classes don't represent valid POJO classes
   */
  private RootClassInfoImpl(
    StatementManagerImpl mgr,
    Class clazz,
    Map, TypeClassInfoImpl> types
  ) {
    super(mgr, clazz, RootEntity.class);
    // first make sure the class is abstract
    org.apache.commons.lang3.Validate.isTrue(
      Modifier.isAbstract(clazz.getModifiers()),
      "root entity class '%s', must be abstract", clazz.getSimpleName()
    );
    this.ctypes = types;
    this.ntypes = types.values().stream()
      .collect(
        Collectors.toMap(
          tcinfo -> tcinfo.getType(),
          tcinfo -> tcinfo
        )
      );
    validateAndComplementSchema();
  }

  /**
   * Instantiates a new RootClassInfoImpl object.
   *
   * @author paouelle
   *
   * @param  mgr the non-null statement manager
   * @param  clazz the root class of POJO for which to get a class info object for
   * @throws NullPointerException if clazz is null
   * @throws IllegalArgumentException if clazz or any of its type
   *         classes don't represent valid POJO classes
   */
  RootClassInfoImpl(StatementManagerImpl mgr, Class clazz) {
    this(mgr, clazz, RootClassInfoImpl.findTypeInfos(mgr, clazz));
  }

  /**
   * Validates this root entity class and complement its schema with the
   * additional columns defined by the type entities.
   *
   * @author paouelle
   *
   * @throws IllegalArgumentException if the POJO class is improperly annotated
   */
  private void validateAndComplementSchema() {
    // check all tables
    tables().forEach(
      t -> {
        // check type key
        org.apache.commons.lang3.Validate.isTrue(
          t.getTypeKey().isPresent(),
          "%s must annotate one field as a type key for table '%s'",
          clazz.getSimpleName(), t.getName()
        );
        ctypes.values().forEach(
          tcinfo -> {
            tcinfo.getTableImpl(t.getName()).getNonPrimaryKeys().stream()
              .forEach(
                c -> t.addNonPrimaryColumn(c)
              );
          }
        );
      }
    );
  }

  /**
   * {@inheritDoc}
   *
   * @author paouelle
   *
   * @see com.github.helenusdriver.driver.impl.ClassInfoImpl#objectClasses()
   */
  @Override
  public Stream> objectClasses() {
    return Stream.concat(
      Stream.of(clazz), ctypes.values().stream().map(t -> t.getObjectClass())
    );
  }


  /**
   * {@inheritDoc}
   *
   * @author paouelle
   *
   * @see com.github.helenusdriver.driver.impl.ClassInfoImpl#classInfos()
   */
  @Override
  public Stream> classInfos() {
    return Stream.concat(
      Stream.of(this), ctypes.values().stream()
    );
  }

  /**
   * {@inheritDoc}
   *
   * @author paouelle
   *
   * @see com.github.helenusdriver.driver.impl.ClassInfoImpl#newContext()
   */
  @Override
  public Context newContext() {
    return new Context();
  }

  /**
   * {@inheritDoc}
   *
   * @author paouelle
   *
   * @see com.github.helenusdriver.driver.impl.ClassInfoImpl#newContext(java.lang.Object)
   */
  @Override
  @SuppressWarnings({"unchecked", "rawtypes"})
  public POJOContext newContext(T object) {
    return new POJOContext(object);
  }

  /**
   * Gets the class info for the specified type entity defined from this root
   * entity.
   *
   * @author paouelle
   *
   * @param  the type of POJO for the type entity
   *
   * @param  clazz the POJO class of the type entity to retrieve its info
   * @return the corresponding type entity POJO class information or null
   *         if none defined for the given class
   */
  @SuppressWarnings("unchecked")
  public  TypeClassInfoImpl getType(Class clazz) {
    return (TypeClassInfoImpl)ctypes.get(clazz);
  }

  /**
   * Gets the class info for the specified type entity defined from this root
   * entity.
   *
   * @author paouelle
   *
   * @param  name the name of the type entity to retrieve its info
   * @return the corresponding type entity POJO class information or null
   *         if none defined for the given name
   */
  public TypeClassInfoImpl getType(String name) {
    return ntypes.get(name);
  }

  /**
   * {@inheritDoc}
   *
   * @author paouelle
   *
   * @see com.github.helenusdriver.driver.impl.ClassInfoImpl#getObject(com.datastax.driver.core.Row, java.util.Map)
   */
  @Override
  public T getObject(Row row, Map suffixes) {
    if (row == null) {
      return null;
    }
    // extract the type so we know which object we are creating
    for (final ColumnDefinitions.Definition coldef: row.getColumnDefinitions()) {
      // find the table for this column
      final TableInfoImpl table = (TableInfoImpl)getTable(coldef.getTable());

      if (table != null) {
        // find the field in the table for this column
        final FieldInfoImpl field = table.getColumn(coldef.getName());

        if ((field != null) && field.isTypeKey()) { // get the POJO type
          final String type = Objects.toString(field.decodeValue(row), null);
          final TypeClassInfoImpl tcinfo = ntypes.get(type);

          if (tcinfo == null) {
            throw new ObjectConversionException(
              clazz, row, "unknown POJO type: " + type
            );
          }
          return tcinfo.getObject(row, type, suffixes);
        }
      }
    }
    throw new ObjectConversionException(clazz, row, "missing POJO type column");
  }
}