Package com.avaje.ebeaninternal.server.deploy.meta

Source Code of com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor

package com.avaje.ebeaninternal.server.deploy.meta;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.Entity;
import javax.persistence.MappedSuperclass;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.avaje.ebean.annotation.ConcurrencyMode;
import com.avaje.ebean.config.TableName;
import com.avaje.ebean.config.dbplatform.IdGenerator;
import com.avaje.ebean.config.dbplatform.IdType;
import com.avaje.ebean.event.BeanFinder;
import com.avaje.ebean.event.BeanPersistController;
import com.avaje.ebean.event.BeanPersistListener;
import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebeaninternal.server.core.CacheOptions;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType;
import com.avaje.ebeaninternal.server.deploy.ChainedBeanPersistController;
import com.avaje.ebeaninternal.server.deploy.ChainedBeanPersistListener;
import com.avaje.ebeaninternal.server.deploy.ChainedBeanQueryAdapter;
import com.avaje.ebeaninternal.server.deploy.CompoundUniqueContraint;
import com.avaje.ebeaninternal.server.deploy.DRawSqlMeta;
import com.avaje.ebeaninternal.server.deploy.DeployNamedQuery;
import com.avaje.ebeaninternal.server.deploy.DeployNamedUpdate;
import com.avaje.ebeaninternal.server.deploy.InheritInfo;
import com.avaje.ebeaninternal.server.reflect.BeanReflect;

/**
* Describes Beans including their deployment information.
*/
public class DeployBeanDescriptor<T> {

  static class PropOrder implements Comparator<DeployBeanProperty> {

    public int compare(DeployBeanProperty o1, DeployBeanProperty o2) {

      int v2 = o1.getSortOrder();
      int v1 = o2.getSortOrder();
      return (v1 < v2 ? -1 : (v1 == v2 ? 0 : 1));
    }
  }

  private static final PropOrder PROP_ORDER = new PropOrder();

  private static final String I_SCALAOBJECT = "scala.ScalaObject";

  private static final Logger logger = LoggerFactory.getLogger(DeployBeanDescriptor.class);

  /**
   * Map of BeanProperty Linked so as to preserve order.
   */
  private LinkedHashMap<String, DeployBeanProperty> propMap = new LinkedHashMap<String, DeployBeanProperty>();

  private EntityType entityType;

  private final Map<String, DeployNamedQuery> namedQueries = new LinkedHashMap<String, DeployNamedQuery>();

  private final Map<String, DeployNamedUpdate> namedUpdates = new LinkedHashMap<String, DeployNamedUpdate>();

  private final Map<String, DRawSqlMeta> rawSqlMetas = new LinkedHashMap<String, DRawSqlMeta>();

  private DeployBeanPropertyAssocOne<?> unidirectional;

  /**
   * Type of Identity generation strategy used.
   */
  private IdType idType;

  /**
   * The name of an IdGenerator (optional).
   */
  private String idGeneratorName;

  private IdGenerator idGenerator;

  /**
   * The database sequence name (optional).
   */
  private String sequenceName;

  /**
   * Used with Identity columns but no getGeneratedKeys support.
   */
  private String selectLastInsertedId;

  private String lazyFetchIncludes;

  /**
   * The concurrency mode for beans of this type.
   */
  private ConcurrencyMode concurrencyMode;

  private boolean updateChangesOnly;

  /**
   * The tables this bean is dependent on.
   */
  private String[] dependantTables;

  private List<CompoundUniqueContraint> compoundUniqueConstraints;

  /**
   * Extra deployment attributes.
   */
  private HashMap<String, String> extraAttrMap = new HashMap<String, String>();

  /**
   * The base database table.
   */
  private String baseTable;
  private TableName baseTableFull;

  /**
   * Used to provide mechanism to new EntityBean instances. Generated code
   * faster than reflection at this stage.
   */
  private BeanReflect beanReflect;
  private String[] properties;

  /**
   * The EntityBean type used to create new EntityBeans.
   */
  private Class<T> beanType;

