/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.ace.webui.vaadin.component;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.ace.client.repository.Association;
import org.apache.ace.client.repository.ObjectRepository;
import org.apache.ace.client.repository.RepositoryAdmin;
import org.apache.ace.client.repository.RepositoryObject;
import org.apache.ace.client.repository.RepositoryObject.WorkingState;
import org.apache.ace.webui.NamedObject;
import org.apache.ace.webui.UIExtensionFactory;
import org.apache.ace.webui.domain.NamedObjectFactory;
import org.apache.ace.webui.vaadin.AssociationManager;
import org.apache.ace.webui.vaadin.EditWindow;
import org.apache.felix.dm.Component;
import org.apache.felix.dm.DependencyManager;
import org.osgi.framework.ServiceReference;
import org.osgi.service.event.EventHandler;
import com.vaadin.data.Item;
import com.vaadin.data.Property;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.event.ItemClickEvent;
import com.vaadin.event.ItemClickEvent.ItemClickListener;
import com.vaadin.event.Transferable;
import com.vaadin.event.dd.DragAndDropEvent;
import com.vaadin.event.dd.DropHandler;
import com.vaadin.event.dd.TargetDetails;
import com.vaadin.event.dd.acceptcriteria.AcceptCriterion;
import com.vaadin.event.dd.acceptcriteria.Or;
import com.vaadin.terminal.Resource;
import com.vaadin.terminal.ThemeResource;
import com.vaadin.ui.Button;
import com.vaadin.ui.Embedded;
import com.vaadin.ui.Table;
import com.vaadin.ui.Table.CellStyleGenerator;
import com.vaadin.ui.TreeTable;
import com.vaadin.ui.Window.Notification;
/**
* Provides a custom table for displaying artifacts, features and so on.
*/
abstract class BaseObjectPanel<REPO_OBJ extends RepositoryObject, REPO extends ObjectRepository<REPO_OBJ>, LEFT_ASSOC_REPO_OBJ extends RepositoryObject, RIGHT_ASSOC_REPO_OBJ extends RepositoryObject> extends TreeTable implements EventHandler,
CellStyleGenerator, ValueChangeListener {
/**
* Drop handler for associations.
*/
private class AssociationDropHandler implements DropHandler {
public void drop(DragAndDropEvent event) {
Transferable transferable = event.getTransferable();
TargetDetails targetDetails = event.getTargetDetails();
if (!(transferable instanceof Table.TableTransferable) || !(targetDetails instanceof Table.AbstractSelectTargetDetails)) {
return;
}
Table.TableTransferable tt = (Table.TableTransferable) transferable;
Table.AbstractSelectTargetDetails ttd = (Table.AbstractSelectTargetDetails) targetDetails;
// get the active selection, but only if we drag from the same table
Set<?> selection = m_associations.isActiveTable(tt.getSourceComponent()) ? m_associations.getActiveSelection() : null;
if (tt.getSourceComponent().equals(m_leftTable)) {
if (selection != null) {
for (Object item : selection) {
createLeftSideAssociation(item, ttd.getItemIdOver());
}
}
else {
createLeftSideAssociation(tt.getItemId(), ttd.getItemIdOver());
}
}
else if (tt.getSourceComponent().equals(m_rightTable)) {
if (selection != null) {
for (Object item : selection) {
createRightSideAssociation(ttd.getItemIdOver(), item);
}
}
else {
createRightSideAssociation(ttd.getItemIdOver(), tt.getItemId());
}
}
}
public AcceptCriterion getAcceptCriterion() {
return new Or(VerticalLocationIs.MIDDLE);
}
}
private static enum Direction {
BOTH, LEFT, RIGHT;
boolean isGoLeft() {
return this == BOTH || this == LEFT;
}
boolean isGoRight() {
return this == BOTH || this == RIGHT;
}
}
/**
* Provides a small container for {@link UIExtensionFactory} instances.
*/
private static class UIExtensionFactoryHolder implements Comparable<UIExtensionFactoryHolder> {
private final ServiceReference m_serviceRef;
private final WeakReference<UIExtensionFactory> m_extensionFactory;
public UIExtensionFactoryHolder(ServiceReference serviceRef, UIExtensionFactory extensionFactory) {
m_serviceRef = serviceRef;
m_extensionFactory = new WeakReference<UIExtensionFactory>(extensionFactory);
}
/**
* {@inheritDoc}
*/
public int compareTo(UIExtensionFactoryHolder other) {
ServiceReference thatServiceRef = other.m_serviceRef;
ServiceReference thisServiceRef = m_serviceRef;
// Sort in reverse order so that the highest rankings come first...
return thatServiceRef.compareTo(thisServiceRef);
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof UIExtensionFactoryHolder)) {
return false;
}
UIExtensionFactoryHolder other = (UIExtensionFactoryHolder) obj;
return m_serviceRef.equals(other.m_serviceRef);
}
/**
* @return the {@link UIExtensionFactory}, can be <code>null</code> if it has been GC'd before this method call.
*/
public UIExtensionFactory getUIExtensionFactory() {
return m_extensionFactory.get();
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return m_serviceRef.hashCode() ^ m_extensionFactory.hashCode();
}
}
protected static final String ICON = "icon";
protected static final String OBJECT_NAME = "name";
protected static final String OBJECT_DESCRIPTION = "description";
protected static final String ACTION_UNLINK = "unlink";
protected static final String ACTION_DELETE = "delete";
protected static final int ICON_HEIGHT = 16;
protected static final int ICON_WIDTH = 16;
/** Empirically determined (most common width appears to be 30px). */
protected static final int FIXED_COLUMN_WIDTH = 30;
protected final AssociationHelper m_associations;
protected final AssociationManager m_associationManager;
protected final Class<REPO_OBJ> m_entityType;
private final List<UIExtensionFactoryHolder> m_extensionFactories;
private final String m_extensionPoint;
protected BaseObjectPanel<LEFT_ASSOC_REPO_OBJ, ?, ?, ?> m_leftTable;
protected BaseObjectPanel<RIGHT_ASSOC_REPO_OBJ, ?, ?, ?> m_rightTable;
/**
* Creates a new {@link BaseObjectPanel} instance.
*
* @param associations
* the associations for this panel;
* @param associationRemover
* the association remove to use for removing associations;
* @param name
* the name of this panel;
* @param extensionPoint
* the extension point to listen for;
* @param hasEdit
* <code>true</code> if double clicking an row in this table should show an editor, <code>false</code> to
* disallow editing.
*/
public BaseObjectPanel(AssociationHelper associations, AssociationManager associationRemover, String name, String extensionPoint, boolean hasEdit, Class<REPO_OBJ> entityType) {
super(name + "s");
m_associations = associations;
m_associationManager = associationRemover;
m_extensionFactories = new ArrayList<UIExtensionFactoryHolder>();
m_extensionPoint = extensionPoint;
m_entityType = entityType;
setSizeFull();
setAnimationsEnabled(false);
setCellStyleGenerator(this);
setSelectable(true);
setMultiSelect(true);
setImmediate(true);
setDragMode(TableDragMode.MULTIROW);
setColumnCollapsingAllowed(true);
defineTableColumns();
setItemIconPropertyId(ICON);
setHierarchyColumn(ICON);
setSortAscending(true);
setSortContainerPropertyId(OBJECT_NAME);
if (hasEdit) {
addListener(new ItemClickListener() {
public void itemClick(ItemClickEvent event) {
if (event.isDoubleClick()) {
handleItemDoubleClick(event.getItemId());
}
}
});
}
addListener(new Property.ValueChangeListener() {
@Override
public void valueChange(Property.ValueChangeEvent event) {
Property property = event.getProperty();
if (BaseObjectPanel.this == property) {
updateActiveTable();
}
}
});
}
/**
* Called by the dependency manager in case a new {@link UIExtensionFactory} is registered.
*
* @param ref
* the service reference of the new extension;
* @param factory
* the extension instance itself.
*/
public final void addExtension(ServiceReference ref, UIExtensionFactory factory) {
synchronized (m_extensionFactories) {
m_extensionFactories.add(new UIExtensionFactoryHolder(ref, factory));
}
}
@Override
public String getStyle(Object itemId, Object propertyId) {
Item item = getItem(itemId);
if (propertyId == null) {
updateItemIcon(itemId);
// no propertyId, styling row
if (m_associations.isAssociated(itemId)) {
return "associated";
}
if (m_associations.isRelated(itemId)) {
return "related";
}
// Try to highlight the parent of any dynamic link...
Collection<?> children = getChildren(itemId);
if (children != null && !children.isEmpty()) {
Set<?> activeSelection = m_associations.getActiveSelection();
if (Collections.disjoint(children, activeSelection)) {
// Not in the active selection, check whether we've got an associated or related child...
for (Object child : children) {
if (m_associations.isAssociated(child)) {
return "associated-parent";
}
if (m_associations.isRelated(child)) {
return "related-parent";
}
}
}
else {
// one of the children is selected...
return "associated-parent";
}
}
}
else if (OBJECT_DESCRIPTION.equals(propertyId)) {
return "description";
}
else if (ACTION_UNLINK.equals(propertyId)) {
Button unlinkButton = (Button) item.getItemProperty(propertyId).getValue();
if (unlinkButton != null) {
boolean enabled = m_associations.isAssociated(itemId);
unlinkButton.setEnabled(enabled);
}
}
return null;
}
/**
* @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
*/
public final void handleEvent(org.osgi.service.event.Event event) {
final RepositoryObject entity = (RepositoryObject) event.getProperty(RepositoryObject.EVENT_ENTITY);
final String topic = event.getTopic();
synchronized (getApplication()) {
if (isSupportedEntity(entity)) {
handleEvent(topic, entity, event);
}
else if (RepositoryAdmin.TOPIC_LOGIN.equals(topic)) {
populate();
}
}
}
/**
* Called by the dependency manager upon initialization of this component.
*
* @param component
* the component representing this object.
*/
public void init(Component component) {
populate();
DependencyManager dm = component.getDependencyManager();
component.add(dm
.createServiceDependency()
.setInstanceBound(true)
.setService(UIExtensionFactory.class, "(" + UIExtensionFactory.EXTENSION_POINT_KEY + "=" + m_extensionPoint + ")")
.setCallbacks("addExtension", "removeExtension"));
}
/**
* Removes all current items and (re)populates this table.
*/
public void populate() {
removeAllItems();
for (REPO_OBJ object : getAllRepositoryObjects()) {
addToTable(object);
}
// Ensure the table is properly sorted...
sort();
}
/**
* Called by the dependency manager in case a {@link UIExtensionFactory} is unregistered.
*
* @param ref
* the service reference of the extension;
* @param factory
* the extension instance itself.
*/
public final void removeExtension(ServiceReference ref, UIExtensionFactory factory) {
synchronized (m_extensionFactories) {
m_extensionFactories.remove(new UIExtensionFactoryHolder(ref, factory));
}
}
/**
* Sets the tables that are associated this this panel.
*
* @param leftTable
* the left-side table to associate with, can be <code>null</code>;
* @param rightTable
* the right-side table to associate with, can be <code>null</code>.
*/
public final void setAssociatedTables(BaseObjectPanel<LEFT_ASSOC_REPO_OBJ, ?, ?, ?> leftTable, BaseObjectPanel<RIGHT_ASSOC_REPO_OBJ, ?, ?, ?> rightTable) {
m_leftTable = leftTable;
m_rightTable = rightTable;
setDropHandler(new AssociationDropHandler());
}
/**
* Creates the left-hand side associations for a given repository object.
*
* @param leftObjectId
* the (left-hand side) repository object to create the associations for.
* @param rightObjectId
* the repository object to create the left-hand side associations;
*/
final void createLeftSideAssociation(Object leftObjectId, Object rightObjectId) {
Association<LEFT_ASSOC_REPO_OBJ, REPO_OBJ> association = doCreateLeftSideAssociation(String.valueOf(leftObjectId), String.valueOf(rightObjectId));
if (association != null) {
m_leftTable.recalculateRelations(Direction.RIGHT);
// Request the focus again...
focus();
}
}
/**
* Creates the right-hand side associations for a given repository object.
*
* @param leftObjectId
* the repository object to create the right-hand side associations;
* @param rightObjectId
* the (right-hand side) repository object to create the associations for.
*/
final void createRightSideAssociation(Object leftObjectId, Object rightObjectId) {
Association<REPO_OBJ, RIGHT_ASSOC_REPO_OBJ> association = doCreateRightSideAssociation(String.valueOf(leftObjectId), String.valueOf(rightObjectId));
if (association != null) {
m_rightTable.recalculateRelations(Direction.LEFT);
// Request the focus again...
focus();
}
}
/**
* Recalculates all relations.
*/
final void recalculateRelations(Direction direction) {
Set<String> associated = new HashSet<String>();
Set<String> related = new HashSet<String>();
collectRelations(direction, associated, related);
m_associations.updateRelations(associated, related);
refreshAllRowCaches(direction);
}
/**
* Removes the left-hand side associations for a given repository object.
*
* @param leftObject
* the (left-hand side) repository object to remove the associations for.
* @param rightObject
* the repository object to remove the left-hand side associations;
*/
final void removeLeftSideAssociation(LEFT_ASSOC_REPO_OBJ leftObject, REPO_OBJ rightObject) {
if (doRemoveLeftSideAssociation(leftObject, rightObject)) {
m_associations.clear();
m_leftTable.recalculateRelations(Direction.RIGHT);
// Request the focus again...
focus();
}
}
/**
* Removes the right-hand side associations for a given repository object.
*
* @param leftObject
* the repository object to remove the right-hand side associations;
* @param rightObject
* the (right-hand side) repository object to remove the associations for.
*/
final void removeRightSideAssocation(REPO_OBJ leftObject, RIGHT_ASSOC_REPO_OBJ rightObject) {
if (doRemoveRightSideAssociation(leftObject, rightObject)) {
m_associations.clear();
m_rightTable.recalculateRelations(Direction.LEFT);
// Request the focus again...
focus();
}
}
/**
* Updates the active table and recalculates all relations.
*/
final void updateActiveTable() {
m_associations.clear();
m_associations.updateActiveTable(this);
recalculateRelations(Direction.BOTH);
// request the focus...
focus();
}
/**
* Adds a given repository object to this table.
*
* @param object
* the repository object to add, cannot be <code>null</code>.
*/
protected final void addToTable(REPO_OBJ object) {
String itemId = object.getDefinition();
String parentId = getParentId(object);
if ((parentId != null) && !containsId(parentId)) {
Item parentItem = addItem(parentId);
if (parentItem != null) {
populateParentItem(object, parentId, parentItem);
}
}
Item item = addItem(itemId);
if (item != null) {
populateItem(object, item);
}
if (parentId != null) {
setParent(itemId, parentId);
setCollapsed(parentId, true);
setItemIcon(object);
}
setChildrenAllowed(itemId, false);
// Request the focus again...
focus();
}
/**
* Collects the item-IDs of the directly associated entities and the related entities based on the current
* selection.
*
* @param associated
* the collection with associated item-IDs, will be filled by this method;
* @param related
* the collection with related item-IDs, will be filled by this method.
*/
protected final void collectRelations(Direction direction, Collection<String> associated, Collection<String> related) {
Set<?> value = (Set<?>) getValue();
List<REPO_OBJ> selection = new ArrayList<REPO_OBJ>();
for (Object itemID : value) {
REPO_OBJ obj = getFromId(itemID);
if (obj != null) {
selection.add(obj);
}
}
collectRelations(direction, selection, new HashSet<Class<?>>(), associated, related);
}
protected abstract EditWindow createEditor(NamedObject object, List<UIExtensionFactory> extensions);
/**
* Factory method to create an embeddable icon.
*
* @param name
* the name of the icon to use (is also used as tooltip text);
* @param res
* the resource denoting the actual icon.
* @return an embeddable icon, never <code>null</code>.
*/
protected Embedded createIcon(String name, Resource res) {
Embedded embedded = new Embedded(name, res);
embedded.setType(Embedded.TYPE_IMAGE);
embedded.setDescription(name);
embedded.setHeight(ICON_HEIGHT + "px");
embedded.setWidth(ICON_WIDTH + "px");
return embedded;
}
/**
* Factory method to create an icon resource.
*
* @param iconName
* the base name of the icon to use, it will be appended with '.png'.
* @return a {@link Resource} denoting the icon.
*/
protected ThemeResource createIconResource(String iconName) {
return new ThemeResource("icons/" + iconName.toLowerCase() + ".png");
}
/**
* Creates a remove-item button for the given repository object.
*
* @param object
* the object to create a remove-item button, cannot be <code>null</code>.
* @return a remove-item button, never <code>null</code>.
*/
protected final Button createRemoveItemButton(REPO_OBJ object) {
return createRemoveItemButton(object, getDisplayName(object));
}
/**
* Creates a remove-item button for the given repository object.
*
* @param object
* the object to create a remove-item button, cannot be <code>null</code>;
* @param displayName
* the display name for the description of the button, cannot be <code>null</code>.
* @return a remove-item button, never <code>null</code>.
*/
protected final Button createRemoveItemButton(RepositoryObject object, String displayName) {
Button result = new Button();
result.setIcon(createIconResource("trash"));
result.setData(object.getDefinition());
result.setStyleName("small tiny");
result.setDescription("Delete " + displayName);
result.setDisableOnClick(true);
result.addListener(new Button.ClickListener() {
public void buttonClick(Button.ClickEvent event) {
try {
handleItemRemoveObject(event.getButton().getData());
}
catch (Exception e) {
// ACE-246: notify user when the removal failed!
getWindow().showNotification("Failed to remove item!", "<br/>Reason: " + e.getMessage(), Notification.TYPE_ERROR_MESSAGE);
}
}
});
return result;
}
/**
* Creates a remove-link button for the given repository object.
*
* @param object
* the object to create a remove-link button, cannot be <code>null</code>.
* @return a remove-link button, never <code>null</code>.
*/
protected final Button createRemoveLinkButton(REPO_OBJ object) {
return createRemoveLinkButton(object, getDisplayName(object));
}
/**
* Creates a remove-link button for the given repository object.
*
* @param object
* the object to create a remove-link button, cannot be <code>null</code>;
* @param displayName
* the display name for the description of the button, cannot be <code>null</code>.
* @return a remove-link button, never <code>null</code>.
*/
protected final Button createRemoveLinkButton(RepositoryObject object, String displayName) {
Button result = new Button();
result.setIcon(createIconResource("unlink"));
result.setStyleName("small tiny");
result.setData(object.getDefinition());
result.setDescription("Unlink " + displayName);
// Only enable this button when actually selected...
result.setEnabled(false);
result.setDisableOnClick(true);
result.addListener(new Button.ClickListener() {
public void buttonClick(Button.ClickEvent event) {
handleItemRemoveLink(event.getButton().getData());
}
});
return result;
}
/**
* Defines the table columns for this panel.
*/
protected void defineTableColumns() {
addContainerProperty(ICON, Resource.class, null, "", null, ALIGN_CENTER);
addContainerProperty(OBJECT_NAME, String.class, null);
addContainerProperty(OBJECT_DESCRIPTION, String.class, null);
addContainerProperty(ACTION_UNLINK, Button.class, null, "", null, ALIGN_CENTER);
addContainerProperty(ACTION_DELETE, Button.class, null, "", null, ALIGN_CENTER);
setColumnWidth(ICON, ICON_WIDTH);
setColumnWidth(ACTION_UNLINK, FIXED_COLUMN_WIDTH);
setColumnWidth(ACTION_DELETE, FIXED_COLUMN_WIDTH);
setColumnCollapsible(ICON, false);
setColumnCollapsible(ACTION_UNLINK, false);
setColumnCollapsible(ACTION_DELETE, false);
}
/**
* Does the actual creation of the left-hand side associations for a given repository object.
*
* @param leftObjectId
* the (left-hand side) object ID to create the associations for.
* @param rightObjectId
* the object ID to craete the left-hand side associations;
* @return the created {@link Association}, or <code>null</code> if the association could not be created.
*/
protected Association<LEFT_ASSOC_REPO_OBJ, REPO_OBJ> doCreateLeftSideAssociation(String leftObjectId, String rightObjectId) {
return null;
}
/**
* Does the actual creation of the right-hand side associations for a given repository object.
*
* @param leftObjectId
* the object ID to create the right-hand side associations;
* @param rightObjectId
* the (right-hand side) object ID to create the associations for.
* @return the created {@link Association}, or <code>null</code> if the association could not be created.
*/
protected Association<REPO_OBJ, RIGHT_ASSOC_REPO_OBJ> doCreateRightSideAssociation(String leftObjectId, String rightObjectId) {
return null;
}
/**
* Does the actual removal of the left-hand side associations for a given repository object.
*
* @param leftObject
* the (left-hand side) repository object to remove the associations for.
* @param rightObject
* the repository object to remove the left-hand side associations;
* @return <code>true</code> if the associations were removed, <code>false</code> if not.
*/
protected boolean doRemoveLeftSideAssociation(LEFT_ASSOC_REPO_OBJ leftObject, REPO_OBJ rightObject) {
return m_leftTable != null;
}
/**
* Does the actual removal of the right-hand side associations for a given repository object.
*
* @param leftObject
* the repository object to remove the right-hand side associations;
* @param rightObject
* the (right-hand side) repository object to remove the associations for.
* @return <code>true</code> if the associations were removed, <code>false</code> if not.
*/
protected boolean doRemoveRightSideAssociation(REPO_OBJ leftObject, RIGHT_ASSOC_REPO_OBJ rightObject) {
return m_rightTable != null;
}
/**
* Returns all repository objects.
*
* @return an {@link Iterable} with all repository objects, never <code>null</code>.
*/
protected final Iterable<REPO_OBJ> getAllRepositoryObjects() {
return getRepository().get();
}
/**
* Returns a user-friendly name for a given repository object.
*
* @param object
* the repository object to get the display name for, cannot be <code>null</code>.
* @return the display name, never <code>null</code>.
*/
protected abstract String getDisplayName(REPO_OBJ object);
/**
* Converts a table-id back to a concrete {@link RepositoryObject}.
*
* @param id
* the identifier of the {@link RepositoryObject}, cannot be <code>null</code>.
* @return a {@link RepositoryObject} instance for the given ID, can be <code>null</code> in case no such object is
* found.
*/
protected final REPO_OBJ getFromId(Object id) {
return getRepository().get((String) id);
}
/**
* @param object
* @return a display name for the parent of the given repository object, cannot be <code>null</code>.
*/
protected String getParentDisplayName(REPO_OBJ object) {
return object.getDefinition();
}
/**
* Determines the parent Id of a given repository object.
*
* @param object
* the repository object to determine the parent for, cannot be <code>null</code>.
* @return the ID of the parent for the given repository object, or <code>null</code> in case no parent could be
* determined.
*/
protected String getParentId(REPO_OBJ object) {
return null;
}
/**
* Returns the actual repository for objects.
*
* @return the actual repository for obtaining the repository objects, cannot be <code>null</code>.
*/
protected abstract REPO getRepository();
/**
* Returns the repository administrator.
*
* @return the repository admin, never <code>null</code>.
*/
protected abstract RepositoryAdmin getRepositoryAdmin();
/**
* Determines the working state for the given repository object.
*
* @param object
* the repository object to determine the working state for, cannot be <code>null</code>.
* @return the working state for the given repository object, never <code>null</code>.
*/
protected WorkingState getWorkingState(RepositoryObject object) {
return getRepositoryAdmin().getWorkingState(object);
}
/**
* Helper method to return the working state icon for the given repository object.
*
* @param object
* the repository object to get the icon for, cannot be <code>null</code>.
* @return an icon representing the working state of the given repository object, never <code>null</code>.
*/
protected Resource getWorkingStateIcon(RepositoryObject object) {
String name = getWorkingState(object).name();
return createIconResource("resource_workingstate_" + name);
}
/**
* @param topic
* the topic of the event;
* @param entity
* the entity of the event;
* @param event
* the original event.
*
* @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
*/
protected abstract void handleEvent(String topic, RepositoryObject entity, org.osgi.service.event.Event event);
/**
* Called whenever the user double clicks on a row.
*
* @param itemId
* the row/item ID of the double clicked item.
*/
protected void handleItemDoubleClick(Object itemId) {
RepositoryObject object = getFromId(itemId);
NamedObject namedObject = NamedObjectFactory.getNamedObject(object);
if (namedObject != null) {
showEditWindow(namedObject);
}
}
/**
* Called by the remove-link button to remove a link.
*
* @param itemID
* the ID of the item to remove from the repository, cannot be <code>null</code>.
*/
protected void handleItemRemoveLink(Object itemID) {
Set<?> selection = m_associations.getActiveSelection();
if (selection != null) {
if (m_associations.isActiveTable(m_leftTable)) {
for (Object itemId : selection) {
removeLeftSideAssociation(m_leftTable.getFromId(itemId), getFromId(itemID));
}
}
else if (m_associations.isActiveTable(m_rightTable)) {
for (Object itemId : selection) {
removeRightSideAssocation(getFromId(itemID), m_rightTable.getFromId(itemId));
}
}
}
}
/**
* Called by the remove-item button to remove a repository object from the repository.
*
* @param itemID
* the ID of the item to remove from the repository, cannot be <code>null</code>.
*/
protected void handleItemRemoveObject(Object itemID) {
getRepository().remove(getFromId(itemID));
}
/**
* Returns whether the given {@link RepositoryObject} can be handled by this panel.
*
* @param entity
* the entity to test, cannot be <code>null</code>.
* @return <code>true</code> if the entity is supported by this panel, <code>false</code> if not.
*/
protected abstract boolean isSupportedEntity(RepositoryObject entity);
/**
* Populates the given table item with information from the given repository object.
*
* @param object
* the repository object to take the information from, cannot be <code>null</code>;
* @param item
* the table item to populate, cannot be <code>null</code>.
*/
protected abstract void populateItem(REPO_OBJ object, Item item);
/**
* Populates the given table item with information about the parent for a given repository object.
*
* @param object
* the repository object to take the information from, cannot be <code>null</code>;
* @param parentId
* the ID of the parent, cannot be <code>null</code>;
* @param item
* the table item to populate, cannot be <code>null</code>.
*/
protected void populateParentItem(REPO_OBJ object, String parentId, Item item) {
item.getItemProperty(OBJECT_NAME).setValue(getParentDisplayName(object));
item.getItemProperty(OBJECT_DESCRIPTION).setValue("");
// XXX add unlink button when we can correctly determine dynamic links...
// item.getItemProperty(ACTION_UNLINK).setValue(new RemoveLinkButton(object));
// we *must* set a non-null icon for the parent as well to ensure that the tree-table open/collapse icon is
// rendered properly...
setItemIcon(parentId, createIconResource("resource_workingstate_unchanged"));
}
protected final void refreshAllRowCaches(Direction direction) {
if (direction.isGoLeft()) {
BaseObjectPanel ptr = this;
while (ptr != null) {
ptr.refreshRowCache();
ptr = ptr.m_leftTable;
}
}
if (direction.isGoRight()) {
BaseObjectPanel ptr = this;
while (ptr != null) {
ptr.refreshRowCache();
ptr = ptr.m_rightTable;
}
}
}
/**
* Removes a given repository object from this table.
*
* @param object
* the repository object to remove, cannot be <code>null</code>.
*/
protected final void removeFromTable(RepositoryObject object) {
String itemID = object.getDefinition();
Object parentID = getParent(itemID);
if (removeItem(itemID)) {
if ((parentID != null) && !hasChildren(parentID)) {
removeItem(parentID);
}
}
// Request the focus again...
focus();
}
protected final void setItemIcon(REPO_OBJ object) {
if (object != null) {
Resource icon = getWorkingStateIcon(object);
setItemIcon(object.getDefinition(), icon);
}
}
/**
* Shows an edit window for the given named object.
*
* @param object
* the named object to edit;
* @param main
* the main window to use.
*/
protected final void showEditWindow(NamedObject object) {
List<UIExtensionFactory> extensions = getExtensionFactories();
createEditor(object, extensions).show(getParent().getWindow());
}
/**
* Updates a given repository object in this table.
*
* @param object
* the repository object to update, cannot be <code>null</code>.
*/
protected final void update(REPO_OBJ object) {
if (object != null) {
String definition = object.getDefinition();
if (definition != null) {
Item item = getItem(definition);
if (item != null) {
populateItem(object, item);
}
}
}
}
protected final void updateItemIcon(Object itemId) {
setItemIcon(getFromId(itemId));
}
private void collectRelations(Direction direction, List<REPO_OBJ> selection, Set<Class<?>> seenTypes, Collection<String> associated, Collection<String> related) {
// We've already visited this entity...
seenTypes.add(m_entityType);
if (direction.isGoLeft() && m_leftTable != null && !seenTypes.contains(m_leftTable.m_entityType)) {
seenTypes.add(m_leftTable.m_entityType);
for (REPO_OBJ obj : selection) {
List<LEFT_ASSOC_REPO_OBJ> left = obj.getAssociations(m_leftTable.m_entityType);
extractDefinitions(associated, left);
// the associated items of our left-side table are the ones related to us...
m_leftTable.collectRelations(direction, left, seenTypes, related, related);
}
}
if (direction.isGoRight() && m_rightTable != null && !seenTypes.contains(m_rightTable.m_entityType)) {
seenTypes.add(m_rightTable.m_entityType);
for (REPO_OBJ obj : selection) {
List<RIGHT_ASSOC_REPO_OBJ> right = obj.getAssociations(m_rightTable.m_entityType);
extractDefinitions(associated, right);
// the associated items of our right-side table are the ones related to us...
m_rightTable.collectRelations(direction, right, seenTypes, related, related);
}
}
}
private void extractDefinitions(Collection<String> defs, List<? extends RepositoryObject> objects) {
if (defs == null) {
return;
}
for (RepositoryObject obj : objects) {
defs.add(obj.getDefinition());
}
}
/**
* @return a list of current extension factories, properly ordered, never <code>null</code>.
*/
private List<UIExtensionFactory> getExtensionFactories() {
List<UIExtensionFactory> extensions;
synchronized (m_extensionFactories) {
// Sort the list of extension factories...
Collections.sort(m_extensionFactories);
// Walk through the holders and fetch the extension factories one by one...
extensions = new ArrayList<UIExtensionFactory>(m_extensionFactories.size());
for (UIExtensionFactoryHolder holder : m_extensionFactories) {
UIExtensionFactory extensionFactory = holder.getUIExtensionFactory();
// Make sure only to use non-GCd factories...
if (extensionFactory != null) {
extensions.add(extensionFactory);
}
}
}
return extensions;
}
}