/**
   * Convenience method to load fields from the datastore. Note that if the fieldNumbers is
   * null/empty we still should call the persistence handler since it may mean that the version
   * field needs loading.
   *
   * @param fieldNumbers The field numbers.
   */
  protected void loadFieldsFromDatastore(int[] fieldNumbers) {
    if (myLC.isNew() && myLC.isPersistent() && !isFlushedNew()) {
      // Not yet flushed new persistent object to datastore so no point in "loading"
      return;
    }

    if ((flags & FLAG_NEED_INHERITANCE_VALIDATION)
        != 0) // TODO Merge this into fetch object handler
    {
      String className =
          getStoreManager().getClassNameForObjectID(myID, myEC.getClassLoaderResolver(), myEC);
      if (!getObject().getClass().getName().equals(className)) {
        myEC.removeObjectFromLevel1Cache(myID);
        myEC.removeObjectFromLevel2Cache(myID);
        throw new NucleusObjectNotFoundException(
            "Object with id "
                + myID
                + " was created without validating of type "
                + getObject().getClass().getName()
                + " but is actually of type "
                + className);
      }
      flags &= ~FLAG_NEED_INHERITANCE_VALIDATION;
    }

    // TODO If the field has "loadFetchGroup" defined, then add it to the fetch plan etc
    getStoreManager().getPersistenceHandler().fetchObject(this, fieldNumbers);
  }
Example #2
0
  /**
   * Constructor, using the ObjectProvider of the "owner" and the field name.
   *
   * @param op The owner ObjectProvider
   * @param mmd Metadata for the member
   */
  public Map(ObjectProvider op, AbstractMemberMetaData mmd) {
    super(op, mmd);

    // Set up our "delegate"
    this.delegate = new java.util.HashMap();

    ExecutionContext ec = ownerOP.getExecutionContext();
    allowNulls = SCOUtils.allowNullsInContainer(allowNulls, mmd);
    useCache = SCOUtils.useContainerCache(ownerOP, mmd);

    if (!SCOUtils.mapHasSerialisedKeysAndValues(mmd)
        && mmd.getPersistenceModifier() == FieldPersistenceModifier.PERSISTENT) {
      ClassLoaderResolver clr = ec.getClassLoaderResolver();
      this.backingStore =
          (MapStore)
              ((BackedSCOStoreManager) ownerOP.getStoreManager())
                  .getBackingStoreForField(clr, mmd, java.util.Map.class);
    }

    if (NucleusLogger.PERSISTENCE.isDebugEnabled()) {
      NucleusLogger.PERSISTENCE.debug(
          SCOUtils.getContainerInfoMessage(
              ownerOP,
              ownerMmd.getName(),
              this,
              useCache,
              allowNulls,
              SCOUtils.useCachedLazyLoading(ownerOP, ownerMmd)));
    }
  }
