/**
   * Create business object instances via API (not via process instances) and validate if they're
   * created and if a business object query for a given primary key returns the corresponding BO.
   */
  @Test
  public void CreateCustomersCheck() {
    DeployedModelDescription model =
        sf.getQueryService().getModels(DeployedModelQuery.findActiveForId(MODEL_NAME2)).get(0);
    for (int i = 1; i <= 3; i++) {
      createCustomer(model, i);
    }

    String businessObjectQualifiedId = new QName(model.getId(), "Customer").toString();
    BusinessObjectQuery query =
        BusinessObjectQuery.findForBusinessObject(businessObjectQualifiedId);
    query.setPolicy(new BusinessObjectQuery.Policy(BusinessObjectQuery.Option.WITH_VALUES));
    BusinessObjects bos = sf.getQueryService().getAllBusinessObjects(query);
    Assert.assertEquals("Objects", 1, bos.getSize());
    BusinessObject bo = bos.get(0);
    List<BusinessObject.Value> values = bo.getValues();
    Assert.assertEquals("Values", 3, values.size());

    query = BusinessObjectQuery.findWithPrimaryKey(businessObjectQualifiedId, 2);
    query.setPolicy(new BusinessObjectQuery.Policy(BusinessObjectQuery.Option.WITH_VALUES));
    bos = sf.getQueryService().getAllBusinessObjects(query);
    Assert.assertEquals("Objects", 1, bos.getSize());
    bo = bos.get(0);
    values = bo.getValues();
    Assert.assertEquals("Values", 1, values.size());
    checkValue(values, true, "firstName", "Danny2");
  }
  /**
   * Create some instances for a given BO and validate if they can be queried:
   *
   * <ul>
   *   <li>where the qualified business object id is set
   *   <li>the primary key is passed to findForBusinessObject()
   *   <li>the qualified business object id is passed to findForBusinessObject and the primary key
   *       is set as a data filter
   *   <li>the qualified business object id is passed to findForBusinessObject and an attribute of
   *       the BO is set as a data filter
   * </ul>
   */
  @Test
  public void CheckFiltering() throws Exception {
    DeployedModelDescription model =
        sf.getQueryService().getModels(DeployedModelQuery.findActiveForId(MODEL_NAME3)).get(0);

    String businessObjectQualifiedId = new QName(model.getId(), "Fund").toString();
    for (int i = 1; i <= 9; i++) {
      final Map<String, Object> fund = CollectionUtils.newMap();
      fund.put("AccountNumber", "100100" + i);
      fund.put("AccountName", "Fund" + i);

      sf.getWorkflowService()
          .createBusinessObjectInstance(businessObjectQualifiedId, (Serializable) fund);
    }

    BusinessObjectQuery query =
        BusinessObjectQuery.findForBusinessObject(businessObjectQualifiedId);
    query.setPolicy(new BusinessObjectQuery.Policy(BusinessObjectQuery.Option.WITH_VALUES));
    BusinessObjects bos = sf.getQueryService().getAllBusinessObjects(query);
    Assert.assertEquals("Objects", 1, bos.getSize());
    BusinessObject bo = bos.get(0);
    List<BusinessObject.Value> values = bo.getValues();
    Assert.assertEquals("Values", 9, values.size());

    query = BusinessObjectQuery.findWithPrimaryKey(businessObjectQualifiedId, "1001003");
    query.setPolicy(new BusinessObjectQuery.Policy(BusinessObjectQuery.Option.WITH_VALUES));
    bos = sf.getQueryService().getAllBusinessObjects(query);
    Assert.assertEquals("Objects", 1, bos.getSize());
    bo = bos.get(0);
    values = bo.getValues();
    Assert.assertEquals("Values", 1, values.size());
    checkValue(values, true, "AccountNumber", "1001003");

    query = BusinessObjectQuery.findForBusinessObject(businessObjectQualifiedId);
    query.where(DataFilter.isEqual("Fund", "AccountNumber", "1001005"));
    query.setPolicy(new BusinessObjectQuery.Policy(BusinessObjectQuery.Option.WITH_VALUES));
    bos = sf.getQueryService().getAllBusinessObjects(query);
    Assert.assertEquals("Objects", 1, bos.getSize());
    bo = bos.get(0);
    values = bo.getValues();
    Assert.assertEquals("Values", 1, values.size());
    checkValue(values, true, "AccountNumber", "1001005");

    query = BusinessObjectQuery.findForBusinessObject(businessObjectQualifiedId);
    query.where(DataFilter.isEqual("Fund", "AccountName", "Fund7"));
    query.setPolicy(new BusinessObjectQuery.Policy(BusinessObjectQuery.Option.WITH_VALUES));
    bos = sf.getQueryService().getAllBusinessObjects(query);
    Assert.assertEquals("Objects", 1, bos.getSize());
    bo = bos.get(0);
    values = bo.getValues();
    Assert.assertEquals("Values", 1, values.size());
    checkValue(values, true, "AccountName", "Fund7");
  }
  /**
   * Validate if BusinessObjectQuery returns business objects which are defined in the models
   * MODEL_NAME2 and MODEL_NAME3. Note: If BusinessObjectsList is executed standalone than
   * bos.size() == expected.size(). But if it is executed after CheckFiltering2 then bos.size() >
   * expected.size() because CheckFiltering2 deployes MODEL_NAME3 as a new version. This means that
   * bos.size() == expected.size()+2 in this case.
   */
  @Test
  public void BusinessObjectsList() {
    BusinessObjectQuery query = BusinessObjectQuery.findAll();
    query.setPolicy(new BusinessObjectQuery.Policy(BusinessObjectQuery.Option.WITH_DESCRIPTION));

    BusinessObjects bos = sf.getQueryService().getAllBusinessObjects(query);

    List<String> expected =
        CollectionUtils.newArrayListFromElements(
            Arrays.asList(
                new QName(MODEL_NAME2, "Account").toString(),
                new QName(MODEL_NAME2, "Customer").toString(),
                new QName(MODEL_NAME2, "Order").toString(),
                new QName(MODEL_NAME2, "Fund").toString(),
                new QName(MODEL_NAME2, "FundGroup").toString(),
                new QName(MODEL_NAME3, "Employee").toString(),
                new QName(MODEL_NAME3, "Fund").toString()));

    List<String> removedEntries = CollectionUtils.newArrayList(expected.size());

    for (BusinessObject bo : bos) {
      String qualifiedBOId = new QName(bo.getModelId(), bo.getId()).toString();
      if (expected.remove(qualifiedBOId)) {
        removedEntries.add(qualifiedBOId);
      } else {
        Assert.assertTrue(
            "Not expected entry: " + qualifiedBOId, removedEntries.contains(qualifiedBOId));
      }
    }
    Assert.assertTrue("Missing business objects: " + expected, expected.isEmpty());
  }
  @Before
  public void setup() {
    for (int customerId = 1; customerId <= 5; customerId++) {
      ProcessInstance pi =
          sf.getWorkflowService()
              .startProcess(new QName(MODEL_NAME2, "OrderCreation").toString(), null, true);
      List<ActivityInstance> w = getWorklist();
      Assert.assertEquals("worklist", 1, w.size());
      ActivityInstance ai = w.get(0);
      Assert.assertEquals("process instance", pi.getOID(), ai.getProcessInstanceOID());
      Assert.assertEquals("activity instance", "EnterOrderData", ai.getActivity().getId());
      Map<String, Object> order = CollectionUtils.newMap();
      order.put("date", new Date());
      order.put("customerId", customerId);
      ai =
          complete(
              ai, PredefinedConstants.DEFAULT_CONTEXT, Collections.singletonMap("Order", order));

      try {
        ProcessInstanceStateBarrier.instance().await(pi.getOID(), ProcessInstanceState.Completed);
      } catch (Exception e) {
      }
      w = getWorklist();
    }
  }
  /**
   * Test if the business object query returns all business object instances for a given business
   * object id. The BO instances was created by the OrderCreation process resp. EnterOrderData
   * activity in the setup() method.
   */
  @Test
  public void CheckOrders() {
    DeployedModelDescription model =
        sf.getQueryService().getModels(DeployedModelQuery.findActiveForId(MODEL_NAME2)).get(0);

    BusinessObjectQuery query =
        BusinessObjectQuery.findForBusinessObject(new QName(model.getId(), "Order").toString());
    query.setPolicy(new BusinessObjectQuery.Policy(BusinessObjectQuery.Option.WITH_VALUES));

    BusinessObjects bos = sf.getQueryService().getAllBusinessObjects(query);
    Assert.assertEquals("Objects", 1, bos.getSize());
    BusinessObject bo = bos.get(0);
    List<BusinessObject.Value> values = bo.getValues();
    Assert.assertTrue("Expected at least 5 values: " + values.size(), 5 <= values.size());
    checkValue(values, false, "customerId", 1, 2, 3, 4, 5);
  }
  /**
   * Validate if BO instances can be created once only with the same primary key and that they can
   * be queried either via findWithPrimaryKey() or with help of data filters.
   */
  @Test
  public void CreateOrdersCheck() {
    DeployedModelDescription model =
        sf.getQueryService().getModels(DeployedModelQuery.findActiveForId(MODEL_NAME2)).get(0);
    createOrder(model, 666);
    try {
      createOrder(model, 666);
      Assert.fail("Extected BPMRT03825 error message");
    } catch (ObjectExistsException ex) {
      Assert.assertEquals("Error code", "BPMRT03825", ex.getError().getId());
    }

    String businessObjectQualifiedId = new QName(model.getId(), "Order").toString();
    BusinessObjectQuery query =
        BusinessObjectQuery.findWithPrimaryKey(businessObjectQualifiedId, 666);
    query.setPolicy(new BusinessObjectQuery.Policy(BusinessObjectQuery.Option.WITH_VALUES));
    BusinessObjects bos = sf.getQueryService().getAllBusinessObjects(query);
    Assert.assertEquals("Objects", 1, bos.getSize());
    BusinessObject bo = bos.get(0);
    List<Value> values = bo.getValues();
    Assert.assertEquals("Values", 1, values.size());
    checkValue(values, true, "customerId", 666);

    query = BusinessObjectQuery.findForBusinessObject(businessObjectQualifiedId);
    query
        .getFilter()
        .addOrTerm()
        .or(DataFilter.isEqual("Order", "customerId", 2))
        .or(DataFilter.isEqual("Order", "customerId", 4));
    query.setPolicy(new BusinessObjectQuery.Policy(BusinessObjectQuery.Option.WITH_VALUES));
    bos = sf.getQueryService().getAllBusinessObjects(query);
    Assert.assertEquals("Objects", 1, bos.getSize());
    bo = bos.get(0);
    values = bo.getValues();
    Assert.assertEquals("Values", 2, values.size());
    checkValue(values, true, "customerId", 2, 4);
  }
  /**
   * The following test case should ensure that
   *
   * <ul>
   *   <li>Any modifications to an attribute of a BOI via API isn't reflected to process data which
   *       are using the BO
   *   <li>Any modifications to an attribute of a BOI via the process data is only reflected to the
   *       BOI which is attached to the synthetic process instance and that it doesn't affect other
   *       BOIs which are used in other processes
   * </ul>
   */
  @Test
  public void checkFilteringOnBusinessObjectAttrChange() {
    // setup
    final int customerIdOffset = 100;
    final int customerCount = 3;
    for (int customerId = 1; customerId <= customerCount; customerId++) {
      ProcessInstance pi =
          sf.getWorkflowService()
              .startProcess(new QName(MODEL_NAME2, "DistributedOrder").toString(), null, true);
      List<ActivityInstance> w = getWorklist(pi);
      Assert.assertEquals("worklist", 1, w.size());
      ActivityInstance ai = w.get(0);
      Assert.assertEquals("activity instance", "CreateOrder", ai.getActivity().getId());
      Map<String, Object> order = CollectionUtils.newMap();
      order.put("date", new Date());
      order.put("customerId", customerIdOffset + customerId);
      order.put("items", "item " + customerId);
      ai =
          complete(
              ai, PredefinedConstants.DEFAULT_CONTEXT, Collections.singletonMap("Order", order));

      try {
        ActivityInstanceStateBarrier.instance().await(ai.getOID(), ActivityInstanceState.Completed);
      } catch (Exception e) {
      }
    }

    // after DistributeCreation activity is completed we have the following state:
    // * 2 asynchronous subprocesses are started: one which copies the data and the
    //   other one which doesn't
    // * 3 synchronous subprocesses are triggered: one with shared data, one with separate
    //   but copied data and the last one with separate data without copying
    // This results into the following state:
    // * Each process has created four business object instances
    //   * One which is attached to a synthetic process instance
    //   * 3 other BOIs which are attached to a real process instance
    String businessObjectQualifiedId = new QName(MODEL_NAME2, "Order").toString();
    BusinessObjectQuery businessObjectQuery =
        BusinessObjectQuery.findForBusinessObject(businessObjectQualifiedId);
    businessObjectQuery
        .getFilter()
        .addAndTerm()
        .add(DataFilter.greaterThan("Order", "customerId", customerIdOffset));
    businessObjectQuery.setPolicy(
        new BusinessObjectQuery.Policy(
            BusinessObjectQuery.Option.WITH_VALUES, BusinessObjectQuery.Option.WITH_DESCRIPTION));
    BusinessObjects bos = sf.getQueryService().getAllBusinessObjects(businessObjectQuery);
    Assert.assertEquals("Only one business object, namely Order, is expected", 1, bos.getSize());
    Assert.assertEquals(
        "Business object instances count isn't the same as started process ergo the count of the synthetic process instances",
        customerCount,
        getTotalSize(bos));

    // Wait that all ShowOrder processes are started (unfortunately we cannot use
    // ProcessInstanceStateBarrier here
    // because of the async processes.
    ProcessInstanceQuery piQuery = ProcessInstanceQuery.findAlive("ShowOrder");
    boolean waitForPIs = true;
    while (waitForPIs) {
      long instanceCount = sf.getQueryService().getProcessInstancesCount(piQuery);
      waitForPIs = instanceCount != (customerCount * 5);

      if (waitForPIs) {
        try {
          Thread.sleep(100);
        } catch (InterruptedException e) {
        }
      }
    }

    BusinessObject bo = bos.get(0);
    BusinessObject.Value customer101 = null;
    for (BusinessObject.Value boValue : bo.getValues()) {
      Map<?, ?> boAttr = (Map<?, ?>) boValue.getValue();
      Integer customerId = (Integer) boAttr.get("customerId");
      if (Integer.valueOf(customerIdOffset + 1).equals(customerId)) {
        customer101 = boValue;
      }
    }
    Assert.assertNotNull("Customer " + customerIdOffset + 1 + " not found", customer101);

    // Update BOI via API...
    ((Map) customer101.getValue()).put("items", "newitems");
    sf.getWorkflowService()
        .updateBusinessObjectInstance(businessObjectQualifiedId, customer101.getValue());

    // ...and validate if no process data is modified
    piQuery = ProcessInstanceQuery.findActive();
    FilterTerm filter = piQuery.getFilter().addAndTerm();
    filter.add(
        DataFilter.between(
            "Order", "customerId", customerIdOffset, customerIdOffset + customerCount));
    filter.add(DataFilter.like("Order", "items", "item%"));
    filter.addAndTerm().add(ProcessInstanceHierarchyFilter.ROOT_PROCESS);
    piQuery.setPolicy(SubsetPolicy.UNRESTRICTED);
    ProcessInstances rootPIs = sf.getQueryService().getAllProcessInstances(piQuery);
    // Root process instances are the DistributedOrder processes and the ShowOrder processes which
    // was started
    // as async processes and which had copied the data
    Assert.assertEquals(
        "Changes in BOIs must not be reflected in process instance data",
        customerCount * 2,
        rootPIs.getTotalCount());

    // Update BOI for a given process via data path...
    long piOid = rootPIs.get(0).getOID();
    ((Map) customer101.getValue()).put("items", "newitems1");
    sf.getWorkflowService().setOutDataPath(piOid, "OrderDataPath", (Map) customer101.getValue());

    // ...and validate if the BOI is updated...
    businessObjectQuery =
        BusinessObjectQuery.findWithPrimaryKey(
            businessObjectQualifiedId, ((Map) customer101.getValue()).get("customerId"));
    businessObjectQuery.setPolicy(
        new BusinessObjectQuery.Policy(BusinessObjectQuery.Option.WITH_VALUES));
    bos = sf.getQueryService().getAllBusinessObjects(businessObjectQuery);
    Assert.assertEquals("Only one business object, namely Order, is expected", 1, bos.getSize());
    List<BusinessObject.Value> boValues = bos.get(0).getValues();
    Assert.assertEquals(1, boValues.size());
    Assert.assertEquals("newitems1", ((Map) boValues.get(0).getValue()).get("items"));

    // ...but the other process instance data should be untouched
    piQuery = ProcessInstanceQuery.findActive();
    filter = piQuery.getFilter().addAndTerm();
    filter.add(
        DataFilter.between(
            "Order", "customerId", customerIdOffset, customerIdOffset + customerCount));
    filter.add(DataFilter.like("Order", "items", "item%"));
    filter.addAndTerm().add(ProcessInstanceHierarchyFilter.ROOT_PROCESS);
    piQuery.setPolicy(SubsetPolicy.UNRESTRICTED);
    rootPIs = sf.getQueryService().getAllProcessInstances(piQuery);
    Assert.assertEquals(
        "Changes in BOIs must not be reflected in process instance data",
        (customerCount * 2) - 1,
        rootPIs.getTotalCount());
  }
  /**
   * Create some instances for a given BO and validate if they can be queried where:
   *
   * <ul>
   *   <li>the qualified business object id is set
   *   <li>the primary key is passed to findForBusinessObject()
   *   <li>the qualified business object id is passed to findForBusinessObject and an attribute of
   *       the BO is set as a data filter
   *   <li>the qualified business object id is passed to findForBusinessObject and the query is
   *       restricted to the currently active model
   *   <li>the qualified business object id is passed to findForBusinessObject and across all
   *       deployed model versions
   *   <li>the qualified business object id is passed to findForBusinessObject and the query is
   *       restricted to a given modelOid
   * </ul>
   */
  @Test
  public void CheckFiltering2() throws Exception {
    DeployedModelDescription model =
        sf.getQueryService().getModels(DeployedModelQuery.findActiveForId(MODEL_NAME3)).get(0);

    String businessObjectQualifiedId = new QName(model.getId(), "Employee").toString();

    createEmployee(businessObjectQualifiedId, "1", "Florin");
    createEmployee(businessObjectQualifiedId, "Sid", "2");

    BusinessObjectQuery query =
        BusinessObjectQuery.findForBusinessObject(businessObjectQualifiedId);
    query.setPolicy(new BusinessObjectQuery.Policy(BusinessObjectQuery.Option.WITH_VALUES));
    BusinessObjects bos = sf.getQueryService().getAllBusinessObjects(query);
    Assert.assertEquals("Objects", 1, bos.getSize());
    BusinessObject bo = bos.get(0);
    List<BusinessObject.Value> values = bo.getValues();
    Assert.assertEquals("Values", 2, values.size());

    query = BusinessObjectQuery.findWithPrimaryKey(businessObjectQualifiedId, "1");
    query.setPolicy(new BusinessObjectQuery.Policy(BusinessObjectQuery.Option.WITH_VALUES));
    bos = sf.getQueryService().getAllBusinessObjects(query);
    Assert.assertEquals("Objects", 1, bos.getSize());
    bo = bos.get(0);
    values = bo.getValues();
    Assert.assertEquals("Values", 1, values.size());
    checkValue(values, true, "EmpID", "1");

    query = BusinessObjectQuery.findWithPrimaryKey(businessObjectQualifiedId, "Sid");
    query.setPolicy(new BusinessObjectQuery.Policy(BusinessObjectQuery.Option.WITH_VALUES));
    bos = sf.getQueryService().getAllBusinessObjects(query);
    Assert.assertEquals("Objects", 1, bos.getSize());
    bo = bos.get(0);
    values = bo.getValues();
    Assert.assertEquals("Values", 1, values.size());
    checkValue(values, true, "EmpID", "Sid");

    query = BusinessObjectQuery.findForBusinessObject(businessObjectQualifiedId);
    query.where(DataFilter.isEqual("Employee", "EmpName", "2"));
    query.setPolicy(new BusinessObjectQuery.Policy(BusinessObjectQuery.Option.WITH_VALUES));
    bos = sf.getQueryService().getAllBusinessObjects(query);
    Assert.assertEquals("Objects", 1, bos.getSize());
    bo = bos.get(0);
    values = bo.getValues();
    Assert.assertEquals("Values", 1, values.size());
    checkValue(values, true, "EmpName", "2");

    query = BusinessObjectQuery.findForBusinessObject(businessObjectQualifiedId);
    query.where(DataFilter.isEqual("Employee", "EmpName", "Florin"));
    query.setPolicy(new BusinessObjectQuery.Policy(BusinessObjectQuery.Option.WITH_VALUES));
    bos = sf.getQueryService().getAllBusinessObjects(query);
    Assert.assertEquals("Objects", 1, bos.getSize());
    bo = bos.get(0);
    values = bo.getValues();
    Assert.assertEquals("Values", 1, values.size());
    checkValue(values, true, "EmpName", "Florin");

    RtEnvHome.deployModel(sf.getAdministrationService(), null, MODEL_NAME3);
    createEmployee(businessObjectQualifiedId, "3", "Meyer");
    query =
        BusinessObjectQuery.findForBusinessObject(
            PredefinedConstants.ACTIVE_MODEL, businessObjectQualifiedId);
    query.setPolicy(new BusinessObjectQuery.Policy(BusinessObjectQuery.Option.WITH_VALUES));
    bos = sf.getQueryService().getAllBusinessObjects(query);
    Assert.assertEquals("Objects", 1, bos.getSize());
    bo = bos.get(0);
    values = bo.getValues();
    Assert.assertEquals("Values", 1, values.size());

    query = BusinessObjectQuery.findForBusinessObject(businessObjectQualifiedId);
    query.setPolicy(new BusinessObjectQuery.Policy(BusinessObjectQuery.Option.WITH_VALUES));
    bos = sf.getQueryService().getAllBusinessObjects(query);
    Assert.assertEquals("Objects", 2, bos.getSize());
    Assert.assertEquals("Values", 3, getTotalSize(bos));

    query =
        BusinessObjectQuery.findForBusinessObject(model.getModelOID(), businessObjectQualifiedId);
    query.setPolicy(new BusinessObjectQuery.Policy(BusinessObjectQuery.Option.WITH_VALUES));
    bos = sf.getQueryService().getAllBusinessObjects(query);
    Assert.assertEquals("Objects", 1, bos.getSize());
    bo = bos.get(0);
    values = bo.getValues();
    Assert.assertEquals("Values", 2, values.size());
  }
  private static IActivity consume(
      IActivity startActivity,
      LinkedList<ITransition> unconsumed,
      HashSet<ITransition> visited,
      boolean forward,
      boolean supportsLoops) {
    int unchanged = 0;
    while (!unconsumed.isEmpty()) {
      ITransition transition = unconsumed.element();
      IActivity target = forward ? transition.getToActivity() : transition.getFromActivity();

      if (startActivity == target) {
        // unsupported loop
        break;
      }

      JoinSplitType inJsType = forward ? target.getJoinType() : target.getSplitType();
      if (JoinSplitType.And == inJsType) {
        List<ITransition> pending = CollectionUtils.newList();
        ModelElementList<ITransition> transitions =
            forward ? target.getInTransitions() : target.getOutTransitions();
        for (ITransition incoming : transitions) {
          if (unconsumed.remove(incoming)) {
            pending.add(incoming);
          }
        }
        if (pending.size() == transitions.size()) // all incoming transitions consumed
        {
          if (unconsumed.isEmpty()) {
            return target;
          }
        } else {
          if (!unconsumed
              .isEmpty()) // unable to consume all transitions, but there are more branches, put
          // them all back to the end
          {
            unconsumed.addAll(pending);
            unchanged++;
          }
          if (unchanged == unconsumed.size()) {
            return null;
          }
          continue;
        }
      } else {
        unconsumed.remove(transition);
      }
      unchanged = 0;

      ModelElementList<ITransition> transitions =
          forward ? target.getOutTransitions() : target.getInTransitions();
      if (transitions.isEmpty()) {
        return null;
      }

      JoinSplitType outJsType = forward ? target.getSplitType() : target.getJoinType();
      if (JoinSplitType.Xor == outJsType && transitions.size() > 1) {
        return consumeXor(startActivity, unconsumed, visited, forward, supportsLoops, transitions);
      } else {
        for (ITransition out : transitions) {
          if (visited.contains(out)) // loop
          {
            if (!supportsLoops) {
              return null;
            }
          } else {
            visited.add(out);
            unconsumed.add(out);
          }
        }
      }
    }
    return null;
  }