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

org.tentackle.wurblet.ModelWurblet Maven / Gradle / Ivy

There is a newer version: 21.16.1.0
Show newest version
/**
 * Tentackle - http://www.tentackle.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


package org.tentackle.wurblet;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.tentackle.common.Constants;
import org.tentackle.model.Attribute;
import org.tentackle.model.Entity;
import org.tentackle.model.Model;
import org.tentackle.model.ModelDefaults;
import org.tentackle.model.ModelException;
import org.tentackle.model.Relation;
import org.tentackle.model.RelationType;
import org.tentackle.model.SelectionType;
import org.tentackle.sql.Backend;
import org.tentackle.sql.BackendFactory;
import org.wurbelizer.wurbel.JavaSourceType;
import org.wurbelizer.wurbel.WurbelException;
import org.wurbelizer.wurbel.Wurbler;
import org.wurbelizer.wurblet.AbstractWurblet;



/**
 * Extended {@link AbstractWurblet} providing basic functionality for the persistent object model.
 *
 * @author  harald
 */
public class ModelWurblet extends AbstractWurblet {

  private static final Pattern ANNOTATION_PATTERN = Pattern.compile("\\s*([\\w\\.]+)\\.class");  // only 1-line annos for now

  /** the name of the model directory. */
  private String modelDirName;

  /** name of the model. */
  private String modelName;

  /** the original parsed mapping. */
  private Entity entity;

  /** wurblet specific arguments. */
  private List args;

  /** whether context attribute is valid (even if set in model). */
  private boolean contextIdAttributeValid;

  /** != null if detected whether this is a pdo or not. */
  private Boolean isPdo;

  /** true if --remote option set. */
  private boolean remote;

  /** the pdo classname if it's a pdo. */
  private String pdoClassName;

  /** the wurblet key parser. */
  private WurbletParameterParser parser;

  /** the joins. */
  private List joins;


  /**
   * Creates a wurblet.
   */
  public ModelWurblet() {
    super();
  }

  /**
   * Gets the name of the model directory.
   *
   * @return the model dir name
   */
  public String getModelDirName() {
    return modelDirName;
  }

