package org.nightlabs.jfire.personrelation.trade.ui;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jdo.FetchPlan;
import javax.jdo.JDOHelper;
import javax.security.auth.login.LoginException;
import org.apache.commons.lang.ArrayUtils;
import org.apache.log4j.Logger;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.widgets.Composite;
import org.nightlabs.base.ui.notification.NotificationAdapterJob;
import org.nightlabs.base.ui.notification.SelectionManager;
import org.nightlabs.jdo.NLJDOHelper;
import org.nightlabs.jdo.ObjectID;
import org.nightlabs.jfire.base.JFireEjb3Factory;
import org.nightlabs.jfire.base.login.ui.Login;
import org.nightlabs.jfire.person.Person;
import org.nightlabs.jfire.personrelation.PersonRelationType;
import org.nightlabs.jfire.personrelation.dao.PersonRelationDAO;
import org.nightlabs.jfire.personrelation.id.PersonRelationTypeID;
import org.nightlabs.jfire.personrelation.trade.ui.tucked.TuckedPersonRelationTree;
import org.nightlabs.jfire.personrelation.trade.ui.tucked.TuckedPersonRelationTreeController;
import org.nightlabs.jfire.personrelation.trade.ui.tucked.TuckedPersonRelationTreeLabelProviderDelegate;
import org.nightlabs.jfire.personrelation.trade.ui.tucked.TuckedPersonRelationTreeNode;
import org.nightlabs.jfire.personrelation.trade.ui.tucked.TuckedPropertySetTreeLabelProviderDelegate;
import org.nightlabs.jfire.personrelation.ui.AbstractPersonRelationTreeView;
import org.nightlabs.jfire.personrelation.ui.tree.NodalHierarchyHandler;
import org.nightlabs.jfire.personrelation.ui.tree.PersonRelationTree;
import org.nightlabs.jfire.personrelation.ui.tree.PersonRelationTreeController;
import org.nightlabs.jfire.personrelation.ui.tree.PersonRelationTreeUtil;
import org.nightlabs.jfire.prop.id.PropertySetID;
import org.nightlabs.jfire.trade.LegalEntity;
import org.nightlabs.jfire.trade.TradeManagerRemote;
import org.nightlabs.jfire.trade.dao.LegalEntityDAO;
import org.nightlabs.jfire.trade.ui.TradePlugin;
import org.nightlabs.jfire.transfer.id.AnchorID;
import org.nightlabs.notification.NotificationEvent;
import org.nightlabs.notification.NotificationListener;
/**
* A slightly different portrayal of nodes in the {@link PersonRelationTree}; i.e. they are 'tucked' with the idea
* of space conservation when search results returns a long list of hits.
*
* See notes on "Search by Association".
*
* @author khaireel (at) nightlabs (dot) de
*/
public class TuckedPersonRelationTreeView extends
AbstractPersonRelationTreeView<TuckedPersonRelationTreeNode, TuckedPersonRelationTree> {
public static final String ID_VIEW = TuckedPersonRelationTreeView.class.getName();
private static final Logger logger = Logger.getLogger(TuckedPersonRelationTreeView.class);
private static final int DEFAULT_MAX_SEARCH_DEPTH = 100;
// This handles the system of listeners needed to engage the Lazy-Tree-nodes to perform the
// behaviour that automatically expands a given set of hiearchical paths, whenever available.
protected NodalHierarchyHandler<TuckedPersonRelationTreeNode, TuckedPersonRelationTree> nodalHierachyHandler = null;
protected Set<PersonRelationTypeID> allowedRelationTypeIDs;
// The currentPersonID that has focus.
protected PropertySetID currentPersonID = null;
@Override
public void createPartContents(Composite parent) {
super.createPartContents(parent);
// Initialise a system of listeners for the personRelationTree that will expand it accordingly.
// This is expected to be cleaner, more efficient, extendable, and more debug-friendly.
nodalHierachyHandler = new NodalHierarchyHandler<TuckedPersonRelationTreeNode, TuckedPersonRelationTree>(getPersonRelationTree());
}
/**
* Creates a new instance of the PersonRelationTree, and initialises it as completely as possible; i.e. supply the
* delegates it requires.
*/
@Override
protected TuckedPersonRelationTree createAndInitPersonRelationTree(Composite parent) {
TuckedPersonRelationTree personRelationTree = new TuckedPersonRelationTree(parent, false); // Without the drill-down adapter.
// Require additional fetch group(s) when dealing with the label providers related to the tree.
Object[] fetchGroupPersonRelation = ArrayUtils.addAll(PersonRelationTreeController.FETCH_GROUPS_PERSON_RELATION, new String[] {Person.FETCH_GROUP_FULL_DATA} );
PersonRelationTreeController<TuckedPersonRelationTreeNode> personRelationTreeController = personRelationTree.getPersonRelationTreeController();
personRelationTreeController.setPersonRelationFetchGroups((String[]) fetchGroupPersonRelation);
// Special delegate(s).
personRelationTree.addPersonRelationTreeLabelProviderDelegate(new TuckedPropertySetTreeLabelProviderDelegate(personRelationTreeController));
personRelationTree.addPersonRelationTreeLabelProviderDelegate(new TuckedPersonRelationTreeLabelProviderDelegate(personRelationTreeController));
return personRelationTree;
}
/**
* Set up the ORDERED set of context-menus into the {@link TuckedPersonRelationTree}.
*/
@Override
protected void registerContextMenuContibutions(TuckedPersonRelationTree personRelationTree) {
personRelationTree.addContextMenuContribution(new SelectBusinessPartnerTreeItemAction("Focus trade on this business partner"));
}
/**
* Initialises all other listeners that the {@link TuckedPersonRelationTree} requires for its fundamental operational behaviour.
*/
@Override
protected void initPersonRelationTreeListeners(TuckedPersonRelationTree personRelationTree) {
// Notifies other view(s) that may wish to react upon the current selection in the tree, in the TradePlugin.ZONE_SALE.
personRelationTree.addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
if (event.getSelection().isEmpty())
return;
Object selectedElement = getPersonRelationTree().getFirstSelectedElement();
if (selectedElement instanceof TuckedPersonRelationTreeNode) {
TuckedPersonRelationTreeNode node = (TuckedPersonRelationTreeNode) selectedElement;
if (logger.isInfoEnabled() && node != null) {
logger.debug(":: Click: " + node.toDebugString());
String str = "\n" + PersonRelationTreeUtil.showObjectIDs("PS-IDs to root", node.getPropertySetIDsToRoot(), 10);
str += "\n" + PersonRelationTreeUtil.showObjectIDs("PR-IDs to root", node.getJDOObjectIDsToRoot(), 10);
logger.info(str);
}
if (node != null) {
PropertySetID personID = node.getPropertySetID();
if (personID != null)
SelectionManager.sharedInstance().notify(new NotificationEvent(this, TradePlugin.ZONE_SALE, personID, Person.class));
}
}
}
});
}
/**
* Creates a NotificationListener that defines the behaviour of this View with respect to whatever Perspective.
*/
@Override
protected NotificationListener createAndRegisterNotificationListenerLegalEntitySelected(TuckedPersonRelationTree personRelationTree) {
final NotificationListener notificationListener = new NotificationAdapterJob("Processing legal entity...")
{
public void notify(org.nightlabs.notification.NotificationEvent notificationEvent) {
// Kai. Revised behaviour and display of the PersonRelationTree (consolidated and optimised: from jfire_1.0 + BEHR).
// i.e. The root of the tree represents the mother of all organisation(s) currently having the currently
// input Person; and the Person entry itself (can also be several instances) is expanded from
// whichever branch(es) it comes from. The input Person itself will be duly highlighted.
// --> If multiple instances exists, then (at least for now) ONE of them will be selected.
// --> Also, it is possible to have multiple rootS, symbolising multiple motherS of organisationS.
//
// The root (or roots) of the PersonRelationTree shall now have a new interpreted meaning. It refers
// to the c^ \elemOf C.
Object subject = notificationEvent.getFirstSubject();
PropertySetID personID = null;
if ( !(subject instanceof PropertySetID) ) {
AnchorID legalEntityID = (AnchorID) notificationEvent.getFirstSubject();
LegalEntity legalEntity = null;
if (legalEntityID != null) {
legalEntity = LegalEntityDAO.sharedInstance().getLegalEntity(
legalEntityID,
new String[] { FetchPlan.DEFAULT, LegalEntity.FETCH_GROUP_PERSON },
NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT,
getProgressMonitor()
);
}
personID = (PropertySetID) (legalEntity == null ? null : JDOHelper.getObjectId(legalEntity.getPerson()));
}
else
personID = (PropertySetID) subject;
// Ensures that we don't unnecessarily retrieve the relationRootNodes for one that has already been retrieved and on display.
if (personID != null && (currentPersonID == null || currentPersonID != personID)) {
if (logger.isDebugEnabled())
logger.debug("---> personID:" + PersonRelationTreeUtil.showObjectID(personID) + ", currentPersonID:" + PersonRelationTreeUtil.showObjectID(currentPersonID));
// Starting with the personID, we retrieve outgoing paths from it. Each path traces the personID's
// relationship up through the hierachy of organisations, and terminates under one of the following
// three conditions:
// 1. When it reaches the mother of all subsidiary organisations (known as c^ \elemof C).
// 2. When it detects a cyclic / nested-cyclic relationship between subsidiary-groups.
// 3. When the length of the path reaches the preset DefaultMaximumSearchDepth.
// For the sake of simplicity, let c^ be the terminal element in a path. Then all the unique c^'s
// collated from the returned paths are the new roots for PersonRelationTree.
// See original comments in revision #16575.
//
// Since 2010.01.24. Kai.
// The method 'getRelationRootNodes()' returns two Sets of Deque-paths in a single Map, distinguished by the following keys:
// 1. Key[PropertySetID.class] :: Deque-path(s) to root(s) containing only PropertySetIDs.
// 2. Key[PersonRelationID.class] :: Deque-path(s) to root(s) containing contigious PersonRelationID elements ending with the terminal PropertySetID.
try {
Map<Class<? extends ObjectID>, List<Deque<ObjectID>>> relatablePathsToRoots = PersonRelationDAO.sharedInstance().getRelationRootNodes(
getAllowedPersonRelationTypes(), personID, DEFAULT_MAX_SEARCH_DEPTH, getProgressMonitor());
((TuckedPersonRelationTreeController)getPersonRelationTree().getPersonRelationTreeController()).setTuckedPaths(relatablePathsToRoots);
final Set<PropertySetID> rootIDs = nodalHierachyHandler.initRelatablePathsToRoots(relatablePathsToRoots);
if (logger.isDebugEnabled())
logger.debug(PersonRelationTreeUtil.showObjectIDs("---> rootIDs:", rootIDs, 10));
// Done and ready. Update the tree.
currentPersonID = personID;
getPersonRelationTree().getDisplay().asyncExec(new Runnable() {
public void run() {
if (!getPersonRelationTree().isDisposed())
getPersonRelationTree().setInputPersonIDs(rootIDs);
}
});
} catch (Exception e) {
e.printStackTrace(); // Failed to retrieve path!?? Something must have been null.
}
}
}
};
SelectionManager.sharedInstance().addNotificationListener(
TradePlugin.ZONE_SALE,
LegalEntity.class, notificationListener
);
personRelationTree.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent event) {
SelectionManager.sharedInstance().removeNotificationListener(
TradePlugin.ZONE_SALE,
LegalEntity.class, notificationListener
);
}
});
return notificationListener;
}
/**
* Given a set of {@link PropertySetID}s, invoke the {@link TradeManagerRemote} to retrieve the respective
* {@link AnchorID}s, and subsequently triggers a {@link NotificationEvent} through the {@link TradePlugin}.ZONE_SALE (for example),
* by passing the {@link AnchorID}s in order of appearance from the input {@link PropertySetID}s.
*/
@Override
protected void prepareAndHandleNotification(PropertySetID... propertySetIDs) {
try {
TradeManagerRemote tm = JFireEjb3Factory.getRemoteBean(TradeManagerRemote.class, Login.getLogin().getInitialContextProperties());
AnchorID[] anchorIDs = new AnchorID[propertySetIDs.length];
for (int i=0; i<propertySetIDs.length; i++) {
LegalEntity le = propertySetIDs[i] == null ? null :
tm.getLegalEntityForPerson(propertySetIDs[i], new String[] {FetchPlan.DEFAULT}, NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT);
anchorIDs[i] = (AnchorID) (le == null ? null : JDOHelper.getObjectId(le));
}
SelectionManager.sharedInstance().notify(
new NotificationEvent(this, TradePlugin.ZONE_SALE, anchorIDs, new Class<?>[] {LegalEntity.class}));
} catch (LoginException e) {
throw new RuntimeException(e);
}
}
/**
* @return the Set of {@link PersonRelationTypeID}s, which guides the search for the appropriate paths from the
* person-relation graph, given an input {@link PropertySetID}.
*/
protected Set<PersonRelationTypeID> getAllowedPersonRelationTypes() {
if (allowedRelationTypeIDs == null) {
allowedRelationTypeIDs = new HashSet<PersonRelationTypeID>();
allowedRelationTypeIDs.add(PersonRelationType.PredefinedRelationTypes.subsidiary);
allowedRelationTypeIDs.add(PersonRelationType.PredefinedRelationTypes.employed);
allowedRelationTypeIDs.add(PersonRelationType.PredefinedRelationTypes.child);
allowedRelationTypeIDs.add(PersonRelationType.PredefinedRelationTypes.friend);
}
return allowedRelationTypeIDs;
}
}