/** * Performs a deep comparison (using reflection) of two objects to determine whether they are * different. * * <p>If the object is a Question then the answer is treated specially because the original value * of the answer from our point of view is the value provided by the client in an Answer fact. If * no such fact exists then the value on the question itself is used. Scenarios are: * * <ul> * <li>Question which client has just answered - the new object is different if the answer is * not the value provided by the client. i.e. if the rules have changed it to something else * e.g. converting text to upper case. * <li>Another question - the new object is different if the answer is not the value on the * original object. * </ul> * * @param originalObject * @param newObject * @return */ private boolean different(TohuObject originalObject, TohuObject newObject) { if (!originalObject.equals(newObject)) { return true; } // special handling for Question answers if (originalObject instanceof Question) { Question originalQuestion = (Question) originalObject; String originalAnswer; if (clientAnswers != null && clientAnswers.containsKey(originalQuestion.getId())) { // original answer is the one provided by the client originalAnswer = clientAnswers.get(originalQuestion.getId()); } else { // original answer not provided by client so is contained in the original question originalAnswer = originalQuestion.getAnswer() == null ? null : originalQuestion.getAnswer().toString(); } Question newQuestion = (Question) newObject; String newAnswer = newQuestion.getAnswer() == null ? null : newQuestion.getAnswer().toString(); if (originalAnswer == null ? newAnswer != null : !originalAnswer.equals(newAnswer)) { return true; } } Class<?> clazz = originalObject.getClass(); do { // compare all non-static non-transient fields for (Field field : clazz.getDeclaredFields()) { int modifiers = field.getModifiers(); if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers)) { boolean answerField = field.isAnnotationPresent(Question.AnswerField.class); // answer fields are skipped because we have checked this already if (!answerField) { field.setAccessible(true); try { Object originalValue = field.get(originalObject); Object newValue = field.get(newObject); if (originalValue == null ? newValue != null : !originalValue.equals(newValue)) { return true; } } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } } } clazz = clazz.getSuperclass(); } while (clazz != null); return false; }
/** * @see * org.drools.event.rule.WorkingMemoryEventListener#objectRetracted(org.drools.event.rule.ObjectRetractedEvent) */ public void objectRetracted(ObjectRetractedEvent event) { logger.debug( "==> [ObjectRetracted: handle=" + event.getFactHandle() + "; object=" + event.getOldObject() + "]"); if (event.getOldObject() instanceof TohuObject) { TohuObject oldObject = (TohuObject) event.getOldObject(); String id = oldObject.getId(); logger.debug("==> ObjectRemoved: Removing Fact with ID [" + id + "] from Working Memry"); TohuObject originalObject = getOriginalObject(id); processChange(id, originalObject, null, oldObject, event.getFactHandle()); } }
/** * @see * org.drools.event.rule.WorkingMemoryEventListener#objectUpdated(org.drools.event.rule.ObjectUpdatedEvent) */ public void objectUpdated(ObjectUpdatedEvent event) { logger.debug( "==> [ObjectUpdated handle=" + event.getFactHandle() + "; object=" + event.getOldObject() + "]"); if (event.getObject() instanceof TohuObject) { TohuObject newObject = (TohuObject) event.getObject(); String id = newObject.getId(); logger.debug( "==> ObjectUpdated: Updating Fact with ID [" + id + "] that exists in Working Memry"); TohuObject originalObject = getOriginalObject(id); processChange(id, originalObject, newObject, newObject, event.getFactHandle()); } }
/** * Makes copies of the original value all the objects that we wish to track. * * <p>Shallow copies are sufficient since none of our objects contain children. (All lists are * stored as comma-delimited strings so they can be serialized nicely.) * * @param originalObjects */ public void initialise(Collection<?> originalObjects) { this.originalObjects = new HashMap<String, TohuObject>(); for (Object object : originalObjects) { if (object instanceof TohuObject) { TohuObject i = (TohuObject) object; try { this.originalObjects.put(i.getId(), (TohuObject) i.clone()); } catch (CloneNotSupportedException e) { // ignore } } else if (object instanceof Answer) { Answer answer = (Answer) object; storeClientAnswer(answer); } } }
/** * @see * org.drools.event.rule.WorkingMemoryEventListener#objectInserted(org.drools.event.rule.ObjectInsertedEvent) */ public void objectInserted(ObjectInsertedEvent event) { logger.debug( "==> [ObjectInserted: handle=" + event.getFactHandle() + "; object=" + event.getObject() + "]"); if (event.getObject() instanceof TohuObject) { TohuObject newObject = (TohuObject) event.getObject(); String id = newObject.getId(); TohuObject originalObject = getOriginalObject(id); logger.debug( "==>ObjectInserted: Inserting Tohu Fact with ID [" + id + "] into working memry"); processChange(id, originalObject, newObject, newObject, event.getFactHandle()); } else if (event.getObject() instanceof Answer) { Answer answer = (Answer) event.getObject(); logger.debug( "==>ObjectInserted: Inserting Answer Fact with value [" + answer.getValue() + "] into working memry"); storeClientAnswer(answer); } }
/** * Processes an object change from originalObject to newObject and determines whether this is a * create, update or delete. * * <p>Replaces any previous change for the same object. * * @param id * @param originalObject * @param newObject * @param recentObject A recent instance of this object which is used only for removing objects * from lists. This is required because it is possible for both oldObject and newObject to be * null if we are processing a delete right after a create. */ private void processChange( String id, TohuObject originalObject, TohuObject newObject, TohuObject recentObject, FactHandle factHandle) { // remove previous change if (create != null) { create.remove(recentObject); } if (update != null) { update.remove(recentObject); } if (delete != null) { if (recentObject instanceof InvalidAnswer) { delete.remove(recentObject); } else { delete.remove(new ItemId((Item) recentObject)); } } // determine what we need to do boolean isCreate = (originalObject == null || !originalObject.isActive()) && newObject != null && newObject.isActive(); boolean isUpdate = originalObject != null && originalObject.isActive() && newObject != null && newObject.isActive() && different(originalObject, newObject); boolean isDelete = originalObject != null && originalObject.isActive() && (newObject == null || !newObject.isActive()); // make the change if (isCreate) { if (create == null) { create = new HashMap<Object, FactHandle>(); } create.put(newObject, factHandle); } if (isUpdate) { if (update == null) { update = new ArrayList<Object>(); } update.add(newObject); } if (isDelete) { if (delete == null) { delete = new ArrayList<Object>(); } if (recentObject instanceof InvalidAnswer) { delete.add(recentObject); } else { delete.add(new ItemId((Item) recentObject)); } } // remove empty lists if (create != null && create.isEmpty()) { create = null; } if (update != null && update.isEmpty()) { update = null; } if (delete != null && delete.isEmpty()) { delete = null; } }