Package nexj.core.persistence.sql

Source Code of nexj.core.persistence.sql.RangeInfo

// Copyright 2010 NexJ Systems Inc. This software is licensed under the terms of the Eclipse Public License 1.0
package nexj.core.persistence.sql;

import java.io.StringWriter;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import javax.transaction.Status;

import nexj.core.meta.Attribute;
import nexj.core.meta.Metaclass;
import nexj.core.meta.Metadata;
import nexj.core.meta.Primitive;
import nexj.core.meta.integration.service.Service;
import nexj.core.meta.persistence.DataSourceAdapter;
import nexj.core.meta.persistence.DataSourceType;
import nexj.core.meta.persistence.sql.RelationalDatabase;
import nexj.core.meta.persistence.sql.RelationalSchema;
import nexj.core.meta.persistence.sql.Table;
import nexj.core.meta.persistence.sql.upgrade.RelationalSchemaUpgrade;
import nexj.core.meta.upgrade.ScriptUpgrade;
import nexj.core.meta.upgrade.Upgrade;
import nexj.core.meta.upgrade.VersionUpgrade;
import nexj.core.meta.workflow.State;
import nexj.core.persistence.Cursor;
import nexj.core.persistence.DuplicateKeyException;
import nexj.core.persistence.InvalidQueryException;
import nexj.core.persistence.LockTimeoutException;
import nexj.core.persistence.OID;
import nexj.core.persistence.OptimisticLockException;
import nexj.core.persistence.Query;
import nexj.core.persistence.QueryTimeoutException;
import nexj.core.persistence.SchemaVersion;
import nexj.core.rpc.TransferObject;
import nexj.core.runtime.Instance;
import nexj.core.runtime.InstanceArrayList;
import nexj.core.runtime.InstanceList;
import nexj.core.runtime.UnitOfWork;
import nexj.core.runtime.ValidationException;
import nexj.core.scripting.Function;
import nexj.core.scripting.Pair;
import nexj.core.scripting.ScriptingException;
import nexj.core.scripting.Symbol;
import nexj.core.util.Binary;
import nexj.core.util.Invalid;
import nexj.core.util.J2EEUtil;
import nexj.core.util.Lookup;
import nexj.core.util.UncheckedException;
import nexj.core.util.Undefined;
import nexj.test.util.AssertUtil;

/**
* Base test case for SQLAdapter.
*/
public abstract class SQLAdapterTest extends SQLDataTest
{
   public SQLAdapterTest(String name)
   {
      super(name);
   }

   /**
    * Check if the supplied version is compatible with the current SQLAdapter.
    * @param sDBVersion The version returned by DatabaseMetaData.getDatabaseProductVersion().
    * @return The current SQLAdapter is applicable for the specified version.
    */
   protected boolean isCompatibleVersion(String sDBVersion)
   {
      return true;
   }

   public void testAdapterCompatibility() throws SQLException
   {
      SQLConnection connection = m_adapter.getConnection();
      Connection con = connection.getConnection();
      RelationalDatabase db = new RelationalDatabase(null);

      try
      {
         String sDBVersion = con.getMetaData().getDatabaseProductVersion();

         // iterate over DataSourceType instead of DataSource in case no DataSources defined
         for (Iterator/*<DataSourceType>*/ itr =
                 m_context.getMetadata().getDataSourceTypeIterator();
              itr.hasNext();)
         {
            db.setType((DataSourceType)itr.next());

            for (Iterator/*<DataSourceType>*/ itr2 = db.getType().getAdapterIterator();
                 itr2.hasNext();)
            {
               db.setDriver(null);
               db.setAdapter((DataSourceAdapter)itr2.next());
               db.setDefaultProperties(J2EEUtil.NONE);
               assertEquals(m_adapter.getClass().equals(db.getAdapter().getClassObject()) &&
                            isCompatibleVersion(sDBVersion),
                            m_adapter.isCompatible(con, db));
            }
         }
      }
      finally
      {
         connection.decRef();
      }
   }

   public void testUpgrade()
   {
      Upgrade upgrade = getMetadata().getUpgrade("Main");

      upgrade.validate(getMetadata(), null);

      VersionUpgrade firstVersion = upgrade.getFirstVersion();
      Lookup stateMap = Upgrade.getInitialState(firstVersion);
      SchemaVersion version = new SchemaVersion();

      version.setNamespace(getMetadata().getNamespace());
      version.setUpgradable(true);

      for (VersionUpgrade u = firstVersion; u != null; u = u.getNext())
      {
         version.setVersion(u.getName());
         version.setStep(0);

         if (u.getName() == null)
         {
            u.apply(Upgrade.getState(stateMap, u));
         }
         else if (u instanceof RelationalSchemaUpgrade)
         {
            m_adapter.upgrade((RelationalSchemaUpgrade)u, Upgrade.getState(stateMap, u), version);
         }
         else if (u instanceof ScriptUpgrade)
         {
            Function fun = ((ScriptUpgrade)u).getFunction();

            if (fun != null)
            {
               m_context.getMachine().invoke(fun, (Pair)null);
            }
         }
      }
   }

   public void testRead()
   {
      Pair attributes;
      Query query;

      // Lazy association type adjustment
      Instance instance = new Instance(getMetadata().getMetaclass("Principal"), true, m_context);

      instance.cache(new OID(new Object[]{Binary.parse("00000000000000000000000000000002")}));
      query = Query.createRead(getMetadata().getMetaclass("UserGroupAssoc"), parse("(user)"),
         parse("(= (@) (oid #z00000000000000000000000000000003))"),
         null, -1, 0, false, Query.SEC_NONE, m_context);
      assertEquals("User", ((Instance)query.read().getInstance(0).getValue("user")).getLazyMetaclass().getName());
      m_context.removeInstance(instance);
      instance = null;

      // Unsupported where clause
      try
      {
         Query.createRead(getMetadata().getMetaclass("Contact"), null, parse("(> (myfun firstName) 0)"), null, -1, 0, false, Query.SEC_NONE, m_context);
         fail("Expected InvalidQueryException");
      }
      catch (InvalidQueryException e)
      {
         assertEquals("err.persistence.unsupportedExpression", e.getErrorCode());
      }
     
      attributes = parse("(firstName lastName (addresses country city street code) (user name))");
      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(= classCode \"CON\")"),
         parse("((firstName . #t)(lastName . #t)((@ addresses) . #t))"),
         -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("read.xml"), query.read(), attributes);

      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(and (null? (@ noAddresses city)) (= classCode \"CON\"))"),
         parse("((firstName . #t)(lastName . #t)((@ addresses) . #t))"),
         -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("read.xml"), query.read(), attributes);

      String sSQL = ((SQLGenerator)query.getGenerator()).getSQL();

      query = Query.createRead(getMetadata().getMetaclass("Contact"),
         parse("((user name) lastName (addresses country city street code) firstName)"),
         parse("(and (null? (@ noAddresses city)) (= classCode \"CON\"))"),
         parse("((firstName . #t)(lastName . #t)((@ addresses) . #t))"),
         -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("read.xml"), query.read(), attributes);

      assertEquals(sSQL, ((SQLGenerator)query.getGenerator()).getSQL());

      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(= classCode \"CON\")"),
         parse("((firstName . #t)(lastName . #t)((@) . #t)((@ addresses) . #t))"),
         2, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readCount.xml"), query.read(), attributes);

      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(= classCode \"CON\")"),
         parse("((firstName . #t)(lastName . #t)((@) . #t)((@ addresses) . #t))"),
         2, 2, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readOffset.xml"), query.read(), attributes);

      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(and (like? firstName \"J*\") (= lastName \"Smith\") (in? (@ readPrincipal name) \"users\" \"QA\" \"jsmith\")))"),
         parse("((firstName . #t)(lastName . #t)((@) . #t)((@ addresses) . #t))"),
         -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readWhere.xml"), query.read(), attributes);

      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(and (like? firstName \"J*\") (= lastName \"Smith\") (in? (@ readPrincipal name) \"users\" \"QA\" \"jsmith\" " +
                "\"1\" \"2\" \"3\" \"4\" \"5\" \"6\" \"7\" \"8\" \"9\" \"10\" \"11\" \"12\" \"13\" \"14\" \"15\" \"16\" " +
                "\"17\" \"18\" \"19\" \"20\" \"21\" \"22\" \"23\" \"24\" \"25\" \"26\" \"27\" \"28\" \"29\" \"30\")))"),
         parse("((firstName . #t)(lastName . #t)((@) . #t)((@ addresses) . #t))"),
         -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readWhere.xml"), query.read(), attributes);

      List inList = new ArrayList(3000);

      for (int i = 0; i < 3000; ++i)
      {
         inList.add(String.valueOf(i));
      }

      inList.add("users");
      inList.add("QA");
      inList.add("jsmith");

      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(and (like? firstName \"J*\") (= lastName \"Smith\"))")
            .and(Pair.attribute("readPrincipal name").in(new Pair(inList))),
         parse("((firstName . #t)(lastName . #t)((@) . #t)((@ addresses) . #t))"),
         -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readWhere.xml"), query.read(), attributes);
      inList = null;

      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes, parse("(= lastName ())"),
         parse("((firstName . #t)(lastName . #t)((@) . #t)((@ addresses) . #t))"),
         -1, 0, false, Query.SEC_NODE, m_context);
      AssertUtil.assertEquals(getURL("readWhereNull.xml"), query.read(), attributes);

      attributes = parse("(type country city)");
      query = Query.createRead(getMetadata().getMetaclass("Address"), attributes,
         new Pair(Symbol.EQ,
            new Pair(parse("(@@ Contact addresses)"),
            new Pair(new OID(new Object[]{Binary.parse("00000000000000000000000000000001")})))),
         parse("((type . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readReverse.xml"), query.read(), attributes);

      query = Query.createRead(getMetadata().getMetaclass("Address"), attributes,
         new Pair(Symbol.OR, new Pair(Boolean.FALSE, new Pair(
            new Pair(Symbol.EQ,
               new Pair(parse("(@@ User contact addresses)"),
                  new Pair(new OID(new Object[]{Binary.parse("00000000000000000000000000000001")}))))))),
         parse("((type . #t) ((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readReverse2.xml"), query.read(), attributes);

      query = Query.createRead(getMetadata().getMetaclass("Address"), attributes,
               parse("(= (@@ Contact businessAddress2) (oid #z00000000000000000000000000000001))"),
         parse("((type . #t) ((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readReverse3.xml"), query.read(), attributes);

      query = Query.createRead(getMetadata().getMetaclass("BusinessAddress"),
         parse("(type country city street code)"),
         parse("(like? city \"T*\")"), parse("(((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readClassWhere.xml"), query.read(), attributes);

      attributes = parse("(firstName (addresses city))");
      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(and (like? (@ addresses city) \"T*\") (= classCode \"CON\"))"),
         parse("(((@ addresses city) . #t) ((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readCollectionWhere.xml"), query.read(), attributes);

      attributes = parse("(firstName lastName (addresses country city street code) (businessAddress2 country city street code) (user name))");
      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(= classCode \"CON\")"),
         parse("((firstName . #t)(lastName . #t)((@ addresses) . #t))"),
         -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readAssocWhere.xml"), query.read(), attributes);

      attributes = parse("(firstName lastName (qaUser name) (user name))");
      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(= classCode \"CON\")"),
         parse("((firstName . #t)(lastName . #t))"), -1, 0, false, Query.SEC_NONE, m_context);

      InstanceList list = query.read();

      AssertUtil.assertEquals(getURL("readAssocWhere2.xml"), list, attributes);

      query.setMaxCount(1000000);

      Cursor cursor = query.openCursor();

      try
      {
         int i = 0;

         while ((instance = cursor.next()) != null)
         {
            assertSame(list.getInstance(i++), instance);
         }

         assertEquals(list.size(), i);
      }
      finally
      {
         cursor.close();
      }

      attributes = parse("(addresses)");
      cursor = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(= classCode \"CON\")"), null, -1, 0, false, Query.SEC_NONE, m_context).openCursor();

      try
      {
         cursor.next();
         cursor.next();
         instance = cursor.next();
         assertEquals(2, ((InstanceList)instance.getValue("addresses")).size());
      }
      finally
      {
         cursor.close();
      }
     