  private List<BeanPersistController> persistControllers = new ArrayList<BeanPersistController>(2);
  private List<BeanPersistListener<T>> persistListeners = new ArrayList<BeanPersistListener<T>>(2);
  private List<BeanQueryAdapter> queryAdapters = new ArrayList<BeanQueryAdapter>(2);

  private CacheOptions cacheOptions = new CacheOptions();

  /**
   * If set overrides the find implementation. Server side only.
   */
  private BeanFinder<T> beanFinder;

  /**
   * The table joins for this bean. Server side only.
   */
  private ArrayList<DeployTableJoin> tableJoinList = new ArrayList<DeployTableJoin>(2);

  /**
   * Inheritance information. Server side only.
   */
  private InheritInfo inheritInfo;

  private String name;

  private boolean processedRawSqlExtend;

  /**
   * Construct the BeanDescriptor.
   */
  public DeployBeanDescriptor(Class<T> beanType) {
    this.beanType = beanType;
  }

  /**
   * Return true if this beanType is an abstract class.
   */
  public boolean isAbstract() {
    return Modifier.isAbstract(beanType.getModifiers());
  }

  public boolean isScalaObject() {
    Class<?>[] interfaces = beanType.getInterfaces();
    for (int i = 0; i < interfaces.length; i++) {
      String iname = interfaces[i].getName();
      if (I_SCALAOBJECT.equals(iname)) {
        return true;
      }
    }
    return false;
  }

  public Collection<DRawSqlMeta> getRawSqlMeta() {
    if (!processedRawSqlExtend) {
      rawSqlProcessExtend();
      processedRawSqlExtend = true;
    }
    return rawSqlMetas.values();
  }

  /**
   * Process the "extend" attributes of raw SQL. Aka inherit the query and
   * column mapping.
   */
  private void rawSqlProcessExtend() {

    for (DRawSqlMeta rawSqlMeta : rawSqlMetas.values()) {
      String extend = rawSqlMeta.getExtend();
      if (extend != null) {
        DRawSqlMeta parentQuery = rawSqlMetas.get(extend);
        if (parentQuery == null) {
          throw new RuntimeException("parent query [" + extend + "] not found for sql-select " + rawSqlMeta.getName());
        }
        rawSqlMeta.extend(parentQuery);
      }
    }
  }

  public DeployBeanTable createDeployBeanTable() {

    DeployBeanTable beanTable = new DeployBeanTable(getBeanType());
    beanTable.setBaseTable(baseTable);
    beanTable.setIdProperties(propertiesId());

    return beanTable;
  }

  /**
   * Check all the properties to see if they all have read and write methods
   * (required if using "subclassing" but not for "enhancement").
   */
  public boolean checkReadAndWriteMethods() {

    boolean missingMethods = false;

    for (DeployBeanProperty prop : propMap.values()) {
      if (!prop.isTransient()) {
        String m = "";
        if (prop.getReadMethod() == null) {
          m += " missing readMethod ";
        }
        if (prop.getWriteMethod() == null) {
          m += " missing writeMethod ";
        }
        if (!"".equals(m)) {
          m += ". Should it be transient?";
          String msg = "Bean property " + getFullName() + "." + prop.getName() + " has " + m;
          logger.error(msg);
          missingMethods = true;
        }
      }
    }
    return !missingMethods;
  }

  public void setEntityType(EntityType entityType) {
    this.entityType = entityType;
  }

  public boolean isEmbedded() {
    return EntityType.EMBEDDED.equals(entityType);
  }

  public boolean isBaseTableType() {
    EntityType et = getEntityType();
    return EntityType.ORM.equals(et);
  }

  public EntityType getEntityType() {
    if (entityType == null) {
      entityType = EntityType.ORM;
    }
    return entityType;
  }

  public void add(DRawSqlMeta rawSqlMeta) {
    rawSqlMetas.put(rawSqlMeta.getName(), rawSqlMeta);
    if ("default".equals(rawSqlMeta.getName())) {
      setEntityType(EntityType.SQL);
    }
  }

  public void add(DeployNamedUpdate namedUpdate) {
    namedUpdates.put(namedUpdate.getName(), namedUpdate);
  }

  public void add(DeployNamedQuery namedQuery) {
    namedQueries.put(namedQuery.getName(), namedQuery);
    if ("default".equals(namedQuery.getName())) {
      setEntityType(EntityType.SQL);
    }
  }