  /**
   * Gets the pdo class from the source.
* Looks for annotations {@code @DomainObjectService}, {@code @PersistentObjectService} and interface extensions. * * @return the pdo name * @throws WurbelException if pdo class cannot be determined from the source file */ public String getPdoClassName() throws WurbelException { if (isPdo == null) { // not determined yet isPdo = false; for (int ndx=0; ; ndx++) { String annotation = getContainer().getProperty(Wurbler.PROPSPACE_WURBLET, JavaSourceType.ANNOTATION + ndx); if (annotation == null) { break; } if (annotation.startsWith("@DomainObjectService") || annotation.startsWith("@PersistentObjectService")) { Matcher matcher = ANNOTATION_PATTERN.matcher(annotation); if (matcher.find()) { pdoClassName = matcher.group(1); if (getContainer().getVerbosity().isDebug()) { getContainer().getLogger().info("pdoClassName: " + pdoClassName); } isPdo = true; return pdoClassName; } throw new WurbelException("malformed annotation: " + annotation); } } // may be an interface that extends PersistentObject String persistentIface = getContainer().getProperty(Wurbler.PROPSPACE_WURBLET, JavaSourceType.EXTENDS + 0); if (persistentIface != null) { int ndx = persistentIface.indexOf('<'); if (ndx > 0) { persistentIface = persistentIface.substring(ndx + 1); ndx = persistentIface.lastIndexOf('>'); if (ndx > 0) { pdoClassName = persistentIface.substring(0, ndx); if (pdoClassName.length() > 1 && pdoClassName.indexOf('<') < 0) { // not T or some other generic type isPdo = true; return pdoClassName; } } } } // may be an abstract class (if inheritance is used) pdoClassName = getContainer().getProperty(Wurbler.PROPSPACE_WURBLET, JavaSourceType.DEFINITION); if (pdoClassName != null) { /** * If something of: * extends AbstractPersistentObject implements AdressePersistence * or * extends UmzugsListePersistenceImpl * implements UmzugsErfassungsListePersistence * or * , P extends UmzugsListePersistenceImpl> * extends AbstractPersistentObject implements UmzugsListePersistence */ if (getContainer().getVerbosity().isDebug()) { getContainer().getLogger().info(getContainer().getProperty(Wurbler.PROPSPACE_WURBLET, JavaSourceType.CLASS_NAME) + ": definition = '" + pdoClassName + "'"); } int ndx = pdoClassName.indexOf("extends"); if (ndx >= 0) { int ndx1 = pdoClassName.indexOf('<'); int ndx2 = pdoClassName.indexOf(','); if (ndx1 > ndx && ndx2 > ndx1) { pdoClassName = pdoClassName.substring(ndx1 + 1, ndx2).trim(); isPdo = true; return pdoClassName; } } ndx = pdoClassName.indexOf("T extends"); if (ndx >= 0) { pdoClassName = pdoClassName.substring(ndx + 9); ndx = pdoClassName.indexOf('<'); if (ndx > 0) { pdoClassName = pdoClassName.substring(0, ndx).trim(); isPdo = true; return pdoClassName; } } } } if (isPdo) { return pdoClassName; } throw new WurbelException("cannot determine the pdo-class from the java-source"); } /** * Returns whether this is a pdo. * * @return true if pdo */ public boolean isPdo() { if (isPdo == null) { try { getPdoClassName(); // sets isPdo! } catch (WurbelException wex) { // ignore } } return isPdo; } /** * Returns true if --remote option set. * * @return true if remote */ public boolean isRemote() { return remote; } /** * Sets the remote option explicitly. * * @param remote true if remoting enabled */ public void setRemote(boolean remote) { this.remote = remote; } /** * Returns whether the entity is part of an inheritance tree. * * @return truf if part of an inheritance tree */ public boolean isPartOfInheritanceHierarchy() { return isPdo() && getEntity().getTopSuperEntity().isAbstract(); } /** * Returns whether the class is defined using java generics. *

* Generics are used in abstract inheritable classes. * Final concrete PDO classes must not use generics. * Otherwise the generated wurblet code will not compile. * * @return true if class is generified */ public boolean isGenerified() { String definition = getContainer().getProperty(Wurbler.PROPSPACE_WURBLET, JavaSourceType.DEFINITION); return definition != null && definition.startsWith("<"); } /** * Gets the methodname.
* From the guardname or from arg "--method=<.....>" if present. * * @return the method name * @throws WurbelException if no guardname */ public String getMethodName() throws WurbelException { String methodName = getOption("method"); if (methodName == null) { methodName = getGuardName(); } return methodName; } /** * Gets the name of the modelfile. * * @return the name * @throws org.wurbelizer.wurbel.WurbelException if model could not be determined */ public String getModelName() throws WurbelException { if (modelName == null) { modelName = getOption("model"); if (modelName == null) { // determine from source file modelName = getPdoClassName(); } if (modelName == null) { throw new WurbelException("model not specified"); } } return modelName; } /** * Applies the semantics of {@link #getClassName()} to another entity.
* Example: *

   * getEntity() -> Firma
   * getClassName() -> "MyFirmaPersistenceImpl"
   * Assumed that otherEntity = Kontakt (which is a superclass of Firma, for example), then:
   * deriveClassNameForEntity(otherEntity) -> "MyKontaktPersistenceImpl"
   * 
* @param otherEntity the other entity * @return the derived classname * @throws WurbelException if this classname does not contain the entity name as a substring */ public String deriveClassNameForEntity(Entity otherEntity) throws WurbelException { String className = getClassName(); String entityName = getEntity().getName(); int ndx = className.indexOf(entityName); if (ndx < 0) { throw new WurbelException(className + " does not contain the entity name '" + entityName + "' substring"); } String lead = className.substring(0, ndx); String tail = className.substring(ndx + entityName.length()); return lead + otherEntity.getName() + tail; } /** * Sorts the given list of entities by inheritance level plus classid. * * @param entities the entities * @return the sorted entities (same reference as argument) */ public List orderByInheritanceLevelAndClassId(List entities) { Collections.sort(entities, (Entity o1, Entity o2) -> { int rv = o1.getInheritanceLevel() - o2.getInheritanceLevel(); if (rv == 0) { rv = Long.compare(o1.getClassId(), o2.getClassId()); } return rv; }); return entities; } /** * Returns whether context attribute is valid (even if set in model). * * @return true if context id attribute is valid */ public boolean isContextIdAttributeValid() { return contextIdAttributeValid; } /** * Gets the model entity. * * @return the entity */ public Entity getEntity() { return entity; } /** * Gets the wurblet arguments. * * @return the wurblet args */ public List getArgs() { return args; } /** * Gets the wurblet options.
* The options got the leading '--' removed. * * @return the option args */ public List getOptionArgs() { return parser.getOptionArgs(); } /** * Gets the option if set.
* Options come in two flavours: *
    *
  1. without a value. Example: --remote
  2. *
  3. with a value. Example: --model=modlog.map
  4. *
* * @param option the option * @return the empty string (case 1), the value (case 2) or null if option not set */ public String getOption(String option) { int equalsOffset = option.length(); for (String arg: getOptionArgs()) { if (arg.equals(option)) { return ""; } if (arg.startsWith(option) && arg.charAt(equalsOffset) == '=') { return arg.substring(equalsOffset + 1); } } return null; } /** * Gets all parameters. * * @return the parameters */ public List getAllParameters() { return parser.getAllParameters(); } /** * Gets the method parameters. * * @return the method parameters */ public List getMethodParameters() { return parser.getMethodParameters(); } /** * Gets the expression parameters. * * @return the parameters used within the expression */ public List getExpressionParameters() { return parser.getExpressionParameters(); } /** * Gets the select/where expression. * * @return the expression */ public WurbletParameterExpression getExpression() { return parser.getExpression(); } /** * Gets the extra parameters. * * @return the parameters used within the expression */ public List getExtraParameters() { return parser.getExtraParameters(); } /** * Gets the sorting parameters. * * @return the sorting parameters, empty if no "order by" */ public List getSortingParameters() { return parser.getSortingParameters(); } /** * Returns whether sorting is configured for this wurblet. * * @return true if sorting defined in args */ public boolean isWithSorting() { return !getSortingParameters().isEmpty(); } /** * Gets the joins. * * @return the joins */ public List getJoins() { return joins; } /** * Creates the method name to select a relation. * * @param relation the relation * @return the method name */ public String createRelationSelectMethodName(Relation relation) { String text = "select"; if (relation.getMethodName() != null) { if (relation.getRelationType() == RelationType.LIST) { text += "By"; } text += relation.getMethodName(); } else { if (relation.getRelationType() == RelationType.LIST) { text += "By" + relation.getEntity().getName() + "Id"; } else { if (relation.isSelectionCached()) { text += "Cached"; } } } if (text.equals("select")) { text = "select"; } return text; } /** * Creates the method name to select a relation. * * @param relation the relation * @return the method name */ public String createListRelationDeleteMethodName(Relation relation) { String text = "deleteBy"; if (relation.getMethodName() != null) { text += relation.getMethodName(); } else { text += relation.getEntity().getName() + "Id"; } return text; } /** * {@inheritDoc} *

* Overridden to load the map file. * * @throws WurbelException if running the wurblet failed */ @Override public void run() throws WurbelException { super.run(); args = Arrays.asList(container.getArgs()); joins = new ArrayList<>(); parser = new WurbletParameterParser(args); modelDirName = getContainer().getProperty(Wurbler.PROPSPACE_EXTRA, "model"); File modelDir = new File(modelDirName); if (!modelDir.exists()) { getContainer().getLogger().info("creating " + modelDir); modelDir.mkdirs(); } if (!modelDir.isDirectory()) { throw new WurbelException(modelDir + " is not a directory"); } // set the backends to validate the model, if any. // this is a comma separated list of backends, null if none, all for all backends in classpath String backends = getContainer().getProperty(Wurbler.PROPSPACE_EXTRA, "backends"); if (backends != null) { Collection backendList = new ArrayList<>(); if ("all".equalsIgnoreCase(backends)) { backendList.addAll(BackendFactory.getInstance().getAllBackends()); } else { for (String backend: backends.split(",")) { backendList.add(BackendFactory.getInstance().getBackendByName(backend.trim())); } } Model.getInstance().getEntityFactory().setBackends(backendList); } // scan optional model defaults ModelDefaults modelDefaults = null; String modelDefaultsStr = getContainer().getProperty(Wurbler.PROPSPACE_EXTRA, "modelDefaults"); if (modelDefaultsStr != null) { try { modelDefaults = new ModelDefaults(modelDefaultsStr); } catch (ModelException mex) { throw new WurbelException(mex.getMessage(), mex); } } // load the model (if not yet done) Model model = Model.getInstance(); try { model.loadModel(modelDirName, modelDefaults); } catch (ModelException mex) { WurbelException wex = new WurbelException("errors in model loaded from directory '" + modelDirName + "'", mex); if (model instanceof TentackleWurbletsModel && !mex.getEntities().isEmpty() && ((TentackleWurbletsModel) model).getLoadingException() == null) { // delay first exception to concrete classes if exception could be associated to entities ((TentackleWurbletsModel) model).setLoadingException(wex); } else { throw wex; } } // get the entity try { if (getModelName().indexOf(File.separatorChar) >= 0) { // is a filename (load it if it's not already loaded) entity = model.loadByFileName(modelDefaults, getModelName()); } else { // is an entity name entity = model.getByEntityName(getModelName()); } } catch (ModelException mex) { throw new WurbelException("errors in model loaded from file '" + getModelName() + "'", mex); } if (entity == null) { Throwable delayedModelException = null; if (model instanceof TentackleWurbletsModel) { WurbelException wex = ((TentackleWurbletsModel) model).getLoadingException(); if (wex != null) { delayedModelException = wex.getCause(); } } throw new WurbelException("no such entity '" + getModelName() + "' in model " + modelDir, delayedModelException); } remote = entity.getOptions().isRemote(); // override global option if (getOption("remote") != null) { remote = true; } if (getOption("noremote") != null) { remote = false; } contextIdAttributeValid = entity.getContextIdAttribute() != null; Set componentKeyEntities = new HashSet<>(); // setup wurblet parameters for (WurbletParameter par: getAllParameters()) { Entity parEntity = entity; // the parameter's entity if (par.isComponentKey() || par.isRelationKey() && !par.getComponentEntityName().isEmpty()) { if (par.isSortKey()) { throw new WurbelException("sorting not allowed for component keys: " + par); } if (!entity.isRootEntityAccordingToModel()) { throw new WurbelException("component keys are only allowed for root-entities: " + par); } try { parEntity = model.getByEntityName(par.getComponentEntityName()); } catch (ModelException mex) { throw new WurbelException("model errors while loading '" + par.getComponentEntityName() + "'", mex); } if (parEntity == null && !par.isRelationKey()) { // try relation-name instead of entity-name Relation relation = entity.getRelation(par.getComponentEntityName(), true); if (relation != null) { parEntity = relation.getForeignEntity(); } } if (parEntity == null) { throw new WurbelException("no such entity '" + par.getComponentEntityName() + "' in " + par); } boolean rootOk = false; Entity joinedEntity = parEntity; outer: while (joinedEntity != null) { for (Entity rootEntity: joinedEntity.getRootEntities()) { if (rootEntity.equals(entity)) { rootOk = true; break outer; } } joinedEntity = joinedEntity.getSuperEntity(); } if (!rootOk) { throw new WurbelException(parEntity + " is not a component of " + entity + ": " + par); } // remember related entities for check against joins later Entity topEntity = parEntity.getTopSuperEntity(); componentKeyEntities.add(topEntity); componentKeyEntities.addAll(parEntity.getAllSubEntities()); } if (par.isRelationKey()) { Relation relation = parEntity.getRelation(par.getRelationName(), true); if (relation == null) { throw new WurbelException("no such relation '" + par.getRelationName() +"' in " + parEntity + ": " + par); } par.setRelation(relation); parEntity = relation.getForeignEntity(); // must work! if (par.getRelationComponentEntityName() != null) { try { parEntity = model.getByEntityName(par.getRelationComponentEntityName()); } catch (ModelException mex) { throw new WurbelException("model errors while loading '" + par.getRelationComponentEntityName() + "'", mex); } if (parEntity == null) { throw new WurbelException("no such related entity '" + par.getRelationComponentEntityName() + "' in " + par); } } if (relation.isComposite()) { // misuse of .relation as a component key? for (Entity root: parEntity.getRootEntities()) { if (root.equals(entity)) { Entity topEntity = parEntity.getTopSuperEntity(); componentKeyEntities.add(topEntity); componentKeyEntities.addAll(parEntity.getAllSubEntities()); break; } } } } Attribute attribute = parEntity.getAttributeByJavaName(par.getName(), true); if (attribute == null) { throw new WurbelException("no such attribute '" + par.getName() + "' in " + parEntity + ": " + par); } par.setAttribute(attribute); if (attribute.getEntity().equals(entity) && attribute.getOptions().isContextId()) { // the contextId or an attribute with context scope is already part of the where-clause // --> context-clause not necessary contextIdAttributeValid = false; } } // setup joins for (String joinName: parser.getJoinNames()) { Relation join = entity.getRelation(joinName, true); if (join == null) { throw new WurbelException("no such relation to join: " + joinName); } if (join.getSelectionType() != SelectionType.LAZY && join.getSelectionType() != SelectionType.EAGER) { throw new WurbelException("joined relation '" + join.getName() + "' must be lazy or eager"); } if (remote && !join.isComposite() && !join.isSerialized() && join.getSelectionType() != SelectionType.EAGER) { throw new WurbelException("joined non-composite relation '" + join.getName() + "' must be serialized for remote access"); } // verify that join is not used by a component key if (componentKeyEntities.contains(join.getForeignEntity())) { throw new WurbelException("join '" + join.getName() + "' is already used by a component key"); } joins.add(join); } if (contextIdAttributeValid) { // if context id option set for an attribute /** * Furthermore, if _all_ unique parameters are part of the where-clause, we * don't need a contextAttribute as well. However, in that case, we need * to makeValidContext in selects. */ int uniqueAttributeCount = 0; for (Attribute attribute: entity.getAttributes()) { if (attribute.getOptions().isDomainKey()) { uniqueAttributeCount++; } } int uniqueKeyCount = 0; for (WurbletParameter key: getExpressionParameters()) { Attribute attribute = key.getAttribute(); if (attribute != null && attribute.getOptions().isDomainKey()) { uniqueKeyCount++; } } if (uniqueAttributeCount > 0 && uniqueAttributeCount == uniqueKeyCount) { contextIdAttributeValid = false; } } /** * Throw delayed wurbel execption if the real cause if related to this entity */ if (model instanceof TentackleWurbletsModel) { WurbelException wex = ((TentackleWurbletsModel) model).getLoadingException(); if (wex != null && wex.getCause() instanceof ModelException) { ModelException mex = (ModelException) wex.getCause(); if (mex.getEntities().contains(entity)) { throw wex; } } } } // ----------------- utility methods to simplify writing wurblets ---------------------------------- /** * Checks whether attribute is the pdo ID. * * @param attribute the attribute * @return true if pdo id */ public boolean isIdAttribute(Attribute attribute) { return attribute.getJavaName().equals(Constants.CN_ID); } /** * Checks whether attribute is the pdo serial. * * @param attribute the attribute * @return true if pdo serial */ public boolean isSerialAttribute(Attribute attribute) { return attribute.getJavaName().equals(Constants.CN_SERIAL); } /** * Checks whether attribute is the pdo ID or serial. * * @param attribute the attribute * @return true if pdo id or serial */ public boolean isIdOrSerialAttribute(Attribute attribute) { return isIdAttribute(attribute) || isSerialAttribute(attribute); } /** * Checks whether attribute is derived from a superclass. * * @param attribute the attribute * @return true if derived from superclass */ public boolean isAttributeDerived(Attribute attribute) { return attribute.getOptions().isDerived() || attribute.getEntity() != entity; } /** * Determines whether setter/getter need to be used to access an attribute. * * @param attribute the attribute * @return true if use set/get */ public boolean isSetGetRequired(Attribute attribute) { return attribute.getOptions().isSetGet() || isAttributeDerived(attribute); } /** * Adds a string to a comma separated list. * * @param builder the string builder * @param appendStr the string to append */ public void appendCommaSeparated(StringBuilder builder, String appendStr) { if (builder.length() > 0) { builder.append(", "); } builder.append(appendStr); } /** * Prepends a string to a comma separated list. * * @param builder the string builder * @param prependStr the string to prepend */ public void prependCommaSeparated(StringBuilder builder, String prependStr) { if (builder.length() > 0) { builder.insert(0, ", "); } builder.insert(0, prependStr); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy