@Test
 public void testCoalesceNotificationsSet() {
   InvalidateCacheNotificationCoalescer coalescer = new InvalidateCacheNotificationCoalescer();
   ICacheEntryFilter<Object, Object> filter1 =
       new KeyCacheEntryFilter<Object, Object>(
           CollectionUtility.<Object>arrayList(CodeType1.class));
   ICacheEntryFilter<Object, Object> filter2 =
       new KeyCacheEntryFilter<Object, Object>(
           CollectionUtility.<Object>arrayList(CodeType2.class));
   List<InvalidateCacheNotification> testList =
       CollectionUtility.arrayList(
           new InvalidateCacheNotification(CACHE_ID_1, filter1),
           new InvalidateCacheNotification(CACHE_ID_1, filter2),
           new InvalidateCacheNotification(CACHE_ID_2, filter2),
           new InvalidateCacheNotification(CACHE_ID_3, new AllCacheEntryFilter<>()),
           new InvalidateCacheNotification(CACHE_ID_3, filter2));
   List<InvalidateCacheNotification> res = coalescer.coalesce(testList);
   assertEquals(3, res.size());
   for (InvalidateCacheNotification notification : res) {
     if (CACHE_ID_1.equals(notification.getCacheId())) {
       Set<?> keys = ((KeyCacheEntryFilter<?, ?>) notification.getFilter()).getKeys();
       assertEquals(2, keys.size());
       assertTrue(keys.contains(CodeType1.class));
       assertTrue(keys.contains(CodeType2.class));
     } else if (CACHE_ID_2.equals(notification.getCacheId())) {
       Set<?> keys = ((KeyCacheEntryFilter<?, ?>) notification.getFilter()).getKeys();
       assertEquals(1, keys.size());
       assertTrue(keys.contains(CodeType2.class));
     } else if (CACHE_ID_3.equals(notification.getCacheId())) {
       assertTrue(notification.getFilter() instanceof AllCacheEntryFilter);
     } else {
       fail("invalid cacheId" + notification.getCacheId());
     }
   }
 }
 protected boolean checkValueEqualToDefaultValue(Object value, Object defaultValue) {
   // Now compare the given value to the found default value
   if (value == null && defaultValue == null) {
     return true;
   }
   if (value == null || defaultValue == null) {
     return false;
   }
   if (defaultValue instanceof JSONObject) {
     if (value instanceof JSONObject) {
       JSONObject jsonValue = (JSONObject) value;
       JSONObject jsonDefaultValue = (JSONObject) defaultValue;
       // Special case: The property cannot be removed, but maybe  we can remove some of the
       // objects attributes
       return filterDefaultObject(jsonValue, jsonDefaultValue);
     }
     if (value instanceof JSONArray) {
       JSONArray jsonValue = (JSONArray) value;
       JSONObject jsonDefaultValue = (JSONObject) defaultValue;
       // Special case: Apply default value object to each element in the array
       filterDefaultObject(jsonValue, jsonDefaultValue);
     }
     return false;
   }
   // Convert JSONArrays to collections to be able to compare them
   if (value instanceof JSONArray) {
     value = jsonArrayToCollection((JSONArray) value, true);
   }
   if (defaultValue instanceof JSONArray) {
     defaultValue = jsonArrayToCollection((JSONArray) defaultValue, false);
   }
   if (value instanceof List<?> && defaultValue instanceof List<?>) {
     return CollectionUtility.equalsCollection(
         (List<?>) value, (List<?>) defaultValue); // same order
   }
   if (value instanceof Collection<?> && defaultValue instanceof Collection<?>) {
     return CollectionUtility.equalsCollection(
         (Collection<?>) value, (Collection<?>) defaultValue); // any order
   }
   try {
     // Try to match types (to make Integer "1" equal to Double "1.0")
     value = TypeCastUtility.castValue(value, defaultValue.getClass());
   } catch (RuntimeException e) {
     // Types do not match
     return false;
   }
   return CompareUtility.equals(value, defaultValue);
 }
 /**
  * Removes all properties from "valueObject" where the value matches the corresponding value in
  * "defaultValueObject".
  *
  * @return <code>true</code> of the two objects are completely equal and no properties remain in
  *     "valueObject". This means that the valueObject itself MAY be removed. Return value <code>
  *     false</code> means that not all properties are equal (but nevertheless, some properties may
  *     have been removed from valueObject).
  */
 protected boolean filterDefaultObject(JSONObject valueObject, JSONObject defaultValueObject) {
   boolean sameKeys =
       CollectionUtility.equalsCollection(valueObject.keySet(), defaultValueObject.keySet());
   for (Iterator it = valueObject.keys(); it.hasNext(); ) {
     String prop = (String) it.next();
     Object subValue = valueObject.opt(prop);
     Object subDefaultValue = defaultValueObject.opt(prop);
     boolean valueEqualToDefaultValue = checkValueEqualToDefaultValue(subValue, subDefaultValue);
     if (valueEqualToDefaultValue) {
       // Property value value is equal to the static default value -> remove the property
       it.remove();
     } else {
       // Special case: Check if there is a "pseudo" default value, which will not
       // be removed itself, but might have sub-properties removed.
       subDefaultValue = defaultValueObject.opt("~" + prop);
       checkValueEqualToDefaultValue(subValue, subDefaultValue);
     }
   }
   // Even more special case: If valueObject is now empty and it used to have the same keys as
   // the defaultValueObject, it is considered equal to the default value and MAY be removed.
   if (valueObject.length() == 0 && sameKeys) {
     return true;
   }
   return false;
 }
  public void dispatchNotifications(List<ClientNotificationMessage> notifications) {
    IClientSessionRegistry notificationService = BEANS.get(IClientSessionRegistry.class);
    if (notifications == null) {
      LOG.error("Notifications null. Please check your configuration");
      return;
    }

    for (ClientNotificationMessage message : notifications) {
      ClientNotificationAddress address = message.getAddress();
      Serializable notification = message.getNotification();
      LOG.debug("Processing client notification {}", notification);

      if (address.isNotifyAllNodes()) {
        // notify all nodes
        LOG.debug("Notify all nodes");
        dispatch(notification);
      } else if (address.isNotifyAllSessions()) {
        // notify all sessions
        LOG.debug("Notify all sessions");
        for (IClientSession session : notificationService.getAllClientSessions()) {
          dispatch(session, notification);
        }
      } else if (CollectionUtility.hasElements(address.getSessionIds())) {
        // notify all specified sessions
        LOG.debug("Notify sessions by session id: {}", address.getSessionIds());
        for (String sessionId : address.getSessionIds()) {
          IClientSession session = notificationService.getClientSession(sessionId);
          if (session == null) {
            LOG.warn("received notification for invalid session '{}'.", sessionId);
          } else {
            dispatch(session, notification);
          }
        }
      } else if (CollectionUtility.hasElements(address.getUserIds())) {
        LOG.debug("Notify sessions by user id: {}", address.getUserIds());
        for (String userId : address.getUserIds()) {
          for (IClientSession session : notificationService.getClientSessionsForUser(userId)) {
            dispatch(session, notification);
          }
        }
      }
    }
  }
  protected void generateObjectTypeHierarchyRec(
      JSONObject json, List<String> currentParentObjectTypes, Map<String, List<String>> targetMap) {
    if (json == null) {
      return;
    }
    if (targetMap == null) {
      throw new IllegalArgumentException("Argument 'targetMap' must not be null");
    }
    for (Iterator it = json.keys(); it.hasNext(); ) {
      String objectType = (String) it.next();
      Object subHierarchy = json.opt(objectType);

      // Create a copy of the current object type list and add the current type to the front
      List<String> newParentObjectTypes = new ArrayList<>();
      newParentObjectTypes.add(objectType);
      if (currentParentObjectTypes != null) {
        newParentObjectTypes.addAll(currentParentObjectTypes);
      }

      if (subHierarchy instanceof JSONObject && ((JSONObject) subHierarchy).length() > 0) {
        generateObjectTypeHierarchyRec((JSONObject) subHierarchy, newParentObjectTypes, targetMap);
      }

      // Store current result
      List<String> existingParentObjectTypes = targetMap.get(objectType);
      if (existingParentObjectTypes != null) {
        throw new IllegalStateException(
            "Object type '"
                + objectType
                + "' has ambiguous parent object types: ["
                + CollectionUtility.format(existingParentObjectTypes)
                + "] vs. ["
                + CollectionUtility.format(newParentObjectTypes)
                + "]");
      }
      targetMap.put(objectType, newParentObjectTypes);
    }
  }
  @Test
  public void testVisit() throws Exception {
    final BlockingCountDownLatch latch = new BlockingCountDownLatch(3);

    IFuture<Void> future1 =
        Jobs.getJobManager()
            .schedule(
                new IRunnable() {

                  @Override
                  public void run() throws Exception {
                    latch.countDownAndBlock();
                  }
                },
                Jobs.newInput()
                    .withRunContext(RunContexts.copyCurrent())
                    .withExceptionHandling(null, false));

    IFuture<Void> future2 =
        Jobs.getJobManager()
            .schedule(
                new IRunnable() {

                  @Override
                  public void run() throws Exception {
                    latch.countDownAndBlock();
                  }
                },
                Jobs.newInput()
                    .withRunContext(RunContexts.copyCurrent())
                    .withExceptionHandling(null, false));

    IFuture<Void> future3 =
        Jobs.getJobManager()
            .schedule(
                new IRunnable() {

                  @Override
                  public void run() throws Exception {
                    latch.countDownAndBlock();
                  }
                },
                Jobs.newInput()
                    .withRunContext(RunContexts.copyCurrent())
                    .withExceptionHandling(null, false));

    assertTrue(latch.await());

    // RUN THE TEST
    final Set<IFuture<?>> protocol = new HashSet<>();
    Jobs.getJobManager()
        .visit(
            Jobs.newFutureFilterBuilder().andMatchFuture(future1, future2, future3).toFilter(),
            new IVisitor<IFuture<?>>() {

              @Override
              public boolean visit(IFuture<?> future) {
                protocol.add(future);
                return true;
              }
            });

    // VERIFY
    assertEquals(CollectionUtility.hashSet(future1, future2, future3), protocol);
  }
  @Test
  public void testShutdown() throws Exception {
    final Set<String> protocol =
        Collections.synchronizedSet(
            new HashSet<String>()); // synchronized because modified/read by different threads.

    final BlockingCountDownLatch setupLatch = new BlockingCountDownLatch(3);
    final BlockingCountDownLatch verifyLatch = new BlockingCountDownLatch(3);

    Jobs.getJobManager()
        .schedule(
            new IRunnable() {

              @Override
              public void run() throws Exception {
                try {
                  setupLatch.countDownAndBlock();
                } catch (InterruptedException e) {
                  protocol.add("interrupted-1");
                } finally {
                  verifyLatch.countDown();
                }
              }
            },
            Jobs.newInput()
                .withRunContext(RunContexts.copyCurrent())
                .withExceptionHandling(null, false));

    Jobs.getJobManager()
        .schedule(
            new IRunnable() {

              @Override
              public void run() throws Exception {
                try {
                  setupLatch.countDownAndBlock();
                } catch (InterruptedException e) {
                  protocol.add("interrupted-2");
                } finally {
                  verifyLatch.countDown();
                }
              }
            },
            Jobs.newInput()
                .withRunContext(RunContexts.copyCurrent())
                .withExceptionHandling(null, false));

    Jobs.getJobManager()
        .schedule(
            new IRunnable() {

              @Override
              public void run() throws Exception {
                try {
                  setupLatch.countDownAndBlock();
                } catch (InterruptedException e) {
                  protocol.add("interrupted-3");
                } finally {
                  verifyLatch.countDown();
                }
              }
            },
            Jobs.newInput()
                .withRunContext(RunContexts.copyCurrent())
                .withExceptionHandling(null, false));

    assertTrue(setupLatch.await());

    // RUN THE TEST
    Jobs.getJobManager().shutdown();

    // VERIFY
    assertTrue(verifyLatch.await());

    assertEquals(
        CollectionUtility.hashSet("interrupted-1", "interrupted-2", "interrupted-3"), protocol);

    try {
      Jobs.schedule(mock(IRunnable.class), Jobs.newInput());
      fail("AssertionError expected");
    } catch (AssertionException e) {
      // NOOP
    }
  }
 protected Set<String> getExistingTables() {
   StringArrayHolder tables = new StringArrayHolder();
   SQL.selectInto(SQLs.SELECT_TABLE_NAMES, new NVPair("result", tables));
   return CollectionUtility.hashSet(tables.getValue());
 }
 @Override
 protected List<String> getConfiguredFileExtensions() {
   return CollectionUtility.arrayList("png", "bmp", "jpg", "jpeg", "gif");
 }