Java tutorial
/* * #%L * Alfresco Repository WAR Community * %% * Copyright (C) 2005 - 2016 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of * the paid license agreement will prevail. Otherwise, the software is * provided under the following open source license terms: * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * #L% */ package org.alfresco.web.ui.repo.component.property; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; import javax.faces.component.UIComponent; import javax.faces.component.UIInput; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.el.ValueBinding; import javax.faces.event.AbortProcessingException; import javax.faces.event.ActionEvent; import javax.faces.event.FacesEvent; import javax.transaction.UserTransaction; import org.alfresco.model.ContentModel; import org.alfresco.query.PagingRequest; import org.alfresco.repo.search.SearcherException; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authority.AuthorityDAO; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.search.LimitBy; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PersonService.PersonInfo; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; import org.alfresco.web.app.Application; import org.alfresco.web.bean.repository.DataDictionary; import org.alfresco.web.bean.repository.Node; import org.alfresco.web.bean.repository.Repository; import org.alfresco.web.bean.repository.User; import org.alfresco.web.ui.common.Utils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.web.jsf.FacesContextUtils; /** * Base class for all association editor components * * @author gavinc */ public abstract class BaseAssociationEditor extends UIInput { private final static Log logger = LogFactory.getLog(BaseAssociationEditor.class); private final static String ACTION_SEPARATOR = ";"; private final static int ACTION_NONE = -1; private final static int ACTION_REMOVE = 0; private final static int ACTION_SELECT = 1; private final static int ACTION_ADD = 2; private final static int ACTION_CHANGE = 3; private final static int ACTION_CANCEL = 4; private final static int ACTION_SEARCH = 5; private final static int ACTION_SET = 6; private final static String MSG_ERROR_ASSOC = "error_association"; private final static String FIELD_CONTAINS = "_contains"; private final static String FIELD_AVAILABLE = "_available"; /** I18N message strings */ private final static String MSG_ADD_TO_LIST_BUTTON = "add_to_list_button"; private final static String MSG_SELECT_BUTTON = "select_button"; private static final String MSG_NO_SELECTED_ITEMS = "no_selected_items"; private final static String MSG_SEARCH_SELECT_ITEMS = "search_select_items"; private final static String MSG_SEARCH_SELECT_ITEM = "search_select_item"; private final static String MSG_SELECTED_ITEMS = "selected_items"; private final static String MSG_REMOVE = "remove"; private final static String MSG_OK = "ok"; private final static String MSG_CANCEL = "cancel"; private final static String MSG_SEARCH = "search"; private final static String MSG_CHANGE = "change"; private final static String MSG_WARN_CANNOT_VIEW = "warn_cannot_view_target_details"; private static final String PERMISSION_SERVICE = "PermissionService"; protected String associationName; protected String availableOptionsSize; protected String selectItemMsg; protected String selectItemsMsg; protected String selectedItemsMsg; protected String noSelectedItemsMsg; protected String searchTerm; protected Boolean disabled; protected boolean showAvailable = false; /** Map of the original associations keyed by the noderef of the child */ protected Map<String, Object> originalAssocs; protected Map<String, Object> added; protected Map<String, Object> removed; /** List containing the currently available options */ protected List<NodeRef> availableOptions; protected String changingAssociation; protected boolean highlightedRow; // ------------------------------------------------------------------------------ // Component implementation /** * Default constructor */ public BaseAssociationEditor() { setRendererType(null); } /** * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) */ @SuppressWarnings("unchecked") public void restoreState(FacesContext context, Object state) { Object values[] = (Object[]) state; // standard component attributes are restored by the super class super.restoreState(context, values[0]); this.associationName = (String) values[1]; this.originalAssocs = (Map<String, Object>) values[2]; this.availableOptions = (List<NodeRef>) values[3]; this.availableOptionsSize = (String) values[4]; this.selectItemMsg = (String) values[5]; this.selectItemsMsg = (String) values[6]; this.selectedItemsMsg = (String) values[7]; this.changingAssociation = (String) values[8]; this.disabled = (Boolean) values[9]; this.searchTerm = (String) values[10]; } /** * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) */ public Object saveState(FacesContext context) { Object values[] = new Object[11]; // standard component attributes are saved by the super class values[0] = super.saveState(context); values[1] = this.associationName; values[2] = this.originalAssocs; values[3] = this.availableOptions; values[4] = this.availableOptionsSize; values[5] = this.selectItemMsg; values[6] = this.selectItemsMsg; values[7] = this.selectedItemsMsg; values[8] = this.changingAssociation; values[9] = this.disabled; values[10] = this.searchTerm; // NOTE: we don't save the state of the added and removed maps as these // need to be rebuilt everytime return (values); } /** * @see javax.faces.component.UIComponent#decode(javax.faces.context.FacesContext) */ @SuppressWarnings("unchecked") public void decode(FacesContext context) { Map requestMap = context.getExternalContext().getRequestParameterMap(); Map valuesMap = context.getExternalContext().getRequestParameterValuesMap(); String fieldId = getHiddenFieldName(); String value = (String) requestMap.get(fieldId); int action = ACTION_NONE; String removeRef = null; if (value != null && value.length() != 0) { // break up the action into it's parts int sepIdx = value.indexOf(ACTION_SEPARATOR); if (sepIdx != -1) { action = Integer.parseInt(value.substring(0, sepIdx)); removeRef = value.substring(sepIdx + 1); } else { action = Integer.parseInt(value); } } // gather the current state and queue an event String[] addedItems = (String[]) valuesMap.get(fieldId + FIELD_AVAILABLE); String contains = (String) requestMap.get(fieldId + FIELD_CONTAINS); AssocEditorEvent event = new AssocEditorEvent(this, action, addedItems, removeRef, contains); queueEvent(event); super.decode(context); } /** * @see javax.faces.component.UIComponent#broadcast(javax.faces.event.FacesEvent) */ public void broadcast(FacesEvent event) throws AbortProcessingException { if (event instanceof AssocEditorEvent) { AssocEditorEvent assocEvent = (AssocEditorEvent) event; Node node = (Node) getValue(); switch (assocEvent.Action) { case ACTION_SEARCH: { this.showAvailable = true; this.availableOptions = new ArrayList<NodeRef>(); this.searchTerm = assocEvent.Contains; getAvailableOptions(FacesContext.getCurrentInstance(), assocEvent.Contains); break; } case ACTION_SELECT: { this.showAvailable = true; break; } case ACTION_ADD: { addTarget(node, assocEvent.ToAdd); break; } case ACTION_REMOVE: { removeTarget(node, assocEvent.RemoveRef); break; } case ACTION_CHANGE: { this.changingAssociation = assocEvent.RemoveRef; this.showAvailable = true; break; } case ACTION_CANCEL: { this.showAvailable = false; break; } case ACTION_SET: { if (assocEvent.ToAdd != null && assocEvent.ToAdd.length > 0) { removeTarget(node, this.changingAssociation); addTarget(node, assocEvent.ToAdd); } break; } } } else { super.broadcast(event); } } /** * @see javax.faces.component.UIComponent#encodeBegin(javax.faces.context.FacesContext) */ public void encodeBegin(FacesContext context) throws IOException { if (isRendered() == false) { return; } // reset the highlighted row flag this.highlightedRow = false; ResponseWriter out = context.getResponseWriter(); // get the child associations currently on the node and any that have been added NodeService nodeService = Repository.getServiceRegistry(context).getNodeService(); // show the editable association component AssociationDefinition assocDef = getAssociationDefinition(context); if (assocDef == null) { logger.warn("Failed to find association definition for association '" + associationName + "'"); // add an error message as the property is not defined in the data dictionary String msg = MessageFormat.format(Application.getMessage(context, MSG_ERROR_ASSOC), new Object[] { this.associationName }); Utils.addErrorMessage(msg); } else { String targetType = assocDef.getTargetClass().getName().toString(); boolean allowMany = assocDef.isTargetMany(); populateAssocationMaps((Node) getValue(), nodeService); if (isDisabled()) { // show the current list of associations in a read-only form renderReadOnlyAssociations(context, out, nodeService); } else { // start outer table out.write("<table border='0' cellspacing='4' cellpadding='0' class='multiValueSelector'>"); if (allowMany) { out.write("<tr><td colspan='2'>1. "); out.write(getSelectItemsMsg()); out.write("</td></tr>"); // show the search field renderSearchField(context, out); // show available options for this association renderAvailableOptions(context, out, nodeService, targetType, allowMany); // add the Add to List button out.write("<tr><td colspan='2'>2. <input type='submit' value='"); out.write(Application.getMessage(context, MSG_ADD_TO_LIST_BUTTON)); out.write("' onclick=\""); out.write(generateFormSubmit(context, Integer.toString(ACTION_ADD))); out.write("\"/>"); // add some padding out.write("<tr><td height='6'></td></tr>"); out.write("<tr><td colspan='2'>"); out.write(getSelectedItemsMsg()); out.write("</td></tr>"); // show all the current associations out.write( "<tr><td colspan='2'><table cellspacing='0' cellpadding='2' border='0' class='selectedItems'>"); out.write("<tr><td colspan='2' class='selectedItemsHeader'>"); out.write(Application.getMessage(context, "name")); out.write("</td></tr>"); renderExistingAssociations(context, out, nodeService, allowMany); out.write("</table></td></tr>"); } else { if (this.showAvailable) { out.write("<tr><td colspan='2'>1. "); out.write(getSelectItemMsg()); out.write("</td></tr>"); // show the search field renderSearchField(context, out); // show available options for this association renderAvailableOptions(context, out, nodeService, targetType, allowMany); // add the ok and cancel buttons out.write("<tr><td colspan='2' align='right'><input type='submit' value='"); out.write(Application.getMessage(context, MSG_OK)); out.write("' onclick=\""); out.write(generateFormSubmit(context, Integer.toString(ACTION_SET))); out.write("\"/> <input type='submit' value='"); out.write(Application.getMessage(context, MSG_CANCEL)); out.write("' onclick=\""); out.write(generateFormSubmit(context, Integer.toString(ACTION_CANCEL))); out.write("\"/></td></tr>"); } else { // show the select button if required if ((allowMany == false && this.originalAssocs.size() == 0 && this.added.size() == 0) || (allowMany == false && this.originalAssocs.size() == 1 && this.removed.size() == 1 && this.added.size() == 0)) { out.write("<tr><td><input type='submit' value='"); out.write(Application.getMessage(context, MSG_SELECT_BUTTON)); out.write("' onclick=\""); out.write(generateFormSubmit(context, Integer.toString(ACTION_SELECT))); out.write("\"/></td></tr>"); } else { // show the current association renderExistingAssociations(context, out, nodeService, allowMany); } } } if (logger.isDebugEnabled()) { logger.debug("number original = " + this.originalAssocs.size()); logger.debug("number added = " + this.added.size()); logger.debug("number removed = " + this.removed.size()); } // close table out.write("</table>"); // output a hidden field containing the current value out.write("<input type='hidden' id='"); out.write(this.getClientId(context)); out.write("_current_value"); out.write("' name='"); out.write(this.getClientId(context)); out.write("_current_value"); out.write("' value='"); // if the current state will leave the node without any associations // do not set a value for the hidden field int numberAssocs = (this.originalAssocs.size() + this.added.size()) - this.removed.size(); if (numberAssocs > 0) { out.write(Integer.toString(numberAssocs)); } out.write("' />"); } } } /** * Returns the name of the association this component is editing * * @return Association name */ public String getAssociationName() { ValueBinding vb = getValueBinding("associationName"); if (vb != null) { this.associationName = (String) vb.getValue(getFacesContext()); } return this.associationName; } /** * Sets the name of the association this component will edit * * @param associationName Name of the association to edit */ public void setAssociationName(String associationName) { this.associationName = associationName; } /** * Determines whether the component should be rendered in a disabled state * * @return Returns whether the component is disabled */ public boolean isDisabled() { if (this.disabled == null) { ValueBinding vb = getValueBinding("disabled"); if (vb != null) { this.disabled = (Boolean) vb.getValue(getFacesContext()); } } if (this.disabled == null) { this.disabled = Boolean.FALSE; } return this.disabled; } /** * Determines whether the component should be rendered in a disabled state * * @param disabled true to disable the component */ public void setDisabled(boolean disabled) { this.disabled = disabled; } /** * Returns the size of the select control * * @return The size of the select control */ public String getAvailableOptionsSize() { if (this.availableOptionsSize == null) { this.availableOptionsSize = "4"; } return this.availableOptionsSize; } /** * Sets the size of the select control * * @param availableOptionsSize The size */ public void setAvailableOptionsSize(String availableOptionsSize) { this.availableOptionsSize = availableOptionsSize; } /** * Returns the message to display when no items have been selected, if one hasn't been * set it defaults to the message in the bundle under key 'no_selected_items'. * * @return The message */ public String getNoSelectedItemsMsg() { ValueBinding vb = getValueBinding("noSelectedItemsMsg"); if (vb != null) { this.noSelectedItemsMsg = (String) vb.getValue(getFacesContext()); } if (this.noSelectedItemsMsg == null) { this.noSelectedItemsMsg = Application.getMessage(getFacesContext(), MSG_NO_SELECTED_ITEMS); } return this.noSelectedItemsMsg; } /** * Sets the no selected items message to display in the UI * * @param noSelectedItemsMsg The message */ public void setNoSelectedItemsMsg(String noSelectedItemsMsg) { this.noSelectedItemsMsg = noSelectedItemsMsg; } /** * Returns the message to display for the selected items, if one hasn't been * set it defaults to the message in the bundle under key 'selected_items'. * * @return The message */ public String getSelectedItemsMsg() { ValueBinding vb = getValueBinding("selectedItemsMsg"); if (vb != null) { this.selectedItemsMsg = (String) vb.getValue(getFacesContext()); } if (this.selectedItemsMsg == null) { this.selectedItemsMsg = Application.getMessage(getFacesContext(), MSG_SELECTED_ITEMS); } return this.selectedItemsMsg; } /** * Sets the selected items message to display in the UI * * @param selectedItemsMsg The message */ public void setSelectedItemsMsg(String selectedItemsMsg) { this.selectedItemsMsg = selectedItemsMsg; } /** * Returns the message to display for select an item, if one hasn't been * set it defaults to the message in the bundle under key 'search_select_item'. * * @return The message */ public String getSelectItemMsg() { ValueBinding vb = getValueBinding("selectItemMsg"); if (vb != null) { this.selectItemMsg = (String) vb.getValue(getFacesContext()); } if (this.selectItemMsg == null) { this.selectItemMsg = Application.getMessage(getFacesContext(), MSG_SEARCH_SELECT_ITEM); } return this.selectItemMsg; } /** * Sets the select an item message to display in the UI * * @param selectItemMsg The message */ public void setSelectItemMsg(String selectItemMsg) { this.selectItemMsg = selectItemMsg; } /** * Returns the message to display for select items, if one hasn't been * set it defaults to the message in the bundle under key 'search_select_items'. * * @return The message */ public String getSelectItemsMsg() { ValueBinding vb = getValueBinding("selectItemsMsg"); if (vb != null) { this.selectItemsMsg = (String) vb.getValue(getFacesContext()); } if (this.selectItemsMsg == null) { this.selectItemsMsg = Application.getMessage(getFacesContext(), MSG_SEARCH_SELECT_ITEMS); } return this.selectItemsMsg; } /** * Sets the select items message to display in the UI * * @param selectItemsMsg The message */ public void setSelectItemsMsg(String selectItemsMsg) { this.selectItemsMsg = selectItemsMsg; } /** * Populates all the internal Maps with the appropriate association reference objects * * @param node The Node we are dealing with * @param nodeService The NodeService */ protected abstract void populateAssocationMaps(Node node, NodeService nodeService); /** * Renders the existing associations in a read-only form * * @param context FacesContext * @param out ResponseWriter * @param nodeService The NodeService * @throws IOException */ protected abstract void renderReadOnlyAssociations(FacesContext context, ResponseWriter out, NodeService nodeService) throws IOException; /** * Renders the existing associations in an editable form * * @param context FacesContext * @param out ResponseWriter * @param nodeService The NodeService * @param allowMany Whether multiple associations are allowed * @throws IOException */ protected abstract void renderExistingAssociations(FacesContext context, ResponseWriter out, NodeService nodeService, boolean allowMany) throws IOException; /** * Updates the component and node state to reflect an association being removed * * @param node The node we are dealing with * @param targetRef The noderef of the child to remove */ protected abstract void removeTarget(Node node, String targetRef); /** * Updates the component and node state to reflect an association being added * * @param node The node we are dealing with * @param toAdd The noderefs of the children to add */ protected abstract void addTarget(Node node, String[] toAdd); /** * Renders an existing association with the appropriate options * * @param context FacesContext * @param out Writer to write output to * @param nodeService The NodeService * @param targetRef The node at the end of the association being rendered * @param allowMany Whether the current association allows multiple children * @throws IOException */ protected void renderExistingAssociation(FacesContext context, ResponseWriter out, NodeService nodeService, NodeRef targetRef, boolean allowMany) throws IOException { boolean accessDenied = false; out.write("<tr><td class='"); if (this.highlightedRow) { out.write("selectedItemsRowAlt"); } else { out.write("selectedItemsRow"); } out.write("'>"); if (ContentModel.TYPE_PERSON.equals(nodeService.getType(targetRef))) { out.write(Utils.encode(User.getFullNameAndUserId(nodeService, targetRef))); } else if (ContentModel.TYPE_AUTHORITY_CONTAINER.equals(nodeService.getType(targetRef))) { // get display name, if not present strip prefix from group id String groupDisplayName = (String) nodeService.getProperty(targetRef, ContentModel.PROP_AUTHORITY_DISPLAY_NAME); if (groupDisplayName == null || groupDisplayName.length() == 0) { String group = (String) nodeService.getProperty(targetRef, ContentModel.PROP_AUTHORITY_NAME); groupDisplayName = group.substring(PermissionService.GROUP_PREFIX.length()); } out.write(Utils.encode(groupDisplayName)); } else { PermissionService permissionService = Repository.getServiceRegistry(context).getPermissionService(); if (permissionService.hasPermission(targetRef, PermissionService.READ) == AccessStatus.ALLOWED) { out.write(Utils.encode(Repository.getDisplayPath(nodeService.getPath(targetRef)))); out.write("/"); out.write(Utils.encode(Repository.getNameForNode(nodeService, targetRef))); } else { accessDenied = true; out.write(Application.getMessage(context, MSG_WARN_CANNOT_VIEW)); } } if (accessDenied) { out.write("</td><td> "); } else { out.write("</td><td class='"); if (this.highlightedRow) { out.write("selectedItemsRowAlt"); } else { out.write("selectedItemsRow"); } out.write("'><a href='#' title='"); out.write(Application.getMessage(context, MSG_REMOVE)); out.write("' onclick=\""); out.write(generateFormSubmit(context, ACTION_REMOVE + ACTION_SEPARATOR + targetRef.toString())); out.write("\"><img src='"); out.write(context.getExternalContext().getRequestContextPath()); out.write("/images/icons/delete.gif' border='0' width='13' height='16'/></a>"); if (allowMany == false) { out.write(" <a href='#' title='"); out.write(Application.getMessage(context, MSG_CHANGE)); out.write("' onclick=\""); out.write(generateFormSubmit(context, ACTION_CHANGE + ACTION_SEPARATOR + targetRef.toString())); out.write("\"><img src='"); out.write(context.getExternalContext().getRequestContextPath()); out.write("/images/icons/edit_icon.gif' border='0' width='12' height='16'/></a>"); } } out.write("</td></tr>"); this.highlightedRow = !this.highlightedRow; } /** * Renders the search fields * * @param context Faces Context * @param out The Response Writer * @throws IOException */ protected void renderSearchField(FacesContext context, ResponseWriter out) throws IOException { // TODO: externalise the max and size attributes out.write("<tr><td colspan='2'><input type='text' maxlength='1024' size='32' name='"); out.write(getClientId(context) + FIELD_CONTAINS); out.write("'"); if (this.searchTerm != null) { out.write(" value=\""); out.write(Utils.encode(this.searchTerm)); out.write("\""); } out.write("/> <input type='submit' value='"); out.write(Application.getMessage(context, MSG_SEARCH)); out.write("' onclick=\""); out.write(generateFormSubmit(context, Integer.toString(ACTION_SEARCH))); out.write("\"/></td></tr>"); } /** * Renders the <None> message * * @param context Faces Context * @param out Response Writer * @throws IOException */ protected void renderNone(FacesContext context, ResponseWriter out) throws IOException { out.write("<tr><td class='selectedItemsRow'>"); out.write(getNoSelectedItemsMsg()); out.write("</td></tr>"); } /** * Renders the list of available options for a new association * * @param context FacesContext * @param out Writer to write output to * @param nodeService The NodeService * @param targetType The type of the child at the end of the association * @param allowMany Whether the current association allows multiple children * @throws IOException */ protected void renderAvailableOptions(FacesContext context, ResponseWriter out, NodeService nodeService, String targetType, boolean allowMany) throws IOException { boolean itemsPresent = (this.availableOptions != null && this.availableOptions.size() > 0); out.write("<tr><td colspan='2'><select "); if (itemsPresent == false) { // rather than having a very slim select box set the width if there are no results out.write("style='width:240px;' "); } out.write("name='"); out.write(getClientId(context) + FIELD_AVAILABLE); out.write("' size='"); out.write(getAvailableOptionsSize()); out.write("'"); if (allowMany) { out.write(" multiple"); } out.write(">"); if (itemsPresent) { Node currentNode = (Node) getValue(); for (NodeRef item : this.availableOptions) { // show all the available options apart from the current node as we don't // want to create recursive associations!! if (item.toString().equals(currentNode.getNodeRef().toString()) == false) { if (ContentModel.TYPE_PERSON.equals(nodeService.getType(item))) { // if the node represents a person, show the full name and userid instead of the name String userName = (String) nodeService.getProperty(item, ContentModel.PROP_USERNAME); if (userName != null && (userName.equals(AuthenticationUtil.getGuestUserName()) == false)) { out.write("<option value='"); out.write(item.toString()); out.write("'>"); out.write(Utils.encode(User.getFullNameAndUserId(nodeService, item))); out.write("</option>"); } } else if (ContentModel.TYPE_AUTHORITY_CONTAINER.equals(nodeService.getType(item))) { // if the node represents a group, show the authority display name instead of the name String groupDisplayName = (String) nodeService.getProperty(item, ContentModel.PROP_AUTHORITY_DISPLAY_NAME); if (groupDisplayName == null || groupDisplayName.length() == 0) { String group = (String) nodeService.getProperty(item, ContentModel.PROP_AUTHORITY_NAME); groupDisplayName = group.substring(PermissionService.GROUP_PREFIX.length()); } out.write("<option value='"); out.write(item.toString()); out.write("'>"); out.write(Utils.encode(groupDisplayName)); out.write("</option>"); } else { out.write("<option value='"); out.write(item.toString()); out.write("'>"); out.write(Utils.encode(Repository.getDisplayPath(nodeService.getPath(item)))); out.write("/"); out.write(Utils.encode(Repository.getNameForNode(nodeService, item))); out.write("</option>"); } } } } out.write("</select></td></tr>"); } /** * Retrieves the AssociationDefinition for the association we are representing * * @param context Faces Context * @return The AssociationDefinition for the association, null if a definition does not exist */ protected AssociationDefinition getAssociationDefinition(FacesContext context) { // get some metadata about the association from the data dictionary DataDictionary dd = (DataDictionary) FacesContextUtils.getRequiredWebApplicationContext(context) .getBean(Application.BEAN_DATA_DICTIONARY); return dd.getAssociationDefinition((Node) getValue(), this.associationName); } /** * Retrieves the available options for the current association * * @param context Faces Context * @param contains The contains part of the query */ protected void getAvailableOptions(FacesContext context, String contains) { AssociationDefinition assocDef = getAssociationDefinition(context); if (assocDef != null) { // find and show all the available options for the current association String type = assocDef.getTargetClass().getName().toString(); if (type.equals(ContentModel.TYPE_AUTHORITY_CONTAINER.toString())) { UserTransaction tx = null; try { tx = Repository.getUserTransaction(context, true); tx.begin(); String safeContains = null; if (contains != null && contains.length() > 0) { safeContains = Utils.remove(contains.trim(), "\""); safeContains = safeContains.toLowerCase(); } // get all available groups AuthorityService authorityService = Repository.getServiceRegistry(context) .getAuthorityService(); Set<String> groups = authorityService.getAllAuthoritiesInZone(AuthorityService.ZONE_APP_DEFAULT, AuthorityType.GROUP); this.availableOptions = new ArrayList<NodeRef>(groups.size()); // get the NodeRef for each matching group AuthorityDAO authorityDAO = (AuthorityDAO) FacesContextUtils .getRequiredWebApplicationContext(context).getBean("authorityDAO"); if (authorityDAO != null) { List<String> matchingGroups = new ArrayList<String>(); String groupDisplayName; for (String group : groups) { // get display name, if not present strip prefix from group id groupDisplayName = authorityService.getAuthorityDisplayName(group); if (groupDisplayName == null || groupDisplayName.length() == 0) { groupDisplayName = group.substring(PermissionService.GROUP_PREFIX.length()); } // if a search string is present make sure the group matches // otherwise just add the group name to the sorted set if (safeContains != null) { if (groupDisplayName.toLowerCase().indexOf(safeContains) != -1) { matchingGroups.add(group); } } else { matchingGroups.add(group); } } // sort the group names Collections.sort(matchingGroups, new SimpleStringComparator()); // go through the sorted set and get the NodeRef for each group for (String groupName : matchingGroups) { NodeRef groupRef = authorityDAO.getAuthorityNodeRefOrNull(groupName); if (groupRef != null) { this.availableOptions.add(groupRef); } } } // commit the transaction tx.commit(); } catch (Throwable err) { Utils.addErrorMessage(MessageFormat.format( Application.getMessage(context, Repository.ERROR_GENERIC), err.getMessage()), err); this.availableOptions = Collections.<NodeRef>emptyList(); try { if (tx != null) { tx.rollback(); } } catch (Exception tex) { } } } else if (type.equals(ContentModel.TYPE_PERSON.toString())) { List<Pair<QName, String>> filter = (contains != null && contains.trim().length() > 0) ? Utils.generatePersonFilter(contains.trim()) : null; // Always sort by last name, then first name List<Pair<QName, Boolean>> sort = new ArrayList<Pair<QName, Boolean>>(); sort.add(new Pair<QName, Boolean>(ContentModel.PROP_LASTNAME, true)); sort.add(new Pair<QName, Boolean>(ContentModel.PROP_FIRSTNAME, true)); // Log the filtering if (logger.isDebugEnabled()) logger.debug("Query filter: " + filter); // How many to limit too? int maxResults = Application.getClientConfig(context).getSelectorsSearchMaxResults(); if (maxResults <= 0) { maxResults = Utils.getPersonMaxResults(); } List<PersonInfo> persons = Repository.getServiceRegistry(context).getPersonService() .getPeople(filter, true, sort, new PagingRequest(maxResults, null)).getPage(); // Save the results List<NodeRef> nodes = new ArrayList<NodeRef>(persons.size()); for (PersonInfo person : persons) { nodes.add(person.getNodeRef()); } this.availableOptions = nodes; } else { // for all other types/aspects perform a lucene search StringBuilder query = new StringBuilder("+TYPE:\""); if (assocDef.getTargetClass().isAspect()) { query = new StringBuilder("+ASPECT:\""); } else { query = new StringBuilder("+TYPE:\""); } query.append(type); query.append("\""); if (contains != null && contains.trim().length() != 0) { String safeContains = null; if (contains != null && contains.length() > 0) { safeContains = Utils.remove(contains.trim(), "\""); safeContains = safeContains.toLowerCase(); } query.append(" AND +@"); String nameAttr = Repository .escapeQName(QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "name")); query.append(nameAttr); query.append(":\"*" + safeContains + "*\""); } int maxResults = Application.getClientConfig(context).getSelectorsSearchMaxResults(); if (logger.isDebugEnabled()) { logger.debug("Query: " + query.toString()); logger.debug("Max results size: " + maxResults); } SearchParameters searchParams = new SearchParameters(); searchParams.addStore(Repository.getStoreRef()); searchParams.setLanguage(SearchService.LANGUAGE_LUCENE); searchParams.setQuery(query.toString()); if (maxResults > 0) { searchParams.setLimit(maxResults); searchParams.setLimitBy(LimitBy.FINAL_SIZE); } ResultSet results = null; try { results = Repository.getServiceRegistry(context).getSearchService().query(searchParams); this.availableOptions = results.getNodeRefs(); } catch (SearcherException se) { logger.info("Search failed for: " + query, se); Utils.addErrorMessage( Application.getMessage(FacesContext.getCurrentInstance(), Repository.ERROR_QUERY)); } finally { if (results != null) { results.close(); } } } if (logger.isDebugEnabled()) logger.debug("Found " + this.availableOptions.size() + " available options"); } } /** * We use a hidden field per picker instance on the page. * * @return hidden field name */ protected String getHiddenFieldName() { return getClientId(getFacesContext()); } /** * Generate FORM submit JavaScript for the specified action * * @param context FacesContext * @param action Action string * * @return FORM submit JavaScript */ protected String generateFormSubmit(FacesContext context, String action) { return Utils.generateFormSubmit(context, this, getHiddenFieldName(), action); } // ------------------------------------------------------------------------------ // Inner classes /** * Class representing an action relevant to the AssociationEditor component. */ public static class AssocEditorEvent extends ActionEvent { private static final long serialVersionUID = 7346758616063937703L; public int Action; public String[] ToAdd; public String RemoveRef; public String Contains; public AssocEditorEvent(UIComponent component, int action, String[] toAdd, String removeRef, String contains) { super(component); this.Action = action; this.ToAdd = toAdd; this.RemoveRef = removeRef; this.Contains = contains; } } /** * Comparator used for ordering groups */ private static class SimpleStringComparator implements Comparator<String> { public int compare(final String obj1, final String obj2) { if (obj1 == null && obj2 == null) return 0; if (obj1 == null) return -1; if (obj2 == null) return 1; return ((String) obj1).compareToIgnoreCase((String) obj2); } } }