  public Map<String, DeployNamedQuery> getNamedQueries() {
    return namedQueries;
  }

  public Map<String, DeployNamedUpdate> getNamedUpdates() {
    return namedUpdates;
  }

  public String[] getProperties() {
    return properties;
  }

  public void setProperties(String[] props) {
    this.properties = props;
  }

  public BeanReflect getBeanReflect() {
    return beanReflect;
  }

  /**
   * Return the class type this BeanDescriptor describes.
   */
  public Class<T> getBeanType() {
    return beanType;
  }

  /**
   * Set the BeanReflect used to create new instances of an EntityBean. This
   * could use reflection or code generation to do this.
   */
  public void setBeanReflect(BeanReflect beanReflect) {
    this.beanReflect = beanReflect;
  }

  /**
   * Returns the Inheritance mapping information. This will be null if this type
   * of bean is not involved in any ORM inheritance mapping.
   */
  public InheritInfo getInheritInfo() {
    return inheritInfo;
  }

  /**
   * Set the ORM inheritance mapping information.
   */
  public void setInheritInfo(InheritInfo inheritInfo) {
    this.inheritInfo = inheritInfo;
  }

  /**
   * Return the cache options.
   */
  public CacheOptions getCacheOptions() {
    return cacheOptions;
  }

  public boolean isNaturalKeyProperty(String name) {
    return name.equals(cacheOptions.getNaturalKey());
  }

  public DeployBeanPropertyAssocOne<?> getUnidirectional() {
    return unidirectional;
  }

  public void setUnidirectional(DeployBeanPropertyAssocOne<?> unidirectional) {
    this.unidirectional = unidirectional;
  }

  /**
   * Return the concurrency mode used for beans of this type.
   */
  public ConcurrencyMode getConcurrencyMode() {
    return concurrencyMode;
  }

  /**
   * Set the concurrency mode used for beans of this type.
   */
  public void setConcurrencyMode(ConcurrencyMode concurrencyMode) {
    this.concurrencyMode = concurrencyMode;
  }

  public boolean isUpdateChangesOnly() {
    return updateChangesOnly;
  }

  public void setUpdateChangesOnly(boolean updateChangesOnly) {
    this.updateChangesOnly = updateChangesOnly;
  }

  /**
   * Return the tables this bean is dependant on. This implies that if any of
   * these tables are modified then cached beans may be invalidated.
   */
  public String[] getDependantTables() {
    return dependantTables;
  }

  /**
   * Add a compound unique constraint.
   */
  public void addCompoundUniqueConstraint(CompoundUniqueContraint c) {
    if (compoundUniqueConstraints == null) {
      compoundUniqueConstraints = new ArrayList<CompoundUniqueContraint>();
    }
    compoundUniqueConstraints.add(c);
  }

  /**
   * Return the compound unique constraints (can be null).
   */
  public CompoundUniqueContraint[] getCompoundUniqueConstraints() {
    if (compoundUniqueConstraints == null) {
      return null;
    } else {
      return compoundUniqueConstraints.toArray(new CompoundUniqueContraint[compoundUniqueConstraints.size()]);
    }
  }

  /**
   * Set the tables this bean is dependant on. This implies that if any of these
   * tables are modified then cached beans may be invalidated.
   */
  public void setDependantTables(String[] dependantTables) {
    this.dependantTables = dependantTables;
  }

  /**
   * Return the beanFinder. Usually null unless overriding the finder.
   */
  public BeanFinder<T> getBeanFinder() {
    return beanFinder;
  }

  /**
   * Set the BeanFinder to use for beans of this type. This is set to override
   * the finding from the default.
   */
  public void setBeanFinder(BeanFinder<T> beanFinder) {
    this.beanFinder = beanFinder;
  }

  /**
   * Return the BeanPersistController (could be a chain of them, 1 or null).
   */
  public BeanPersistController getPersistController() {
    if (persistControllers.size() == 0) {
      return null;
    } else if (persistControllers.size() == 1) {
      return persistControllers.get(0);
    } else {
      return new ChainedBeanPersistController(persistControllers);
    }
  }

