package org.nightlabs.jfire.personrelation.ui.tree; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import javax.jdo.FetchPlan; import javax.jdo.JDODetachedFieldAccessException; import javax.jdo.JDOHelper; import org.apache.log4j.Logger; import org.nightlabs.jdo.NLJDOHelper; import org.nightlabs.jdo.ObjectID; import org.nightlabs.jfire.base.ui.jdo.tree.lazy.ActiveJDOObjectLazyTreeController; import org.nightlabs.jfire.jdo.notification.TreeNodeMultiParentResolver; import org.nightlabs.jfire.person.Person; import org.nightlabs.jfire.personrelation.PersonRelation; import org.nightlabs.jfire.personrelation.PersonRelationComparator; import org.nightlabs.jfire.personrelation.PersonRelationFilterCriteria; import org.nightlabs.jfire.personrelation.PersonRelationParentResolver; import org.nightlabs.jfire.personrelation.PersonRelationType; import org.nightlabs.jfire.personrelation.dao.PersonRelationDAO; import org.nightlabs.jfire.personrelation.id.PersonRelationID; import org.nightlabs.jfire.personrelation.id.PersonRelationTypeID; import org.nightlabs.jfire.personrelation.ui.resource.Messages; import org.nightlabs.jfire.prop.PropertySet; import org.nightlabs.jfire.prop.dao.PropertySetDAO; import org.nightlabs.jfire.prop.dao.TrimmedPropertySetDAO; import org.nightlabs.jfire.prop.id.PropertySetID; import org.nightlabs.jfire.prop.id.StructFieldID; import org.nightlabs.progress.NullProgressMonitor; import org.nightlabs.progress.ProgressMonitor; import org.nightlabs.progress.SubProgressMonitor; import org.nightlabs.util.CollectionUtil; public class DefaultPersonRelationTreeControllerDelegate<N extends PersonRelationTreeNode> extends AbstractPersonRelationTreeControllerDelegate { private static final Logger logger = Logger.getLogger(DefaultPersonRelationTreeControllerDelegate.class); public static final String[] FETCH_GROUPS_PERSON = { FetchPlan.DEFAULT, // we only need the display-name, hence this should be sufficient }; public static final String[] FETCH_GROUPS_PERSON_RELATION = { FetchPlan.DEFAULT, PersonRelation.FETCH_GROUP_PERSON_RELATION_TYPE, PersonRelationType.FETCH_GROUP_NAME, PersonRelationType.FETCH_GROUP_ICON16x16DATA, PersonRelation.FETCH_GROUP_FROM_ID, PersonRelation.FETCH_GROUP_TO, PersonRelation.FETCH_GROUP_FROM_PERSON_RELATION_IDS, }; private PersonRelationTreeController<N> treeController; private Set<PersonRelationTypeID> personRelationTypeIncludeIDs; private Set<PersonRelationTypeID> personRelationTypeExcludeIDs; public DefaultPersonRelationTreeControllerDelegate(PersonRelationTreeController<N> treeController) { super(); this.treeController = treeController; } /** * @param personRelationTypeIncludeIDs * A list of {@link PersonRelationTypeID} this the results of * this treeController should be limited to. Or <code>null</code> * in order to not to limit the result. */ public void setPersonRelationTypeIncludeIDs(Set<PersonRelationTypeID> personRelationTypeIncludeIDs) { this.personRelationTypeIncludeIDs = personRelationTypeIncludeIDs; } /** * @param personRelationTypeExcludeIDs * A list of {@link PersonRelationTypeID} that should be excluded from the results * this treeController should display. Or <code>null</code> * in order to not to limit the result. */ public void setPersonRelationTypeExcludeIDs(Set<PersonRelationTypeID> personRelationTypeExcludeIDs) { this.personRelationTypeExcludeIDs = personRelationTypeExcludeIDs; } @Override public Collection<Class<? extends Object>> getJDOObjectClasses() { Set<Class<? extends Object>> classes = new HashSet<Class<? extends Object>>(); classes.add(PersonRelation.class); classes.add(Person.class); return classes; } @Override public Collection<Object> retrieveJDOObjects(Set<ObjectID> objectIDs, ProgressMonitor monitor) { monitor.beginTask(Messages.getString("org.nightlabs.jfire.personrelation.ui.PersonRelationTreeController.task.retrievingObjects.name"), 110); //$NON-NLS-1$ try { Set<PropertySetID> personIDs = null; Set<PersonRelationID> personRelationIDs = null; Set<ObjectID> objectIDsLeft = new HashSet<ObjectID>(objectIDs); // [Note #1]: This separates the PropertySetIDs from the PersonRelationIDs. // -----------------------------------------------------------------------------------------------------------| for (ObjectID objectID : objectIDs) if (objectID instanceof PropertySetID) { if (personIDs == null) personIDs = new HashSet<PropertySetID>(); personIDs.add((PropertySetID) objectID); objectIDsLeft.remove(objectID); } else if (objectID instanceof PersonRelationID) { if (personRelationIDs == null) personRelationIDs = new HashSet<PersonRelationID>(); personRelationIDs.add((PersonRelationID) objectID); objectIDsLeft.remove(objectID); } Collection<Object> result = new ArrayList<Object>(objectIDs.size()); int tixPerson = personIDs != null && !personIDs.isEmpty() ? 100 : 0; int tixRelation = personRelationIDs != null && !personRelationIDs.isEmpty() ? 100 : 0; tixPerson = 100 * 100 / Math.max(1, tixPerson + tixRelation); tixRelation = 100 * 100 / Math.max(1, tixPerson + tixRelation); monitor.worked(10); // [Note #2]: This handles each separated Set independently. // -----------------------------------------------------------------------------------------------------------| if (personIDs != null && !personIDs.isEmpty()) result = retrieveJDOObjectsByPropertySetIDs(result, personIDs, monitor, tixPerson); // Modularised. Since 2010.04.10. else monitor.worked(tixPerson); if (personRelationIDs != null && !personRelationIDs.isEmpty()) result = retrieveJDOObjectsByPersonRelationIDs(result, personRelationIDs, monitor, tixRelation); // Modularised. Since 2010.04.10. else monitor.worked(tixRelation); return result; } finally { monitor.done(); } } /** * Modularised for extended classes. * @see DefaultPersonRelationTreeControllerDelegate#retrieveJDOObjects(Set, ProgressMonitor) */ protected Collection<Object> retrieveJDOObjectsByPropertySetIDs(Collection<Object> result, Set<PropertySetID> personIDs, ProgressMonitor monitor, int tix) { if (getTrimmedPersonStructFields() == null) { result.addAll(PropertySetDAO.sharedInstance().getPropertySets( personIDs, FETCH_GROUPS_PERSON, NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT, new SubProgressMonitor(monitor, tix) )); } else { result.addAll(TrimmedPropertySetDAO.sharedInstance().getTrimmedPropertySets( personIDs, getTrimmedPersonStructFields(), FETCH_GROUPS_PERSON, NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT, new SubProgressMonitor(monitor, tix) )); } return result; } /** * Modularised for extended classes. * @see DefaultPersonRelationTreeControllerDelegate#retrieveJDOObjects(Set, ProgressMonitor) */ protected Collection<Object> retrieveJDOObjectsByPersonRelationIDs(Collection<Object> result, Set<PersonRelationID> personRelationIDs, ProgressMonitor monitor, int tix) { List<PersonRelation> personRelations = PersonRelationDAO.sharedInstance().getPersonRelations( personRelationIDs, getPersonRelationFetchGroups(), NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT, new SubProgressMonitor(monitor, tix) ); result.addAll(personRelations); replaceToPersonsWithTrimmedVersion(monitor, personRelations); return result; } protected void replaceToPersonsWithTrimmedVersion(ProgressMonitor monitor, List<PersonRelation> personRelations) { Set<StructFieldID> trimmedRelationToStructFields = getTrimmedPersonStructFields(); if (trimmedRelationToStructFields != null) { Map<Person, Collection<PersonRelation>> relationsToReplace = new HashMap<Person, Collection<PersonRelation>>(); for (PersonRelation personRelation : personRelations) { try { if (personRelation.getTo() != null) { Collection<PersonRelation> relations = relationsToReplace.get(personRelation.getTo()); if (relations == null) { relations = new LinkedList<PersonRelation>(); relationsToReplace.put(personRelation.getTo(), relations); } relations.add(personRelation); } } catch (JDODetachedFieldAccessException e) { // to was not detached, ignore } } Set<PropertySetID> propIDs = new HashSet<PropertySetID>(); for (Person person : relationsToReplace.keySet()) { propIDs.add((PropertySetID) JDOHelper.getObjectId(person)); } Collection<? extends PropertySet> trimmedPropertySets = TrimmedPropertySetDAO.sharedInstance().getTrimmedPropertySets(propIDs, trimmedRelationToStructFields, new String[] {PropertySet.FETCH_GROUP_FULL_DATA}, NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT, monitor); try { Field toField = PersonRelation.class.getDeclaredField("to"); toField.setAccessible(true); for (PropertySet propertySet : trimmedPropertySets) { Collection<PersonRelation> relations = relationsToReplace.get(propertySet); for (PersonRelation personRelation : relations) { toField.set(personRelation, propertySet); } } } catch (Exception e) { logger.error("Failed replacing PersonRelation-to with trimmed version", e); } } } protected Set<StructFieldID> getTrimmedPersonStructFields() { return null; } // /** // * This is the abstract method from the super class, which we don't use in this {@link PersonRelationTreeController}'s implementation. // * Instead, we are in favour of the other method: retrieveChildCount(Set<ObjectID> parentIDs, ProgressMonitor monitor). // * See comments in the superclass {@link ActiveJDOObjectLazyTreeController}. // */ // @Override // protected Collection<Object> retrieveJDOObjects(Set<ObjectID> objectIDs, ProgressMonitor monitor) { return null; } /** * @return the FetchGroups to retrieve the relevant information for a {@link PersonRelation}. * Override this accordingly. */ protected String[] getPersonRelationFetchGroups() { return fetchGroupPersonRelation; } private String[] fetchGroupPersonRelation = FETCH_GROUPS_PERSON_RELATION; public void setPersonRelationFetchGroups(String[] fetchGroupPersonRelation) { this.fetchGroupPersonRelation = fetchGroupPersonRelation; } @Override public Map<ObjectID, Long> retrieveChildCount(Set<? extends PersonRelationTreeNode> parentNodes, Set<ObjectID> parentIDs, ProgressMonitor monitor) { Collection<PropertySetID> rootPersonIDs = getRootPersonIDs(); if (rootPersonIDs == null) return Collections.emptyMap(); monitor.beginTask(Messages.getString("org.nightlabs.jfire.personrelation.ui.PersonRelationTreeController.task.retrievingChildCounts.name"), 110); //$NON-NLS-1$ try { Set<PropertySetID> personIDs = null; Set<PersonRelationID> personRelationIDs = null; Map<ObjectID, Long> result = new HashMap<ObjectID, Long>(parentIDs.size()); // [Note #1]: This separates the PropertySetIDs from the PersonRelationIDs. // -----------------------------------------------------------------------------------------------------------| for (ObjectID objectID : parentIDs) { if (objectID == null) { result.put(objectID, new Long(rootPersonIDs.size())); } else if (objectID instanceof PropertySetID) { if (personIDs == null) personIDs = new HashSet<PropertySetID>(); personIDs.add((PropertySetID) objectID); } else if (objectID instanceof PersonRelationID) { if (personRelationIDs == null) personRelationIDs = new HashSet<PersonRelationID>(); personRelationIDs.add((PersonRelationID) objectID); } } int tixPerson = personIDs != null && !personIDs.isEmpty() ? 100 : 1; int tixRelation = personRelationIDs != null && !personRelationIDs.isEmpty() ? 100 : 1; tixPerson = 100 * 100 / Math.max(1, tixPerson + tixRelation); tixRelation = 100 * 100 / Math.max(1, tixPerson + tixRelation); monitor.worked(10); // [Note #2]: This handles each separated Set independently. // -----------------------------------------------------------------------------------------------------------| if (personIDs != null && !personIDs.isEmpty()) result = retrieveChildCountByPropertySetIDs(result, parentNodes, personIDs, monitor, tixPerson); // Modularised. Since 2010.03.23. else monitor.worked(1); if (personRelationIDs != null && !personRelationIDs.isEmpty()) result = retrieveChildCountByPersonRelationIDs(result, parentNodes, personRelationIDs, monitor, tixRelation); // Modularised. Since 2010.03.23. else monitor.worked(1); return result; } finally { monitor.done(); } } /** * Modularised for extended classes. * Attention: The given parentNodes are not in a one-to-one correspondence with the given personIDs. In fact, one must assume that there are more nodes than * there are personIDs, and if a related node containing a particular personID is required, then one must write codes to search for the * correct node. * * @see DefaultPersonRelationTreeControllerDelegate#retrieveChildCount(Set, ProgressMonitor) */ protected Map<ObjectID, Long> retrieveChildCountByPropertySetIDs(Map<ObjectID, Long> result, Set<? extends PersonRelationTreeNode> parentNodes, Set<PropertySetID> personIDs, ProgressMonitor monitor, int tix) { ProgressMonitor subMonitor = new SubProgressMonitor(monitor, tix); subMonitor.beginTask(Messages.getString("org.nightlabs.jfire.personrelation.ui.PersonRelationTreeController.task.retrievingChildCounts.name"), personIDs.size()); //$NON-NLS-1$ for (PropertySetID personID : personIDs) { PersonRelationFilterCriteria filterCriteria = createPersonRelationFilterCriteria(personID); long personRelationCount = PersonRelationDAO.sharedInstance().getPersonRelationCount(filterCriteria, new NullProgressMonitor()); result.put(personID, personRelationCount); subMonitor.worked(1); } subMonitor.done(); return result; } private PersonRelationFilterCriteria createPersonRelationFilterCriteria(PropertySetID personID) { PersonRelationFilterCriteria filterCriteria = new PersonRelationFilterCriteria(); filterCriteria.setPersonRelationTypeIncludeIDs(personRelationTypeIncludeIDs); filterCriteria.setPersonRelationTypeExcludeIDs(personRelationTypeExcludeIDs); filterCriteria.setFromPersonID(personID); return filterCriteria; } /** * Modularised for extended classes. * Attention: The given parentNodes are not in a one-to-one correspondence with the given personIDs. In fact, one must assume that there are more nodes than * there are personIDs, and if a related node containing a particular personID is required, then one must write codes to search for the * correct node. * * @see DefaultPersonRelationTreeControllerDelegate#retrieveChildCount(Set, ProgressMonitor) */ protected Map<ObjectID, Long> retrieveChildCountByPersonRelationIDs (Map<ObjectID, Long> result, Set<? extends PersonRelationTreeNode> parentNodes, Set<PersonRelationID> personRelationIDs, ProgressMonitor monitor, int tix) { // Note: The set of personRelationIDs should be enough to process whatever information is required, but if further node-related information // is required, based on the personRelationIDs, then the reference to that node should already be available in the given set of parentNodes. // Though it must be noted that the order of appearances in the two Sets may not necessarily be in sync. List<PersonRelation> personRelations = PersonRelationDAO.sharedInstance().getPersonRelations( personRelationIDs, new String[] { FetchPlan.DEFAULT, PersonRelation.FETCH_GROUP_TO_ID, }, NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT, new SubProgressMonitor(monitor, tix / 2) ); ProgressMonitor subMonitor = new SubProgressMonitor(monitor, tix / 2); subMonitor.beginTask(Messages.getString("org.nightlabs.jfire.personrelation.ui.PersonRelationTreeController.task.retrievingChildCounts.name"), personRelations.size()); //$NON-NLS-1$ // In order to count with the filter, where we ensure that none of the unnecessary children should been repeated, // we need to check the IDs for comparison. But we don't need to make the server return us all the filtered IDs. Just to count them, and get the number. for (PersonRelation personRelation : personRelations) { PersonRelationID personRelationID = (PersonRelationID) JDOHelper.getObjectId(personRelation); long personRelationCount = 0; // Revised version. We now check our reference to the Set of parentNodes, to make sure we have the correct Node. N node = null; List<N> treeNodes = treeController.getTreeNodeList(personRelationID); if (treeNodes == null) { // Guard. Revert to the original methods. // But this SHOULD NEVER HAPPEN. personRelationCount = PersonRelationDAO.sharedInstance().getPersonRelationCount(createPersonRelationFilterCriteria(personRelation.getToID()), new SubProgressMonitor(monitor, 80)); result.put(personRelationID, personRelationCount); subMonitor.worked(1); } else { if (treeNodes.size() == 1) node = treeNodes.get(0); else { // If the mapping key for personRelationID returns more than one node, then we essentially need to find the correct one. for (N treeNode : treeNodes) if (parentNodes.contains(treeNode)) { node = treeNode; break; } } // -------------------------------------------------------------------------------------------------- ++ ------>> Set<PropertySetID> toPropertySetIDsToExclude = CollectionUtil.createHashSetFromCollection(node.getPropertySetIDsToRoot()); PersonRelationFilterCriteria filterCriteria = createPersonRelationFilterCriteria(personRelation.getToID()); filterCriteria.setToPropertySetIDsToExclude(toPropertySetIDsToExclude); personRelationCount = PersonRelationDAO.sharedInstance().getPersonRelationCount(filterCriteria, new SubProgressMonitor(monitor, 80)); // -------------------------------------------------------------------------------------------------- ++ ------>> result.put(personRelationID, personRelationCount); subMonitor.worked(1); } } subMonitor.done(); return result; } // -------------------------------------------------------------------------------------------------- ++ ------>> /** * This is the abstract method from the super class, which we don't use in this {@link DefaultPersonRelationTreeControllerDelegate} implementation. * Instead, we are in favour of the other method: retrieveChildObjectIDs(N parentNode, ProgressMonitor monitor). * See comments in the superclass {@link ActiveJDOObjectLazyTreeController}. */ @Override public Collection<ObjectID> retrieveChildObjectIDs(PersonRelationTreeNode parentNode, ProgressMonitor monitor) { // The filter: Don't add child if its ID is already listed in the parentNode's path to the root. Collection<PropertySetID> rootPersonIDs = getRootPersonIDs(); if (rootPersonIDs == null) return Collections.emptyList(); Collection<ObjectID> result = new ArrayList<ObjectID>(); // Note: It is from the index position of this Collection that we know the position of the child-nodes. // And if we are to impose any ordering of the nodes, this should be the place to affect it. Kai. ObjectID parentID = parentNode.getJdoObjectID(); monitor.beginTask(Messages.getString("org.nightlabs.jfire.personrelation.ui.PersonRelationTreeController.task.retrievingChildIDs.name"), 100); //$NON-NLS-1$ try { // [Note #1]: This handles the retrieval by the instance of the parentID. // -----------------------------------------------------------------------------------------------------------| if (parentID == null) result.addAll(rootPersonIDs); else if (parentID instanceof PropertySetID) result.addAll(retrieveChildObjectIDsByPropertySetIDs((N) parentNode, monitor)); // Modularised. Since 2010.03.23. else if (parentID instanceof PersonRelationID) result.addAll(retrieveChildObjectIDsByPersonRelationIDs((N) parentNode, monitor)); // Modularised. Since 2010.03.23. return result; } finally { monitor.done(); } } /** * Modularised for extended classes. * @see DefaultPersonRelationTreeControllerDelegate#retrieveChildObjectIDs(PersonRelationTreeNode, ProgressMonitor) */ protected Collection<ObjectID> retrieveChildObjectIDsByPropertySetIDs(N parentNode, ProgressMonitor monitor) { ObjectID parentID = parentNode.getJdoObjectID(); Set<PropertySetID> toPropertySetIDsToExclude = CollectionUtil.createHashSetFromCollection(parentNode.getPropertySetIDsToRoot()); PersonRelationFilterCriteria filterCriteria = createPersonRelationFilterCriteria((PropertySetID)parentID); filterCriteria.setToPropertySetIDsToExclude(toPropertySetIDsToExclude); Collection<PersonRelationID> filteredPersonRelationIDs = PersonRelationDAO.sharedInstance().getPersonRelationIDs( filterCriteria, new SubProgressMonitor(monitor, 80)); return CollectionUtil.castCollection(filteredPersonRelationIDs); } /** * Modularised for extended classes. * @see DefaultPersonRelationTreeControllerDelegate#retrieveChildObjectIDs(PersonRelationTreeNode, ProgressMonitor) */ protected Collection<ObjectID> retrieveChildObjectIDsByPersonRelationIDs(N parentNode, ProgressMonitor monitor) { Collection<ObjectID> result = new ArrayList<ObjectID>(); ObjectID parentID = parentNode.getJdoObjectID(); Set<PropertySetID> toPropertySetIDsToExclude = CollectionUtil.createHashSetFromCollection(parentNode.getPropertySetIDsToRoot()); Collection<PersonRelationID> personRelationIDs = Collections.singleton((PersonRelationID)parentID); List<PersonRelation> personRelations = PersonRelationDAO.sharedInstance().getPersonRelations( personRelationIDs, new String[] { FetchPlan.DEFAULT, PersonRelation.FETCH_GROUP_TO_ID, }, NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT, new SubProgressMonitor(monitor, 20) ); if (!personRelations.isEmpty()) { // -------------------------------------------------------------------------------------------------- ++ ------>> PersonRelation personRelation = personRelations.iterator().next(); PersonRelationFilterCriteria filterCriteria = createPersonRelationFilterCriteria(personRelation.getToID()); filterCriteria.setToPropertySetIDsToExclude(toPropertySetIDsToExclude); Collection<PersonRelationID> filteredPersonRelationIDs = PersonRelationDAO.sharedInstance().getPersonRelationIDs( filterCriteria, new SubProgressMonitor(monitor, 80)); // -------------------------------------------------------------------------------------------------- ++ ------>> result.addAll(filteredPersonRelationIDs); } return result; } protected PersonRelationComparator personRelationComparator = null; public PersonRelationComparator getPersonRelationComparator() { if (personRelationComparator == null) personRelationComparator = new PersonRelationComparator(); // Default. return personRelationComparator; } @Override public TreeNodeMultiParentResolver getPersonRelationParentResolverDelegate() { return new PersonRelationParentResolver(); } }