      attributes = parse("(fullName)");
      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(any (= (@ addresses city) \"Toronto\"))"),
         parse("((firstName . #t)(lastName . #t)((@ addresses) . #t))"),
         -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readAny.xml"), query.read(), attributes);

      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(any (= (@ businessAddress3 city) \"Toronto\"))"),
         parse("((firstName . #t)(lastName . #t)((@ addresses) . #t))"),
         -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readAny2.xml"), query.read(), attributes);

      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(any (= (@) ()))"), parse("((firstName . #t)(lastName . #t)((@ addresses) . #t))"),
         -1, 0, false, Query.SEC_NONE, m_context);
      assertEquals(0, query.read().size());

      attributes = parse("(name typeCode)");
      query = Query.createRead(getMetadata().getMetaclass("Principal"), attributes, null,
         parse("((name . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readPolymorphic.xml"), query.read(), attributes);
     
      attributes = parse("(fullName)");
      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(instance? (@) Patient)"),
         parse("((firstName . #t)(lastName . #t)((@ addresses) . #t))"),
         -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readSubclass.xml"), query.read(), attributes);

      query  = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(and (instance? (@) Patient) (not (like? (@ lastName) \"[S]*\")))"),
         parse("((firstName . #t)(lastName . #t)((@ addresses) . #t))"),
         -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readSubclass.xml"), query.read(), attributes);

      query = Query.createRead(getMetadata().getMetaclass("Address"), null,
         parse("(= typeEnum (oid \"address\" \"en\" \"Business\"))"),
         parse("(((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readEnum.xml"), query.read(), null);

      query = Query.createRead(getMetadata().getMetaclass("Address"), null,
         parse("(= (@ typeEnum type) \"Business\")"),
         parse("(((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readEnum.xml"), query.read(), null);

      query = Query.createRead(getMetadata().getMetaclass("Address"), null,
         parse("(and (= typeEnum (oid \"address\" \"en\" \"Business\")) (= (@ typeEnum type) \"Business\"))"),
         parse("(((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readEnum.xml"), query.read(), null);

      query = Query.createRead(getMetadata().getMetaclass("Address"), null,
         parse("(and (= typeEnum (oid \"address\" \"en\" \"Business\")) (= (@ typeEnum type) \"1\"))"),
         parse("(((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      assertEquals(0, query.read().getCount());

      query = Query.createRead(getMetadata().getMetaclass("ExternalVisit"), null,
         parse("(= patient (oid #z00000000000000000000000000000006))"),
         parse("(((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readExternal.xml"), query.read(), null);

      query = Query.createRead(getMetadata().getMetaclass("Patient"), parse("(externalVisits)"),
         parse("(and (= firstName \"Sarah\") (= lastName \"Johnson\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context);
      assertEquals(3, ((InstanceList)query.read().getInstance(0).getValue("externalVisits")).size());

      // Comparison of LOB values, class is not important as long as the attribute is LOB

      Query.createRead(getMetadata().getMetaclass("SysRule"), parse("(name)"),
         parse("(= action \"test\")"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read();
      Query.createRead(getMetadata().getMetaclass("SysRule"), parse("(name)"),
         parse("(not (= action \"test\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read();
      Query.createRead(getMetadata().getMetaclass("SysTimer"), parse("(period)"),
         parse("(= data #z0)"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read();
      Query.createRead(getMetadata().getMetaclass("SysTimer"), parse("(period)"),
         parse("(not (= data #z0))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read();

      // Dynamic derived associations

      attributes = parse("(contact)");
      query = Query.createRead(getMetadata().getMetaclass("Address"), attributes,
         parse("(!= (@ contact (instance? (@) Patient) birthdate) ())"),
         parse("(((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readDynDerived.xml"), query.read(), attributes);

      attributes = parse("(lastName)");
      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(!= (@ (instance? (@) Patient) birthdate) ())"),
         parse("(((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readDynTypeCast.xml"), query.read(), attributes);

      list = Query.createRead(getMetadata().getMetaclass("Contact"), parse("(lastName)"),
         parse("(!= (@ addresses (= (@ country) \"Canada\") contact (= (@ firstName) \"Joe\") addresses) ())"),
         parse("(((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context).read();
      assertEquals(1, list.size());
      assertEquals("Joe", list.getInstance(0).getValue("firstName"));

      query = Query.createRead(getMetadata().getMetaclass("Visit"), null,
         parse("(= (@@ Address contact (instance? (@) Patient) visits) (oid #z00000000000000000000000000000008))"),
         parse("(((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readDynReverse.xml"), query.read(), null);

      // Calculated attributes
      attributes = parse("(isTorontoBasedEmployee tax lastNameLengthPlus1 lastNameInitial)");
      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(= classCode \"CON\")"),
         parse("(((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readCalculated.xml"), query.read(), attributes);

      attributes = parse("((businessAddress3 deletable))");
      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(= classCode \"CON\")"),
         parse("(((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readCalculatedSubq.xml"), query.read(), attributes);

      Query.createRead(getMetadata().getMetaclass("Patient"), parse("(visibleDescription)"),
         null, null, 1, 0, false, Query.SEC_NONE, m_context);

      list = Query.createRead(getMetadata().getMetaclass("Contact"), null,
         Pair.attribute("visible"), null, -1, 0, false, Query.SEC_NONE, m_context).read();

      for (int i = 0; i < list.size(); ++i)
      {
         assertEquals(Boolean.TRUE, list.getInstance(i).getValue("visible"));
      }

      assertEquals(2, Query.createRead(getMetadata().getMetaclass("Contact"), null,
         parse("(and (= classCode \"CON\") (> (/ businessAddressCount 2) 0))"), null, -1, 0, false,
         Query.SEC_NONE, m_context).read().size());

      assertEquals(2, Query.createRead(getMetadata().getMetaclass("Address"), null,
         parse("(= (@@ User addresses) (oid #z00000000000000000000000000000001))"), null, -1, 0, false,
         Query.SEC_NONE, m_context).read().size());

      // Annotations
      attributes = parse("(firstName (: _fnLen (string-length (@ firstName))) (: _n 123) (: _ln (@ lastName)) (: _x2 (let ((x 2)) (* x x))) (: _f (lambda (x) 1)))");
      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(= classCode \"CON\")"),
         parse("(((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readAnnotations.xml"), query.read(), attributes);

      // Annotated queries with cached classes (no caching)
      attributes = parse("((: l (string-length (@ name))))");
      query = Query.createRead(getMetadata().getMetaclass("Country"), attributes,
         null, parse("(((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readAnnotationsCached.xml"), query.read(), attributes);

      // Aggregate functions
      attributes = parse("(firstName lastName addressCount (: _countryCnt (count (@ addresses country))) " +
         "(: _uniqueCountryCnt (count (unique (@ addresses country)))) (: _nullCodeCnt (count (null? (@ addresses code)))) " +
         "(: _avg (average (string-length (@ addresses city)))) (: _min (minimum (string-length (@ addresses city)))) " +
         "(: _max (maximum (@ addresses city))) (: _sum (sum (string-length (@ addresses city)))))");
      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(= classCode \"CON\")"), parse("(((@) . #t))"),
         -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readAggregate.xml"), query.read(), attributes);

      // Aggregate queries
      list = Query.createAggregate(getMetadata().getMetaclass("Contact"), null,
         parse("(= classCode \"CON\")"), null, null, null, -1, 0, Query.SEC_NONE, m_context).read();
      assertEquals(1, list.size());
      instance = list.getInstance(0);
      assertEquals("Object", instance.getClassName());
      assertNull(instance.getOID());

      attributes = parse("((: c (count (@))) (: n 1))");
      query = Query.createAggregate(getMetadata().getMetaclass("Contact"), attributes,
         parse("(= classCode \"CON\")"), null, null, null, -1, 0, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readGroupByCountAll.xml"), query.read(), attributes);

      attributes = parse("((: c (* 2 (count (@)))))");
      query = Query.createAggregate(getMetadata().getMetaclass("Contact"), attributes,
         parse("(= classCode \"CON\")"), parse("((@))"), null, parse("(((@) . #t))"), -1, 0, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readGroupByIndentity.xml"), query.read(), attributes);

      query = Query.createAggregate(getMetadata().getMetaclass("Contact"), parse("(classCode (: c (* 2 (count (@)))) (: n 1))"),
         parse("(= classCode \"CON\")"), parse("(classCode)"), null, parse("((classCode . #t))"),
         -1, 0, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readGroupByAttribute.xml"), query.read(),
         parse("((: classCode (@ classCode)) (: c (* 2 (count (@)))) (: n 1))"));

      attributes = parse("((: fnl (string-length (@ firstName))))");
      query = Query.createAggregate(getMetadata().getMetaclass("Contact"), attributes,
         parse("(= classCode \"CON\")"), parse("(classCode (string-length (@ firstName)))"), null,
         parse("((classCode . #t) ((string-length (@ firstName)) . #t))"), -1, 0, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readGroupByExpr.xml"), query.read(), attributes);

      attributes = parse("((: c (* 2 (count (@)))) (: ac (count (@ addresses))))");
      query = Query.createAggregate(getMetadata().getMetaclass("Contact"), attributes,
         parse("(= classCode \"CON\")"), parse("((@))"), null,
         parse("(((@) . #t))"), -1, 0, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readGroupByIdentityCountAssoc.xml"), query.read(), attributes);

      attributes = parse("((: classCode (@ classCode)) (: c (* 2 (count (@)))) (: ac (count (@ addresses))))");
      query = Query.createAggregate(getMetadata().getMetaclass("Contact"), attributes,
         parse("(= classCode \"CON\")"), parse("(classCode)"), parse("(> (count (@ addresses)) 1)"),
         parse("((classCode . #t))"), -1, 0, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readGroupByCountAssoc.xml"), query.read(), attributes);

      attributes = parse("((: c (* 2 (count (@)))) (: a (@ addresses)))");
      query = Query.createAggregate(getMetadata().getMetaclass("Contact"), attributes,
         parse("(= classCode \"CON\")"), parse("((@) addresses)"), null,
         parse("((addresses . #t) ((@) . #t))"), -1, 0, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readGroupByAssoc.xml"), query.read(), attributes);

      query = Query.createAggregate(getMetadata().getMetaclass("Contact"),
         parse("(firstName (: c (count (@))))"),
         parse("(= classCode \"CON\")"), parse("(firstName)"), null,
         parse("((firstName . #t))"), -1, 0, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readGroupByCase.xml"), query.read(),
         parse("((: firstName (@ firstName)) (: c (count (@))))"));

      attributes = parse("((: c (* 2 (count (@)))))");
      query = Query.createAggregate(getMetadata().getMetaclass("Contact"), attributes,
         parse("(= classCode \"CON\")"), parse("(addresses)"), null,
         parse("((addresses . #t))"), -1, 0, Query.SEC_NONE, m_context);
      cursor = query.openCursor();

      try
      {
         AssertUtil.assertEquals(getURL("readGroupByCursorCol.xml"), cursor.next(1024), attributes);
      }
      finally
      {
         cursor.close();
      }

      // Aggregate queries with cached classes (no caching)

      query = Query.createAggregate(getMetadata().getMetaclass("Country"), null,
         null, null, null, null, -1, 0, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readGroupByCachedDefault.xml"), query.read(), null);

      attributes = parse("((: c (count (@))))");
      query = Query.createAggregate(getMetadata().getMetaclass("Country"), attributes,
         null, null, null, null, -1, 0, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readGroupByCachedCountAll.xml"), query.read(), attributes);

      attributes = parse("((: m (maximum (@ countryEnum name))))");
      query = Query.createAggregate(getMetadata().getMetaclass("Address"), attributes,
         null, parse("((@))"), null, parse("(((@) . #t))"), -1, 0, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readGroupByCachedAssoc.xml"), query.read(), attributes);

      attributes = parse("((: c (count (@))))");
      query = Query.createAggregate(getMetadata().getMetaclass("Address"), attributes,
         null, parse("((@ countryEnum name))"), null, parse("(((@ countryEnum name) . #t))"), -1, 0, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readGroupByAssocCached.xml"), query.read(), attributes);

      // Nested aggregate functions
      try
      {
         Query.createAggregate(getMetadata().getMetaclass("Contact"), parse("((: ac (minimum (count (@ addresses)))))"),
            null, parse("((@))"), null, null, -1, 0, Query.SEC_NONE, m_context);
         fail("Expected InvalidQueryException");
      }
      catch (InvalidQueryException e)
      {
         assertEquals("err.persistence.nestedAggregate", e.getErrorCode());
      }

      // Unsupported expression
      try
      {
         Query.createAggregate(getMetadata().getMetaclass("Contact"), parse("((: fnl (sin (string-length (@ firstName)))))"),
            null, parse("(classCode (sin (string-length (@ firstName))))"), null, null, -1, 0, Query.SEC_NONE, m_context);
         fail("Expected InvalidQueryException");
      }
      catch (InvalidQueryException e)
      {
         assertEquals("err.persistence.unsupportedExpression", e.getErrorCode());
      }

      // Invalid having clause
      try
      {
         Query.createAggregate(getMetadata().getMetaclass("Contact"), null, null,
            parse("(addresses)"), parse("(> (@) (oid #z1))"), null, -1, 0, Query.SEC_NONE, m_context).read();
         fail("Expected InvalidQueryException");
      }
      catch (InvalidQueryException e)
      {
         assertEquals("err.persistence.ungroupedHaving", e.getErrorCode());
      }

      // Query macros
      assertEquals(2, Query.createRead(getMetadata().getMetaclass("Address"), null,
         parse("(and isQuery (= contact (oid #z00000000000000000000000000000001)))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read().size());

      assertEquals("R", Query.createRead(getMetadata().getMetaclass("Address"), parse("(cityq)"),
         parse("(= contact (oid #z00000000000000000000000000000001))"),
         parse("((city . #t))"), -1, 0, false, Query.SEC_NONE, m_context).read().getInstance(0).getValue("cityq"));

      assertEquals("Richmond Hill", ((InstanceList)Query.createRead(getMetadata().getMetaclass("Contact"), parse("((addresses cityq))"),
         parse("(= (@) (oid #z00000000000000000000000000000001))"),
         parse("(((@ addresses city) . #t))"), -1, 0, false, Query.SEC_NONE, m_context)
         .read().getInstance(0).getValue("addresses")).getInstance(0).getValue("cityq"));

      // Class caching
      attributes = parse("(name)");
      query = Query.createRead(getMetadata().getMetaclass("Country"), attributes,
         parse("(= name \"Canada\")"), null, -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readCachedInstance.xml"), query.read(), attributes);

      query = Query.createRead(getMetadata().getMetaclass("Country"), attributes,
         parse("(= name \"Canada\")"), null, -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readCachedInstance.xml"), query.read(), attributes);

      // Instance caching
      attributes = parse("(name country)");
      query = Query.createRead(getMetadata().getMetaclass("City"), attributes,
         parse("(= name \"Toronto\")"), null, -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readCachedClass.xml"), query.read(), attributes);

      query = Query.createRead(getMetadata().getMetaclass("City"), attributes,
         parse("(= name \"Toronto\")"), null, -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readCachedClass.xml"), query.read(), attributes);

      query = Query.createRead(getMetadata().getMetaclass("City"), attributes,
         parse("(= (@) (oid \"Toronto\"))"), null, -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readCachedClass.xml"), query.read(), attributes);

      query = Query.createRead(getMetadata().getMetaclass("City"), attributes,
         parse("(= (@) (oid \"Toronto1\"))"), null, -1, 0, false, Query.SEC_NONE, m_context);
      assertEquals(0, query.read().size());

      query = Query.createRead(getMetadata().getMetaclass("City"), attributes,
         parse("(like? name \"Toro*\")"), null, -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readCachedClass.xml"), query.read(), attributes);

      attributes = parse("((countryEnum name) (cityEnum name country))");
      query = Query.createRead(getMetadata().getMetaclass("Address"), attributes,
         null, parse("(((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readCachedJoin.xml"), query.read(), attributes);

      query = Query.createRead(getMetadata().getMetaclass("Address"), attributes,
         null, parse("(((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readCachedJoin.xml"), query.read(), attributes);

      attributes = parse("((primaryAddress cityEnum))");
      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(= (@ primaryAddress cityEnum name) \"Richmond Hill\")"),
         parse("(((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readCachedMix.xml"), query.read(), attributes);
     
      // Wrong subclass
      query = Query.createRead(getMetadata().getMetaclass("Patient"), null,
         parse("(= (@) (oid #z00000000000000000000000000000001))"), null, -1, 0, false, Query.SEC_NONE, m_context);
      assertEquals(0, query.read().size());

      // Reverse assoc subclass, just create the query
      query = Query.createRead(getMetadata().getMetaclass("BusinessAddress"), null,
         parse("(= (@@ Contact addresses) '())"), null, -1, 0, false, Query.SEC_NONE, m_context);
     
      query = Query.createRead(getMetadata().getMetaclass("Principal"), null,
         parse("(= (@@ User) ())"), null, -1, 0, false, Query.SEC_NONE, m_context);

      // Overriden attribute type, just create the query
      query = Query.createRead(getMetadata().getMetaclass("Patient"), parse("((businessAddress2 main))"),
         null, null, -1, 0, false, Query.SEC_NONE, m_context);

      // SysReader with dispatch

      Instance r1 = new Instance(getMetadata().getMetaclass("SysReader"), Instance.NEW, m_context);

      r1.setValue("class", Symbol.define("Principal"));
      r1.setValue("attributes", parse("(name)"));
      r1.setValue("where", parse("(= name \"users\")"));
      r1.invoke("read");

      Instance r2 = new Instance(getMetadata().getMetaclass("SysReader"), Instance.NEW, m_context);

      r2.setValue("class", Symbol.define("UserGroupAssoc"));
      r2.setValue("attributes", parse("(user group)"));
      r2.setValue("orderBy", parse("(((@) . #t))"));
     
      InstanceList p = new InstanceArrayList();
      p.add(r1);
      r2.setValue("parents", p);
     
      List a = new ArrayList();
      a.add(parse("(ugassocs)"));
      r2.setValue("associations", a);
      r2.setValue("dispatchParent", r1);
      r2.setValue("dispatchAttribute", null);
      r2.setValue("dispatchValues", parse("(Group)"));
     
      AssertUtil.assertEquals(getURL("readFMDispatch1.xml"), r2.invoke("read"), parse("(user group)"));

      r2 = new Instance(getMetadata().getMetaclass("SysReader"), Instance.NEW, m_context);

      r2.setValue("class", Symbol.define("UserGroupAssoc"));
      r2.setValue("attributes", parse("(user group)"));
      r2.setValue("orderBy", parse("(((@) . #t))"));

      p = new InstanceArrayList();
      p.add(r1);
      r2.setValue("parents", p);

      a = new ArrayList();
      a.add(parse("(ugassocs)"));
      r2.setValue("associations", a);
      r2.setValue("dispatchParent", r1);
      r2.setValue("dispatchAttribute", parse("(typeCode)"));
      r2.setValue("dispatchValues", parse("(\"G\")"));

      AssertUtil.assertEquals(getURL("readFMDispatch2.xml"), r2.invoke("read"), parse("(user group)"));

      Instance r3 = new Instance(getMetadata().getMetaclass("SysReader"), Instance.NEW, m_context);

      r3.setValue("class", Symbol.define("Principal"));
      r3.setValue("attributes", parse("(name)"));
      r3.setValue("orderBy", parse("(((@ name) . #t))"));
     
      p = new InstanceArrayList();
      p.add(r1);
      r3.setValue("parents", p);
     
      a = new ArrayList();
      a.add(null);
      r3.setValue("associations", a);
      r3.setValue("dispatchParent", r1);
      r3.setValue("dispatchAttribute", null);
      r3.setValue("dispatchValues", parse("(Principal)"));
      r3.setValue("dispatchGroup", r2);
      r3.setValue("dispatched", Boolean.FALSE);
     
      r2.setValue("dispatched", Boolean.TRUE);

      AssertUtil.assertEquals(getURL("readFMDispatch3.xml"), r3.invoke("read"), parse("(name)"));
      assertEquals(Boolean.TRUE, r2.getValue("dispatched"));
      assertEquals(Boolean.FALSE, r3.getValue("dispatched"));
     
      r2.setValue("dispatched", Boolean.FALSE);

      AssertUtil.assertEquals(getURL("readFMDispatch4.xml"), r3.invoke("read"), parse("(name)"));
      assertEquals(Boolean.TRUE, r2.getValue("dispatched"));
      assertEquals(Boolean.TRUE, r3.getValue("dispatched"));

      // nameAttribute
      assertEquals("Joe Test [Employee] {jtest}", m_context.getUser().getName());

      // Query CLOB field to see if it is null/not null
      // (Direct IS NULL queries on LOBs fail in Sybase 12.5.4)
      query = Query.createRead(getMetadata().getMetaclass("SysRule"), null,
         parse("(null? condition)"),
         null, -1, 0, false, Query.SEC_NONE, m_context);
      assertEquals(2, query.read().getCount());

      query = Query.createRead(getMetadata().getMetaclass("SysRule"), null,
         parse("(not (null? condition))"),
         null, -1, 0, false, Query.SEC_NONE, m_context);
      assertEquals(4, query.read().getCount());     
   }

   public void testLoad()
   {
      Metaclass metaclass = getMetadata().getMetaclass("Patient");
      Query query = Query.createRead(metaclass, null, parse("(= lastName \"Johnson\")"),
         null, -1, 0, false, Query.SEC_NONE, m_context);
      Instance instance = query.read().getInstance(0);
      Pair attributes = Pair.fromArray(Pair.toArray(parse("(type (@@ Contact fullName user) (@@ Contact user) (addresses))")));

      instance.invoke("load", attributes);

      String[] names = new String[]{"type" , "fullName", "user", "addresses"};

      for (int i = 0; i < names.length; ++i)
      {
         assertFalse(instance.getValueDirect(metaclass.getAttribute(names[i]).getOrdinal()) instanceof Undefined);
      }

      reset();

      instance = query.read().getInstance(0);
      instance.load(Pair.fromArray(Pair.toArray(attributes))); // copy the attributes as load() modifies the list

      for (int i = 0; i < names.length; ++i)
      {
         assertFalse(instance.getValueDirect(metaclass.getAttribute(names[i]).getOrdinal()) instanceof Undefined);
      }

      Metaclass addressMetaclass = (Metaclass)metaclass.getAttribute("addresses").getType();
      Attribute cityAttribute = addressMetaclass.getAttribute("city");

      assertTrue(((InstanceList)instance.getValue("addresses")).getInstance(0).getValueDirect(cityAttribute.getOrdinal()) instanceof Undefined);

      instance.invoke("load", Pair.fromArray(Pair.toArray(parse("((addresses city))"))));

      assertFalse(((InstanceList)instance.getValue("addresses")).getInstance(0).getValueDirect(cityAttribute.getOrdinal()) instanceof Undefined);

      instance = new Instance(metaclass, Instance.NEW, m_context);
      instance.invoke("load", attributes);
   }

   /**
    * The Lock Timeout test takes a very long time to complete (timeout length defined in RDBMS).
    * Hence, do not enable this test in regular test runs.
    */
   public void skipTestLockTimeoutException()
   {
      UnitOfWork[] uowArray = new UnitOfWork[2];

      uowArray[0] = m_context.beginTransaction(false);

      InstanceList list =
         Query.createRead(getMetadata().getMetaclass("Contact"),
                          null, null, null, -1, 0, true, Query.SEC_NONE, m_context).read();

      int nTimeout = 1200; // the query timeout in seconds known to be greater than RDBMS setting
      long nStart = System.currentTimeMillis();

      try
      {
         uowArray[1] = m_context.beginTransaction(false);
         assertTrue(list.size() > 0); // need at least one to place lock on

         Query query = Query.createRead(getMetadata().getMetaclass("Contact"),
                                        null, Pair.attribute("").eq(list.get(0)),
                                        null, -1, 0, true, Query.SEC_NONE, m_context);

         query.setTimeout(nTimeout); // ensure query will terminate eventually
         query.read();
         fail(); // LockTimeoutException expected
      }
      catch (LockTimeoutException e) // exception expected == pass
      {
      }
      finally
      {
         m_context.rollbackAndResume(uowArray[1]);
         m_context.rollbackAndResume(uowArray[0]);
      }

      // assert exception was not forced by query timeout because it took less than timeout
      assertTrue(System.currentTimeMillis() - nStart < nTimeout * 1000);
   }

   public void testMatch() throws Exception
   {
      Pair attributes = parse("(firstName (addresses city))");
      Query query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(and (match? (@ addresses city) \"Toronto\" 0.0) (= classCode \"CON\"))"),
         parse("(((@ addresses city) . #t) ((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readCollectionWhere.xml"), query.read(), attributes);

      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(and (match? (@ addresses city) '(and \"Toronto\" \"abc\") 0.0) (= classCode \"CON\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context);
      assertEquals(0, query.read().size());

      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(and (match? (@ addresses city) \"Toronto'\" 0.0) (= classCode \"CON\"))"),
         parse("(((@ addresses city) . #t) ((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      query.read(); // test escape of quotes

      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(and (match? (@ addresses city) '(like? \"Toront\") 0.0) (= classCode \"CON\"))"),
         parse("(((@ addresses city) . #t) ((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readCollectionWhere.xml"), query.read(), attributes);

      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(and (match? (@ addresses city) '(and \"Toronto\" (not \"Hill\")) 0.0) (= classCode \"CON\"))"),
         parse("(((@ addresses city) . #t) ((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readCollectionWhere.xml"), query.read(), attributes);

      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(and (match? (@ addresses city) '(or \"Toronto\" \"abc\") 0.0) (= classCode \"CON\"))"),
         parse("(((@ addresses city) . #t) ((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readCollectionWhere.xml"), query.read(), attributes);

      query = Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         parse("(and (match? (@ addresses city) '(* (1.0 \"Toronto\") (0.0 \"Toronto\")) 0.0) (= classCode \"CON\"))"),
         parse("(((@ addresses city) . #t) ((@) . #t))"), -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("readCollectionWhere.xml"), query.read(), attributes);
   }

   public void testNew()
   {
      Metaclass metaclass = getMetadata().getMetaclass("Principal");
      OID oid = new OID(new Object[]{Binary.parse("00000000000000000000000000000004")});
      Instance principal = m_context.findInstance(metaclass, oid);

      if (principal == null)
      {
         principal = new Instance(metaclass, m_context);
         principal.cache(oid);
      }

      Instance contact = new Instance(getMetadata().getMetaclass("Contact"), Instance.NEW, m_context);

      contact.setValue("firstName", "Ozzy");
      contact.setValue("lastName", "Osbourne");
      contact.setValue("testCategory", "");
      contact.setValue("readPrincipal", principal);
      contact.setValue("version", Primitive.ZERO_INTEGER);

      // Test async invocation on new instances
      getMetadata().getMetaclass("SysQueue").invoke("invoke", new Object[]{contact, Symbol.define("testAsync")});
      getMetadata().getMetaclass("SysQueue").invoke("invoke", new Object[]{contact, Symbol.define("testAsync"), new Object[]{"test"}});

      InstanceList list = (InstanceList)contact.getValue("addresses");
      Instance address = new Instance(getMetadata().getMetaclass("Address"), Instance.NEW, m_context);

      address.setValue("type", "Business");
      address.setValue("country", "USA");

      list.add(address);

      Instance cottage = Query.createRead(getMetadata().getMetaclass("AddressType"),
         parse("(type)"), parse("(and (= type \"Cottage\") (= locale \"en\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read().getInstance(0);

      address = new Instance(getMetadata().getMetaclass("Address"), Instance.NEW, m_context);

      address.setValue("typeEnum", cottage);
      address.setValue("country", "Canada");

      list.add(address);
      contact.setValue("primaryAddress", address);

      contact = new Instance(getMetadata().getMetaclass("Contact"), Instance.NEW, m_context);

      contact.setValue("firstName", "Ronnie");
      contact.setValue("lastName", "Dio");
      contact.setValue("readPrincipal", principal);
      contact.setValue("version", Primitive.ZERO_INTEGER);

      list = (InstanceList)contact.getValue("phones");
      Instance phone = new Instance(getMetadata().getMetaclass("Phone"), Instance.NEW, m_context);

      phone.setValue("type", "Business");
      phone.setValue("number", "(111) 222-3344");

      list.add(phone);
      commit();

      assertEquals(Primitive.ZERO_INTEGER, m_context.getMachine().getGlobalEnvironment().getVariable(Symbol.define("update-count")));
      assertEquals(Primitive.createInteger(2), m_context.getMachine().getGlobalEnvironment().getVariable(Symbol.define("commit-count")));
     
      assertEquals(7, Query.createRead(getMetadata().getMetaclass("Contact"), null,
         parse("(= classCode \"CON\")"), null, -1, 0, false, Query.SEC_NONE, m_context).read().size());

      assertEquals(2, Query.createRead(getMetadata().getMetaclass("Contact"), null,
         parse("(in? lastName \"Osbourne\" \"Dio\")"), null,
         -1, 0, false, Query.SEC_NONE, m_context).read().size());

      list = Query.createRead(getMetadata().getMetaclass("Contact"), null,
         parse("(= testCategory \"\")"), null,
         -1, 0, false, Query.SEC_NONE, m_context).read();

      assertEquals(1, list.size());
      assertEquals("", list.getInstance(0).getValue("testCategory"));

      assertEquals(8, Query.createRead(getMetadata().getMetaclass("Contact"), null,
         parse("(null? testCategory)"), null, -1, 0, false, Query.SEC_NONE, m_context).read().size());

      assertEquals(1, Query.createRead(getMetadata().getMetaclass("Address"), null,
         parse("(and (= (@ contact firstName) \"Ozzy\") (= country \"USA\"))"), null,
         -1, 0, false, Query.SEC_NONE, m_context).read().size());

      assertEquals(1, Query.createRead(getMetadata().getMetaclass("Address"), null,
         parse("(and (= (@ contact firstName) \"Ozzy\") (= country \"Canada\"))"), null,
         -1, 0, false, Query.SEC_NONE, m_context).read().size());

      assertEquals(1, Query.createRead(getMetadata().getMetaclass("Phone"), null,
         parse("(= number \"(111) 222-3344\")"), null,
         -1, 0, false, Query.SEC_NONE, m_context).read().size());

      Instance ref = new Instance(getMetadata().getMetaclass("PrincipalRef2"), Instance.NEW, m_context);

      ref.setValue("name", "a");
      ref.setValue("count", Primitive.createInteger(3));

      ref = new Instance(getMetadata().getMetaclass("PrincipalRef2"), Instance.NEW, m_context);

      ref.setValue("name", "b");

      commit();

      m_context.removeInstance(ref);

      ref = Query.createRead(getMetadata().getMetaclass("PrincipalRef2"), null,
         parse("(= name \"b\")"), null, -1, 0, false, Query.SEC_NONE, m_context).read().getInstance(0);
      ref.setValue("count", Primitive.createInteger(7));

      commit();

      assertEquals(1, Query.createRead(getMetadata().getMetaclass("PrincipalRef2"), null,
         parse("(and (= name \"a\") (= count 3))"), null,
         -1, 0, false, Query.SEC_NONE, m_context).read().size());

      assertEquals(1, Query.createRead(getMetadata().getMetaclass("PrincipalRef2"), null,
         parse("(and (= name \"b\") (= count 7))"), null,
         -1, 0, false, Query.SEC_NONE, m_context).read().size());

      // KeyGenerator.MaxPlus1 and KeyGenerator.Counter test

      new Instance(getMetadata().getMetaclass("Max1Test"), Instance.NEW, m_context);
      new Instance(getMetadata().getMetaclass("Max1Test"), Instance.NEW, m_context);
      new Instance(getMetadata().getMetaclass("CounterTest"), Instance.NEW, m_context);
      new Instance(getMetadata().getMetaclass("CounterTest"), Instance.NEW, m_context);

      commit();

      assertEquals(2, Query.createRead(getMetadata().getMetaclass("Max1Test"), null,
         null, null, -1, 0, false, Query.SEC_NONE, m_context).read().size());

      assertEquals(2, Query.createRead(getMetadata().getMetaclass("CounterTest"), null,
         null, null, -1, 0, false, Query.SEC_NONE, m_context).read().size());

      // Test non-primitive initializer
      new Instance(getMetadata().getMetaclass("ContactWithInits"), Instance.NEW, m_context);

      commit();
      reset();

      list = Query.createRead(getMetadata().getMetaclass("ContactWithInits"),
         parse("((initAddress city))"), null, null, -1, 0, false, Query.SEC_NONE, m_context).read();

      assertEquals(1, list.size());
      assertEquals("Canada", ((Instance)list.getInstance(0).getValue("initAddress")).getValue("country"));

      // Duplicate unique constraint test
      m_context.setSecure(false);

      Instance wfqueue = new Instance(getMetadata().getMetaclass("SysWorkflowQueue"), Instance.NEW, m_context);

      wfqueue.setOID(new OID(new Object[]{Binary.parse("00000000000000000000000000000001")}));
      wfqueue.setValue("name", "custom");
      wfqueue.setValue("type", "WFLIFOQueue");
      wfqueue.setValue("caption", "LIFO");
      wfqueue.setValue("customized", Boolean.FALSE);

      m_context.setSecure(true);
     
      try
      {
         commit();
         fail("Expected DuplicateKeyException");
      }
      catch (DuplicateKeyException e)
      {
         assertEquals("err.persistence.duplicateKeyClass", e.getErrorCode());
         assertSame(wfqueue, e.getOIDHolder());
         assertEquals("SysWorkflowQueue", e.getClassName());
      }

      rollback();
     
      // Duplicate unique index test
      m_context.setSecure(false);

      wfqueue = new Instance(getMetadata().getMetaclass("SysWorkflowQueue"), Instance.NEW, m_context);
      wfqueue.setValue("name", "lifo");
      wfqueue.setValue("type", "WFLIFOQueue");
      wfqueue.setValue("caption", "LIFO");
      wfqueue.setValue("customized", Boolean.FALSE);

      m_context.setSecure(true);

      try
      {
         commit();
         fail("Expected DuplicateKeyException");
      }
      catch (DuplicateKeyException e)
      {
         assertEquals("err.persistence.duplicateKey", e.getErrorCode());
         assertSame(wfqueue, e.getOIDHolder());
         assertEquals("SysWorkflowQueue", e.getClassName());
      }

      rollback();

      // Composite data source
      metaclass = getMetadata().findMetaclass("CompositeVisit");

      if (metaclass != null)
      {
         Instance visit = new Instance(metaclass, Instance.NEW, m_context);

         visit.setValue("startDate", new Timestamp(0));
         visit.setValue("reason", "a");
         visit.setAnnotation(":shard", new Pair("DefaultRelationalDatabase"));

         visit = new Instance(metaclass, Instance.NEW, m_context);

         visit.setValue("startDate", new Timestamp(1000));
         visit.setValue("reason", "b");
         visit.setAnnotation(":shard", new Pair("ExternalRelationalDatabase"));

         commit();

         metaclass.setValue(":shard", new Pair("DefaultRelationalDatabase"));
         list = Query.createRead(metaclass, parse("(startDate endDate requests)"),
            parse("(and (= startDate #m1970-01-01) (= reason \"a\"))"), null, -1, 0,
            false, Query.SEC_NONE, m_context).read();

         assertEquals(1, list.size());

         metaclass.setValue(":shard", new Pair("ExternalRelationalDatabase"));
         list = Query.createRead(metaclass, parse("(startDate endDate requests)"),
            parse("(and (= startDate #m1970-01-01) (= reason \"a\"))"), null, -1, 0,
            false, Query.SEC_NONE, m_context).read();

         assertEquals(0, list.size());

         list = Query.createRead(metaclass, parse("(startDate endDate requests)"),
            parse("(and (= startDate #m1970-01-01T00:00:01) (= reason \"b\"))"), null, -1, 0,
            false, Query.SEC_NONE, m_context).read();

         assertEquals(1, list.size());

         metaclass = getMetadata().getMetaclass("CompositeVisit2");
         metaclass.setValue(":shard", new Pair("DefaultRelationalDatabase"));
         list = Query.createRead(metaclass, parse("(startDate endDate requests)"),
            parse("(and (= startDate #m1970-01-01) (= reason \"a\"))"), null, -1, 0,
            false, Query.SEC_NONE, m_context).read();

         assertEquals(1, list.size());
      }
   }

   public void testQueryTimeoutException() throws Exception // exception needed for MySQL
   {
      UnitOfWork[] uowArray = new UnitOfWork[2];

      uowArray[0] = m_context.beginTransaction(false);

      InstanceList list =
         Query.createRead(getMetadata().getMetaclass("Contact"),
                          null, null, null, -1, 0, true, Query.SEC_NONE, m_context).read();

      try
      {
         uowArray[1] = m_context.beginTransaction(false);
         assertTrue(list.size() > 0); // need at least one to place lock on

         Query query = Query.createRead(getMetadata().getMetaclass("Contact"),
                                        null, Pair.attribute("").eq(list.get(0)),
                                        null, -1, 0, true, Query.SEC_NONE, m_context);

         query.setTimeout(1); // test applications specified query timeout
         query.read();
         fail(); // QueryTimeoutException expected
      }
      catch (QueryTimeoutException e) // exception expected == pass
      {
      }
      finally
      {
         m_context.rollbackAndResume(uowArray[1]);
         m_context.rollbackAndResume(uowArray[0]);
      }
   }

   public void testUpdate()
   {
      // Old value with removal

      InstanceList list = Query.createRead(getMetadata().getMetaclass("Contact"),
         parse("(firstName lastName (addresses type typeEnum city))"),
         parse("(and (= lastName \"Test\") (= firstName \"Joe\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read();

      assertEquals(1, list.size());

      Instance contact = list.getInstance(0);

      assertEquals("Test", contact.getOldValue("lastName"));
      assertFalse(contact.isDirty("lastName"));
      assertEquals("Test", contact.getPreValue("lastName"));
      assertFalse(contact.isUpdated("lastName"));

      contact.setValue("lastName", "Best");

      assertEquals("Test", contact.getOldValue("lastName"));
      assertTrue(contact.isDirty("lastName"));
      assertEquals("Test", contact.getPreValue("lastName"));
      assertTrue(contact.isUpdated("lastName"));

      assertEquals("CON", contact.getOldValueDirect(contact.getMetaclass().getAttribute("classCode").getOrdinal()));

      contact.setValue("type", null);

      assertEquals("Joe Best {jtest}", contact.getValue("fullName"));
      assertEquals("Joe Test [Employee] {jtest}", contact.getOldValue("fullName"));
      assertFalse(contact.isDirty("fullName"));
      assertEquals("Joe Test [Employee] {jtest}", contact.getPreValue("fullName"));
      assertFalse(contact.isUpdated("fullName"));

      assertEquals(2, ((InstanceList)contact.getValue("addresses")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("addresses")).size());
      assertFalse(contact.isDirty("addresses"));
      assertEquals(2, ((InstanceList)contact.getPreValue("addresses")).size());
      assertFalse(contact.isUpdated("addresses"));

      ((InstanceList)contact.getValue("addresses")).remove(0);

      assertEquals(1, ((InstanceList)contact.getValue("addresses")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("addresses")).size());
      assertTrue(contact.isDirty("addresses"));
      assertEquals(2, ((InstanceList)contact.getPreValue("addresses")).size());
      assertTrue(contact.isUpdated("addresses"));

      Query.createRead(getMetadata().getMetaclass("Contact"),
         parse("(firstName lastName (addresses type typeEnum city))"),
         parse("(and (= lastName \"Test\") (= firstName \"Joe\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read();

      assertEquals(1, ((InstanceList)contact.getValue("addresses")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("addresses")).size());
      assertTrue(contact.isDirty("addresses"));
      assertEquals(2, ((InstanceList)contact.getPreValue("addresses")).size());
      assertTrue(contact.isUpdated("addresses"));

      contact.setValue("lastUpdated", new Timestamp(3));

      assertSame(Invalid.VALUE,
         contact.getValueDirect(contact.getMetaclass().getAttribute("lastUpdatedMillisecond").getOrdinal()));

      contact.invoke("update");

      assertEquals("Test", contact.getOldValue("lastName"));
      assertTrue(contact.isDirty("lastName"));
      assertEquals("Best", contact.getPreValue("lastName"));
      assertFalse(contact.isUpdated("lastName"));
      assertEquals("Joe Best {jtest}", contact.getValue("fullName"));
      assertEquals("Joe Test [Employee] {jtest}", contact.getOldValue("fullName"));
      assertFalse(contact.isDirty("fullName"));
      assertEquals("Joe Best {jtest}", contact.getPreValue("fullName"));
      assertFalse(contact.isUpdated("fullName"));
      assertEquals(1, ((InstanceList)contact.getValue("addresses")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("addresses")).size());
      assertTrue(contact.isDirty("addresses"));
      assertEquals(1, ((InstanceList)contact.getPreValue("addresses")).size());
      assertFalse(contact.isUpdated("addresses"));

      rollback();

      assertEquals(2, ((InstanceList)contact.getValue("addresses")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("addresses")).size());
      assertFalse(contact.isDirty("addresses"));
      assertEquals(2, ((InstanceList)contact.getPreValue("addresses")).size());
      assertFalse(contact.isUpdated("addresses"));

      ((InstanceList)contact.getValue("addresses")).remove(0);

      assertEquals(1, ((InstanceList)contact.getValue("addresses")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("addresses")).size());
      assertTrue(contact.isDirty("addresses"));
      assertEquals(2, ((InstanceList)contact.getPreValue("addresses")).size());
      assertTrue(contact.isUpdated("addresses"));

      Query.createRead(getMetadata().getMetaclass("Contact"),
         parse("(firstName lastName (addresses type typeEnum city))"),
         parse("(and (= lastName \"Test\") (= firstName \"Joe\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read();

      assertEquals(1, ((InstanceList)contact.getValue("addresses")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("addresses")).size());
      assertTrue(contact.isDirty("addresses"));
      assertEquals(2, ((InstanceList)contact.getPreValue("addresses")).size());
      assertTrue(contact.isUpdated("addresses"));

      rollback();

      assertEquals(2, ((InstanceList)contact.getValue("addresses")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("addresses")).size());
      assertFalse(contact.isDirty("addresses"));
      assertEquals(2, ((InstanceList)contact.getPreValue("addresses")).size());
      assertFalse(contact.isUpdated("addresses"));

      assertEquals(2, ((InstanceList)contact.getValue("phones")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("phones")).size());

      ((InstanceList)contact.getValue("phones")).remove(0);

      assertEquals(1, ((InstanceList)contact.getValue("phones")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("phones")).size());

      Query.createRead(getMetadata().getMetaclass("Contact"),
         parse("(phones)"),
         parse("(and (= lastName \"Test\") (= firstName \"Joe\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read();

      assertEquals(1, ((InstanceList)contact.getValue("phones")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("phones")).size());

      rollback();

      assertEquals(2, ((InstanceList)contact.getValue("phones")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("phones")).size());

      ((InstanceList)contact.getValue("phones")).remove(0);

      assertEquals(1, ((InstanceList)contact.getValue("phones")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("phones")).size());

      Query.createRead(getMetadata().getMetaclass("Contact"),
         parse("(phones)"),
         parse("(and (= lastName \"Test\") (= firstName \"Joe\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read();

      assertEquals(1, ((InstanceList)contact.getValue("phones")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("phones")).size());

      rollback();
     
      assertEquals(2, ((InstanceList)contact.getValue("phones")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("phones")).size());
     
      reset();

      // Old value with adding
     
      list = Query.createRead(getMetadata().getMetaclass("Contact"),
         parse("(firstName lastName (addresses type typeEnum city))"),
         parse("(and (= lastName \"Test\") (= firstName \"Joe\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read();

      contact = list.getInstance(0);

      assertEquals(2, ((InstanceList)contact.getValue("addresses")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("addresses")).size());

      Instance address = new Instance(getMetadata().getMetaclass("Address"), Instance.NEW, m_context);

      address.setValue("contact", contact);

      assertEquals(3, ((InstanceList)contact.getValue("addresses")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("addresses")).size());
      assertTrue(contact.isDirty("addresses"));
      assertEquals(2, ((InstanceList)contact.getPreValue("addresses")).size());
      assertTrue(contact.isUpdated("addresses"));

      Query.createRead(getMetadata().getMetaclass("Contact"),
         parse("(firstName lastName (addresses type typeEnum city))"),
         parse("(and (= lastName \"Test\") (= firstName \"Joe\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read();

      assertEquals(3, ((InstanceList)contact.getValue("addresses")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("addresses")).size());
      assertTrue(contact.isDirty("addresses"));
      assertEquals(2, ((InstanceList)contact.getPreValue("addresses")).size());
      assertTrue(contact.isUpdated("addresses"));

      contact.invoke("update");

      assertEquals(3, ((InstanceList)contact.getValue("addresses")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("addresses")).size());
      assertTrue(contact.isDirty("addresses"));
      assertEquals(3, ((InstanceList)contact.getPreValue("addresses")).size());
      assertFalse(contact.isUpdated("addresses"));

      rollback();

      assertEquals(2, ((InstanceList)contact.getValue("addresses")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("addresses")).size());
      assertFalse(contact.isDirty("addresses"));
      assertEquals(2, ((InstanceList)contact.getPreValue("addresses")).size());
      assertFalse(contact.isUpdated("addresses"));

      address = new Instance(getMetadata().getMetaclass("Address"), Instance.NEW, m_context);
      address.setValue("contact", contact);

      assertEquals(3, ((InstanceList)contact.getValue("addresses")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("addresses")).size());
      assertTrue(contact.isDirty("addresses"));
      assertEquals(2, ((InstanceList)contact.getPreValue("addresses")).size());
      assertTrue(contact.isUpdated("addresses"));

      Query.createRead(getMetadata().getMetaclass("Contact"),
         parse("(firstName lastName (addresses type typeEnum city))"),
         parse("(and (= lastName \"Test\") (= firstName \"Joe\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read();

      assertEquals(3, ((InstanceList)contact.getValue("addresses")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("addresses")).size());
      assertTrue(contact.isDirty("addresses"));
      assertEquals(2, ((InstanceList)contact.getPreValue("addresses")).size());
      assertTrue(contact.isUpdated("addresses"));

      rollback();
     
      assertEquals(2, ((InstanceList)contact.getValue("addresses")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("addresses")).size());
      assertFalse(contact.isDirty("addresses"));
      assertEquals(2, ((InstanceList)contact.getPreValue("addresses")).size());
      assertFalse(contact.isUpdated("addresses"));

      assertEquals(2, ((InstanceList)contact.getValue("phones")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("phones")).size());

      ((InstanceList)contact.getValue("phones")).remove(0);

      assertEquals(1, ((InstanceList)contact.getValue("phones")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("phones")).size());

      Query.createRead(getMetadata().getMetaclass("Contact"),
         parse("(phones)"),
         parse("(and (= lastName \"Test\") (= firstName \"Joe\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read();

      assertEquals(1, ((InstanceList)contact.getValue("phones")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("phones")).size());

      rollback();

      assertEquals(2, ((InstanceList)contact.getValue("phones")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("phones")).size());

      Instance phone = new Instance(getMetadata().getMetaclass("Phone"), Instance.NEW, m_context);

      phone.setValue("contact", contact);

      assertEquals(3, ((InstanceList)contact.getValue("phones")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("phones")).size());

      Query.createRead(getMetadata().getMetaclass("Contact"),
         parse("(phones)"),
         parse("(and (= lastName \"Test\") (= firstName \"Joe\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read();

      assertEquals(3, ((InstanceList)contact.getValue("phones")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("phones")).size());

      rollback();

      assertEquals(2, ((InstanceList)contact.getValue("phones")).size());
      assertEquals(2, ((InstanceList)contact.getOldValue("phones")).size());

      reset();
     
      // Other tests
     
      list = Query.createRead(getMetadata().getMetaclass("Contact"),
         parse("(firstName lastName (addresses type typeEnum city))"),
         parse("(= lastName \"Test\")"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read();

      assertEquals(2, list.size());
     
      Instance cottage = Query.createRead(getMetadata().getMetaclass("AddressType"),
         parse("(type)"), parse("(and (= type \"Cottage\") (= locale \"en\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read().getInstance(0);
     
      for (int i = 0; i < list.size(); ++i)
      {
         contact = list.getInstance(i);
         contact.setValue("lastName", "QA");

         InstanceList addrs = (InstanceList)contact.getValue("addresses");

         for (int k = 0; k < addrs.size(); ++k)
         {
            Instance addr = addrs.getInstance(k);

            if ("Home".equals(addr.getValue("type")))
            {
               addr.setValue("type", "Residence");
               contact.setValue("primaryAddress", addr);
            }
            else if (((Instance)addr.getValue("typeEnum")).getValue("type").equals("Business"))
            {
               addr.setValue("typeEnum", cottage);
            }
         }
      }

      commit();
      assertEquals(Primitive.createInteger(2), m_context.getMachine().getGlobalEnvironment().getVariable(Symbol.define("update-count")));
      assertEquals(Primitive.createInteger(2), m_context.getMachine().getGlobalEnvironment().getVariable(Symbol.define("commit-count")));

      Pair attributes = parse("(firstName lastName type (addresses type country city street code) (user name) (primaryAddress type))");
      Pair orderBy = parse("((firstName . #t)(lastName . #t)((@ addresses) . #t))");
      Query query = Query.createRead(getMetadata().getMetaclass("Contact"),
         attributes, parse("(= classCode \"CON\")"), orderBy, -1, 0, false, Query.SEC_NONE, m_context);
      list = query.read();

      AssertUtil.assertEquals(getURL("update.xml"), list, attributes);

      for (Iterator itr = list.iterator(); itr.hasNext();)
      {
         ((Instance)itr.next()).setValue("firstName", "");
      }

      commit();

      for (Iterator itr = list.iterator(); itr.hasNext();)
      {
         ((Instance)itr.next()).setValue("type", null);
      }

      commit();
      reset();

      query = Query.createRead(getMetadata().getMetaclass("Contact"),
         attributes, parse("(= classCode \"CON\")"), orderBy,
         -1, 0, false, Query.SEC_NONE, m_context);
      list = query.read();

      AssertUtil.assertEquals(getURL("updateNull.xml"), list, attributes);

      m_context.setSecure(false);

      query = Query.createRead(getMetadata().getMetaclass("SysRule"), parse("(action)"),
         null, parse("(((@) . #t))"), 1, 0, false, Query.SEC_NONE, m_context);
      list = query.read();

      char[] charArray = new char[20000];

      Arrays.fill(charArray, 'a');

      String sCLOB = new String(charArray);

      list.getInstance(0).setValue("action", sCLOB);

      commit();
      reset();

      assertEquals(sCLOB, query.read().getInstance(0).getValue("action"));
   }

   public void testRanges()
   {
      class RangeInfo
      {
         private String m_sName;
         private Object m_value;
         private String m_sErrCode;

         public RangeInfo(String sName, Object value, String sErrCode)
         {
            m_sName = sName;
            m_value = value;
            m_sErrCode = sErrCode;
         }

         public void setValue(Instance instance)
         {
            instance.setValue(m_sName, m_value);
         }

         public String getErrorCode()
         {
            return m_sErrCode;
         }
        
         public void assertException(ValidationException e, Instance instance)
         {
            assertNotNull(m_sErrCode);

            assertSame(e.getOIDHolder(), instance);
            assertEquals(1, e.getAttributeCount());
            assertEquals(m_sName, e.getAttributeIterator().next());

            ValidationException x = (ValidationException)e.findException(m_sName);

            assertNotNull(x);
            assertEquals(m_sErrCode, x.getErrorCode());
            assertSame(x.getOIDHolder(), instance);

            if (getLogger().isDebugEnabled())
            {
               getLogger().debug(x.getMessage());
            }
         }
      }

      RangeInfo[] rangeInfoArray = new RangeInfo[]
      {
         new RangeInfo("s", "1", null),
         new RangeInfo("s", "12", "err.validation.maxDataLength"),
         new RangeInfo("bin", Binary.parse("11"), null),
         new RangeInfo("bin", Binary.parse("1122"), "err.validation.maxDataLength"),
         new RangeInfo("n1", new Integer(1000), "err.validation.numberRange"),
         new RangeInfo("n1", new Integer(256), "err.validation.numberRange"),
         new RangeInfo("n1", new Integer(255), null),
         new RangeInfo("n1", new Integer(0), null),
         new RangeInfo("n1", new Integer(-1), "err.validation.numberRange"),
         new RangeInfo("n2", new Integer(40000), "err.validation.numberRange"),
         new RangeInfo("n2", new Integer(32768), "err.validation.numberRange"),
         new RangeInfo("n2", new Integer(32767), null),
         new RangeInfo("n2", new Integer(-32768), null),
         new RangeInfo("n2", new Integer(-32769), "err.validation.numberRange"),
         new RangeInfo("dec", new BigDecimal("12345678901.234567890"), "err.validation.numberRange"),
         new RangeInfo("dec", new BigDecimal("100000.234567890"), "err.validation.numberRange"),
         new RangeInfo("dec", new BigDecimal("-100000.234567890"), "err.validation.numberRange"),
         new RangeInfo("dec", new BigDecimal("99999.99999"), null),
         new RangeInfo("dec", new BigDecimal("-99999.99999"), null),
         new RangeInfo("dec", new BigDecimal("99999.999991"), null),
         new RangeInfo("dec", new BigDecimal("-99999.999991"), null),
         new RangeInfo("dec", new BigDecimal("99999.999999"), "err.validation.numberRange"),
         new RangeInfo("dec", new BigDecimal("-99999.999999"), "err.validation.numberRange"),
         new RangeInfo("tm", new Timestamp(0x8000000000000000L), "err.validation.dateRange"),
         new RangeInfo("tm", new Timestamp(0x7FFFFFFFFFFFFFFFL), "err.validation.dateRange"),
         new RangeInfo("tm", new Timestamp(0), null),
      };

      Metaclass metaclass = m_context.getMetadata().getMetaclass("RangeTest");

      for (int i = 0; i < rangeInfoArray.length; ++i)
      {
         RangeInfo info = rangeInfoArray[i];
         Instance instance = new Instance(metaclass, Instance.NEW, m_context);

         info.setValue(instance);

         try
         {
            commit();

            if (info.getErrorCode() != null)
            {
               fail("Expected exception");
            }
         }
         catch (ValidationException e)
         {
            info.assertException(e, instance);
         }
         catch (Exception t)
         {
            fail("Unexpected exception " + t.getClass().getName());
         }
         finally
         {
            reset();
         }
      }

      Instance instance = new Instance(metaclass, Instance.NEW, m_context);

      commit();

      for (int i = 0; i < rangeInfoArray.length; ++i)
      {
         RangeInfo info = rangeInfoArray[i];

         info.setValue(instance);

         try
         {
            commit();

            if (info.getErrorCode() != null)
            {
               fail("Expected exception");
            }
         }
         catch (ValidationException e)
         {
            info.assertException(e, instance);
         }
         catch (Exception t)
         {
            fail("Unexpected exception " + t.getClass().getName());
         }
         finally
         {
            rollback();
         }
      }
   }

   public void testInvalidation()
   {
      Instance address = Query.createRead(getMetadata().getMetaclass("Address"),
         null, parse("(= street \"100 Front St E\")"), null,
         -1, 0, false, Query.SEC_NONE, m_context).read().getInstance(0);

      address.setValue("type", "Home");
      commit();
      reset();

      Instance contact = Query.createRead(getMetadata().getMetaclass("Contact"),
         parse("(businessAddressCount)"), parse("(and (= firstName \"Joe\") (= lastName \"Test\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read().getInstance(0);

      assertEquals(Primitive.ZERO_INTEGER, contact.getValue("businessAddressCount"));
      reset();

      address = Query.createRead(getMetadata().getMetaclass("Address"),
         null, parse("(= street \"100 Front St E\")"), null,
         -1, 0, false, Query.SEC_NONE, m_context).read().getInstance(0);

      address.setValue("type", "Business");
      commit();
      reset();

      contact = Query.createRead(getMetadata().getMetaclass("Contact"),
         parse("(businessAddressCount)"), parse("(and (= firstName \"Joe\") (= lastName \"Test\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read().getInstance(0);

      assertEquals(Primitive.ONE_INTEGER, contact.getValue("businessAddressCount"));
      reset();

      address = Query.createRead(getMetadata().getMetaclass("Address"),
         null, parse("(= street \"100 Front St E\")"), null,
         -1, 0, false, Query.SEC_NONE, m_context).read().getInstance(0);
      address.invoke("delete");
      commit();
      reset();

      contact = Query.createRead(getMetadata().getMetaclass("Contact"),
         parse("(businessAddressCount)"), parse("(and (= firstName \"Joe\") (= lastName \"Test\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read().getInstance(0);

      assertEquals(Primitive.ZERO_INTEGER, contact.getValue("businessAddressCount"));
   }

   public void testConcurrentResultSets()
   {
      Query query = Query.createRead(getMetadata().getMetaclass("Contact"), parse("(firstName)"),
                                     null, null, -1, 0, false, Query.SEC_NONE, m_context);
      Cursor cursor1 = query.openCursor();
      Cursor cursor2 = query.openCursor();

      assertTrue(cursor2.next(-1).size() > 0);
      assertTrue(cursor1.next(-1).size() > 0);
      cursor1.close();
      cursor2.close();
   }

   public void testDelete()
   {
      m_context.requireTransaction();

      InstanceList list = Query.createRead(getMetadata().getMetaclass("Contact"),
         parse("(firstName lastName)"),
         parse("(and (= firstName \"Joe\") (= lastName \"Test\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read();

      assertEquals(1, list.size());

      Instance instance = list.getInstance(0);

      list = (InstanceList)instance.getValue("addresses");
      instance.setDeleted();

      for (int i = 0; i < list.size(); ++i)
      {
         list.getInstance(i).setDeleted();
      }

      commit();

      assertEquals(Primitive.ZERO_INTEGER, m_context.getMachine().getGlobalEnvironment().getVariable(Symbol.define("update-count")));
      assertEquals(Primitive.ZERO_INTEGER, m_context.getMachine().getGlobalEnvironment().getVariable(Symbol.define("commit-count")));

      Pair attributes = parse("(firstName lastName (addresses country city street code) (user name))");
      Query query = Query.createRead(getMetadata().getMetaclass("Contact"),
         attributes, parse("(= classCode \"CON\")"),
         parse("((firstName . #t)(lastName . #t)((@ addresses) . #t))"),
         -1, 0, false, Query.SEC_NONE, m_context);
      AssertUtil.assertEquals(getURL("delete.xml"), query.read(), attributes);

      list = Query.createRead(getMetadata().getMetaclass("User"), parse("(ugassocs)"),
         parse("(= name \"jsmith\")"), null, -1, 0, false, Query.SEC_NONE, m_context).read();

      assertEquals(1, list.size());

      for (Iterator itr = ((InstanceList)list.getInstance(0).getValue("ugassocs")).iterator(); itr.hasNext();)
      {
         ((Instance)itr.next()).delete();
      }

      commit();

      // cached delete

      Metaclass country = getMetadata().getMetaclass("Country");
      int nCountryCount = Query.createRead(country, null, null, null, -1, 0,
         false, Query.SEC_NONE, m_context).read().size();

      instance = new Instance(country, Instance.NEW, m_context);
      instance.setValue("name", "test");
      commit();

      assertEquals(nCountryCount + 1, Query.createRead(country, null, null, null, -1, 0,
         false, Query.SEC_NONE, m_context).read().size());

      instance.delete();
      commit();

      assertEquals(nCountryCount, Query.createRead(country, null, null, null, -1, 0,
         false, Query.SEC_NONE, m_context).read().size());
   }
  
   public void testSharedLocking()
   {
      m_context.requireTransaction();
      m_context.getUnitOfWork().setLocking(true);

      Pair attributes = parse("(fullName (addresses type city))");
      Pair where = parse("(= classCode \"CON\")");
      Pair orderBy = parse("(((@) . #t)((@ addresses) . #t))");
      Query query = Query.createRead(getMetadata().getMetaclass("Contact"),
         attributes, where, orderBy, -1, 0, false, Query.SEC_NONE, m_context);
      InstanceList list = query.read();

      assertEquals(5, list.size());

      for (int i = 0; i != list.size(); ++i)
      {
         assertTrue(m_context.isLocked(list.getInstance(i)));
      }

      execute("upd_addr");

      Query.createRead(getMetadata().getMetaclass("Address"), parse("(contact type city)"),
         null, null, -1, 0, false, Query.SEC_NONE, m_context).read();
     
      AssertUtil.assertEquals(getURL("shlockUpdPes1.xml"), list, attributes);
     
      Query.createRead(getMetadata().getMetaclass("Contact"),
         attributes, where, orderBy, -1, 0, false, Query.SEC_NONE, m_context).read();

      InstanceList addresses = (InstanceList)list.getInstance(0).getValue("addresses");
     
      AssertUtil.assertEquals(getURL("shlockUpdPes2.xml"), list, attributes);
     
      execute("upd_cont");
     
      try
      {
         Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
            where, orderBy, -1, 0, false, Query.SEC_NONE, m_context).read();
         fail("Expected OptimisticLockException");
      }
      catch (OptimisticLockException e)
      {
      }

      m_context.getUnitOfWork().unlock(list.getInstance(0));
      assertFalse(m_context.isLocked(list.getInstance(0)));

      Query.createRead(getMetadata().getMetaclass("Contact"), attributes,
         where, orderBy, -1, 0, false, Query.SEC_NONE, m_context).read();

      assertSame(addresses, list.getInstance(0).getValue("addresses"));
      assertTrue(m_context.isLocked(list.getInstance(0)));

      AssertUtil.assertEquals(getURL("shlockUpdOpt.xml"), list, attributes);
   }

   public void testRulesEngine()
   {
      // ((Patient'new '(gender . "M") `(type . ,((ContactType'read '(type) '(= type "Company") '() -1 0 #f)'get 0)))))'icon)
     
      assertEquals("icon-c",
         ((Instance)getMetadata().getMetaclass("Patient").invoke("new",
            new Object[]{new Pair(Symbol.define("gender"), "M"), new Pair(Symbol.define("type"),
               ((InstanceList)getMetadata().getMetaclass("ContactType").invoke("read",
               new Object[]{parse("(type)"), parse("(= type \"Company\")"), null,
                  new Integer(-1), new Integer(0), Boolean.FALSE})).get(0))})).getValue("icon"));

      assertEquals("icon-m",
         ((Instance)getMetadata().getMetaclass("Patient").invoke("new",
            new Object[]{new Pair(Symbol.define("gender"), "M"), new Pair(Symbol.define("type"),
               ((InstanceList)getMetadata().getMetaclass("ContactType").invoke("read",
               new Object[]{parse("(type)"), parse("(= type \"Person\")"), null,
                  new Integer(-1), new Integer(0), Boolean.FALSE})).get(0))})).getValue("icon"));

      assertEquals("icon-c",
         ((Instance)getMetadata().getMetaclass("Patient").invoke("new",
            new Object[]{new Pair(Symbol.define("gender"), "H"), new Pair(Symbol.define("type"),
               ((InstanceList)getMetadata().getMetaclass("ContactType").invoke("read",
               new Object[]{parse("(type)"), parse("(= type \"Company\")"), null,
                  new Integer(-1), new Integer(0), Boolean.FALSE})).get(0))})).getValue("icon"));
   }
  
   /**
    * test m_bDenorm in SQLUpdate gets set properly for each instance of the batch
    * (need at least 2 instances in batch for test to work)
    * originally only occurred if SQLAdapter did not support "NoRowsBlock" functionality
    */
   public void testSQLUpdateDenorm()
   {
      Instance principal1 = new Instance(getMetadata().getMetaclass("PrincipalRef"), Instance.NEW, m_context);
      Instance principal2 = new Instance(getMetadata().getMetaclass("PrincipalRef"), Instance.NEW, m_context);

      principal1.setValue("name", "test1");
      principal2.setValue("name", "test2");
      commit();

      principal1.setValue("count", new Integer(1));
      principal2.setValue("count", new Integer(2));
      commit();
   }
  
   public void testTransactions()
   {
      Metaclass contact = getMetadata().getMetaclass("Contact");
     
      contact.invoke("testTxSupported", new Object[]{null, Boolean.TRUE});
     
      assertEquals(1, m_context.getUnitOfWorkCount());
      assertNotNull(m_context.getUnitOfWork().getTransaction());
      assertEquals(Status.STATUS_ACTIVE, m_context.getUnitOfWork().getStatus());
      m_context.commitAndResume(null);
      assertEquals(1, m_context.getUnitOfWorkCount());
      assertNull(m_context.getUnitOfWork().getTransaction());
      assertEquals(Status.STATUS_NO_TRANSACTION, m_context.getUnitOfWork().getStatus());
      m_context.complete(true);
      assertEquals(0, m_context.getUnitOfWorkCount());
      assertEquals(1, Query.createRead(contact, null, parse("(and (= firstName \"tx\") (= lastName \"new\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read().size());
      assertEquals(0, Query.createRead(contact, null, parse("(and (= firstName \"tx\") (= lastName \"error\"))"),
         null, -1, 0, false, Query.SEC_NONE, m_context).read().size());
   }

   public void testSchemaUnicodeValidation() throws Exception
   {
      RelationalSchema schema = (RelationalSchema)m_database.getSchema();
      Table origVersionTable = schema.getVersionTable(); // note original
      Table versionTable = schema.getTable("Version");
      RelationalDatabase origDS = (RelationalDatabase)schema.getDataSource();
      RelationalDatabase ds = (RelationalDatabase)origDS.clone();

      try
      {
         schema.setDataSource(ds);
         schema.setVersionTable(versionTable);

         try
         {
            m_adapter.getVersion(schema); // one of getVersion() has to throw exception
            ds.setUnicode(!ds.isUnicode()); // try the oposite Unicode flag
            m_adapter.getVersion(schema); // one of getVersion() has to throw exception
            fail("Unicode flag validation exception expected.");
         }
         catch (UncheckedException e)
         {
            // test online upgrade prevention
            assertEquals("err.persistence.sql.unicodeMismatch", e.getErrorCode());
         }

         // ensure correct result was provided as per the created schema
         SQLConnection con = m_adapter.getConnection();
         Statement stmt = null;
         ResultSet rs = null;

         try
         {
            stmt = con.getConnection().createStatement();
            rs = stmt.executeQuery(
               "select namespace from " +
               versionTable.getFullName(m_adapter.createSchemaManager().getOwner()));

            // 1 == the namespace column
            assertEquals(Boolean.valueOf(origDS.isUnicode()), m_adapter.isUnicode(schema, rs, 1));
         }
         finally
         {
            try
            {
               if (rs != null)
               {
                  rs.close();
               }
            }
            catch (Throwable t)
            {}

            try
            {
               if (stmt != null)
               {
                  stmt.close();
               }
            }
            catch (Throwable t)
            {}

            con.decRef();
         }

         StringWriter writer = new StringWriter();
         SQLSchemaManager manager = m_adapter.createSchemaManager();

         manager.setSQLAppender(manager.new SQLWriterAppender(writer));
         manager.upgrade(schema);

         // test offline upgrade script guard generation
         assertTrue(writer.toString().contains(getUnicodeCheckGuard(ds.isUnicode())));

         ds.setUnicode(!ds.isUnicode()); // try the oposite Unicode flag
         writer = new StringWriter();
         manager.setSQLAppender(manager.new SQLWriterAppender(writer));
         manager.upgrade(schema);

         // test offline upgrade script guard generation
         assertTrue(writer.toString().contains(getUnicodeCheckGuard(ds.isUnicode())));
      }
      finally
      {
         schema.setVersionTable(origVersionTable); // reset so testUpgrade() will not fail
         schema.setDataSource(origDS); // reset to original
      }
   }

   public void testWorkflow()
   {
      Metaclass reqClass = getMetadata().getMetaclass("HRRequest");
      Instance req = (Instance)reqClass.invoke("new",
         new Object[]
         {
            new Pair(Symbol.define("start"), Primitive.toTimestamp("2000-01-01 00:00:00")),
            new Pair(Symbol.define("end"), Primitive.toTimestamp("2000-02-01 00:00:00")),
            new Pair(Symbol.define("applicant"), m_context.getUser())
         });
      Metaclass workflowClass = getMetadata().getMetaclass(Metadata.WORKFLOW_CLASS_NAME);
      InstanceList workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{req});
     
      assertEquals(1, workflowList.size());
     
      commit();

      Pair attributes = parse("(name version class serializedState serializedVariables (assignments ordinal caption assignee manual (nextStates ordinal caption)) (timers ordinal))");
      Pair orderBy = parse("(((@ assignments ordinal) . #t) ((@ assignments nextStates ordinal) . #t))");
     
      m_context.setSecure(false);
      AssertUtil.assertEqualsIgnoreOIDs(getURL("workflow-HRA1.xml"),
         Query.createRead(workflowClass, attributes, null, orderBy,
            -1, 0, false, Query.SEC_NONE, m_context).read(), attributes);
      m_context.setSecure(true);

      req.setValue("approved", Boolean.TRUE);

      commit();

      m_context.setSecure(false);
      workflowList = Query.createRead(workflowClass, attributes, null, orderBy,
         -1, 0, false, Query.SEC_NONE, m_context).read();

      AssertUtil.assertEqualsIgnoreOIDs(getURL("workflow-HRA2.xml"), workflowList, attributes);
     
      Instance wfassignment = ((InstanceList)workflowList.getInstance(0).getValue("assignments")).getInstance(0);
     
      wfassignment.setValue("nextState", ((InstanceList)wfassignment.getValue("nextStates")).getInstance(0));
     
      wfassignment = ((InstanceList)workflowList.getInstance(0).getValue("assignments")).getInstance(1);;
      wfassignment.setValue("nextState", ((InstanceList)wfassignment.getValue("nextStates")).getInstance(0));
      m_context.setSecure(true);
     
      commit();

      m_context.setSecure(false);
      workflowList = Query.createRead(workflowClass, attributes, null, orderBy,
         -1, 0, false, Query.SEC_NONE, m_context).read();
      m_context.setSecure(true);

      AssertUtil.assertEqualsIgnoreOIDs(getURL("workflow-HRA3.xml"), workflowList, attributes);
   }

   public void testWorkflow2()
   {
      Metaclass reqClass = getMetadata().getMetaclass("HRRequest");
      Instance req = (Instance)reqClass.invoke("new",
         new Object[]
         {
            new Pair(Symbol.define("start"), Primitive.toTimestamp("2000-01-01 00:00:00")),
            new Pair(Symbol.define("end"), Primitive.toTimestamp("2000-02-01 00:00:00")),
            new Pair(Symbol.define("applicant"), m_context.getUser())
         });

      Metaclass workflowClass = getMetadata().getMetaclass(Metadata.WORKFLOW_CLASS_NAME);
      InstanceList workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{req});

      assertEquals(1, workflowList.size());

      m_context.setSecure(false);
     
      for (int i = workflowList.size() - 1; i >= 0; --i)
      {
         workflowList.getInstance(i).invoke("delete");
      }

      m_context.setSecure(true);

      workflowClass.invoke("start", new Object[]{req, "HolidayRequest_C", Boolean.FALSE});

      commit();

      Pair attributes = parse("(name version class serializedState serializedVariables (assignments ordinal caption assignee manual (nextStates ordinal caption)) (timers ordinal))");
      Pair orderBy = parse("(((@ assignments ordinal) . #t) ((@ assignments nextStates ordinal) . #t) ((@ timers ordinal) . #t))");

      m_context.setSecure(false);
      AssertUtil.assertEqualsIgnoreOIDs(getURL("workflow-HRC1.xml"),
         Query.createRead(workflowClass, attributes, null, orderBy,
            -1, 0, false, Query.SEC_NONE, m_context).read(), attributes);
      m_context.setSecure(true);

      req.setValue("approved", Boolean.TRUE);

      commit();

      m_context.setSecure(false);
      workflowList = Query.createRead(workflowClass, attributes, null, orderBy,
         -1, 0, false, Query.SEC_NONE, m_context).read();

      AssertUtil.assertEqualsIgnoreOIDs(getURL("workflow-HRC2.xml"), workflowList, attributes);
     
      Instance wfassignment = ((InstanceList)workflowList.getInstance(0).getValue("assignments")).getInstance(0);
     
      wfassignment.setValue("nextState", ((InstanceList)wfassignment.getValue("nextStates")).getInstance(0));
     
      wfassignment = ((InstanceList)workflowList.getInstance(0).getValue("assignments")).getInstance(1);;
      wfassignment.setValue("nextState", ((InstanceList)wfassignment.getValue("nextStates")).getInstance(0));
      m_context.setSecure(true);

      commit();

      m_context.setSecure(false);
      workflowList = Query.createRead(workflowClass, attributes, null, orderBy,
         -1, 0, false, Query.SEC_NONE, m_context).read();
      m_context.setSecure(true);

      AssertUtil.assertEqualsIgnoreOIDs(getURL("workflow-HRC3.xml"), workflowList, attributes);
     
      req.setValue("comment", "");
      workflowClass.invoke("start", new Object[]{req, "Exception", Boolean.FALSE});
     
      assertEquals("done", req.getValue("comment"));
     
      // Test startWorkflow static method
      req.setValue("comment", "");
      commit();
      m_context.setSecure(false);
      workflowClass.invoke("startWorkflow", new Object[]{"Exception", "HRRequest", "(= comment \"\")" , Boolean.FALSE});
      m_context.setSecure(true);
      assertEquals("done", req.getValue("comment"));

      req.setValue("comment", "");
      commit();
      m_context.setSecure(false);
      workflowClass.invoke("startWorkflow", new Object[]{"Exception", "HRRequest", Pair.attribute("comment").eq(""), Boolean.FALSE});
      m_context.setSecure(true);
      assertEquals("done", req.getValue("comment"));
   }

   public void testService()
   {
      Metaclass serviceClass = getMetadata().getMetaclass(Metadata.SERVICE_CLASS_NAME);
      TransferObject tobj = new TransferObject();

      tobj.setValue("ego", "!");

      Instance instance = (Instance)serviceClass.invoke("invoke", new Object[]{"Counter", tobj, null, new Integer(5)});

      assertEquals(Boolean.TRUE, instance.invoke("done"));

      TransferObject result = (TransferObject)instance.invoke("result");

      assertNotSame(tobj, result);
      assertEquals(3, result.getValueCount());
      assertEquals("~boo~", result.getValue("boo"));
      assertEquals("!+++++", result.getValue("ego"));
      assertEquals(new Integer(5), result.getValue("iterations"));

      State state = (State)instance.getValue("state");

      assertTrue(state.isFinal());
      assertNull(state.getValue(Service.OUTPUT));
      assertEquals(new Integer(5), state.getValue("i"));
      assertEquals(new Integer(5), state.getValue("count"));

      assertEquals(Instance.DELETED, instance.getState());

      commit();

      instance = (Instance)serviceClass.invoke("invoke", new Object[]{"Jump", "jmp", null});
      assertEquals("yo! jmp", instance.invoke("result"));

      commit();

      // Test Loops.service, which uses the Loop service element.
      ArrayList matrixRowList = new ArrayList(2);
      ArrayList matrixRow = new ArrayList(3);

      tobj = new TransferObject(2);
      tobj.setValue("data", matrixRowList);

      matrixRowList.add(matrixRow);
      matrixRow.add(Primitive.createInteger(8));
      matrixRow.add(Primitive.createInteger(7));
      matrixRow.add(Primitive.createInteger(6));
      matrixRowList.add(matrixRow = new ArrayList(2));
      matrixRow.add(Primitive.createInteger(5));
      matrixRow.add(Primitive.createInteger(4));

      instance = (Instance)serviceClass.invoke("invoke", new Object[]{"Loops", tobj, null, Primitive.createInteger(2)});
      result = (TransferObject)instance.invoke("result");
      assertEquals(Primitive.createInteger(64), ((List)((List)result.getValue("data")).get(0)).get(0));
      assertEquals(Primitive.createInteger(49), ((List)((List)result.getValue("data")).get(0)).get(1));
      assertEquals(Primitive.createInteger(36), ((List)((List)result.getValue("data")).get(0)).get(2));
      assertEquals(Primitive.createInteger(25), ((List)((List)result.getValue("data")).get(1)).get(0));
      assertEquals(1, ((List)((List)result.getValue("data")).get(1)).size());

      commit();
   }

   public void testUnicodeCharset()
   {
      final String FIRST_NAME = new String(new char[]{(char)26222, (char)29983});
      final String LAST_NAME = "\u9053";

      if (!m_adapter.isUnicode())
      {
         return;
      }

      Metaclass metaclass = getMetadata().getMetaclass("Principal");
      OID oid = new OID(new Object[]{Binary.parse("00000000000000000000000000000004")});
      Instance principal = m_context.findInstance(metaclass, oid);

      if (principal == null)
      {
         principal = new Instance(metaclass, m_context);
         principal.cache(oid);
      }

      Instance contact = new Instance(getMetadata().getMetaclass("Contact"), Instance.NEW, m_context);

      contact.setValue("firstName", FIRST_NAME);
      contact.setValue("lastName", LAST_NAME);
      contact.setValue("readPrincipal", principal);
      contact.setValue("version", Primitive.ZERO_INTEGER);
      commit();

      InstanceList resultList;

      resultList = Query.createRead(getMetadata().getMetaclass("Contact"), null,
         parse("(= lastName \"" + LAST_NAME.charAt(0) + "\")"), null,
         -1, 0, false, Query.SEC_NONE, m_context).read();
      assertEquals(1, resultList.size());
      assertEquals(FIRST_NAME, resultList.getInstance(0).getValue("firstName"));

      resultList = Query.createRead(getMetadata().getMetaclass("Contact"), null,
         parse("(like? firstName \"" + FIRST_NAME.charAt(0) + "*\")"), null,
         -1, 0, false, Query.SEC_NONE, m_context).read();
      assertEquals(1, resultList.size());
      assertEquals(FIRST_NAME, resultList.getInstance(0).getValue("firstName"));
      assertEquals(LAST_NAME, resultList.getInstance(0).getValue("lastName"));

      resultList = Query.createRead(getMetadata().getMetaclass("Contact"), null,
         parse("(like? firstName \"*" + FIRST_NAME.charAt(0) + "*\")"), null,
         -1, 0, false, Query.SEC_NONE, m_context).read();
      assertEquals(1, resultList.size());
      assertEquals(FIRST_NAME, resultList.getInstance(0).getValue("firstName"));
      assertEquals(LAST_NAME, resultList.getInstance(0).getValue("lastName"));

      resultList = Query.createRead(getMetadata().getMetaclass("Contact"), null,
         parse("(like? firstName \"*" + FIRST_NAME.charAt(1) + "*\")"), null,
         -1, 0, false, Query.SEC_NONE, m_context).read();
      assertEquals(1, resultList.size());
      assertEquals(FIRST_NAME, resultList.getInstance(0).getValue("firstName"));
      assertEquals(LAST_NAME, resultList.getInstance(0).getValue("lastName"));
   }

   /**
    * Tests that the finally script for a TryCatch gets executed when an unhandled
    * exception is thrown on a different branch of the Fork/Join.
    */
   public void testWorkflowTryFinallyInFork() throws Exception
   {
      Metaclass assignmentClass = getMetadata().getMetaclass("SysWorkflowAssignment");
      InstanceList assignmentList;
      Metaclass primaryClass = getMetadata().getMetaclass("WorkflowTrace");
      Instance primary = (Instance)primaryClass.invoke("new");

      // Test 1: No exception
      primary.setValue("trace", null);
      primary.setValue("throwUncaughtEx", Boolean.FALSE);
      primary.setValue("throwInnerEx", Boolean.FALSE);
      primary.invoke("goForkTry");
      assignmentList = Query.createRead(assignmentClass, null, Boolean.TRUE, null, -1, 0, false, Query.SEC_NONE, m_context).read();
      assertEquals(1, assignmentList.size());
      primary.invoke("resumeForkTry");
      commit();
      assertTrue("scrStart;scrLeft1;scrLeft2;scrLeft3;scrRight1;scrLeft4;scrRight2;scrRight3;scrInnerFinally;scrOuterFinally;scrFinish;".equals(primary.getValue("trace")) ||
                 "scrStart;scrLeft1;scrLeft2;scrLeft3;scrLeft4;scrRight1;scrRight2;scrRight3;scrInnerFinally;scrOuterFinally;scrFinish;".equals(primary.getValue("trace")));
      assignmentList = Query.createRead(assignmentClass, null, Boolean.TRUE, null, -1, 0, false, Query.SEC_NONE, m_context).read();
      assertEquals(0, assignmentList.size());


      // Test 2: Caught Exception
      primary.setValue("trace", null);
      primary.setValue("throwUncaughtEx", Boolean.FALSE);
      primary.setValue("throwInnerEx", Boolean.TRUE);
      primary.invoke("goForkTry");
      commit();
     
      // If DEBUG log mode, then State gets sorted so that left branch of Fork/Join is preferred. This prevents
      // entry to right branch's Try block. Thus the right hand (inner) Finally may not be executed.
      assertTrue("scrStart;scrLeft1;scrLeft2;scrLeft3;scrRight1;scrLeft4;scrInnerFinally;scrCatch;scrOuterFinally;scrFinish;".equals(primary.getValue("trace")) ||
                 "scrStart;scrLeft1;scrLeft2;scrLeft3;scrLeft4;scrCatch;scrOuterFinally;scrFinish;".equals(primary.getValue("trace")));
      assignmentList = Query.createRead(assignmentClass, null, Boolean.TRUE, null, -1, 0, false, Query.SEC_NONE, m_context).read();
      assertEquals(0, assignmentList.size());

      // Test 3: Uncaught Exception
      primary.setValue("trace", null);
      primary.setValue("throwUncaughtEx", Boolean.TRUE);
      primary.setValue("throwInnerEx", Boolean.FALSE);

      try
      {
         primary.invoke("goForkTry");
         fail("Exception not thrown");
      }
      catch (ScriptingException ex)
      {
        
          // If DEBUG log mode, then State gets sorted so that left branch of Fork/Join is preferred. This prevents
          // entry to right branch's Try block. Thus the right hand (inner) Finally may not be executed.
         assertTrue("scrStart;scrLeft1;scrLeft2;scrLeft3;scrRight1;scrLeft4;scrInnerFinally;scrOuterFinally;".equals(primary.getValue("trace")) ||
                    "scrStart;scrLeft1;scrLeft2;scrLeft3;scrLeft4;scrOuterFinally;".equals(primary.getValue("trace")));
         assignmentList = Query.createRead(assignmentClass, null, Boolean.TRUE, null, -1, 0, false, Query.SEC_NONE, m_context).read();
         assertEquals(0, assignmentList.size());
         rollback();
      }
   }

   /**
    * Tests the Finally part of a Workflow Try block.
    */
   public void testWorkflowTryFinally() throws Exception
   {
      Metaclass primaryClass = getMetadata().getMetaclass("WorkflowTrace");
      Instance primary = (Instance)primaryClass.invoke("new");

      primary.invoke("go");
      commit();
      assertEquals("THROWBLOCK;END_THROWBLOCK;F_INNER;F_OUTER;", primary.getValue("trace"));


      primary.setValue("throwInnerEx", Boolean.TRUE);
      primary.setValue("trace", null);
      primary.invoke("go");
      commit();
      assertEquals("THROWBLOCK;CAUGHT_INNER;F_INNER;F_OUTER;", primary.getValue("trace"));


      primary.setValue("throwInnerEx", Boolean.FALSE);
      primary.setValue("throwOuterEx", Boolean.TRUE);
      primary.setValue("trace", null);
      primary.invoke("go");
      commit();
      assertEquals("THROWBLOCK;F_INNER;CAUGHT_OUTER;F_OUTER;", primary.getValue("trace"));


      primary.setValue("throwOuterEx", Boolean.FALSE);
      primary.setValue("throwUncaughtEx", Boolean.TRUE);
      primary.setValue("trace", null);

      try
      {
         primary.invoke("go");
         fail();
      }
      catch (ScriptingException ex)
      {
         assertEquals("THROWBLOCK;F_INNER;F_OUTER;", primary.getValue("trace"));
         rollback();
      }


      // An exception whose handler re-enters a try block
      primary.setValue("throwUncaughtEx", Boolean.FALSE);
      primary.setValue("throwOuterEx", Boolean.FALSE);
      primary.setValue("throwOuter2Ex", Boolean.TRUE);
      primary.setValue("trace", null);
      primary.invoke("go");
      commit();
      assertEquals("THROWBLOCK;F_INNER;CAUGHT_OUTER2;RUNNING_IN_OUTER_TRY;END_IN_OUTER_TRY;F_OUTER;", primary.getValue("trace"));
   }

   /**
    * Tests that a Workflow Semaphore handles normal termination and exceptions
    * appropriately.
    */
   public void testWorkflowSemaphoreFlowControl() throws Exception
   {
      Metaclass workflowClass = getMetadata().getMetaclass(Metadata.WORKFLOW_CLASS_NAME);
      Metaclass primaryClass = getMetadata().getMetaclass("WorkflowTrace");
      Metaclass schedulerClass = getMetadata().getMetaclass("TestSysWorkflowSchedulerBatchJob");
      Instance primary = (Instance)primaryClass.invoke("new");
      InstanceList workflowList, assignmentList;
      Instance workflow, assignment;

      // Set scheduler time outside of the queue time window
      schedulerClass.setValue("time", Primitive.toTimestamp(Primitive.createInteger(15000)));


      // Test: no exception
      primary.setValue("trace", null);
      primary.setValue("throwOuterEx", Boolean.FALSE);
      primary.setValue("throwUncaughtEx", Boolean.FALSE);
      primary.invoke("goSemaphore");
      assertEquals("BEFORE;ASSIGNMENT_CREATE;", primary.getValue("trace"));
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Boolean.TRUE, assignment.getValue("semaphore"));
      assertEquals(Primitive.ZERO_INTEGER, assignment.getValue("status"));
      commit();
      assignment.invoke("schedulerRun");
      assertEquals(Instance.DELETED, assignment.getState());
      assertEquals(Instance.DELETED, workflow.getState());
      assertEquals("BEFORE;ASSIGNMENT_CREATE;EXECUTE_SEMAPHORE;END_SEMAPHORE;ASSIGNMENT_DELETE;AFTER;", primary.getValue("trace"));
      commit();
     


      // Test: caught exception
      primary.setValue("throwOuterEx", Boolean.TRUE);
      primary.setValue("throwUncaughtEx", Boolean.FALSE);
      primary.setValue("trace", null);
      primary.invoke("goSemaphore");
      assertEquals("BEFORE;ASSIGNMENT_CREATE;", primary.getValue("trace"));
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Boolean.TRUE, assignment.getValue("semaphore"));
      assertEquals(Primitive.ZERO_INTEGER, assignment.getValue("status"));
      commit();
      assignment.invoke("schedulerRun");
      assertEquals(Instance.DELETED, assignment.getState());
      assertEquals(Instance.DELETED, workflow.getState());
      assertEquals(Primitive.createInteger(2), assignment.getValue("status"));
      assertEquals("BEFORE;ASSIGNMENT_CREATE;EXECUTE_SEMAPHORE;ASSIGNMENT_DELETE;CAUGHT_OUTER;", primary.getValue("trace"));
      commit();


      // Test: uncaught exception
      primary.setValue("throwOuterEx", Boolean.FALSE);
      primary.setValue("throwUncaughtEx", Boolean.TRUE);
      primary.setValue("trace", null);
      primary.invoke("goSemaphore");
      assertEquals("BEFORE;ASSIGNMENT_CREATE;", primary.getValue("trace"));
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Boolean.TRUE, assignment.getValue("semaphore"));
      assertEquals(Primitive.ZERO_INTEGER, assignment.getValue("status"));
      commit();

      try
      {
         assignment.invoke("schedulerRun");
         fail();
      }
      catch (ScriptingException ex)
      {
         assertEquals(Instance.DELETED, assignment.getState());
         assertEquals(Primitive.createInteger(2), assignment.getValue("status"));
         assertEquals("BEFORE;ASSIGNMENT_CREATE;EXECUTE_SEMAPHORE;ASSIGNMENT_DELETE;", primary.getValue("trace"));
         workflow.invoke("delete");
         commit();
      }
   }

   public void testWorkflowSemaphoresInFork() throws Exception
   {
      Metaclass workflowClass = getMetadata().getMetaclass(Metadata.WORKFLOW_CLASS_NAME);
      Metaclass primaryClass = getMetadata().getMetaclass("WorkflowTrace");
      Metaclass schedulerClass = getMetadata().getMetaclass("TestSysWorkflowSchedulerBatchJob");
      Instance primary = (Instance)primaryClass.invoke("new");
      InstanceList workflowList, assignmentList;
      Instance workflow, assignment1, assignment2;
      List invocationList;

      // Initialization
      primary.setValue("trace", null);
      primary.setValue("throwOuterEx", Boolean.FALSE);
      primary.setValue("throwUncaughtEx", Boolean.FALSE);


      /*
       * Test: Two semaphores in a fork.
       */
      schedulerClass.setValue("time", Primitive.toTimestamp(Primitive.createInteger(1000)));
      primary.setValue("trace", null);
      primary.setValue("throwInnerEx", Boolean.FALSE);
      primary.setValue("throwOuterEx", Boolean.FALSE);
      primary.invoke("goForkSemaphore");
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(2, assignmentList.size());
      assignment1 = assignmentList.getInstance(0);
      assignment2 = assignmentList.getInstance(1);

      if (assignment1.getValue("caption").equals("semaphoreRight:2"))
      {
         Instance temp = assignment1;

         assignment1 = assignment2;
         assignment2 = temp;
      }

      assertEquals("semaphoreLeftCaption", assignment1.getValue("caption"));
      assertEquals("semaphoreRight:2", assignment2.getValue("caption"));
      assertTrue(
         (assignment1.getValue("status").equals(Primitive.ONE_INTEGER) &&
         assignment2.getValue("status").equals(Primitive.ZERO_INTEGER)) ||
         (assignment1.getValue("status").equals(Primitive.ZERO_INTEGER) &&
         assignment2.getValue("status").equals(Primitive.ONE_INTEGER))
      );
      assertEquals(Boolean.TRUE, assignment1.getValue("semaphore"));
      assertEquals(Primitive.ONE_INTEGER, assignment1.getValue("status"));
      assertEquals(Boolean.TRUE, assignment2.getValue("semaphore"));
      assertEquals(Primitive.ZERO_INTEGER, assignment2.getValue("status"));
      invocationList = (List)schedulerClass.getValue("asyncInvokeList");
      assertEquals(1, invocationList.size());
      assertEquals(assignment1.getOID(), invocationList.get(0));

      // Simulate run of left semaphore
      assignment1.invoke("schedulerRun");
      assertEquals(Instance.DELETED, assignment1.getState());
      assertEquals(Instance.DIRTY, workflow.getState());
      assertEquals("scrStart;scrLeft1;", primary.getValue("trace"));

      // Left semaphore done, right semaphore should be running
      assertEquals(Boolean.TRUE, assignment2.getValue("semaphore"));
      assertEquals(Primitive.ONE_INTEGER, assignment2.getValue("status"));
      assertEquals(2, invocationList.size());
      assertEquals(assignment2.getOID(), invocationList.get(1));

      // Simulate run of right semaphore
      assignment2.invoke("schedulerRun");
      assertEquals(Instance.DELETED, assignment2.getState());
      assertEquals(Instance.DELETED, workflow.getState());
      assertEquals("scrStart;scrLeft1;scrRight1;scrFinish;", primary.getValue("trace"));
      commit();



      /*
       * Test: Two semaphores in a fork, left Semaphore throws exception
       */
      ((List)schedulerClass.getValue("asyncInvokeList")).clear();
      schedulerClass.setValue("time", Primitive.toTimestamp(Primitive.createInteger(1000)));
      primary.setValue("trace", null);
      primary.setValue("throwInnerEx", Boolean.TRUE);
      primary.setValue("throwOuterEx", Boolean.FALSE);
      primary.invoke("goForkSemaphore");
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(2, assignmentList.size());
      assignment1 = assignmentList.getInstance(0);
      assignment2 = assignmentList.getInstance(1);

      if (assignment1.getValue("caption").equals("semaphoreRight:2"))
      {
         Instance temp = assignment1;

         assignment1 = assignment2;
         assignment2 = temp;
      }

      assertEquals("semaphoreLeftCaption", assignment1.getValue("caption"));
      assertEquals("semaphoreRight:2", assignment2.getValue("caption"));
      assertTrue(
         (assignment1.getValue("status").equals(Primitive.ONE_INTEGER) &&
         assignment2.getValue("status").equals(Primitive.ZERO_INTEGER)) ||
         (assignment1.getValue("status").equals(Primitive.ZERO_INTEGER) &&
         assignment2.getValue("status").equals(Primitive.ONE_INTEGER))
      );
      assertEquals(Boolean.TRUE, assignment1.getValue("semaphore"));
      assertEquals(Primitive.ONE_INTEGER, assignment1.getValue("status"));
      assertEquals(Boolean.TRUE, assignment2.getValue("semaphore"));
      assertEquals(Primitive.ZERO_INTEGER, assignment2.getValue("status"));
      invocationList = (List)schedulerClass.getValue("asyncInvokeList");
      assertEquals(1, invocationList.size());
      assertEquals(assignment1.getOID(), invocationList.get(0));

      // Simulate run of left semaphore
      try
      {
         assignment1.invoke("schedulerRun");
         fail("No exception thrown");
      }
      catch (RuntimeException ex)
      {
         assertEquals(Instance.DELETED, assignment1.getState());
         assertEquals(Instance.DELETED, assignment2.getState());
         assertEquals("scrStart;scrLeft1;", primary.getValue("trace"));
         workflow.invoke("delete");
      }

      commit();



      /*
       * Test: Two semaphores in a fork, right (2nd) Semaphore throws exception
       */
      ((List)schedulerClass.getValue("asyncInvokeList")).clear();
      schedulerClass.setValue("time", Primitive.toTimestamp(Primitive.createInteger(1000)));
      primary.setValue("trace", null);
      primary.setValue("throwInnerEx", Boolean.FALSE);
      primary.setValue("throwOuterEx", Boolean.TRUE);
      primary.invoke("goForkSemaphore");
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(2, assignmentList.size());
      assignment1 = assignmentList.getInstance(0);
      assignment2 = assignmentList.getInstance(1);

      if (assignment1.getValue("caption").equals("semaphoreRight:2"))
      {
         Instance temp = assignment1;

         assignment1 = assignment2;
         assignment2 = temp;
      }

      assertEquals("semaphoreLeftCaption", assignment1.getValue("caption"));
      assertEquals("semaphoreRight:2", assignment2.getValue("caption"));
      assertTrue(
         (assignment1.getValue("status").equals(Primitive.ONE_INTEGER) &&
         assignment2.getValue("status").equals(Primitive.ZERO_INTEGER)) ||
         (assignment1.getValue("status").equals(Primitive.ZERO_INTEGER) &&
         assignment2.getValue("status").equals(Primitive.ONE_INTEGER))
      );
      assertEquals(Boolean.TRUE, assignment1.getValue("semaphore"));
      assertEquals(Primitive.ONE_INTEGER, assignment1.getValue("status"));
      assertEquals(Boolean.TRUE, assignment2.getValue("semaphore"));
      assertEquals(Primitive.ZERO_INTEGER, assignment2.getValue("status"));
      invocationList = (List)schedulerClass.getValue("asyncInvokeList");
      assertEquals(1, invocationList.size());
      assertEquals(assignment1.getOID(), invocationList.get(0));

      // Simulate run of left semaphore
      assignment1.invoke("schedulerRun");
      assertEquals(Instance.DELETED, assignment1.getState());
      assertEquals(Instance.DIRTY, workflow.getState());
      assertEquals("scrStart;scrLeft1;", primary.getValue("trace"));

      // Left semaphore done, right semaphore should be running
      assertEquals(Boolean.TRUE, assignment2.getValue("semaphore"));
      assertEquals(Primitive.ONE_INTEGER, assignment2.getValue("status"));
      assertEquals(2, invocationList.size());
      assertEquals(assignment2.getOID(), invocationList.get(1));

      // Simulate run of right semaphore
      try
      {
         assignment2.invoke("schedulerRun");
         fail("No exception thrown");
      }
      catch (RuntimeException ex)
      {
         assertEquals(Instance.DELETED, assignment1.getState());
         assertEquals(Instance.DELETED, assignment2.getState());
         assertEquals("scrStart;scrLeft1;scrRight1;", primary.getValue("trace"));
         workflow.invoke("delete");
      }
   }

   /**
    * Tests the scheduling of a Workflow Semaphore.
    */
   public void testWorkflowSemaphoreScheduler() throws Exception
   {
      Metaclass workflowClass = getMetadata().getMetaclass(Metadata.WORKFLOW_CLASS_NAME);
      Metaclass primaryClass = getMetadata().getMetaclass("WorkflowTrace");
      Metaclass schedulerClass = getMetadata().getMetaclass("TestSysWorkflowSchedulerBatchJob");
      Instance primary = (Instance)primaryClass.invoke("new");
      Instance primary2 = (Instance)primaryClass.invoke("new");
      Instance primary3 = (Instance)primaryClass.invoke("new");
      Instance primary4 = (Instance)primaryClass.invoke("new");
      Instance scheduler = (Instance)schedulerClass.getValue("instance");
      InstanceList workflowList, assignmentList;
      Instance workflow, assignment;
      List invocationList;

      // Initialization
      primary.setValue("trace", null);
      primary.setValue("throwOuterEx", Boolean.FALSE);
      primary.setValue("throwUncaughtEx", Boolean.FALSE);
      primary2.setValue("trace", null);
      primary2.setValue("throwOuterEx", Boolean.FALSE);
      primary2.setValue("throwUncaughtEx", Boolean.FALSE);
      primary3.setValue("trace", null);
      primary3.setValue("throwOuterEx", Boolean.FALSE);
      primary3.setValue("throwUncaughtEx", Boolean.FALSE);
      primary4.setValue("trace", null);
      primary4.setValue("throwOuterEx", Boolean.FALSE);
      primary4.setValue("throwUncaughtEx", Boolean.FALSE);


      /*
       * Test: One job, inside time window
       */
      schedulerClass.setValue("time", Primitive.toTimestamp(Primitive.createInteger(1000)));
      primary.setValue("trace", null);
      primary.setValue("queueName", "Semaphore");
      primary.invoke("goSemaphore");
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Boolean.TRUE, assignment.getValue("semaphore"));
      assertEquals(Primitive.ONE_INTEGER, assignment.getValue("status"));
      invocationList = (List)schedulerClass.getValue("asyncInvokeList");
      assertEquals(1, invocationList.size());
      assertEquals(assignment.getOID(), invocationList.get(0));

      // Simulate run of Job
      assignment.invoke("schedulerRun");
      assertEquals(Instance.DELETED, assignment.getState());
      assertEquals(Instance.DELETED, workflow.getState());
      assertEquals("BEFORE;ASSIGNMENT_CREATE;EXECUTE_SEMAPHORE;END_SEMAPHORE;ASSIGNMENT_DELETE;AFTER;", primary.getValue("trace"));
      commit();


      /*
       * Test: Two jobs, queue concurrency = 1
       */
      ((List)schedulerClass.getValue("asyncInvokeList")).clear();
      primary.setValue("queueName", "SemaphoreLowConcurrency");
      primary2.setValue("queueName", "SemaphoreLowConcurrency");
      primary.setValue("trace", null);
      primary2.setValue("trace", null);
      primary.invoke("goSemaphore");
      Thread.sleep(50);
      primary2.invoke("goSemaphore");

      // Job 2: NOT RUNNING
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary2});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Primitive.ZERO_INTEGER, assignment.getValue("status"));
      assertEquals("BEFORE;ASSIGNMENT_CREATE;", primary2.getValue("trace"));

      // Job 1: RUNNING
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Primitive.ONE_INTEGER, assignment.getValue("status"));
      invocationList = (List)schedulerClass.getValue("asyncInvokeList");
      assertEquals(1, invocationList.size());
      assertEquals(assignment.getOID(), invocationList.get(0));

      // Simulate run of Job 1
      assignment.invoke("schedulerRun");
      assertEquals(Instance.DELETED, assignment.getState());
      assertEquals(Instance.DELETED, workflow.getState());
      assertEquals("BEFORE;ASSIGNMENT_CREATE;EXECUTE_SEMAPHORE;END_SEMAPHORE;ASSIGNMENT_DELETE;AFTER;", primary.getValue("trace"));

      // Job 1 finished, Job 2 should have been run
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary2});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Primitive.ONE_INTEGER, assignment.getValue("status"));
      invocationList = (List)schedulerClass.getValue("asyncInvokeList");
      assertEquals(2, invocationList.size());
      assertEquals(assignment.getOID(), invocationList.get(1));

      // Simulate run of Job 2
      assignment.invoke("schedulerRun");
      assertEquals(Instance.DELETED, assignment.getState());
      assertEquals(Instance.DELETED, workflow.getState());
      assertEquals("BEFORE;ASSIGNMENT_CREATE;EXECUTE_SEMAPHORE;END_SEMAPHORE;ASSIGNMENT_DELETE;AFTER;", primary2.getValue("trace"));
      commit();


      /*
       * Test: Four jobs. System concurrency: 2.
       * Job 1: Low concurrency queue    RUN...FINISH
       * Job 2: Low concurrency queue    WAIT       |           RUN...FINISH
       * Job 3: High concurrency queue   RUN........|.....FINISH^
       * Job 4: High concurrency queue   WAIT       RUN...........FINISH
       */
      ((List)schedulerClass.getValue("asyncInvokeList")).clear();
      primary.setValue("queueName", "SemaphoreLowConcurrency");
      primary2.setValue("queueName", "SemaphoreLowConcurrency");
      primary3.setValue("queueName", "Semaphore");
      primary4.setValue("queueName", "Semaphore");
      primary.setValue("trace", null);
      primary2.setValue("trace", null);
      primary3.setValue("trace", null);
      primary4.setValue("trace", null);
      primary.invoke("goSemaphore");
      Thread.sleep(50);
      primary2.invoke("goSemaphore");
      Thread.sleep(50);
      primary3.invoke("goSemaphore");
      Thread.sleep(50);
      primary4.invoke("goSemaphore");


      invocationList = (List)schedulerClass.getValue("asyncInvokeList");
      assertEquals(2, invocationList.size());
     

      // Job 4: NOT RUNNING
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary4});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Primitive.ZERO_INTEGER, assignment.getValue("status"));
      assertEquals("BEFORE;ASSIGNMENT_CREATE;", primary4.getValue("trace"));

      // Job 2: NOT RUNNING
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary2});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Primitive.ZERO_INTEGER, assignment.getValue("status"));
      assertEquals("BEFORE;ASSIGNMENT_CREATE;", primary2.getValue("trace"));

      // Job 3: RUNNING
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary3});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Primitive.ONE_INTEGER, assignment.getValue("status"));
      assertEquals(assignment.getOID(), invocationList.get(1));

      // Job 1: RUNNING
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Primitive.ONE_INTEGER, assignment.getValue("status"));
      assertEquals(assignment.getOID(), invocationList.get(0));

      // Simulate run/finish of Job 1
      assignment.invoke("schedulerRun");
      assertEquals(Instance.DELETED, assignment.getState());
      assertEquals(Instance.DELETED, workflow.getState());
      assertEquals("BEFORE;ASSIGNMENT_CREATE;EXECUTE_SEMAPHORE;END_SEMAPHORE;ASSIGNMENT_DELETE;AFTER;", primary.getValue("trace"));
      commit();

      // Job 2 or 4 could run: 4 runs because its queue has higher priority
      assertEquals(3, invocationList.size());

      // Job 4: RUNNING
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary4});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Primitive.ONE_INTEGER, assignment.getValue("status"));
      assertEquals(assignment.getOID(), invocationList.get(2));

      // Job 2: NOT RUNNING
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary2});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Primitive.ZERO_INTEGER, assignment.getValue("status"));
      assertEquals("BEFORE;ASSIGNMENT_CREATE;", primary2.getValue("trace"));
     
      // Job 3: RUNNING
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary3});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Primitive.ONE_INTEGER, assignment.getValue("status"));
      assertEquals(assignment.getOID(), invocationList.get(1));

      // Simulate run/finish of Job 3
      assignment.invoke("schedulerRun");
      assertEquals(Instance.DELETED, assignment.getState());
      assertEquals(Instance.DELETED, workflow.getState());
      assertEquals("BEFORE;ASSIGNMENT_CREATE;EXECUTE_SEMAPHORE;END_SEMAPHORE;ASSIGNMENT_DELETE;AFTER;", primary3.getValue("trace"));
      commit();

      // Job 2 runs
      assertEquals(4, invocationList.size());

      // Job 2: RUNNING
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary2});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Primitive.ONE_INTEGER, assignment.getValue("status"));
      assertEquals(assignment.getOID(), invocationList.get(3));
     
      // Job 4: RUNNING
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary4});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Primitive.ONE_INTEGER, assignment.getValue("status"));
      assertEquals(assignment.getOID(), invocationList.get(2));

      // Simulate run/finish of Job 4
      assignment.invoke("schedulerRun");
      assertEquals(Instance.DELETED, assignment.getState());
      assertEquals(Instance.DELETED, workflow.getState());
      assertEquals("BEFORE;ASSIGNMENT_CREATE;EXECUTE_SEMAPHORE;END_SEMAPHORE;ASSIGNMENT_DELETE;AFTER;", primary4.getValue("trace"));
      assertEquals(4, invocationList.size());

      // Job 2: RUNNING
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary2});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Primitive.ONE_INTEGER, assignment.getValue("status"));
      assertEquals(assignment.getOID(), invocationList.get(3));

      // Simulate run/finish of Job 2
      assignment.invoke("schedulerRun");
      assertEquals(Instance.DELETED, assignment.getState());
      assertEquals(Instance.DELETED, workflow.getState());
      assertEquals("BEFORE;ASSIGNMENT_CREATE;EXECUTE_SEMAPHORE;END_SEMAPHORE;ASSIGNMENT_DELETE;AFTER;", primary2.getValue("trace"));
      assertEquals(4, invocationList.size());


      /*
       * Test: One job, outside of time window.
       */
      ((List)schedulerClass.getValue("asyncInvokeList")).clear();
      schedulerClass.setValue("time", Primitive.toTimestamp(Primitive.createInteger(15000)));
      primary.setValue("trace", null);
      primary.setValue("queueName", "Semaphore");
      primary.invoke("goSemaphore");
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Boolean.TRUE, assignment.getValue("semaphore"));
      assertEquals(Primitive.ZERO_INTEGER, assignment.getValue("status"));
      invocationList = (List)schedulerClass.getValue("asyncInvokeList");
      assertEquals(0, invocationList.size());

      // Beginning of next time window is at 60 seconds.
      assertEquals(Primitive.toTimestamp(Primitive.createLong(60000)), scheduler.getValue("start"));
      assertEquals(Primitive.createLong(45000), scheduler.getValue("period"));

      // Simulate timer tick at beginning of next window
      schedulerClass.setValue("time", Primitive.toTimestamp(Primitive.createInteger(60000)));
      m_context.setSecure(false);
      scheduler.invoke("run");
      m_context.setSecure(true);

      assertEquals(Primitive.ONE_INTEGER, assignment.getValue("status"));
      assertEquals(1, invocationList.size());
      assertEquals(assignment.getOID(), invocationList.get(0));

      // Simulate run of Job
      assignment.invoke("schedulerRun");
      assertEquals(Instance.DELETED, assignment.getState());
      assertEquals(Instance.DELETED, workflow.getState());
      assertEquals("BEFORE;ASSIGNMENT_CREATE;EXECUTE_SEMAPHORE;END_SEMAPHORE;ASSIGNMENT_DELETE;AFTER;", primary.getValue("trace"));
      commit();
   }

   /**
    * Tests the throttling of a Workflow Semaphore.
    */
   public void testWorkflowSemaphoreThrottling() throws Exception
   {
      Metaclass workflowClass = getMetadata().getMetaclass(Metadata.WORKFLOW_CLASS_NAME);
      Metaclass primaryClass = getMetadata().getMetaclass("WorkflowTrace");
      Metaclass schedulerClass = getMetadata().getMetaclass("TestSysWorkflowSchedulerBatchJob");
      Metaclass throttleCounterBatchJobClass = getMetadata().getMetaclass("SysQueueThrottleCounterBatchJob");
      Instance primary = (Instance)primaryClass.invoke("new");
      Instance primary2 = (Instance)primaryClass.invoke("new");
      Instance primary3 = (Instance)primaryClass.invoke("new");
      Instance scheduler = (Instance)schedulerClass.getValue("instance");
      Instance throttleCounterBatchJob = (Instance)throttleCounterBatchJobClass.getValue("instance");
      InstanceList workflowList, assignmentList;
      Instance workflow, assignment;
      List invocationList;

      // Initialization
      primary.setValue("trace", null);
      primary.setValue("throwOuterEx", Boolean.FALSE);
      primary.setValue("throwUncaughtEx", Boolean.FALSE);
      primary2.setValue("trace", null);
      primary2.setValue("throwOuterEx", Boolean.FALSE);
      primary2.setValue("throwUncaughtEx", Boolean.FALSE);
      primary3.setValue("trace", null);
      primary3.setValue("throwOuterEx", Boolean.FALSE);
      primary3.setValue("throwUncaughtEx", Boolean.FALSE);


      /*
       * Test: 3 jobs:
       * Job 1: started at 0 seconds, runs immediately (in window)
       * Job 2: started at 8 seconds, runs immediately (in window)
       * Job 3: started at 9 seconds, doesn't run (throttle exceeded)
       *
       * @ 60 seconds: Timer tick, job counter reset, job 3 resumed.
       */
      schedulerClass.setValue("time", Primitive.toTimestamp(Primitive.ZERO_INTEGER));
      ((List)schedulerClass.getValue("asyncInvokeList")).clear();
      primary.setValue("queueName", "SemaphoreThrottled");
      primary2.setValue("queueName", "SemaphoreThrottled");
      primary3.setValue("queueName", "SemaphoreThrottled");
      primary.setValue("trace", null);
      primary2.setValue("trace", null);
      primary3.setValue("trace", null);
      primary.invoke("goSemaphore");

      invocationList = (List)schedulerClass.getValue("asyncInvokeList");
      assertEquals(1, invocationList.size());

      // Job 1: RUNNING
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Primitive.ONE_INTEGER, assignment.getValue("status"));
      assertEquals(assignment.getOID(), invocationList.get(0));

      // Simulate run/finish of Job 1
      assignment.invoke("schedulerRun");
      assertEquals(Instance.DELETED, assignment.getState());
      assertEquals(Instance.DELETED, workflow.getState());
      assertEquals("BEFORE;ASSIGNMENT_CREATE;EXECUTE_SEMAPHORE;END_SEMAPHORE;ASSIGNMENT_DELETE;AFTER;", primary.getValue("trace"));
      commit();


      // @ 8 seconds
      schedulerClass.setValue("time", Primitive.toTimestamp(Primitive.createInteger(8000)));
      primary2.invoke("goSemaphore");

      // Job 2: RUNNING
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary2});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Primitive.ONE_INTEGER, assignment.getValue("status"));
      assertEquals(2, invocationList.size());
      assertEquals(assignment.getOID(), invocationList.get(1));

      // Simulate run/finish of Job 2
      assignment.invoke("schedulerRun");
      assertEquals(Instance.DELETED, assignment.getState());
      assertEquals(Instance.DELETED, workflow.getState());
      assertEquals("BEFORE;ASSIGNMENT_CREATE;EXECUTE_SEMAPHORE;END_SEMAPHORE;ASSIGNMENT_DELETE;AFTER;", primary2.getValue("trace"));
      commit();

      // @ 9 seconds
      schedulerClass.setValue("time", Primitive.toTimestamp(Primitive.createInteger(9000)));
      primary3.invoke("goSemaphore");

      // Job 3: NOT RUNNING
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary3});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Primitive.ZERO_INTEGER, assignment.getValue("status"));
      assertEquals("BEFORE;ASSIGNMENT_CREATE;", primary3.getValue("trace"));
      assertEquals(2, invocationList.size());


      // @ 60 seconds
      m_context.setSecure(false);
      throttleCounterBatchJob.invoke("run");
      m_context.setSecure(true);
      schedulerClass.setValue("time", Primitive.toTimestamp(Primitive.createInteger(60000)));
      scheduler.invoke("schedule");
      assertEquals(3, invocationList.size());
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary3});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Primitive.ONE_INTEGER, assignment.getValue("status"));
      assertEquals(assignment.getOID(), invocationList.get(2));

      // Simulate run/finish of Job 3
      assignment.invoke("schedulerRun");
      assertEquals(Instance.DELETED, assignment.getState());
      assertEquals(Instance.DELETED, workflow.getState());
      assertEquals("BEFORE;ASSIGNMENT_CREATE;EXECUTE_SEMAPHORE;END_SEMAPHORE;ASSIGNMENT_DELETE;AFTER;", primary3.getValue("trace"));
      commit();
   }

   /**
    * Tests the Workflow Queue ProcessEvent macro (which is implemented
    * with a Semaphore)
    */
   public void testWorkflowProcessEvent() throws Exception
   {
      Metaclass workflowClass = getMetadata().getMetaclass(Metadata.WORKFLOW_CLASS_NAME);
      Metaclass primaryClass = getMetadata().getMetaclass("WorkflowTrace");
      Metaclass schedulerClass = getMetadata().getMetaclass("TestSysWorkflowSchedulerBatchJob");
      Instance primary = (Instance)primaryClass.invoke("new");
      InstanceList workflowList, assignmentList, timerList;
      Instance workflow, assignment, timeoutTimer;
      List invocationList;

      // Initialization
      primary.setValue("trace", null);
      primary.setValue("throwOuterEx", Boolean.FALSE);
      primary.setValue("throwUncaughtEx", Boolean.FALSE);

      /*
       * Test: One job, inside time window.
       */
      schedulerClass.setValue("time", Primitive.toTimestamp(Primitive.createInteger(1000)));
      primary.setValue("trace", null);
      primary.setValue("queueName", "Semaphore");
      timerList = getNonInitialTimers();
      assertEquals(0, timerList.size());
      primary.invoke("goProcessEvent");
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Boolean.TRUE, assignment.getValue("semaphore"));
      assertEquals(Primitive.ONE_INTEGER, assignment.getValue("status"));
      invocationList = (List)schedulerClass.getValue("asyncInvokeList");
      assertEquals(1, invocationList.size());
      assertEquals(assignment.getOID(), invocationList.get(0));
      assertEquals("BEGIN;", primary.getValue("trace"));
      timerList = getNonInitialTimers();
      assertEquals(0, timerList.size());


      // Simulate run
      assignment.invoke("schedulerRun");
      assertEquals(Instance.DELETED, assignment.getState());
      assertEquals(Instance.DELETED, workflow.getState());
      assertEquals("BEGIN;PROCESS_EVENT;PROCESS_EVENT_DONE;END;", primary.getValue("trace"));
      timerList = getNonInitialTimers();
      assertEquals(0, timerList.size());
      commit();


      /*
       * Test 2: One job, outside time window, canceled by class event.
       */
      schedulerClass.setValue("time", Primitive.toTimestamp(Primitive.createInteger(30000*3)));
      ((List)schedulerClass.getValue("asyncInvokeList")).clear();
      primary.setValue("trace", null);
      primary.setValue("queueName", "Semaphore");
      timerList = getNonInitialTimers();
      assertEquals(0, timerList.size());
      primary.invoke("goProcessEvent");
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Boolean.TRUE, assignment.getValue("semaphore"));
      assertEquals(Primitive.ZERO_INTEGER, assignment.getValue("status"));
      invocationList = (List)schedulerClass.getValue("asyncInvokeList");
      assertEquals(0, invocationList.size());
      assertEquals("BEGIN;", primary.getValue("trace"));

      // Scheduler runs
      commit();
      timerList = getNonInitialTimers();
      assertEquals(2, timerList.size())// Scheduler timer and the TimerEvent timer
      assertEquals(Instance.CLEAN, assignment.getState());
      assertEquals(Instance.CLEAN, workflow.getState());
      assertEquals("BEGIN;", primary.getValue("trace"));

      // Cancel event
      primary.invoke("resumeProcessEvent");
      assertEquals(Instance.DELETED, assignment.getState());
      assertEquals(Instance.DELETED, workflow.getState());
      assertEquals("BEGIN;CLASS_EVENT;END;", primary.getValue("trace"));
      timerList = getNonInitialTimers();
      assertEquals(1, timerList.size())// The scheduler timer remains
      timerList.getInstance(0).delete();
      commit();


      /*
       * Test 3: One job, outside time window, times out by Timer Event.
       */
      schedulerClass.setValue("time", Primitive.toTimestamp(Primitive.createInteger(30000*1)));
      ((List)schedulerClass.getValue("asyncInvokeList")).clear();
      primary.setValue("trace", null);
      primary.setValue("queueName", "Semaphore");
      timerList = getNonInitialTimers();
      assertEquals(0, timerList.size());
      primary.invoke("goProcessEvent");
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{primary});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assignmentList = (InstanceList)workflow.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Boolean.TRUE, assignment.getValue("semaphore"));
      assertEquals(Primitive.ZERO_INTEGER, assignment.getValue("status"));
      invocationList = (List)schedulerClass.getValue("asyncInvokeList");
      assertEquals(0, invocationList.size());
      assertEquals("BEGIN;", primary.getValue("trace"));

      // Scheduler runs
      commit();
      timerList = getNonInitialTimers();
      assertEquals(2, timerList.size())// Scheduler timer and the TimerEvent timer
      assertEquals(Instance.CLEAN, assignment.getState());
      assertEquals(Instance.CLEAN, workflow.getState());
      assertEquals("BEGIN;", primary.getValue("trace"));

      // Timeout event
      timeoutTimer = getTimeoutTimer(workflow);
      assertNotNull(timeoutTimer);
      workflow.invoke("timeout", new Object[] {
         timeoutTimer.getValue("ordinal")
      });
      assertEquals(Instance.DELETED, assignment.getState());
      assertEquals(Instance.DELETED, workflow.getState());
      assertEquals("BEGIN;TIMED_OUT;END;", primary.getValue("trace"));
      timeoutTimer.delete()// Simulated timeout must remove timer manually
      commit();
      timerList = getNonInitialTimers();
      assertEquals(1, timerList.size())// The scheduler timer remains
      timerList.getInstance(0).delete();
      commit();
   }

   protected Instance getTimeoutTimer(Instance workflow) throws Exception
   {
      InstanceList timerList = getNonInitialTimers();
      int i = 0;

      while (i < timerList.size())
      {
         Instance timer = timerList.getInstance(i);

         if (timer.getValue("workflow") == workflow)
         {
            return timer;
         }

         i++;
      }

      return null;
   }

   protected InstanceList getNonInitialTimers() throws Exception
   {
      Metaclass timerClass = getMetadata().getMetaclass("SysTimer");
      InstanceList timerList = Query.createRead(timerClass, null, Boolean.TRUE, null, -1, 0, false, Query.SEC_NONE, m_context).read();
      int i = 0;

      while (i < timerList.size())
      {
         Instance timer = timerList.getInstance(i);
         OID timerOID = timer.getOID();

         assertEquals(1, timerOID.getCount());

         if (((Binary)timerOID.getValue(0)).toString().equals("00000000000000000000000000000001"))
         {
            timerList.remove(i);
         }
         else
         {
            i++;
         }
      }

      return timerList;
   }

   /**
    * @param bUnicode if the substring is a check for a unicode DB.
    * @return The substring to search for that garantees that a Unicode flag check guard is present
    *         to prevent upgrades of DBs who's state does not match connection's Unicode flag.
    */
   protected abstract String getUnicodeCheckGuard(boolean bUnicode);

   /**
    * Tests that a Service Semaphore handles normal termination and exceptions
    * appropriately.
    */
   public void testServiceSemaphoreFlowControl() throws Exception
   {
      Metaclass serviceClass = getMetadata().getMetaclass(Metadata.SERVICE_CLASS_NAME);
      Metaclass schedulerClass = getMetadata().getMetaclass("TestSysWorkflowSchedulerBatchJob");
      TransferObject tobj = new TransferObject();
      TransferObject result;
      Instance instance, assignment;
      InstanceList assignmentList;

      // Set scheduler time outside of the queue time window
      schedulerClass.setValue("time", Primitive.toTimestamp(Primitive.createInteger(15000)));


      // Test: no exception
      tobj.setValue("trace", "");
      tobj.setValue("throwOuterEx", Boolean.FALSE);
      tobj.setValue("throwUncaughtEx", Boolean.FALSE);
      instance = (Instance)serviceClass.invoke("invoke", new Object[]{"Semaphore", tobj, null});
      assertEquals(Boolean.FALSE, instance.invoke("done"));
      assertEquals("BEFORE;", tobj.getValue("trace"));
      assignmentList = (InstanceList)instance.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Boolean.TRUE, assignment.getValue("semaphore"));
      assertEquals(Primitive.ZERO_INTEGER, assignment.getValue("status"));
      commit();
      assignment.invoke("schedulerRun");
      assertEquals(Boolean.TRUE, instance.invoke("done"));
      assertEquals(Instance.DELETED, assignment.getState());
      result = (TransferObject)instance.invoke("result");
      assertEquals("BEFORE;EXECUTE_SEMAPHORE;END_SEMAPHORE;AFTER;", result.getValue("trace"));
      instance.delete();
      commit();


      // Test: caught exception
      tobj.setValue("trace", "");
      tobj.setValue("throwOuterEx", Boolean.TRUE);
      tobj.setValue("throwUncaughtEx", Boolean.FALSE);
      instance = (Instance)serviceClass.invoke("invoke", new Object[]{"Semaphore", tobj, null});
      assertEquals(Boolean.FALSE, instance.invoke("done"));
      assertEquals("BEFORE;", tobj.getValue("trace"));
      assignmentList = (InstanceList)instance.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Boolean.TRUE, assignment.getValue("semaphore"));
      assertEquals(Primitive.ZERO_INTEGER, assignment.getValue("status"));
      commit();
      assignment.invoke("schedulerRun");
      assertEquals(Boolean.TRUE, instance.invoke("done"));
      assertEquals(Instance.DELETED, assignment.getState());
      assertEquals(Primitive.createInteger(2), assignment.getValue("status"));
      result = (TransferObject)instance.invoke("result");
      assertEquals("BEFORE;EXECUTE_SEMAPHORE;CAUGHT_OUTER;", result.getValue("trace"));
      instance.delete();
      commit();


      // Test: uncaught exception
      tobj.setValue("trace", "");
      tobj.setValue("throwOuterEx", Boolean.FALSE);
      tobj.setValue("throwUncaughtEx", Boolean.TRUE);
      instance = (Instance)serviceClass.invoke("invoke", new Object[]{"Semaphore", tobj, null});
      assertEquals(Boolean.FALSE, instance.invoke("done"));
      assertEquals("BEFORE;", tobj.getValue("trace"));
      assignmentList = (InstanceList)instance.getValue("assignments");
      assertEquals(1, assignmentList.size());
      assignment = assignmentList.getInstance(0);
      assertEquals(Boolean.TRUE, assignment.getValue("semaphore"));
      assertEquals(Primitive.ZERO_INTEGER, assignment.getValue("status"));
      commit();

      try
      {
         assignment.invoke("schedulerRun");
         fail();
      }
      catch (ScriptingException ex)
      {
         assertEquals(Boolean.TRUE, instance.invoke("done"));
         assertEquals(Instance.DELETED, assignment.getState());
         assertEquals(Primitive.createInteger(2), assignment.getValue("status"));
         result = (TransferObject)instance.invoke("result");
         assertEquals("BEFORE;EXECUTE_SEMAPHORE;", result.getValue("trace"));
         instance.delete();
         commit();
      }
   }

   /**
    * Tests the Finally part of a Service Try block.
    */
   public void testServiceTryFinally() throws Exception
   {
      Metaclass serviceClass = getMetadata().getMetaclass(Metadata.SERVICE_CLASS_NAME);
      TransferObject tobj = new TransferObject();

      tobj.setValue("trace", "");
      tobj.setValue("throwInnerEx", Boolean.FALSE);
      tobj.setValue("throwOuterEx", Boolean.FALSE);
      tobj.setValue("throwUncaughtEx", Boolean.FALSE);

      Instance instance = (Instance)serviceClass.invoke("invoke", new Object[]{"TryFinally", tobj, null});

      assertEquals(Boolean.TRUE, instance.invoke("done"));

      TransferObject result = (TransferObject)instance.invoke("result");

      assertEquals("THROWBLOCK;END_THROWBLOCK;F_INNER;F_OUTER;", result.getValue("trace"));


      tobj.setValue("trace", "");
      tobj.setValue("throwInnerEx", Boolean.TRUE);
      instance = (Instance)serviceClass.invoke("invoke", new Object[]{"TryFinally", tobj, null});
      result = (TransferObject)instance.invoke("result");
      assertEquals("THROWBLOCK;CAUGHT_INNER;F_INNER;F_OUTER;", result.getValue("trace"));


      tobj.setValue("trace", "");
      tobj.setValue("throwInnerEx", Boolean.FALSE);
      tobj.setValue("throwOuterEx", Boolean.TRUE);
      instance = (Instance)serviceClass.invoke("invoke", new Object[]{"TryFinally", tobj, null});
      result = (TransferObject)instance.invoke("result");
      assertEquals("THROWBLOCK;F_INNER;CAUGHT_OUTER;F_OUTER;", result.getValue("trace"));


      tobj.setValue("trace", "");
      tobj.setValue("throwOuterEx", Boolean.FALSE);
      tobj.setValue("throwUncaughtEx", Boolean.TRUE);

      try
      {
         instance = (Instance)serviceClass.invoke("invoke", new Object[]{"TryFinally", tobj, null});
         fail();
      }
      catch (ScriptingException ex)
      {
         result = (TransferObject)instance.invoke("result");
         assertEquals("THROWBLOCK;F_INNER;F_OUTER;", result.getValue("trace"));
      }
   }


   /**
    * Tests the Loop construct for workflows. Specifically, tests that the workflow may be persisted
    * during loop execution.
    */
   public void testWorkflowLoop()
   {
      Metaclass workflowClass = getMetadata().getMetaclass(Metadata.WORKFLOW_CLASS_NAME);
      InstanceList workflowList;
      Instance contact, workflow;
      Query query = Query.createRead(getMetadata().getMetaclass("Contact"),
         parse("((addresses country state city))"),
         parse("(and (= lastName \"Test\") (= firstName \"Joe\"))"),
         null,
         -1, 0, false, Query.SEC_NONE, m_context);
      InstanceList resultList = query.read();

      assertEquals(1, resultList.size());
      contact = resultList.getInstance(0);

      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{contact});
      assertEquals(0, workflowList.size());

      contact.invoke("start");
      commit();
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{contact});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assertEquals(1, ((List)workflow.getValue("assignments")).size());

      contact.invoke("process");
      commit();
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{contact});
      assertEquals(1, workflowList.size());
      workflow = workflowList.getInstance(0);
      assertEquals(1, ((List)workflow.getValue("assignments")).size());

      contact.invoke("break");
      commit();
      workflowList = (InstanceList)workflowClass.invoke("forInstance",
         new Object[]{contact});
      assertEquals(0, workflowList.size());

      InstanceList addressList = (InstanceList)contact.getValue("addresses");

      addressList.sort(new Comparator()
      {
         public int compare(Object left, Object right)
         {
            return ((Comparable)((Instance)left).getValue("city")).compareTo(((Instance)right).getValue("city"));
         }
      });

      assertEquals(2, addressList.size());
      assertEquals("Hong Kong", addressList.getInstance(0).getValue("city"));
      assertEquals("Richmond Hill", addressList.getInstance(1).getValue("city"));
   }

   public void testWorkflowTrigger() throws Exception
   {
      Metaclass patientClass = getMetadata().getMetaclass("Patient");
      Instance parentPatient, childPatient;

      childPatient = (Instance)patientClass.invoke("new");
      childPatient.setValue("firstName", "child");
      parentPatient = (Instance)patientClass.invoke("new");
      parentPatient.setValue("firstName", "parent");
      ((InstanceArrayList)parentPatient.getValue("children")).add(childPatient);
      commit();

      getLogger().debug("Starting workflow on parent");
      parentPatient.invoke("triggerTestStart");
      commit();

      // Triggering child to wake from queue
      childPatient.invoke("triggerTest");
      commit();

      // Triggering child to wake from script
      childPatient.invoke("triggerTest");
      commit();

      // Triggering child, should not wake parent
      childPatient.invoke("triggerTest");
      commit();

      List workflowList = ((List)getMetadata().getMetaclass("SysWorkflow").invoke("forInstance",
         new Object[]{parentPatient}));

      assertEquals(1, workflowList.size());

      State state = (State)((Instance)workflowList.get(0)).getValue("state");

      assertEquals("6", state.toString());
      parentPatient.invoke("triggerTest");
      assertTrue(state.isFinal());
   }

   /**
    * Tests that an exception thrown in a trigger script step is caught by
    * a workflow try/catch.
    */
   public void testWorkflowTriggerException()
   {
      Metaclass patientClass = getMetadata().getMetaclass("Patient");
      Instance patient = (Instance)patientClass.invoke("new");

      patient.setValue("firstName", "John");
      commit();

      patient.invoke("triggerExceptionStart");
      commit();

      // Fire the trigger that throws an exception
      patient.invoke("triggerException");
      commit();
   }
}
TOP

Related Classes of nexj.core.persistence.sql.RangeInfo

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.