  /**
   * Return the BeanPersistListener (could be a chain of them, 1 or null).
   */
  public BeanPersistListener<T> getPersistListener() {
    if (persistListeners.size() == 0) {
      return null;
    } else if (persistListeners.size() == 1) {
      return persistListeners.get(0);
    } else {
      return new ChainedBeanPersistListener<T>(persistListeners);
    }
  }

  public BeanQueryAdapter getQueryAdapter() {
    if (queryAdapters.size() == 0) {
      return null;
    } else if (queryAdapters.size() == 1) {
      return queryAdapters.get(0);
    } else {
      return new ChainedBeanQueryAdapter(queryAdapters);
    }
  }

  /**
   * Set the Controller.
   */
  public void addPersistController(BeanPersistController controller) {
    persistControllers.add(controller);
  }

  public void addPersistListener(BeanPersistListener<T> listener) {
    persistListeners.add(listener);
  }

  public void addQueryAdapter(BeanQueryAdapter queryAdapter) {
    queryAdapters.add(queryAdapter);
  }

  /**
   * Return true if this bean type should use IdGeneration.
   * <p>
   * If this is false and the Id is null it is assumed that a database auto
   * increment feature is being used to populate the id.
   * </p>
   */
  public boolean isUseIdGenerator() {
    return idType == IdType.GENERATOR;
  }

  /**
   * Return the base table. Only properties mapped to the base table are by
   * default persisted.
   */
  public String getBaseTable() {
    return baseTable;
  }

  /**
   * Return the base table with full structure.
   */
  public TableName getBaseTableFull() {
    return baseTableFull;
  }

  /**
   * Set the base table. Only properties mapped to the base table are by default
   * persisted.
   */
  public void setBaseTable(TableName baseTableFull) {
    this.baseTableFull = baseTableFull;
    this.baseTable = baseTableFull == null ? null : baseTableFull.getQualifiedName();
  }

  public void sortProperties() {

    ArrayList<DeployBeanProperty> list = new ArrayList<DeployBeanProperty>();
    list.addAll(propMap.values());

    Collections.sort(list, PROP_ORDER);

    propMap = new LinkedHashMap<String, DeployBeanProperty>(list.size());
    for (int i = 0; i < list.size(); i++) {
      addBeanProperty(list.get(i));
    }
  }

  /**
   * Add a bean property.
   */
  public DeployBeanProperty addBeanProperty(DeployBeanProperty prop) {
    return propMap.put(prop.getName(), prop);
  }

  /**
   * Get a BeanProperty by its name.
   */
  public DeployBeanProperty getBeanProperty(String propName) {
    return propMap.get(propName);
  }

  public Map<String, String> getExtraAttributeMap() {
    return extraAttrMap;
  }

  /**
   * Get a named extra attribute.
   */
  public String getExtraAttribute(String key) {
    return (String) extraAttrMap.get(key);
  }

  /**
   * Set an extra attribute with a given name.
   *
   * @param key
   *          the name of the extra attribute
   * @param value
   *          the value of the extra attribute
   */
  public void setExtraAttribute(String key, String value) {
    extraAttrMap.put(key, value);
  }

  /**
   * Return the bean class name this descriptor is used for.
   * <p>
   * If this BeanDescriptor is for a table then this returns the table name
   * instead.
   * </p>
   */
  public String getFullName() {
    return beanType.getName();
  }

  /**
   * Return the bean short name.
   */
  public String getName() {
    return name;
  }

  /**
   * Set the bean shortName.
   */
  public void setName(String name) {
    this.name = name;
  }

  /**
   * Return the identity generation type.
   */
  public IdType getIdType() {
    return idType;
  }

  /**
   * Set the identity generation type.
   */
  public void setIdType(IdType idType) {
    this.idType = idType;
  }

  /**
   * Return the DB sequence name (can be null).
   */
  public String getSequenceName() {
    return sequenceName;
  }

  /**
   * Set the DB sequence name.
   */
  public void setSequenceName(String sequenceName) {
    this.sequenceName = sequenceName;
  }