Example #3
0
  /**
   * Constructor, using the ObjectProvider of the "owner" and the field name.
   *
   * @param op The owner ObjectProvider
   * @param mmd Metadata for the member
   */
  public ArrayList(ObjectProvider op, AbstractMemberMetaData mmd) {
    super(op, mmd);

    // Set up our delegate
    this.delegate = new java.util.ArrayList();

    ExecutionContext ec = op.getExecutionContext();
    allowNulls = SCOUtils.allowNullsInContainer(allowNulls, mmd);
    queued = ec.isDelayDatastoreOperationsEnabled();
    useCache = SCOUtils.useContainerCache(op, mmd);

    if (!SCOUtils.collectionHasSerialisedElements(mmd)
        && mmd.getPersistenceModifier() == FieldPersistenceModifier.PERSISTENT) {
      ClassLoaderResolver clr = ec.getClassLoaderResolver();
      this.backingStore =
          (ListStore)
              ((BackedSCOStoreManager) op.getStoreManager())
                  .getBackingStoreForField(clr, mmd, java.util.ArrayList.class);
    }

    if (NucleusLogger.PERSISTENCE.isDebugEnabled()) {
      NucleusLogger.PERSISTENCE.debug(
          SCOUtils.getContainerInfoMessage(
              op,
              ownerMmd.getName(),
              this,
              useCache,
              queued,
              allowNulls,
              SCOUtils.useCachedLazyLoading(op, ownerMmd)));
    }
  }
  /**
   * Method to check if an element is already persistent, or is managed by a different
   * ExecutionContext. If not persistent, this will persist it.
   *
   * @param ec execution context
   * @param element The element
   * @param fieldValues any initial field values to use if persisting the element
   * @return Whether the element was persisted during this call
   */
  protected boolean validateElementForWriting(
      ExecutionContext ec, Object element, FieldValues fieldValues) {
    // TODO Pass in cascade flag and if element not present then throw exception
    // Check the element type for this collection
    if (!elementIsPersistentInterface
        && !validateElementType(ec.getClassLoaderResolver(), element)) {
      throw new ClassCastException(
          Localiser.msg(
              "056033",
              element.getClass().getName(),
              ownerMemberMetaData.getFullFieldName(),
              elementType));
    }

    boolean persisted = false;
    if (elementsAreEmbedded || elementsAreSerialised) {
      // Element is embedded/serialised so has no id
    } else {
      ObjectProvider elementSM = ec.findObjectProvider(element);
      if (elementSM != null && elementSM.isEmbedded()) {
        // Element is already with ObjectProvider and is embedded in another field!
        throw new NucleusUserException(
            Localiser.msg("056028", ownerMemberMetaData.getFullFieldName(), element));
      }

      persisted = SCOUtils.validateObjectForWriting(ec, element, fieldValues);
    }
    return persisted;
  }
 public FieldManager getFieldManagerForResultProcessing(
     ObjectProvider op, Object resultSet, StatementClassMapping resultMappings) {
   ExecutionContext ec = op.getExecutionContext();
   Class<?> cls =
       ec.getClassLoaderResolver().classForName(op.getClassMetaData().getFullClassName());
   Object internalKey = EntityUtils.idToInternalKey(ec, cls, resultSet, true);
   // Need to provide this to the field manager in the form of the pk
   // of the type: Key, Long, encoded String, or unencoded String
   return new KeyOnlyFieldManager(internalKey);
 }
 // TODO Remove this when we support subclasses from a query
 @Override
 public Extent getExtent(ExecutionContext ec, Class c, boolean subclasses) {
   AbstractClassMetaData cmd =
       getMetaDataManager().getMetaDataForClass(c, ec.getClassLoaderResolver());
   validateMetaDataForClass(cmd);
   if (!cmd.isRequiresExtent()) {
     throw new NoExtentException(c.getName());
   }
   if (!getBooleanProperty(GET_EXTENT_CAN_RETURN_SUBCLASSES_PROPERTY, false)) {
     subclasses = false;
   }
   // In order to avoid breaking existing apps I'm hard-coding subclasses to
   // be false.  This breaks spec compliance since the no-arg overload of
   // PersistenceManager.getExtent() is supposed to return subclasses.
   return new DefaultCandidateExtent(ec, c, subclasses, cmd);
 }
 private static void addColumnsToScanForEmbeddedMember(
     Scan scan, List<AbstractMemberMetaData> embMmds, Table table, ExecutionContext ec) {
   AbstractMemberMetaData lastMmd = embMmds.get(embMmds.size() - 1);
   ClassLoaderResolver clr = ec.getClassLoaderResolver();
   AbstractClassMetaData embCmd =
       ec.getMetaDataManager().getMetaDataForClass(lastMmd.getTypeName(), clr);
   int[] embMmdPosns = embCmd.getAllMemberPositions();
   for (int i = 0; i < embMmdPosns.length; i++) {
     AbstractMemberMetaData embMmd = embCmd.getMetaDataForManagedMemberAtAbsolutePosition(i);
     List<AbstractMemberMetaData> subEmbMmds = new ArrayList<AbstractMemberMetaData>(embMmds);
     subEmbMmds.add(embMmd);
     RelationType relationType = embMmd.getRelationType(clr);
     MemberColumnMapping mapping = table.getMemberColumnMappingForEmbeddedMember(subEmbMmds);
     if (RelationType.isRelationSingleValued(relationType)) {
       addColumnsToScanForEmbeddedMember(scan, subEmbMmds, table, ec);
     } else {
       String familyName = HBaseUtils.getFamilyNameForColumn(mapping.getColumn(0));
       String qualifName = HBaseUtils.getQualifierNameForColumn(mapping.getColumn(0));
       scan.addColumn(familyName.getBytes(), qualifName.getBytes());
     }
   }
 }
 public boolean useBackedSCOWrapperForMember(AbstractMemberMetaData mmd, ExecutionContext ec) {
   // Use backed SCO wrapper on relation field (to support legacy), and use simple SCO wrapper on
   // others
   return (mmd.getRelationType(ec.getClassLoaderResolver()) == RelationType.NONE ? false : true);
 }
  /**
   * Utility that does a discriminator candidate query for the specified candidate and subclasses
   * and returns the class name of the instance that has the specified identity (if any).
   *
   * @param storeMgr RDBMS StoreManager
   * @param ec execution context
   * @param id The id
   * @param cmd Metadata for the root candidate class
   * @return Name of the class with this identity (or null if none found)
   */
  public static String getClassNameForIdUsingDiscriminator(
      RDBMSStoreManager storeMgr, ExecutionContext ec, Object id, AbstractClassMetaData cmd) {
    // Check for input error
    if (cmd == null || id == null) {
      return null;
    }

    SQLExpressionFactory exprFactory = storeMgr.getSQLExpressionFactory();
    ClassLoaderResolver clr = ec.getClassLoaderResolver();
    DatastoreClass primaryTable = storeMgr.getDatastoreClass(cmd.getFullClassName(), clr);

    // Form the query to find which one of these classes has the instance with this id
    DiscriminatorStatementGenerator stmtGen =
        new DiscriminatorStatementGenerator(
            storeMgr, clr, clr.classForName(cmd.getFullClassName()), true, null, null);
    stmtGen.setOption(SelectStatementGenerator.OPTION_RESTRICT_DISCRIM);
    SelectStatement sqlStmt = stmtGen.getStatement();

    // Select the discriminator
    JavaTypeMapping discrimMapping = primaryTable.getDiscriminatorMapping(true);
    SQLTable discrimSqlTbl =
        SQLStatementHelper.getSQLTableForMappingOfTable(
            sqlStmt, sqlStmt.getPrimaryTable(), discrimMapping);
    sqlStmt.select(discrimSqlTbl, discrimMapping, null);

    // Restrict to this id
    JavaTypeMapping idMapping = primaryTable.getIdMapping();
    JavaTypeMapping idParamMapping = new PersistableIdMapping((PersistableMapping) idMapping);
    SQLExpression sqlFldExpr =
        exprFactory.newExpression(sqlStmt, sqlStmt.getPrimaryTable(), idMapping);
    SQLExpression sqlFldVal = exprFactory.newLiteralParameter(sqlStmt, idParamMapping, id, "ID");
    sqlStmt.whereAnd(sqlFldExpr.eq(sqlFldVal), true);

    // Perform the query
    try {
      ManagedConnection mconn = storeMgr.getConnection(ec);
      SQLController sqlControl = storeMgr.getSQLController();
      if (ec.getSerializeReadForClass(cmd.getFullClassName())) {
        sqlStmt.addExtension(SQLStatement.EXTENSION_LOCK_FOR_UPDATE, true);
      }

      try {
        PreparedStatement ps =
            SQLStatementHelper.getPreparedStatementForSQLStatement(sqlStmt, ec, mconn, null, null);
        String statement = sqlStmt.getSQLText().toSQL();
        try {
          ResultSet rs = sqlControl.executeStatementQuery(ec, mconn, statement, ps);
          try {
            if (rs != null) {
              while (rs.next()) {
                DiscriminatorMetaData dismd = discrimMapping.getTable().getDiscriminatorMetaData();
                return RDBMSQueryUtils.getClassNameFromDiscriminatorResultSetRow(
                    discrimMapping, dismd, rs, ec);
              }
            }
          } finally {
            rs.close();
          }
        } finally {
          sqlControl.closeStatement(mconn, ps);
        }
      } finally {
        mconn.release();
      }
    } catch (SQLException sqe) {
      NucleusLogger.DATASTORE.error("Exception thrown on querying of discriminator for id", sqe);
      throw new NucleusDataStoreException(sqe.toString(), sqe);
    }

    return null;
  }
  /**
   * Utility that does a union candidate query for the specified candidate(s) and subclasses and
   * returns the class name of the instance that has the specified identity (if any).
   *
   * @param storeMgr RDBMS StoreManager
   * @param ec execution context
   * @param id The id
   * @param rootCmds Metadata for the classes at the root
   * @return Name of the class with this identity (or null if none found)
   */
  public static String getClassNameForIdUsingUnion(
      RDBMSStoreManager storeMgr,
      ExecutionContext ec,
      Object id,
      List<AbstractClassMetaData> rootCmds) {
    // Check for input error
    if (rootCmds == null || rootCmds.isEmpty() || id == null) {
      return null;
    }

    SQLExpressionFactory exprFactory = storeMgr.getSQLExpressionFactory();
    ClassLoaderResolver clr = ec.getClassLoaderResolver();

    // Form a query UNIONing all possible root candidates (and their subclasses)
    Iterator<AbstractClassMetaData> rootCmdIter = rootCmds.iterator();

    AbstractClassMetaData sampleCmd =
        null; // Metadata for sample class in the tree so we can check if needs locking
    SelectStatement sqlStmtMain = null;
    while (rootCmdIter.hasNext()) {
      AbstractClassMetaData rootCmd = rootCmdIter.next();
      DatastoreClass rootTbl = storeMgr.getDatastoreClass(rootCmd.getFullClassName(), clr);
      if (rootTbl == null) {
        // Class must be using "subclass-table" (no table of its own) so find where it is
        AbstractClassMetaData[] subcmds = storeMgr.getClassesManagingTableForClass(rootCmd, clr);
        if (subcmds == null || subcmds.length == 0) {
          // No table for this class so ignore
        } else {
          for (int i = 0; i < subcmds.length; i++) {
            UnionStatementGenerator stmtGen =
                new UnionStatementGenerator(
                    storeMgr,
                    clr,
                    clr.classForName(subcmds[i].getFullClassName()),
                    true,
                    null,
                    null);
            stmtGen.setOption(SelectStatementGenerator.OPTION_SELECT_NUCLEUS_TYPE);
            if (sqlStmtMain == null) {
              sampleCmd = subcmds[i];
              sqlStmtMain = stmtGen.getStatement();

              // WHERE (object id) = ?
              JavaTypeMapping idMapping = sqlStmtMain.getPrimaryTable().getTable().getIdMapping();
              JavaTypeMapping idParamMapping =
                  new PersistableIdMapping((PersistableMapping) idMapping);
              SQLExpression fieldExpr =
                  exprFactory.newExpression(sqlStmtMain, sqlStmtMain.getPrimaryTable(), idMapping);
              SQLExpression fieldVal =
                  exprFactory.newLiteralParameter(sqlStmtMain, idParamMapping, id, "ID");
              sqlStmtMain.whereAnd(fieldExpr.eq(fieldVal), true);
            } else {
              SelectStatement sqlStmt = stmtGen.getStatement();

              // WHERE (object id) = ?
              JavaTypeMapping idMapping = sqlStmt.getPrimaryTable().getTable().getIdMapping();
              JavaTypeMapping idParamMapping =
                  new PersistableIdMapping((PersistableMapping) idMapping);
              SQLExpression fieldExpr =
                  exprFactory.newExpression(sqlStmt, sqlStmt.getPrimaryTable(), idMapping);
              SQLExpression fieldVal =
                  exprFactory.newLiteralParameter(sqlStmt, idParamMapping, id, "ID");
              sqlStmt.whereAnd(fieldExpr.eq(fieldVal), true);

              sqlStmtMain.union(sqlStmt);
            }
          }
        }
      } else {
        UnionStatementGenerator stmtGen =
            new UnionStatementGenerator(
                storeMgr, clr, clr.classForName(rootCmd.getFullClassName()), true, null, null);
        stmtGen.setOption(SelectStatementGenerator.OPTION_SELECT_NUCLEUS_TYPE);
        if (sqlStmtMain == null) {
          sampleCmd = rootCmd;
          sqlStmtMain = stmtGen.getStatement();

          // WHERE (object id) = ?
          JavaTypeMapping idMapping = sqlStmtMain.getPrimaryTable().getTable().getIdMapping();
          JavaTypeMapping idParamMapping = new PersistableIdMapping((PersistableMapping) idMapping);
          SQLExpression fieldExpr =
              exprFactory.newExpression(sqlStmtMain, sqlStmtMain.getPrimaryTable(), idMapping);
          SQLExpression fieldVal =
              exprFactory.newLiteralParameter(sqlStmtMain, idParamMapping, id, "ID");
          sqlStmtMain.whereAnd(fieldExpr.eq(fieldVal), true);
        } else {
          SelectStatement sqlStmt = stmtGen.getStatement();

          // WHERE (object id) = ?
          JavaTypeMapping idMapping = sqlStmt.getPrimaryTable().getTable().getIdMapping();
          JavaTypeMapping idParamMapping = new PersistableIdMapping((PersistableMapping) idMapping);
          SQLExpression fieldExpr =
              exprFactory.newExpression(sqlStmt, sqlStmt.getPrimaryTable(), idMapping);
          SQLExpression fieldVal =
              exprFactory.newLiteralParameter(sqlStmt, idParamMapping, id, "ID");
          sqlStmt.whereAnd(fieldExpr.eq(fieldVal), true);

          sqlStmtMain.union(sqlStmt);
        }
      }
    }

    // Perform the query
    try {
      ManagedConnection mconn = storeMgr.getConnection(ec);
      SQLController sqlControl = storeMgr.getSQLController();
      if (ec.getSerializeReadForClass(sampleCmd.getFullClassName())) {
        sqlStmtMain.addExtension(SQLStatement.EXTENSION_LOCK_FOR_UPDATE, true);
      }

      try {
        PreparedStatement ps =
            SQLStatementHelper.getPreparedStatementForSQLStatement(
                sqlStmtMain, ec, mconn, null, null);
        String statement = sqlStmtMain.getSQLText().toSQL();
        try {
          ResultSet rs = sqlControl.executeStatementQuery(ec, mconn, statement, ps);
          try {
            if (rs != null) {
              while (rs.next()) {
                try {
                  return rs.getString(UnionStatementGenerator.NUC_TYPE_COLUMN).trim();
                } catch (SQLException sqle) {
                }
              }
            }
          } finally {
            rs.close();
          }
        } finally {
          sqlControl.closeStatement(mconn, ps);
        }
      } finally {
        mconn.release();
      }
    } catch (SQLException sqe) {
      NucleusLogger.DATASTORE.error("Exception with UNION statement", sqe);
      throw new NucleusDataStoreException(sqe.toString());
    }

    return null;
  }
  /**
   * Convenience method to get all objects of the specified type.
   *
   * @param ec Execution Context
   * @param mconn Managed Connection
   * @param cmd Metadata for the type to return
   * @param ignoreCache Whether to ignore the cache
   * @param fp Fetch Plan
   * @param filter Optional filter for the candidates
   * @param storeMgr StoreManager in use
   * @return List of objects of the candidate type
   */
  private static List getObjectsOfType(
      final ExecutionContext ec,
      final HBaseManagedConnection mconn,
      final AbstractClassMetaData cmd,
      boolean ignoreCache,
      FetchPlan fp,
      final Filter filter,
      final StoreManager storeMgr) {
    List results = new ArrayList();

    if (!storeMgr.managesClass(cmd.getFullClassName())) {
      storeMgr.manageClasses(ec.getClassLoaderResolver(), cmd.getFullClassName());
    }
    final Table table = storeMgr.getStoreDataForClass(cmd.getFullClassName()).getTable();
    final String tableName = table.getName();
    final int[] fpMembers = fp.getFetchPlanForClass(cmd).getMemberNumbers();
    try {
      final ClassLoaderResolver clr = ec.getClassLoaderResolver();

      Iterator<Result> it =
          (Iterator<Result>)
              AccessController.doPrivileged(
                  new PrivilegedExceptionAction() {
                    public Object run() throws Exception {
                      Scan scan = new Scan();
                      if (filter != null) {
                        scan.setFilter(filter);
                      }

                      // Retrieve all fetch-plan fields
                      for (int i = 0; i < fpMembers.length; i++) {
                        AbstractMemberMetaData mmd =
                            cmd.getMetaDataForManagedMemberAtAbsolutePosition(fpMembers[i]);
                        RelationType relationType = mmd.getRelationType(clr);
                        if (relationType != RelationType.NONE
                            && MetaDataUtils.getInstance()
                                .isMemberEmbedded(
                                    ec.getMetaDataManager(), clr, mmd, relationType, null)) {
                          if (RelationType.isRelationSingleValued(relationType)) {
                            // 1-1 embedded
                            List<AbstractMemberMetaData> embMmds =
                                new ArrayList<AbstractMemberMetaData>();
                            embMmds.add(mmd);
                            addColumnsToScanForEmbeddedMember(scan, embMmds, table, ec);
                          }
                        } else {
                          Column col =
                              table
                                  .getMemberColumnMappingForMember(mmd)
                                  .getColumn(0); // TODO Support multicol mapping
                          byte[] familyName = HBaseUtils.getFamilyNameForColumn(col).getBytes();
                          byte[] qualifName = HBaseUtils.getQualifierNameForColumn(col).getBytes();
                          scan.addColumn(familyName, qualifName);
                        }
                      }

                      VersionMetaData vermd = cmd.getVersionMetaDataForClass();
                      if (cmd.isVersioned() && vermd.getFieldName() == null) {
                        // Add version column
                        byte[] familyName =
                            HBaseUtils.getFamilyNameForColumn(table.getVersionColumn()).getBytes();
                        byte[] qualifName =
                            HBaseUtils.getQualifierNameForColumn(table.getVersionColumn())
                                .getBytes();
                        scan.addColumn(familyName, qualifName);
                      }
                      if (cmd.hasDiscriminatorStrategy()) {
                        // Add discriminator column
                        byte[] familyName =
                            HBaseUtils.getFamilyNameForColumn(table.getDiscriminatorColumn())
                                .getBytes();
                        byte[] qualifName =
                            HBaseUtils.getQualifierNameForColumn(table.getDiscriminatorColumn())
                                .getBytes();
                        scan.addColumn(familyName, qualifName);
                      }
                      if (cmd.getIdentityType() == IdentityType.DATASTORE) {
                        // Add datastore identity column
                        byte[] familyName =
                            HBaseUtils.getFamilyNameForColumn(table.getDatastoreIdColumn())
                                .getBytes();
                        byte[] qualifName =
                            HBaseUtils.getQualifierNameForColumn(table.getDatastoreIdColumn())
                                .getBytes();
                        scan.addColumn(familyName, qualifName);
                      }

                      HTableInterface htable = mconn.getHTable(tableName);
                      ResultScanner scanner = htable.getScanner(scan);
                      if (ec.getStatistics() != null) {
                        // Add to statistics
                        ec.getStatistics().incrementNumReads();
                      }
                      Iterator<Result> it = scanner.iterator();
                      return it;
                    }
                  });

      // Instantiate the objects
      if (cmd.getIdentityType() == IdentityType.APPLICATION) {
        while (it.hasNext()) {
          final Result result = it.next();
          Object obj =
              getObjectUsingApplicationIdForResult(
                  result, cmd, ec, ignoreCache, fpMembers, tableName, storeMgr, table);
          if (obj != null) {
            results.add(obj);
          }
        }
      } else if (cmd.getIdentityType() == IdentityType.DATASTORE) {
        while (it.hasNext()) {
          final Result result = it.next();
          Object obj =
              getObjectUsingDatastoreIdForResult(
                  result, cmd, ec, ignoreCache, fpMembers, tableName, storeMgr, table);
          if (obj != null) {
            results.add(obj);
          }
        }
      } else {
        while (it.hasNext()) {
          final Result result = it.next();
          Object obj =
              getObjectUsingNondurableIdForResult(
                  result, cmd, ec, ignoreCache, fpMembers, tableName, storeMgr, table);
          if (obj != null) {
            results.add(obj);
          }
        }
      }
    } catch (PrivilegedActionException e) {
      throw new NucleusDataStoreException(e.getMessage(), e.getCause());
    }
    return results;
  }
  /**
   * Method to return a statement selecting the candidate table(s) required to cover all possible
   * types for this candidates inheritance strategy.
   *
   * @param storeMgr RDBMS StoreManager
   * @param parentStmt Parent statement (if there is one)
   * @param cmd Metadata for the class
   * @param clsMapping Mapping for the results of the statement
   * @param ec ExecutionContext
   * @param candidateCls Candidate class
   * @param subclasses Whether to create a statement for subclasses of the candidate too
   * @param result The result clause
   * @param candidateAlias alias for the candidate (if any)
   * @param candidateTableGroupName TableGroup name for the candidate (if any)
   * @return The SQLStatement
   * @throws NucleusException if there are no tables for concrete classes in this query (hence would
   *     return null)
   */
  public static SQLStatement getStatementForCandidates(
      RDBMSStoreManager storeMgr,
      SQLStatement parentStmt,
      AbstractClassMetaData cmd,
      StatementClassMapping clsMapping,
      ExecutionContext ec,
      Class candidateCls,
      boolean subclasses,
      String result,
      String candidateAlias,
      String candidateTableGroupName) {
    SQLStatement stmt = null;

    DatastoreIdentifier candidateAliasId = null;
    if (candidateAlias != null) {
      candidateAliasId = storeMgr.getIdentifierFactory().newTableIdentifier(candidateAlias);
    }

    ClassLoaderResolver clr = ec.getClassLoaderResolver();
    List<DatastoreClass> candidateTables = new ArrayList<DatastoreClass>();
    if (cmd.getInheritanceMetaData().getStrategy() == InheritanceStrategy.COMPLETE_TABLE) {
      DatastoreClass candidateTable = storeMgr.getDatastoreClass(cmd.getFullClassName(), clr);
      if (candidateTable != null) {
        candidateTables.add(candidateTable);
      }
      if (subclasses) {
        Collection<String> subclassNames =
            storeMgr.getSubClassesForClass(cmd.getFullClassName(), subclasses, clr);
        if (subclassNames != null) {
          Iterator<String> subclassIter = subclassNames.iterator();
          while (subclassIter.hasNext()) {
            String subclassName = subclassIter.next();
            DatastoreClass tbl = storeMgr.getDatastoreClass(subclassName, clr);
            if (tbl != null) {
              candidateTables.add(tbl);
            }
          }
        }
      }

      Iterator<DatastoreClass> iter = candidateTables.iterator();
      int maxClassNameLength = cmd.getFullClassName().length();
      while (iter.hasNext()) {
        DatastoreClass cls = iter.next();
        String className = cls.getType();
        if (className.length() > maxClassNameLength) {
          maxClassNameLength = className.length();
        }
      }

      iter = candidateTables.iterator();
      while (iter.hasNext()) {
        DatastoreClass cls = iter.next();

        SQLStatement tblStmt =
            new SQLStatement(parentStmt, storeMgr, cls, candidateAliasId, candidateTableGroupName);
        tblStmt.setClassLoaderResolver(clr);
        tblStmt.setCandidateClassName(cls.getType());

        // Add SELECT of dummy column accessible as "NUCLEUS_TYPE" containing the classname
        JavaTypeMapping m = storeMgr.getMappingManager().getMapping(String.class);
        String nuctypeName = cls.getType();
        if (maxClassNameLength > nuctypeName.length()) {
          nuctypeName = StringUtils.leftAlignedPaddedString(nuctypeName, maxClassNameLength);
        }
        StringLiteral lit = new StringLiteral(tblStmt, m, nuctypeName, null);
        tblStmt.select(lit, UnionStatementGenerator.NUC_TYPE_COLUMN);

        if (stmt == null) {
          stmt = tblStmt;
        } else {
          stmt.union(tblStmt);
        }
      }
      if (clsMapping != null) {
        clsMapping.setNucleusTypeColumnName(UnionStatementGenerator.NUC_TYPE_COLUMN);
      }
    } else {
      // "new-table", "superclass-table", "subclass-table"
      List<Class> candidateClasses = new ArrayList<Class>();
      if (ClassUtils.isReferenceType(candidateCls)) {
        // Persistent interface, so find all persistent implementations
        String[] clsNames =
            storeMgr
                .getNucleusContext()
                .getMetaDataManager()
                .getClassesImplementingInterface(candidateCls.getName(), clr);
        for (int i = 0; i < clsNames.length; i++) {
          Class cls = clr.classForName(clsNames[i]);
          DatastoreClass table = storeMgr.getDatastoreClass(clsNames[i], clr);
          candidateClasses.add(cls);
          candidateTables.add(table);
          AbstractClassMetaData implCmd =
              storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForClass(cls, clr);
          if (implCmd.getIdentityType() != cmd.getIdentityType()) {
            throw new NucleusUserException(
                "You are querying an interface ("
                    + cmd.getFullClassName()
                    + ") "
                    + "yet one of its implementations ("
                    + implCmd.getFullClassName()
                    + ") "
                    + " uses a different identity type!");
          } else if (cmd.getIdentityType() == IdentityType.APPLICATION) {
            if (cmd.getPKMemberPositions().length != implCmd.getPKMemberPositions().length) {
              throw new NucleusUserException(
                  "You are querying an interface ("
                      + cmd.getFullClassName()
                      + ") "
                      + "yet one of its implementations ("
                      + implCmd.getFullClassName()
                      + ") "
                      + " has a different number of PK members!");
            }
          }
        }
      } else {
        DatastoreClass candidateTable = storeMgr.getDatastoreClass(cmd.getFullClassName(), clr);
        if (candidateTable != null) {
          // Candidate has own table
          candidateClasses.add(candidateCls);
          candidateTables.add(candidateTable);
        } else {
          // Candidate stored in subclass tables
          AbstractClassMetaData[] cmds = storeMgr.getClassesManagingTableForClass(cmd, clr);
          if (cmds != null && cmds.length > 0) {
            for (int i = 0; i < cmds.length; i++) {
              DatastoreClass table = storeMgr.getDatastoreClass(cmds[i].getFullClassName(), clr);
              Class cls = clr.classForName(cmds[i].getFullClassName());
              candidateClasses.add(cls);
              candidateTables.add(table);
            }
          } else {
            throw new UnsupportedOperationException(
                "No tables for query of " + cmd.getFullClassName());
          }
        }
      }

      for (int i = 0; i < candidateTables.size(); i++) {
        DatastoreClass tbl = candidateTables.get(i);
        Class cls = candidateClasses.get(i);
        StatementGenerator stmtGen = null;
        if (tbl.getDiscriminatorMapping(true) != null
            || QueryUtils.resultHasOnlyAggregates(result)) {
          // Either has a discriminator, or only selecting aggregates so need single select
          stmtGen =
              new DiscriminatorStatementGenerator(
                  storeMgr, clr, cls, subclasses, candidateAliasId, candidateTableGroupName);
          stmtGen.setOption(StatementGenerator.OPTION_RESTRICT_DISCRIM);
        } else {
          stmtGen =
              new UnionStatementGenerator(
                  storeMgr, clr, cls, subclasses, candidateAliasId, candidateTableGroupName);
          if (result == null) {
            // Returning one row per candidate so include distinguisher column
            stmtGen.setOption(StatementGenerator.OPTION_SELECT_NUCLEUS_TYPE);
            clsMapping.setNucleusTypeColumnName(UnionStatementGenerator.NUC_TYPE_COLUMN);
          }
        }
        stmtGen.setParentStatement(parentStmt);
        SQLStatement tblStmt = stmtGen.getStatement();

        if (stmt == null) {
          stmt = tblStmt;
        } else {
          stmt.union(tblStmt);
        }
      }
    }

    return stmt;
  }