  /**
   * Return the SQL used to return the last inserted Id.
   * <p>
   * Used with Identity columns where getGeneratedKeys is not supported.
   * </p>
   */
  public String getSelectLastInsertedId() {
    return selectLastInsertedId;
  }

  /**
   * Set the SQL used to return the last inserted Id.
   */
  public void setSelectLastInsertedId(String selectLastInsertedId) {
    this.selectLastInsertedId = selectLastInsertedId;
  }

  /**
   * Return the name of the IdGenerator that should be used with this type of
   * bean. A null value could be used to specify the 'default' IdGenerator.
   */
  public String getIdGeneratorName() {
    return idGeneratorName;
  }

  /**
   * Set the name of the IdGenerator that should be used with this type of bean.
   */
  public void setIdGeneratorName(String idGeneratorName) {
    this.idGeneratorName = idGeneratorName;
  }

  /**
   * Return the actual IdGenerator for this bean type (can be null).
   */
  public IdGenerator getIdGenerator() {
    return idGenerator;
  }

  /**
   * Set the actual IdGenerator for this bean type.
   */
  public void setIdGenerator(IdGenerator idGenerator) {
    this.idGenerator = idGenerator;
    if (idGenerator != null && idGenerator.isDbSequence()) {
      setSequenceName(idGenerator.getName());
    }
  }

  /**
   * Return the includes for getReference().
   */
  public String getLazyFetchIncludes() {
    return lazyFetchIncludes;
  }

  /**
   * Set includes to use for lazy loading by getReference(). Note queries also
   * build references and includes on the actual association are used for those
   * references.
   */
  public void setLazyFetchIncludes(String lazyFetchIncludes) {
    if (lazyFetchIncludes != null && lazyFetchIncludes.length() > 0) {
      this.lazyFetchIncludes = lazyFetchIncludes;
    }
  }

  /**
   * Summary description.
   */
  public String toString() {
    return getFullName();
  }

  /**
   * Add a TableJoin to this type of bean. For Secondary table properties.
   */
  public void addTableJoin(DeployTableJoin join) {
    tableJoinList.add(join);
  }

  public List<DeployTableJoin> getTableJoins() {
    return tableJoinList;
  }

  /**
   * Return a collection of all BeanProperty deployment information.
   */
  public Collection<DeployBeanProperty> propertiesAll() {
    return propMap.values();
  }

  /**
   * Return the defaultSelectClause using FetchType.LAZY and FetchType.EAGER.
   */
  public String getDefaultSelectClause() {

    StringBuilder sb = new StringBuilder();

    boolean hasLazyFetch = false;

    for (DeployBeanProperty prop : propMap.values()) {
      if (prop.isTransient()) {
        // ignore transient props etc
      } else if (prop instanceof DeployBeanPropertyAssocMany<?>) {
        // ignore the associated many properties
      } else {
        if (prop.isFetchEager()) {
          sb.append(prop.getName()).append(",");
        } else {
          hasLazyFetch = true;
        }
      }
    }

    if (!hasLazyFetch) {
      return null;
    }
    String selectClause = sb.toString();
    return selectClause.substring(0, selectClause.length() - 1);
  }

  /**
   * Parse the include separating by comma or semicolon.
   */
  public Set<String> parseDefaultSelectClause(String rawList) {

    if (rawList == null) {
      return null;
    }

    String[] res = rawList.split(",");

    LinkedHashSet<String> set = new LinkedHashSet<String>(res.length + 3);

    String temp = null;
    for (int i = 0; i < res.length; i++) {
      temp = res[i].trim();
      if (temp.length() > 0) {
        set.add(temp);
      }
    }
    return Collections.unmodifiableSet(set);
  }

  /**
   * Return the Primary Key column assuming it is a single column (not
   * compound). This is for the purpose of defining a sequence name.
   */
  public String getSinglePrimaryKeyColumn() {
    List<DeployBeanProperty> ids = propertiesId();
    if (ids.size() == 1) {
      DeployBeanProperty p = ids.get(0);
      if (p instanceof DeployBeanPropertyAssoc<?>) {
        // its a compound primary key
        return null;
      } else {
        return p.getDbColumn();
      }
    }
    return null;
  }

  /**
   * Return the BeanProperty that make up the unique id.
   * <p>
   * The order of these properties can be relied on to be consistent if the bean
   * itself doesn't change or the xml deployment order does not change.
   * </p>
   */
  public List<DeployBeanProperty> propertiesId() {

    ArrayList<DeployBeanProperty> list = new ArrayList<DeployBeanProperty>(2);

    for (DeployBeanProperty prop : propMap.values()) {
      if (prop.isId()) {
        list.add(prop);
      }
    }

    return list;
  }

  public DeployBeanPropertyAssocOne<?> findJoinToTable(String tableName) {

    List<DeployBeanPropertyAssocOne<?>> assocOne = propertiesAssocOne();
    for (DeployBeanPropertyAssocOne<?> prop : assocOne) {
      DeployTableJoin tableJoin = prop.getTableJoin();
      if (tableJoin != null && tableJoin.getTable().equalsIgnoreCase(tableName)) {
        return prop;
      }
    }
    return null;
  }

  /**
   * Return an Iterator of BeanPropertyAssocOne that are not embedded. These are
   * effectively joined beans. For ManyToOne and OneToOne associations.
   */
  public List<DeployBeanPropertyAssocOne<?>> propertiesAssocOne() {

    ArrayList<DeployBeanPropertyAssocOne<?>> list = new ArrayList<DeployBeanPropertyAssocOne<?>>();

    for (DeployBeanProperty prop : propMap.values()) {
      if (prop instanceof DeployBeanPropertyAssocOne<?>) {
        if (!prop.isEmbedded()) {
          list.add((DeployBeanPropertyAssocOne<?>) prop);
        }
      }
    }

    return list;

  }

  /**
   * Return BeanPropertyAssocMany for this descriptor.
   */
  public List<DeployBeanPropertyAssocMany<?>> propertiesAssocMany() {

    ArrayList<DeployBeanPropertyAssocMany<?>> list = new ArrayList<DeployBeanPropertyAssocMany<?>>();

    for (DeployBeanProperty prop : propMap.values()) {
      if (prop instanceof DeployBeanPropertyAssocMany<?>) {
        list.add((DeployBeanPropertyAssocMany<?>) prop);
      }
    }

    return list;
  }

  /**
   * Returns 'Version' properties on this bean. These are 'Counter' or 'Update
   * Timestamp' type properties. Note version properties can also be on embedded
   * beans rather than on the bean itself.
   */
  public List<DeployBeanProperty> propertiesVersion() {

    ArrayList<DeployBeanProperty> list = new ArrayList<DeployBeanProperty>();

    for (DeployBeanProperty prop : propMap.values()) {
      if (prop instanceof DeployBeanPropertyAssoc<?> == false) {
        if (!prop.isId() && prop.isVersionColumn()) {
          list.add(prop);
        }
      }
    }

    return list;
  }

  /**
   * base properties without the unique id properties.
   */
  public List<DeployBeanProperty> propertiesBase() {

    ArrayList<DeployBeanProperty> list = new ArrayList<DeployBeanProperty>();

    for (DeployBeanProperty prop : propMap.values()) {
      if (prop instanceof DeployBeanPropertyAssoc<?> == false) {
        if (!prop.isId()) {
          list.add(prop);
        }
      }
    }

    return list;
  }

  /**
   * Check the mapping for class inheritance
   */
  public void checkInheritanceMapping() {
    if (inheritInfo == null) {
      checkInheritance(getBeanType());
    }
  }

  /**
   * Check valid mapping annotations on the class hierarchy.
   */
  private void checkInheritance(Class<?> beanType) {
   
    Class<?> parent = beanType.getSuperclass();
    if (parent == null || Object.class.equals(parent)) {
      // all good
      return;
    }
    if (parent.isAnnotationPresent(Entity.class)) {
      String msg = "Checking "+getBeanType()+" and found "+parent+" that has @Entity annotation rather than MappedSuperclass?";
      throw new IllegalStateException(msg);
    }
    if (parent.isAnnotationPresent(MappedSuperclass.class)) {
      // continue checking
      checkInheritance(parent);
    }
  }
}
TOP

Related Classes of com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.