mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
- ALF-8806 RINF 41: Lucene Removal: Fix CopyService - ALF-9028: RINF 41: Fix Aspect cm:copiedFrom - ALF-9029 RINF 49: Lucene Removal: CheckOutCheckInService API - ALF-9032: RINF 49: fixes to cm:workingcopy aspect 28996: Dev branch for De-Lucene work pending patches 29004: Evaluator runs in read-only txn 29006: Additional PermissionCheckedCollection.create method - Use an existing collection's permission check data (cut-off, etc) to wrap a new collection 29007: CopyService and CheckOutCheckInService refactors to remove Lucene CopyService: Removed cm:source property from cm:copiedfrom aspect and replaced with a cm:original association. Added CQ-based APIs to query for copies Added APIs to support bi-directional walking of copy association Fixed sundry uses of cm:copiedfrom esp. all uses related to cm:workingcopy CheckOutCheckInService: Check-out now creates a source aspect cm:checkedOut with 1:1 relationship to cm:workingcopy via cm:workingcopylink Removed explicit use of cm:workingcopy aspect and replaced it with calls to COCI API 29083: Audit tests fail when indexing is turned off. Also removed a getReader() call during rule evaluation, leading to a 'sub-action' read being recorded. 29113: NodeDAO.getNodesWithAspects supports paging 29135: Removed unused patch queries 29139: Basic patch (still terminates with error) to upgrade cm:copiedfrom and cm:workingcopy 29157: Tested patch for cm:copiedfrom and cm:workingcopy aspects git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@29159 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2431 lines
89 KiB
Java
2431 lines
89 KiB
Java
/*
|
|
* Copyright (C) 2005-2010 Alfresco Software Limited.
|
|
*
|
|
* This file is part of Alfresco
|
|
*
|
|
* 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/>.
|
|
*/
|
|
package org.alfresco.web.bean;
|
|
|
|
import java.io.IOException;
|
|
import java.io.Serializable;
|
|
import java.io.StringWriter;
|
|
import java.text.MessageFormat;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
import javax.faces.context.FacesContext;
|
|
import javax.faces.event.AbortProcessingException;
|
|
import javax.faces.event.ActionEvent;
|
|
import javax.transaction.UserTransaction;
|
|
|
|
import org.alfresco.model.ApplicationModel;
|
|
import org.alfresco.model.ContentModel;
|
|
import org.alfresco.repo.search.SearcherException;
|
|
import org.alfresco.repo.site.SiteModel;
|
|
import org.alfresco.repo.web.scripts.FileTypeImageUtils;
|
|
import org.alfresco.service.cmr.coci.CheckOutCheckInService;
|
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
|
import org.alfresco.service.cmr.dictionary.TypeDefinition;
|
|
import org.alfresco.service.cmr.lock.LockService;
|
|
import org.alfresco.service.cmr.ml.MultilingualContentService;
|
|
import org.alfresco.service.cmr.model.FileFolderService;
|
|
import org.alfresco.service.cmr.model.FileInfo;
|
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
|
import org.alfresco.service.cmr.repository.ContentData;
|
|
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
|
|
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.ResultSetRow;
|
|
import org.alfresco.service.cmr.search.SearchParameters;
|
|
import org.alfresco.service.cmr.search.SearchService;
|
|
import org.alfresco.service.cmr.security.PermissionService;
|
|
import org.alfresco.service.namespace.QName;
|
|
import org.alfresco.service.namespace.RegexQNamePattern;
|
|
import org.alfresco.web.app.Application;
|
|
import org.alfresco.web.app.context.IContextListener;
|
|
import org.alfresco.web.app.context.UIContextService;
|
|
import org.alfresco.web.app.servlet.DownloadContentServlet;
|
|
import org.alfresco.web.app.servlet.FacesHelper;
|
|
import org.alfresco.web.bean.content.DocumentDetailsDialog;
|
|
import org.alfresco.web.bean.ml.MultilingualManageDialog;
|
|
import org.alfresco.web.bean.repository.MapNode;
|
|
import org.alfresco.web.bean.repository.Node;
|
|
import org.alfresco.web.bean.repository.NodePropertyResolver;
|
|
import org.alfresco.web.bean.repository.QNameNodeMap;
|
|
import org.alfresco.web.bean.repository.Repository;
|
|
import org.alfresco.web.bean.search.SearchContext;
|
|
import org.alfresco.web.bean.spaces.CreateSpaceWizard;
|
|
import org.alfresco.web.bean.users.UserPreferencesBean;
|
|
import org.alfresco.web.config.ClientConfigElement;
|
|
import org.alfresco.web.config.ViewsConfigElement;
|
|
import org.alfresco.web.ui.common.PanelGenerator;
|
|
import org.alfresco.web.ui.common.Utils;
|
|
import org.alfresco.web.ui.common.Utils.URLMode;
|
|
import org.alfresco.web.ui.common.component.IBreadcrumbHandler;
|
|
import org.alfresco.web.ui.common.component.UIActionLink;
|
|
import org.alfresco.web.ui.common.component.UIBreadcrumb;
|
|
import org.alfresco.web.ui.common.component.UIModeList;
|
|
import org.alfresco.web.ui.common.component.UIPanel.ExpandedEvent;
|
|
import org.alfresco.web.ui.common.component.UIStatusMessage;
|
|
import org.alfresco.web.ui.common.component.data.UIRichList;
|
|
import org.alfresco.web.ui.repo.component.IRepoBreadcrumbHandler;
|
|
import org.alfresco.web.ui.repo.component.UINodeDescendants;
|
|
import org.alfresco.web.ui.repo.component.UINodePath;
|
|
import org.alfresco.web.ui.repo.component.UISimpleSearch;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
import org.springframework.extensions.config.Config;
|
|
import org.springframework.extensions.config.ConfigElement;
|
|
import org.springframework.extensions.config.ConfigService;
|
|
|
|
|
|
/**
|
|
* Bean providing properties and behaviour for the main folder/document browse screen and
|
|
* search results screens.
|
|
*
|
|
* @author Kevin Roast
|
|
*/
|
|
public class BrowseBean implements IContextListener, Serializable
|
|
{
|
|
private static final long serialVersionUID = -3234262484615161360L;
|
|
|
|
/** Public JSF Bean name */
|
|
public static final String BEAN_NAME = "BrowseBean";
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Construction
|
|
|
|
/**
|
|
* Default Constructor
|
|
*/
|
|
public BrowseBean()
|
|
{
|
|
UIContextService.getInstance(FacesContext.getCurrentInstance()).registerBean(this);
|
|
|
|
initFromClientConfig();
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Bean property getters and setters
|
|
|
|
/**
|
|
* @param nodeService The NodeService to set.
|
|
*/
|
|
public void setNodeService(NodeService nodeService)
|
|
{
|
|
this.nodeService = nodeService;
|
|
}
|
|
|
|
protected NodeService getNodeService()
|
|
{
|
|
if (nodeService == null)
|
|
{
|
|
nodeService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getNodeService();
|
|
}
|
|
return nodeService;
|
|
}
|
|
|
|
/**
|
|
* @param checkOutCheckInService The service for check-in and check-out.
|
|
*/
|
|
public void setCheckOutCheckInService(CheckOutCheckInService checkOutCheckInService)
|
|
{
|
|
this.checkOutCheckInService = checkOutCheckInService;
|
|
}
|
|
|
|
protected CheckOutCheckInService getCheckOutCheckInService()
|
|
{
|
|
if (checkOutCheckInService == null)
|
|
{
|
|
checkOutCheckInService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getCheckOutCheckInService();
|
|
}
|
|
return checkOutCheckInService;
|
|
}
|
|
|
|
/**
|
|
* @param searchService The Searcher to set.
|
|
*/
|
|
public void setSearchService(SearchService searchService)
|
|
{
|
|
this.searchService = searchService;
|
|
}
|
|
|
|
protected SearchService getSearchService()
|
|
{
|
|
if (searchService == null)
|
|
{
|
|
searchService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getSearchService();
|
|
}
|
|
return searchService;
|
|
}
|
|
|
|
/**
|
|
* @param userPreferencesBean The UserPreferencesBean to set.
|
|
*/
|
|
public void setUserPreferencesBean(UserPreferencesBean userPreferencesBean)
|
|
{
|
|
this.userPreferencesBean = userPreferencesBean;
|
|
}
|
|
|
|
/**
|
|
* @param lockService The Lock Service to set.
|
|
*/
|
|
public void setLockService(LockService lockService)
|
|
{
|
|
this.lockService = lockService;
|
|
}
|
|
|
|
protected LockService getLockService()
|
|
{
|
|
if (lockService == null)
|
|
{
|
|
lockService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getLockService();
|
|
}
|
|
return lockService;
|
|
}
|
|
|
|
/**
|
|
* @param navigator The NavigationBean to set.
|
|
*/
|
|
public void setNavigator(NavigationBean navigator)
|
|
{
|
|
this.navigator = navigator;
|
|
}
|
|
|
|
/**
|
|
* @param dictionaryService The DictionaryService to set.
|
|
*/
|
|
public void setDictionaryService(DictionaryService dictionaryService)
|
|
{
|
|
this.dictionaryService = dictionaryService;
|
|
}
|
|
|
|
protected DictionaryService getDictionaryService()
|
|
{
|
|
if (dictionaryService == null)
|
|
{
|
|
dictionaryService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getDictionaryService();
|
|
}
|
|
return dictionaryService;
|
|
}
|
|
|
|
/**
|
|
* @param multilingualContentService The Multilingual Content Service to set.
|
|
*/
|
|
public void setMultilingualContentService(MultilingualContentService multilingualContentService)
|
|
{
|
|
this.multilingualContentService = multilingualContentService;
|
|
}
|
|
|
|
protected MultilingualContentService getMultilingualContentService()
|
|
{
|
|
if (multilingualContentService == null)
|
|
{
|
|
multilingualContentService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getMultilingualContentService();
|
|
}
|
|
return multilingualContentService;
|
|
}
|
|
|
|
/**
|
|
* @param fileFolderService The FileFolderService to set.
|
|
*/
|
|
public void setFileFolderService(FileFolderService fileFolderService)
|
|
{
|
|
this.fileFolderService = fileFolderService;
|
|
}
|
|
|
|
protected FileFolderService getFileFolderService()
|
|
{
|
|
if (fileFolderService == null)
|
|
{
|
|
fileFolderService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getFileFolderService();
|
|
}
|
|
return fileFolderService;
|
|
}
|
|
|
|
/**
|
|
* @return Returns the browse View mode. See UIRichList
|
|
*/
|
|
public String getBrowseViewMode()
|
|
{
|
|
return this.browseViewMode;
|
|
}
|
|
|
|
/**
|
|
* @param browseViewMode The browse View mode to set. See UIRichList.
|
|
*/
|
|
public void setBrowseViewMode(String browseViewMode)
|
|
{
|
|
this.browseViewMode = browseViewMode;
|
|
}
|
|
|
|
/**
|
|
* @return Returns true if dashboard view is available for the current node.
|
|
*/
|
|
public boolean isDashboardView()
|
|
{
|
|
return this.dashboardView;
|
|
}
|
|
|
|
/**
|
|
* @param dashboardView The dashboard view mode to set.
|
|
*/
|
|
public void setDashboardView(boolean dashboardView)
|
|
{
|
|
this.dashboardView = dashboardView;
|
|
if (dashboardView == true)
|
|
{
|
|
FacesContext fc = FacesContext.getCurrentInstance();
|
|
fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "dashboard");
|
|
}
|
|
}
|
|
|
|
public int getPageSizeContent()
|
|
{
|
|
return this.pageSizeContent;
|
|
}
|
|
|
|
public void setPageSizeContent(int pageSizeContent)
|
|
{
|
|
this.pageSizeContent = pageSizeContent;
|
|
this.pageSizeContentStr = Integer.toString(pageSizeContent);
|
|
}
|
|
|
|
public int getPageSizeSpaces()
|
|
{
|
|
return this.pageSizeSpaces;
|
|
}
|
|
|
|
public void setPageSizeSpaces(int pageSizeSpaces)
|
|
{
|
|
this.pageSizeSpaces = pageSizeSpaces;
|
|
this.pageSizeSpacesStr = Integer.toString(pageSizeSpaces);
|
|
}
|
|
|
|
public String getPageSizeContentStr()
|
|
{
|
|
return this.pageSizeContentStr;
|
|
}
|
|
|
|
public void setPageSizeContentStr(String pageSizeContentStr)
|
|
{
|
|
this.pageSizeContentStr = pageSizeContentStr;
|
|
}
|
|
|
|
public String getPageSizeSpacesStr()
|
|
{
|
|
return this.pageSizeSpacesStr;
|
|
}
|
|
|
|
public void setPageSizeSpacesStr(String pageSizeSpacesStr)
|
|
{
|
|
this.pageSizeSpacesStr = pageSizeSpacesStr;
|
|
}
|
|
|
|
/**
|
|
* @return Returns the minimum length of a valid search string.
|
|
*/
|
|
public static int getMinimumSearchLength()
|
|
{
|
|
return Application.getClientConfig(FacesContext.getCurrentInstance()).getSearchMinimum();
|
|
}
|
|
|
|
/**
|
|
* @return Returns the panels expanded state map.
|
|
*/
|
|
public Map<String, Boolean> getPanels()
|
|
{
|
|
return this.panels;
|
|
}
|
|
|
|
/**
|
|
* @param panels The panels expanded state map.
|
|
*/
|
|
public void setPanels(Map<String, Boolean> panels)
|
|
{
|
|
this.panels = panels;
|
|
}
|
|
|
|
/**
|
|
* @return Returns the Space Node being used for the current browse screen action.
|
|
*/
|
|
public Node getActionSpace()
|
|
{
|
|
return this.actionSpace;
|
|
}
|
|
|
|
/**
|
|
* @param actionSpace Set the Space Node to be used for the current browse screen action.
|
|
*/
|
|
public void setActionSpace(Node actionSpace)
|
|
{
|
|
if (actionSpace != null)
|
|
{
|
|
for (NodeEventListener listener : getNodeEventListeners())
|
|
{
|
|
listener.created(actionSpace, actionSpace.getType());
|
|
}
|
|
}
|
|
this.actionSpace = actionSpace;
|
|
}
|
|
|
|
/**
|
|
* @return The document node being used for the current operation
|
|
*/
|
|
public Node getDocument()
|
|
{
|
|
return this.document;
|
|
}
|
|
|
|
/**
|
|
* @param document The document node to be used for the current operation
|
|
*/
|
|
public void setDocument(Node document)
|
|
{
|
|
if (document != null)
|
|
{
|
|
for (NodeEventListener listener : getNodeEventListeners())
|
|
{
|
|
listener.created(document, document.getType());
|
|
}
|
|
}
|
|
this.document = document;
|
|
}
|
|
|
|
/**
|
|
* @param contentRichList The contentRichList to set.
|
|
*/
|
|
public void setContentRichList(UIRichList contentRichList)
|
|
{
|
|
this.contentRichList = contentRichList;
|
|
if (this.contentRichList != null)
|
|
{
|
|
this.contentRichList.setInitialSortColumn(
|
|
this.viewsConfig.getDefaultSortColumn(PAGE_NAME_BROWSE));
|
|
this.contentRichList.setInitialSortDescending(
|
|
this.viewsConfig.hasDescendingSort(PAGE_NAME_BROWSE));
|
|
}
|
|
// special case to handle an External Access URL
|
|
// these URLs restart the JSF lifecycle but an old UIRichList is restored from
|
|
// the component tree - which needs clearing "late" in the lifecycle process
|
|
if (externalForceRefresh)
|
|
{
|
|
this.contentRichList.setValue(null);
|
|
externalForceRefresh = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return Returns the contentRichList.
|
|
*/
|
|
public UIRichList getContentRichList()
|
|
{
|
|
return this.contentRichList;
|
|
}
|
|
|
|
/**
|
|
* @param spacesRichList The spacesRichList to set.
|
|
*/
|
|
public void setSpacesRichList(UIRichList spacesRichList)
|
|
{
|
|
this.spacesRichList = spacesRichList;
|
|
if (this.spacesRichList != null)
|
|
{
|
|
// set the initial sort column and direction
|
|
this.spacesRichList.setInitialSortColumn(
|
|
this.viewsConfig.getDefaultSortColumn(PAGE_NAME_BROWSE));
|
|
this.spacesRichList.setInitialSortDescending(
|
|
this.viewsConfig.hasDescendingSort(PAGE_NAME_BROWSE));
|
|
}
|
|
if (externalForceRefresh)
|
|
{
|
|
this.spacesRichList.setValue(null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return Returns the spacesRichList.
|
|
*/
|
|
public UIRichList getSpacesRichList()
|
|
{
|
|
return this.spacesRichList;
|
|
}
|
|
|
|
/**
|
|
* @return Returns the statusMessage component.
|
|
*/
|
|
public UIStatusMessage getStatusMessage()
|
|
{
|
|
return this.statusMessage;
|
|
}
|
|
|
|
/**
|
|
* @param statusMessage The statusMessage component to set.
|
|
*/
|
|
public void setStatusMessage(UIStatusMessage statusMessage)
|
|
{
|
|
this.statusMessage = statusMessage;
|
|
}
|
|
|
|
/**
|
|
* @return Returns the deleteMessage.
|
|
*/
|
|
public String getDeleteMessage()
|
|
{
|
|
return this.deleteMessage;
|
|
}
|
|
|
|
/**
|
|
* @param deleteMessage The deleteMessage to set.
|
|
*/
|
|
public void setDeleteMessage(String deleteMessage)
|
|
{
|
|
this.deleteMessage = deleteMessage;
|
|
}
|
|
|
|
/**
|
|
* Page accessed bean method to get the container nodes currently being browsed
|
|
*
|
|
* @return List of container Node objects for the current browse location
|
|
*/
|
|
public List<Node> getNodes()
|
|
{
|
|
// the references to container nodes and content nodes are transient for one use only
|
|
// we do this so we only query/search once - as we cannot distinguish between node types
|
|
// until after the query. The logic is a bit confusing but otherwise we would need to
|
|
// perform the same query or search twice for every screen refresh.
|
|
if (this.containerNodes == null)
|
|
{
|
|
if (this.navigator.getSearchContext() == null)
|
|
{
|
|
queryBrowseNodes(this.navigator.getCurrentNodeId());
|
|
}
|
|
else
|
|
{
|
|
searchBrowseNodes(this.navigator.getSearchContext());
|
|
}
|
|
}
|
|
List<Node> result = this.containerNodes;
|
|
|
|
// we clear the member variable during invalidateComponents()
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Page accessed bean method to get the content nodes currently being browsed
|
|
*
|
|
* @return List of content Node objects for the current browse location
|
|
*/
|
|
public List<Node> getContent()
|
|
{
|
|
// see comment in getNodes() above for reasoning here
|
|
if (this.contentNodes == null)
|
|
{
|
|
if (this.navigator.getSearchContext() == null)
|
|
{
|
|
queryBrowseNodes(this.navigator.getCurrentNodeId());
|
|
}
|
|
else
|
|
{
|
|
searchBrowseNodes(this.navigator.getSearchContext());
|
|
}
|
|
}
|
|
List<Node> result = this.contentNodes;
|
|
|
|
// we clear the member variable during invalidateComponents()
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Page accessed bean method to get the parent container nodes currently being browsed
|
|
*
|
|
* @return List of parent container Node objects for the current browse location
|
|
*/
|
|
public List<Node> getParentNodes(NodeRef currNodeRef)
|
|
{
|
|
// As per AWC-1507 there are two scenarios for navigating to the space details. First
|
|
// scenario is to show space details of the current space. Second scenario is to show
|
|
// space details of a child space of the current space. For now, added an extra query
|
|
// so that existing context remains unaffected for second scenario, although it does
|
|
// mean that in first scenario there will be an extra query even though parentContainerNodes
|
|
// and containerNodes will contain the same list.
|
|
|
|
if (this.parentContainerNodes == null)
|
|
{
|
|
long startTime = 0;
|
|
if (logger.isDebugEnabled())
|
|
startTime = System.currentTimeMillis();
|
|
|
|
UserTransaction tx = null;
|
|
try
|
|
{
|
|
FacesContext context = FacesContext.getCurrentInstance();
|
|
tx = Repository.getUserTransaction(context, true);
|
|
tx.begin();
|
|
|
|
NodeRef parentRef = getNodeService().getPrimaryParent(currNodeRef).getParentRef();
|
|
|
|
List<FileInfo> children = this.getFileFolderService().list(parentRef);
|
|
this.parentContainerNodes = new ArrayList<Node>(children.size());
|
|
for (FileInfo fileInfo : children)
|
|
{
|
|
// create our Node representation from the NodeRef
|
|
NodeRef nodeRef = fileInfo.getNodeRef();
|
|
|
|
// find it's type so we can see if it's a node we are interested in
|
|
QName type = this.getNodeService().getType(nodeRef);
|
|
|
|
// make sure the type is defined in the data dictionary
|
|
TypeDefinition typeDef = this.getDictionaryService().getType(type);
|
|
|
|
if (typeDef != null)
|
|
{
|
|
MapNode node = null;
|
|
|
|
// look for Space folder node
|
|
if (this.getDictionaryService().isSubClass(type, ContentModel.TYPE_FOLDER) == true &&
|
|
this.getDictionaryService().isSubClass(type, ContentModel.TYPE_SYSTEM_FOLDER) == false)
|
|
{
|
|
// create our Node representation
|
|
node = new MapNode(nodeRef, this.getNodeService(), fileInfo.getProperties());
|
|
node.addPropertyResolver("icon", this.resolverSpaceIcon);
|
|
node.addPropertyResolver("smallIcon", this.resolverSmallIcon);
|
|
|
|
this.parentContainerNodes.add(node);
|
|
}
|
|
else if (ApplicationModel.TYPE_FOLDERLINK.equals(type))
|
|
{
|
|
// create our Folder Link Node representation
|
|
node = new MapNode(nodeRef, this.getNodeService(), fileInfo.getProperties());
|
|
node.addPropertyResolver("icon", this.resolverSpaceIcon);
|
|
node.addPropertyResolver("smallIcon", this.resolverSmallIcon);
|
|
|
|
this.parentContainerNodes.add(node);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (logger.isWarnEnabled())
|
|
logger.warn("Found invalid object in database: id = " + nodeRef + ", type = " + type);
|
|
}
|
|
}
|
|
|
|
// commit the transaction
|
|
tx.commit();
|
|
}
|
|
catch (InvalidNodeRefException refErr)
|
|
{
|
|
Utils.addErrorMessage(MessageFormat.format(Application.getMessage(
|
|
FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {refErr.getNodeRef()}), refErr );
|
|
this.parentContainerNodes = Collections.<Node>emptyList();
|
|
try { if (tx != null) {tx.rollback();} } catch (Exception tex) {}
|
|
}
|
|
catch (Throwable err)
|
|
{
|
|
Utils.addErrorMessage(MessageFormat.format(Application.getMessage(
|
|
FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err);
|
|
this.parentContainerNodes = Collections.<Node>emptyList();
|
|
try { if (tx != null) {tx.rollback();} } catch (Exception tex) {}
|
|
}
|
|
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
long endTime = System.currentTimeMillis();
|
|
logger.debug("Time to query and build map parent nodes: " + (endTime - startTime) + "ms");
|
|
}
|
|
}
|
|
|
|
List<Node> result = this.parentContainerNodes;
|
|
|
|
// we clear the member variable during invalidateComponents()
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Determines whether the current space is a 'Sites' space
|
|
*
|
|
* @return true if the current space is a 'Sites' space
|
|
*/
|
|
public boolean isSitesSpace()
|
|
{
|
|
boolean siteSpace = false;
|
|
|
|
Node currentNode = this.navigator.getCurrentNode();
|
|
if (currentNode != null)
|
|
{
|
|
// check the type of the node to see if it is a 'site' related space
|
|
QName currentNodeType = currentNode.getType();
|
|
|
|
if (SiteModel.TYPE_SITES.isMatch(currentNodeType) ||
|
|
SiteModel.TYPE_SITE.isMatch(currentNodeType) ||
|
|
getDictionaryService().isSubClass(currentNodeType, SiteModel.TYPE_SITE))
|
|
{
|
|
siteSpace = true;
|
|
}
|
|
}
|
|
|
|
return siteSpace;
|
|
}
|
|
|
|
/**
|
|
* Returns the HTML to display if a space is a 'Sites' space
|
|
*
|
|
* @return The HTML to display
|
|
*/
|
|
public String getSitesSpaceWarningHTML()
|
|
{
|
|
FacesContext context = FacesContext.getCurrentInstance();
|
|
String contextPath = context.getExternalContext().getRequestContextPath();
|
|
StringBuilder html = new StringBuilder();
|
|
|
|
try
|
|
{
|
|
html.append("<tr valign='top'>");
|
|
html.append("<td style='background-image: url(");
|
|
html.append(contextPath);
|
|
html.append("/images/parts/whitepanel_4.gif)' ");
|
|
html.append("width='4'></td><td style='padding:4px'>");
|
|
|
|
StringWriter writer = new StringWriter();
|
|
PanelGenerator.generatePanelStart(writer, contextPath, "yellowInner", "#ffffcc");
|
|
html.append(writer.toString());
|
|
|
|
html.append("<table cellpadding='0' cellspacing='0' border='0' width='100%'>");
|
|
html.append("<tr><td valign='top' style='padding-top: 2px' width='20'>");
|
|
html.append("<img src='");
|
|
html.append(contextPath);
|
|
html.append("/images/icons/warning.gif' width='16' height='16' /></td>");
|
|
html.append("<td class='mainSubText'>");
|
|
html.append(Application.getMessage(context, "sites_space_warning"));
|
|
html.append("</td></tr></table>");
|
|
|
|
writer = new StringWriter();
|
|
PanelGenerator.generatePanelEnd(writer, contextPath, "yellowInner");
|
|
html.append(writer.toString());
|
|
|
|
html.append("</td><td style='background-image: url(");
|
|
html.append(contextPath);
|
|
html.append("/images/parts/whitepanel_6.gif)'");
|
|
html.append("width='4'></td></tr>");
|
|
}
|
|
catch (IOException ioe)
|
|
{
|
|
logger.error(ioe);
|
|
}
|
|
|
|
return html.toString();
|
|
}
|
|
|
|
/**
|
|
* Setup the common properties required at data-binding time.
|
|
* <p>
|
|
* These are properties used by components on the page when iterating over the nodes.
|
|
* The properties are available as the Node is a Map so they can be accessed directly
|
|
* by name. Information such as download URL, size and filetype are provided etc.
|
|
* <p>
|
|
* We use a set of anonymous inner classes to provide the implemention for the property
|
|
* getters. The interfaces are only called when the properties are first requested.
|
|
*
|
|
* @param node Node to add the properties too
|
|
*/
|
|
public void setupCommonBindingProperties(Node node)
|
|
{
|
|
// special properties to be used by the value binding components on the page
|
|
node.addPropertyResolver("url", this.resolverUrl);
|
|
|
|
if (ApplicationModel.TYPE_FILELINK.equals(node.getType()))
|
|
{
|
|
node.addPropertyResolver("downloadUrl", this.resolverLinkDownload);
|
|
}
|
|
else
|
|
{
|
|
node.addPropertyResolver("downloadUrl", this.resolverDownload);
|
|
}
|
|
|
|
node.addPropertyResolver("webdavUrl", this.resolverWebdavUrl);
|
|
node.addPropertyResolver("cifsPath", this.resolverCifsPath);
|
|
node.addPropertyResolver("fileType16", this.resolverFileType16);
|
|
node.addPropertyResolver("fileType32", this.resolverFileType32);
|
|
node.addPropertyResolver("size", this.resolverSize);
|
|
node.addPropertyResolver("lang", this.resolverLang);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// IContextListener implementation
|
|
|
|
/**
|
|
* @see org.alfresco.web.app.context.IContextListener#contextUpdated()
|
|
*/
|
|
public void contextUpdated()
|
|
{
|
|
invalidateComponents();
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.web.app.context.IContextListener#areaChanged()
|
|
*/
|
|
public void areaChanged()
|
|
{
|
|
// nothing to do
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.web.app.context.IContextListener#spaceChanged()
|
|
*/
|
|
public void spaceChanged()
|
|
{
|
|
// nothing to do
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// NodeEventListener listeners
|
|
|
|
/**
|
|
* Add a listener to those called by the BrowseBean when nodes are created
|
|
*/
|
|
public void addNodeEventListener(NodeEventListener listener)
|
|
{
|
|
if (this.nodeEventListeners == null)
|
|
{
|
|
this.nodeEventListeners = new HashSet<NodeEventListener>();
|
|
}
|
|
this.nodeEventListeners.add(listener);
|
|
}
|
|
|
|
/**
|
|
* Remove a listener from the list of those called by BrowseBean
|
|
*/
|
|
public void removeNodeEventListener(NodeEventListener listener)
|
|
{
|
|
if (this.nodeEventListeners != null)
|
|
{
|
|
this.nodeEventListeners.remove(listener);
|
|
}
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Navigation action event handlers
|
|
|
|
/**
|
|
* Change the current view mode based on user selection
|
|
*
|
|
* @param event ActionEvent
|
|
*/
|
|
public void viewModeChanged(ActionEvent event)
|
|
{
|
|
UIModeList viewList = (UIModeList)event.getComponent();
|
|
|
|
// get the view mode ID
|
|
String viewMode = viewList.getValue().toString();
|
|
|
|
if (VIEWMODE_DASHBOARD.equals(viewMode) == false)
|
|
{
|
|
// set the page size based on the style of display
|
|
int pageSize = this.viewsConfig.getDefaultPageSize(PAGE_NAME_BROWSE, viewMode);
|
|
setPageSizeContent(pageSize);
|
|
setPageSizeSpaces(pageSize);
|
|
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("Browse view page size set to: " + pageSize);
|
|
|
|
setDashboardView(false);
|
|
|
|
// push the view mode into the lists
|
|
setBrowseViewMode(viewMode);
|
|
|
|
// setup dispatch context for custom views
|
|
this.navigator.setupDispatchContext(this.navigator.getCurrentNode());
|
|
|
|
// browse to appropriate view
|
|
FacesContext fc = FacesContext.getCurrentInstance();
|
|
String outcome = null;
|
|
String viewId = fc.getViewRoot().getViewId();
|
|
if (viewId.equals(BROWSE_VIEW_ID) == false && viewId.equals(CATEGORY_VIEW_ID) == false)
|
|
{
|
|
outcome = "browse";
|
|
}
|
|
fc.getApplication().getNavigationHandler().handleNavigation(fc, null, outcome);
|
|
}
|
|
else
|
|
{
|
|
// special case for Dashboard view
|
|
setDashboardView(true);
|
|
}
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Helper methods
|
|
|
|
/**
|
|
* Query a list of nodes for the specified parent node Id
|
|
*
|
|
* @param parentNodeId Id of the parent node or null for the root node
|
|
*/
|
|
private void queryBrowseNodes(String parentNodeId)
|
|
{
|
|
long startTime = 0;
|
|
if (logger.isDebugEnabled())
|
|
startTime = System.currentTimeMillis();
|
|
|
|
UserTransaction tx = null;
|
|
try
|
|
{
|
|
FacesContext context = FacesContext.getCurrentInstance();
|
|
tx = Repository.getUserTransaction(context, true);
|
|
tx.begin();
|
|
|
|
NodeRef parentRef;
|
|
if (parentNodeId == null)
|
|
{
|
|
// no specific parent node specified - use the root node
|
|
parentRef = this.getNodeService().getRootNode(Repository.getStoreRef());
|
|
}
|
|
else
|
|
{
|
|
// build a NodeRef for the specified Id and our store
|
|
parentRef = new NodeRef(Repository.getStoreRef(), parentNodeId);
|
|
}
|
|
|
|
List<FileInfo> children = this.getFileFolderService().list(parentRef);
|
|
this.containerNodes = new ArrayList<Node>(children.size());
|
|
this.contentNodes = new ArrayList<Node>(children.size());
|
|
|
|
// in case of dynamic config, only lookup once
|
|
Set<NodeEventListener> nodeEventListeners = getNodeEventListeners();
|
|
|
|
for (FileInfo fileInfo : children)
|
|
{
|
|
// create our Node representation from the NodeRef
|
|
NodeRef nodeRef = fileInfo.getNodeRef();
|
|
|
|
// find it's type so we can see if it's a node we are interested in
|
|
QName type = this.getNodeService().getType(nodeRef);
|
|
|
|
// make sure the type is defined in the data dictionary
|
|
TypeDefinition typeDef = this.getDictionaryService().getType(type);
|
|
|
|
if (typeDef != null)
|
|
{
|
|
MapNode node = null;
|
|
|
|
// look for File content node
|
|
if (this.getDictionaryService().isSubClass(type, ContentModel.TYPE_CONTENT))
|
|
{
|
|
// create our Node representation
|
|
node = new MapNode(nodeRef, this.getNodeService(), fileInfo.getProperties());
|
|
setupCommonBindingProperties(node);
|
|
|
|
this.contentNodes.add(node);
|
|
}
|
|
// look for Space folder node
|
|
else if (this.getDictionaryService().isSubClass(type, ContentModel.TYPE_FOLDER) == true &&
|
|
this.getDictionaryService().isSubClass(type, ContentModel.TYPE_SYSTEM_FOLDER) == false)
|
|
{
|
|
// create our Node representation
|
|
node = new MapNode(nodeRef, this.getNodeService(), fileInfo.getProperties());
|
|
node.addPropertyResolver("icon", this.resolverSpaceIcon);
|
|
node.addPropertyResolver("smallIcon", this.resolverSmallIcon);
|
|
|
|
this.containerNodes.add(node);
|
|
}
|
|
// look for File Link object node
|
|
else if (ApplicationModel.TYPE_FILELINK.equals(type))
|
|
{
|
|
// create our File Link Node representation
|
|
node = new MapNode(nodeRef, this.getNodeService(), fileInfo.getProperties());
|
|
// only display the user has the permissions to navigate to the target of the link
|
|
NodeRef destRef = (NodeRef)node.getProperties().get(ContentModel.PROP_LINK_DESTINATION);
|
|
if (destRef != null && new Node(destRef).hasPermission(PermissionService.READ) == true)
|
|
{
|
|
node.addPropertyResolver("url", this.resolverLinkUrl);
|
|
node.addPropertyResolver("downloadUrl", this.resolverLinkDownload);
|
|
node.addPropertyResolver("webdavUrl", this.resolverLinkWebdavUrl);
|
|
node.addPropertyResolver("cifsPath", this.resolverLinkCifsPath);
|
|
node.addPropertyResolver("fileType16", this.resolverFileType16);
|
|
node.addPropertyResolver("fileType32", this.resolverFileType32);
|
|
node.addPropertyResolver("size", this.resolverSize);
|
|
node.addPropertyResolver("lang", this.resolverLang);
|
|
|
|
this.contentNodes.add(node);
|
|
}
|
|
}
|
|
else if (ApplicationModel.TYPE_FOLDERLINK.equals(type))
|
|
{
|
|
// create our Folder Link Node representation
|
|
node = new MapNode(nodeRef, this.getNodeService(), fileInfo.getProperties());
|
|
// only display the user has the permissions to navigate to the target of the link
|
|
NodeRef destRef = (NodeRef)node.getProperties().get(ContentModel.PROP_LINK_DESTINATION);
|
|
if (destRef != null && new Node(destRef).hasPermission(PermissionService.READ) == true)
|
|
{
|
|
node.addPropertyResolver("icon", this.resolverSpaceIcon);
|
|
node.addPropertyResolver("smallIcon", this.resolverSmallIcon);
|
|
|
|
this.containerNodes.add(node);
|
|
}
|
|
}
|
|
|
|
// inform any listeners that a Node wrapper has been created
|
|
if (node != null)
|
|
{
|
|
for (NodeEventListener listener : nodeEventListeners)
|
|
{
|
|
listener.created(node, type);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (logger.isWarnEnabled())
|
|
logger.warn("Found invalid object in database: id = " + nodeRef + ", type = " + type);
|
|
}
|
|
}
|
|
|
|
// commit the transaction
|
|
tx.commit();
|
|
}
|
|
catch (InvalidNodeRefException refErr)
|
|
{
|
|
Utils.addErrorMessage(MessageFormat.format(Application.getMessage(
|
|
FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {refErr.getNodeRef()}), refErr );
|
|
this.containerNodes = Collections.<Node>emptyList();
|
|
this.contentNodes = Collections.<Node>emptyList();
|
|
try { if (tx != null) {tx.rollback();} } catch (Exception tex) {}
|
|
}
|
|
catch (Throwable err)
|
|
{
|
|
Utils.addErrorMessage(MessageFormat.format(Application.getMessage(
|
|
FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err);
|
|
this.containerNodes = Collections.<Node>emptyList();
|
|
this.contentNodes = Collections.<Node>emptyList();
|
|
try { if (tx != null) {tx.rollback();} } catch (Exception tex) {}
|
|
}
|
|
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
long endTime = System.currentTimeMillis();
|
|
logger.debug("Time to query and build map nodes: " + (endTime - startTime) + "ms");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Search for a list of nodes using the specific search context
|
|
*
|
|
* @param searchContext To use to perform the search
|
|
*/
|
|
private void searchBrowseNodes(SearchContext searchContext)
|
|
{
|
|
long startTime = 0;
|
|
if (logger.isDebugEnabled())
|
|
startTime = System.currentTimeMillis();
|
|
|
|
// get the searcher object to build the query
|
|
String query = searchContext.buildQuery(getMinimumSearchLength());
|
|
if (query == null)
|
|
{
|
|
// failed to build a valid query, the user probably did not enter the
|
|
// minimum text required to construct a valid search
|
|
Utils.addErrorMessage(MessageFormat.format(Application.getMessage(FacesContext.getCurrentInstance(), MSG_SEARCH_MINIMUM),
|
|
new Object[] {getMinimumSearchLength()}));
|
|
this.containerNodes = Collections.<Node>emptyList();
|
|
this.contentNodes = Collections.<Node>emptyList();
|
|
return;
|
|
}
|
|
|
|
// perform the search against the repo
|
|
UserTransaction tx = null;
|
|
ResultSet results = null;
|
|
try
|
|
{
|
|
tx = Repository.getUserTransaction(FacesContext.getCurrentInstance(), true);
|
|
tx.begin();
|
|
|
|
// build up the search parameters
|
|
SearchParameters sp = new SearchParameters();
|
|
sp.setLanguage(SearchService.LANGUAGE_LUCENE);
|
|
sp.setQuery(query);
|
|
sp.addStore(Repository.getStoreRef());
|
|
|
|
// limit search results size as configured
|
|
int searchLimit = Application.getClientConfig(FacesContext.getCurrentInstance()).getSearchMaxResults();
|
|
if (searchLimit > 0)
|
|
{
|
|
sp.setLimitBy(LimitBy.FINAL_SIZE);
|
|
sp.setLimit(searchLimit);
|
|
}
|
|
|
|
results = this.getSearchService().query(sp);
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("Search results returned: " + results.length());
|
|
|
|
// create a list of items from the results
|
|
this.containerNodes = new ArrayList<Node>(results.length());
|
|
this.contentNodes = new ArrayList<Node>(results.length());
|
|
if (results.length() != 0)
|
|
{
|
|
// in case of dynamic config, only lookup once
|
|
Set<NodeEventListener> nodeEventListeners = getNodeEventListeners();
|
|
|
|
for (ResultSetRow row: results)
|
|
{
|
|
NodeRef nodeRef = row.getNodeRef();
|
|
|
|
if (this.getNodeService().exists(nodeRef))
|
|
{
|
|
// find it's type so we can see if it's a node we are interested in
|
|
QName type = this.getNodeService().getType(nodeRef);
|
|
|
|
// make sure the type is defined in the data dictionary
|
|
TypeDefinition typeDef = this.getDictionaryService().getType(type);
|
|
|
|
if (typeDef != null)
|
|
{
|
|
MapNode node = null;
|
|
|
|
// look for Space or File nodes
|
|
if (this.getDictionaryService().isSubClass(type, ContentModel.TYPE_FOLDER) &&
|
|
this.getDictionaryService().isSubClass(type, ContentModel.TYPE_SYSTEM_FOLDER) == false)
|
|
{
|
|
// create our Node representation
|
|
node = new MapNode(nodeRef, this.getNodeService(), false);
|
|
|
|
node.addPropertyResolver("path", this.resolverPath);
|
|
node.addPropertyResolver("displayPath", this.resolverDisplayPath);
|
|
node.addPropertyResolver("icon", this.resolverSpaceIcon);
|
|
node.addPropertyResolver("smallIcon", this.resolverSmallIcon);
|
|
|
|
this.containerNodes.add(node);
|
|
}
|
|
else if (this.getDictionaryService().isSubClass(type, ContentModel.TYPE_CONTENT))
|
|
{
|
|
// create our Node representation
|
|
node = new MapNode(nodeRef, this.getNodeService(), false);
|
|
|
|
setupCommonBindingProperties(node);
|
|
|
|
node.addPropertyResolver("path", this.resolverPath);
|
|
node.addPropertyResolver("displayPath", this.resolverDisplayPath);
|
|
|
|
this.contentNodes.add(node);
|
|
}
|
|
// look for File Link object node
|
|
else if (ApplicationModel.TYPE_FILELINK.equals(type))
|
|
{
|
|
// create our File Link Node representation
|
|
node = new MapNode(nodeRef, this.getNodeService(), false);
|
|
// only display the user has the permissions to navigate to the target of the link
|
|
NodeRef destRef = (NodeRef)node.getProperties().get(ContentModel.PROP_LINK_DESTINATION);
|
|
if (new Node(destRef).hasPermission(PermissionService.READ) == true)
|
|
{
|
|
node.addPropertyResolver("url", this.resolverLinkUrl);
|
|
node.addPropertyResolver("downloadUrl", this.resolverLinkDownload);
|
|
node.addPropertyResolver("webdavUrl", this.resolverLinkWebdavUrl);
|
|
node.addPropertyResolver("cifsPath", this.resolverLinkCifsPath);
|
|
node.addPropertyResolver("fileType16", this.resolverFileType16);
|
|
node.addPropertyResolver("fileType32", this.resolverFileType32);
|
|
node.addPropertyResolver("size", this.resolverSize);
|
|
node.addPropertyResolver("lang", this.resolverLang);
|
|
node.addPropertyResolver("path", this.resolverPath);
|
|
node.addPropertyResolver("displayPath", this.resolverDisplayPath);
|
|
|
|
this.contentNodes.add(node);
|
|
}
|
|
}
|
|
else if (ApplicationModel.TYPE_FOLDERLINK.equals(type))
|
|
{
|
|
// create our Folder Link Node representation
|
|
node = new MapNode(nodeRef, this.getNodeService(), false);
|
|
// only display the user has the permissions to navigate to the target of the link
|
|
NodeRef destRef = (NodeRef)node.getProperties().get(ContentModel.PROP_LINK_DESTINATION);
|
|
if (new Node(destRef).hasPermission(PermissionService.READ) == true)
|
|
{
|
|
node.addPropertyResolver("icon", this.resolverSpaceIcon);
|
|
node.addPropertyResolver("smallIcon", this.resolverSmallIcon);
|
|
node.addPropertyResolver("path", this.resolverPath);
|
|
node.addPropertyResolver("displayPath", this.resolverDisplayPath);
|
|
|
|
this.containerNodes.add(node);
|
|
}
|
|
}
|
|
|
|
// inform any listeners that a Node wrapper has been created
|
|
if (node != null)
|
|
{
|
|
for (NodeEventListener listener : nodeEventListeners)
|
|
{
|
|
listener.created(node, type);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (logger.isWarnEnabled())
|
|
logger.warn("Found invalid object in database: id = " + nodeRef + ", type = " + type);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (logger.isWarnEnabled())
|
|
logger.warn("Missing object returned from search indexes: id = " + nodeRef + " search query: " + query);
|
|
}
|
|
}
|
|
}
|
|
|
|
// commit the transaction
|
|
tx.commit();
|
|
}
|
|
catch (InvalidNodeRefException refErr)
|
|
{
|
|
Utils.addErrorMessage(MessageFormat.format(Application.getMessage(
|
|
FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {refErr.getNodeRef()}), refErr );
|
|
this.containerNodes = Collections.<Node>emptyList();
|
|
this.contentNodes = Collections.<Node>emptyList();
|
|
try { if (tx != null) {tx.rollback();} } catch (Exception tex) {}
|
|
}
|
|
catch (SearcherException serr)
|
|
{
|
|
logger.info("Search failed for: " + query, serr);
|
|
Utils.addErrorMessage(Application.getMessage(
|
|
FacesContext.getCurrentInstance(), Repository.ERROR_QUERY));
|
|
this.containerNodes = Collections.<Node>emptyList();
|
|
this.contentNodes = Collections.<Node>emptyList();
|
|
try { if (tx != null) {tx.rollback();} } catch (Exception tex) {}
|
|
}
|
|
catch (Throwable err)
|
|
{
|
|
Utils.addErrorMessage(MessageFormat.format(Application.getMessage(
|
|
FacesContext.getCurrentInstance(), Repository.ERROR_SEARCH), new Object[] {err.getMessage()}), err );
|
|
this.containerNodes = Collections.<Node>emptyList();
|
|
this.contentNodes = Collections.<Node>emptyList();
|
|
try { if (tx != null) {tx.rollback();} } catch (Exception tex) {}
|
|
}
|
|
finally
|
|
{
|
|
if (results != null)
|
|
{
|
|
results.close();
|
|
}
|
|
}
|
|
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
long endTime = System.currentTimeMillis();
|
|
logger.debug("Time to query and build map nodes: " + (endTime - startTime) + "ms");
|
|
}
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Property Resolvers
|
|
|
|
public NodePropertyResolver resolverDownload = new NodePropertyResolver() {
|
|
private static final long serialVersionUID = 4048859853585650378L;
|
|
|
|
public Object get(Node node) {
|
|
return DownloadContentServlet.generateDownloadURL(node.getNodeRef(), node.getName());
|
|
}
|
|
};
|
|
|
|
public NodePropertyResolver resolverUrl = new NodePropertyResolver() {
|
|
private static final long serialVersionUID = -5264085143622470386L;
|
|
|
|
public Object get(Node node) {
|
|
return DownloadContentServlet.generateBrowserURL(node.getNodeRef(), node.getName());
|
|
}
|
|
};
|
|
|
|
public NodePropertyResolver resolverWebdavUrl = new NodePropertyResolver() {
|
|
private static final long serialVersionUID = 9127234483419089006L;
|
|
|
|
public Object get(Node node) {
|
|
return Utils.generateURL(FacesContext.getCurrentInstance(), node, URLMode.WEBDAV);
|
|
}
|
|
};
|
|
|
|
public NodePropertyResolver resolverCifsPath = new NodePropertyResolver() {
|
|
private static final long serialVersionUID = -5804924617772163104L;
|
|
|
|
public Object get(Node node) {
|
|
return Utils.generateURL(FacesContext.getCurrentInstance(), node, URLMode.CIFS);
|
|
}
|
|
};
|
|
|
|
public NodePropertyResolver resolverLinkDownload = new NodePropertyResolver() {
|
|
private static final long serialVersionUID = 7208696954599958859L;
|
|
|
|
public Object get(Node node) {
|
|
NodeRef destRef = (NodeRef)node.getProperties().get(ContentModel.PROP_LINK_DESTINATION);
|
|
if (getNodeService().exists(destRef) == true)
|
|
{
|
|
String destName = Repository.getNameForNode(getNodeService(), destRef);
|
|
return DownloadContentServlet.generateDownloadURL(destRef, destName);
|
|
}
|
|
else
|
|
{
|
|
// TODO: link object is missing - navigate to a page with appropriate message
|
|
return "#";
|
|
}
|
|
}
|
|
};
|
|
|
|
public NodePropertyResolver resolverLinkUrl = new NodePropertyResolver() {
|
|
private static final long serialVersionUID = -1280702397805414147L;
|
|
|
|
public Object get(Node node) {
|
|
NodeRef destRef = (NodeRef)node.getProperties().get(ContentModel.PROP_LINK_DESTINATION);
|
|
if (getNodeService().exists(destRef) == true)
|
|
{
|
|
String destName = Repository.getNameForNode(getNodeService(), destRef);
|
|
return DownloadContentServlet.generateBrowserURL(destRef, destName);
|
|
}
|
|
else
|
|
{
|
|
// TODO: link object is missing - navigate to a page with appropriate message
|
|
return "#";
|
|
}
|
|
}
|
|
};
|
|
|
|
public NodePropertyResolver resolverLinkWebdavUrl = new NodePropertyResolver() {
|
|
private static final long serialVersionUID = -3097558079118837397L;
|
|
|
|
public Object get(Node node) {
|
|
NodeRef destRef = (NodeRef)node.getProperties().get(ContentModel.PROP_LINK_DESTINATION);
|
|
if (getNodeService().exists(destRef) == true)
|
|
{
|
|
return Utils.generateURL(FacesContext.getCurrentInstance(), new Node(destRef), URLMode.WEBDAV);
|
|
}
|
|
else
|
|
{
|
|
// TODO: link object is missing - navigate to a page with appropriate message
|
|
return "#";
|
|
}
|
|
}
|
|
};
|
|
|
|
public NodePropertyResolver resolverLinkCifsPath = new NodePropertyResolver() {
|
|
private static final long serialVersionUID = 673020173327603487L;
|
|
|
|
public Object get(Node node) {
|
|
NodeRef destRef = (NodeRef)node.getProperties().get(ContentModel.PROP_LINK_DESTINATION);
|
|
if (getNodeService().exists(destRef) == true)
|
|
{
|
|
return Utils.generateURL(FacesContext.getCurrentInstance(), new Node(destRef), URLMode.CIFS);
|
|
}
|
|
else
|
|
{
|
|
// TODO: link object is missing - navigate to a page with appropriate message
|
|
return "#";
|
|
}
|
|
}
|
|
};
|
|
|
|
public NodePropertyResolver resolverFileType16 = new NodePropertyResolver() {
|
|
private static final long serialVersionUID = -2690520488415178029L;
|
|
|
|
public Object get(Node node) {
|
|
return FileTypeImageUtils.getFileTypeImage(node.getName(), true);
|
|
}
|
|
};
|
|
|
|
public NodePropertyResolver resolverFileType32 = new NodePropertyResolver() {
|
|
private static final long serialVersionUID = 1991254398502584389L;
|
|
|
|
public Object get(Node node) {
|
|
return FileTypeImageUtils.getFileTypeImage(node.getName(), false);
|
|
}
|
|
};
|
|
|
|
public NodePropertyResolver resolverPath = new NodePropertyResolver() {
|
|
private static final long serialVersionUID = 8008094870888545035L;
|
|
|
|
public Object get(Node node) {
|
|
return node.getNodePath();
|
|
}
|
|
};
|
|
|
|
public NodePropertyResolver resolverDisplayPath = new NodePropertyResolver() {
|
|
private static final long serialVersionUID = -918422848579179425L;
|
|
|
|
public Object get(Node node) {
|
|
// TODO: replace this with a method that shows the full display name - not QNames?
|
|
return Repository.getDisplayPath(node.getNodePath());
|
|
}
|
|
};
|
|
|
|
public NodePropertyResolver resolverSpaceIcon = new NodePropertyResolver() {
|
|
private static final long serialVersionUID = -5644418026591098018L;
|
|
|
|
public Object get(Node node) {
|
|
QNameNodeMap props = (QNameNodeMap)node.getProperties();
|
|
String icon = (String)props.getRaw("app:icon");
|
|
return (icon != null ? icon : CreateSpaceWizard.DEFAULT_SPACE_ICON_NAME);
|
|
}
|
|
};
|
|
|
|
public NodePropertyResolver resolverSmallIcon = new NodePropertyResolver() {
|
|
private static final long serialVersionUID = -150483121767183580L;
|
|
|
|
public Object get(Node node) {
|
|
QNameNodeMap props = (QNameNodeMap)node.getProperties();
|
|
String icon = (String)props.getRaw("app:icon");
|
|
return (icon != null ? icon + "-16" : SPACE_SMALL_DEFAULT);
|
|
}
|
|
};
|
|
|
|
public NodePropertyResolver resolverMimetype = new NodePropertyResolver() {
|
|
private static final long serialVersionUID = -8864267975247235172L;
|
|
|
|
public Object get(Node node) {
|
|
ContentData content = (ContentData)node.getProperties().get(ContentModel.PROP_CONTENT);
|
|
return (content != null ? content.getMimetype() : null);
|
|
}
|
|
};
|
|
|
|
public NodePropertyResolver resolverEncoding = new NodePropertyResolver() {
|
|
private static final long serialVersionUID = -1130974681844152101L;
|
|
|
|
public Object get(Node node) {
|
|
ContentData content = (ContentData)node.getProperties().get(ContentModel.PROP_CONTENT);
|
|
return (content != null ? content.getEncoding() : null);
|
|
}
|
|
};
|
|
|
|
public NodePropertyResolver resolverSize = new NodePropertyResolver() {
|
|
private static final long serialVersionUID = 1273541660444385276L;
|
|
|
|
public Object get(Node node) {
|
|
ContentData content = (ContentData)node.getProperties().get(ContentModel.PROP_CONTENT);
|
|
return (content != null ? new Long(content.getSize()) : 0L);
|
|
}
|
|
};
|
|
|
|
public NodePropertyResolver resolverLang = new NodePropertyResolver() {
|
|
|
|
private static final long serialVersionUID = 5412446489528560367L;
|
|
|
|
public Object get(Node node) {
|
|
|
|
String lang = null;
|
|
|
|
if (node.getAspects().contains(ContentModel.ASPECT_MULTILINGUAL_DOCUMENT))
|
|
{
|
|
Locale locale = null;
|
|
|
|
if(node.hasAspect(ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION))
|
|
{
|
|
// if the translation is empty, the lang of the content is the lang of it's pivot.
|
|
NodeRef pivot = getMultilingualContentService().getPivotTranslation(node.getNodeRef());
|
|
locale = (Locale) getNodeService().getProperty(pivot, ContentModel.PROP_LOCALE);
|
|
}
|
|
else
|
|
{
|
|
locale = (Locale) node.getProperties().get(ContentModel.PROP_LOCALE);
|
|
}
|
|
// the content filter lang defined by the user
|
|
String userLang = userPreferencesBean.getContentFilterLanguage();
|
|
// the node lang
|
|
String nodeLang = locale.getLanguage();
|
|
|
|
// if filter equals all languages : display the lang for each translation
|
|
if (nodeLang == null)
|
|
{
|
|
lang = nodeLang;
|
|
}
|
|
|
|
// if filter is different : display the lang
|
|
else if (!nodeLang.equalsIgnoreCase(userLang))
|
|
{
|
|
lang = nodeLang;
|
|
}
|
|
|
|
// else if the filter is equal to the lang node : nothing to do [lang = null]
|
|
}
|
|
|
|
return lang;
|
|
}
|
|
};
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Navigation action event handlers
|
|
|
|
/**
|
|
* Action called from the Simple Search component.
|
|
* Sets up the SearchContext object with the values from the simple search menu.
|
|
*/
|
|
public void search(ActionEvent event)
|
|
{
|
|
// setup the search text string on the top-level navigation handler
|
|
UISimpleSearch search = (UISimpleSearch)event.getComponent();
|
|
this.navigator.setSearchContext(search.getSearchContext());
|
|
|
|
navigateBrowseScreen();
|
|
}
|
|
|
|
/**
|
|
* Action called to Close the search dialog by returning to the last view node Id
|
|
*/
|
|
public void closeSearch(ActionEvent event)
|
|
{
|
|
// set the current node Id ready for page refresh
|
|
String currentNodeId = this.navigator.getCurrentNodeId();
|
|
this.navigator.setCurrentNodeId(currentNodeId);
|
|
|
|
// setup dispatch context so we go back to the right place
|
|
NodeRef currentNodeRef = new NodeRef(Repository.getStoreRef(), currentNodeId);
|
|
Node currentNode = new Node(currentNodeRef);
|
|
this.navigator.setupDispatchContext(currentNode);
|
|
}
|
|
|
|
/**
|
|
* Update page size based on user selection
|
|
*/
|
|
public void updateSpacesPageSize(ActionEvent event)
|
|
{
|
|
try
|
|
{
|
|
int size = Integer.parseInt(this.pageSizeSpacesStr);
|
|
if (size >= 0)
|
|
{
|
|
this.pageSizeSpaces = size;
|
|
}
|
|
else
|
|
{
|
|
// reset to known value if this occurs
|
|
this.pageSizeSpacesStr = Integer.toString(this.pageSizeSpaces);
|
|
}
|
|
}
|
|
catch (NumberFormatException err)
|
|
{
|
|
// reset to known value if this occurs
|
|
this.pageSizeSpacesStr = Integer.toString(this.pageSizeSpaces);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update page size based on user selection
|
|
*/
|
|
public void updateContentPageSize(ActionEvent event)
|
|
{
|
|
try
|
|
{
|
|
int size = Integer.parseInt(this.pageSizeContentStr);
|
|
if (size >= 0)
|
|
{
|
|
this.pageSizeContent = size;
|
|
}
|
|
else
|
|
{
|
|
// reset to known value if this occurs
|
|
this.pageSizeContentStr = Integer.toString(this.pageSizeContent);
|
|
}
|
|
}
|
|
catch (NumberFormatException err)
|
|
{
|
|
// reset to known value if this occurs
|
|
this.pageSizeContentStr = Integer.toString(this.pageSizeContent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Action called when a folder space is clicked.
|
|
* Navigate into the space.
|
|
*/
|
|
public void clickSpace(ActionEvent event)
|
|
{
|
|
UIActionLink link = (UIActionLink)event.getComponent();
|
|
Map<String, String> params = link.getParameterMap();
|
|
String id = params.get("id");
|
|
if (id != null && id.length() != 0)
|
|
{
|
|
try
|
|
{
|
|
NodeRef ref = new NodeRef(Repository.getStoreRef(), id);
|
|
|
|
// handle special folder link node case
|
|
if (ApplicationModel.TYPE_FOLDERLINK.equals(this.getNodeService().getType(ref)))
|
|
{
|
|
ref = (NodeRef)this.getNodeService().getProperty(ref, ContentModel.PROP_LINK_DESTINATION);
|
|
}
|
|
|
|
clickSpace(ref);
|
|
}
|
|
catch (InvalidNodeRefException refErr)
|
|
{
|
|
Utils.addErrorMessage(MessageFormat.format(Application.getMessage(
|
|
FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Action called when a folder space is clicked.
|
|
*
|
|
* @param nodeRef The node being clicked
|
|
*/
|
|
public void clickSpace(NodeRef nodeRef)
|
|
{
|
|
// refresh UI based on node selection
|
|
updateUILocation(nodeRef);
|
|
}
|
|
|
|
/**
|
|
* Handler called when a path element is clicked - navigate to the appropriate Space
|
|
*/
|
|
public void clickSpacePath(ActionEvent event)
|
|
{
|
|
UINodePath.PathElementEvent pathEvent = (UINodePath.PathElementEvent)event;
|
|
NodeRef ref = pathEvent.NodeReference;
|
|
try
|
|
{
|
|
// refresh UI based on node selection
|
|
this.updateUILocation(ref);
|
|
}
|
|
catch (InvalidNodeRefException refErr)
|
|
{
|
|
Utils.addErrorMessage(MessageFormat.format(Application.getMessage(
|
|
FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {ref.getId()}) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Action called when a folders direct descendant (in the 'list' browse mode) is clicked.
|
|
* Navigate into the the descendant space.
|
|
*/
|
|
public void clickDescendantSpace(ActionEvent event)
|
|
{
|
|
UINodeDescendants.NodeSelectedEvent nodeEvent = (UINodeDescendants.NodeSelectedEvent)event;
|
|
NodeRef nodeRef = nodeEvent.NodeReference;
|
|
if (nodeRef == null)
|
|
{
|
|
throw new IllegalStateException("NodeRef returned from UINodeDescendants.NodeSelectedEvent cannot be null!");
|
|
}
|
|
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("Selected noderef Id: " + nodeRef.getId());
|
|
|
|
try
|
|
{
|
|
// user can either select a descendant of a node display on the page which means we
|
|
// must add the it's parent and itself to the breadcrumb
|
|
ChildAssociationRef parentAssocRef = getNodeService().getPrimaryParent(nodeRef);
|
|
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Selected item getPrimaryParent().getChildRef() noderef Id: " + parentAssocRef.getChildRef().getId());
|
|
logger.debug("Selected item getPrimaryParent().getParentRef() noderef Id: " + parentAssocRef.getParentRef().getId());
|
|
logger.debug("Current value getNavigator().getCurrentNodeId() noderef Id: " + this.navigator.getCurrentNodeId());
|
|
}
|
|
|
|
if (nodeEvent.IsParent == false)
|
|
{
|
|
// a descendant of the displayed node was selected
|
|
// first refresh based on the parent and add to the breadcrumb
|
|
updateUILocation(parentAssocRef.getParentRef());
|
|
|
|
// now add our selected node
|
|
updateUILocation(nodeRef);
|
|
}
|
|
else
|
|
{
|
|
// else the parent ellipses i.e. the displayed node was selected
|
|
updateUILocation(nodeRef);
|
|
}
|
|
}
|
|
catch (InvalidNodeRefException refErr)
|
|
{
|
|
Utils.addErrorMessage(MessageFormat.format(Application.getMessage(
|
|
FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {nodeRef.getId()}) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Action event called by all Browse actions that need to setup a Space context
|
|
* before an action page/wizard is called. The context will be a Node in setActionSpace() which
|
|
* can be retrieved on the action page from BrowseBean.getActionSpace().
|
|
*
|
|
* @param event ActionEvent
|
|
*/
|
|
public void setupSpaceAction(ActionEvent event)
|
|
{
|
|
UIActionLink link = (UIActionLink)event.getComponent();
|
|
Map<String, String> params = link.getParameterMap();
|
|
String id = params.get("id");
|
|
setupSpaceAction(id, true);
|
|
}
|
|
|
|
/**
|
|
* Public helper to setup action pages with Space context
|
|
*
|
|
* @param id of the Space node to setup context for
|
|
*/
|
|
public void setupSpaceAction(String id, boolean invalidate)
|
|
{
|
|
if (id != null && id.length() != 0)
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("Setup for action, setting current space to: " + id);
|
|
|
|
try
|
|
{
|
|
// create the node ref, then our node representation
|
|
NodeRef ref = new NodeRef(Repository.getStoreRef(), id);
|
|
Node node = new Node(ref);
|
|
|
|
// resolve icon in-case one has not been set
|
|
node.addPropertyResolver("icon", this.resolverSpaceIcon);
|
|
|
|
// prepare a node for the action context
|
|
setActionSpace(node);
|
|
|
|
// setup the dispatch context in case it is required
|
|
this.navigator.setupDispatchContext(node);
|
|
}
|
|
catch (InvalidNodeRefException refErr)
|
|
{
|
|
Utils.addErrorMessage(MessageFormat.format(Application.getMessage(
|
|
FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
setActionSpace(null);
|
|
}
|
|
|
|
// clear the UI state in preparation for finishing the next action
|
|
if (invalidate == true)
|
|
{
|
|
// use the context service to notify all registered beans
|
|
UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Acrtion event called by Delete Space actions. We setup the action space as normal, then prepare
|
|
* any special case message string to be shown to the user if they are trying to delete specific spaces.
|
|
*/
|
|
public void setupDeleteAction(ActionEvent event)
|
|
{
|
|
String message = null;
|
|
|
|
setupSpaceAction(event);
|
|
|
|
Node node = getActionSpace();
|
|
if (node != null)
|
|
{
|
|
FacesContext fc = FacesContext.getCurrentInstance();
|
|
NodeRef companyRootRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId(fc));
|
|
if (node.getNodeRef().equals(companyRootRef))
|
|
{
|
|
message = Application.getMessage(fc, MSG_DELETE_COMPANYROOT);
|
|
}
|
|
}
|
|
|
|
setDeleteMessage(message);
|
|
}
|
|
|
|
/**
|
|
* Action event called by all actions that need to setup a Content Document context on the
|
|
* BrowseBean before an action page/wizard is called. The context will be a Node in
|
|
* setDocument() which can be retrieved on the action page from BrowseBean.getDocument().
|
|
*/
|
|
public void setupContentAction(ActionEvent event)
|
|
{
|
|
UIActionLink link = (UIActionLink)event.getComponent();
|
|
Map<String, String> params = link.getParameterMap();
|
|
setupContentAction(params.get("id"), true);
|
|
}
|
|
|
|
/**
|
|
* Action event called by all actions that need to setup a <b>Multilingual</b> Content Document context on the
|
|
* BrowseBean before an action page/wizard is called. The context will be a Node in
|
|
* setDocument() which can be retrieved on the action page from BrowseBean.getDocument().
|
|
*/
|
|
public void setupMLContainerContentAction(ActionEvent event)
|
|
{
|
|
UIActionLink link = (UIActionLink)event.getComponent();
|
|
Map<String, String> params = link.getParameterMap();
|
|
|
|
String id = params.get("id");
|
|
|
|
NodeRef translation = new NodeRef(Repository.getStoreRef(), id);
|
|
|
|
// remember the bean from which the action comes
|
|
FacesContext fc = FacesContext.getCurrentInstance();
|
|
DocumentDetailsDialog docDetails = (DocumentDetailsDialog)FacesHelper.getManagedBean(fc, "DocumentDetailsDialog");
|
|
docDetails.setTranslationDocument(new MapNode(translation));
|
|
MultilingualManageDialog mmDialog = (MultilingualManageDialog)FacesHelper.getManagedBean(fc, "MultilingualManageDialog");
|
|
mmDialog.setTranslationDocument(docDetails.getTranslationDocument());
|
|
|
|
// set the ml container as the current document
|
|
NodeRef mlContainer = getMultilingualContentService().getTranslationContainer(translation);
|
|
|
|
setupContentAction(mlContainer.getId(), true);
|
|
}
|
|
|
|
/**
|
|
* Public helper to setup action pages with content context
|
|
*
|
|
* @param id of the content node to setup context for
|
|
*/
|
|
public void setupContentAction(String id, boolean invalidate)
|
|
{
|
|
if (id != null && id.length() != 0)
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("Setup for action, setting current document to: " + id);
|
|
|
|
try
|
|
{
|
|
// create the node ref, then our node representation
|
|
NodeRef ref = new NodeRef(Repository.getStoreRef(), id);
|
|
Node node = new MapNode(ref);
|
|
|
|
// store the URL to for downloading the content
|
|
if (ApplicationModel.TYPE_FILELINK.equals(node.getType()))
|
|
{
|
|
node.addPropertyResolver("url", this.resolverLinkDownload);
|
|
node.addPropertyResolver("downloadUrl", this.resolverLinkDownload);
|
|
}
|
|
else
|
|
{
|
|
node.addPropertyResolver("url", this.resolverDownload);
|
|
node.addPropertyResolver("downloadUrl", this.resolverDownload);
|
|
}
|
|
node.addPropertyResolver("webdavUrl", this.resolverWebdavUrl);
|
|
node.addPropertyResolver("cifsPath", this.resolverCifsPath);
|
|
node.addPropertyResolver("fileType32", this.resolverFileType32);
|
|
node.addPropertyResolver("mimetype", this.resolverMimetype);
|
|
node.addPropertyResolver("encoding", this.resolverEncoding);
|
|
node.addPropertyResolver("size", this.resolverSize);
|
|
node.addPropertyResolver("lang", this.resolverLang);
|
|
|
|
for (NodeEventListener listener : getNodeEventListeners())
|
|
{
|
|
listener.created(node, node.getType());
|
|
}
|
|
|
|
// get hold of the DocumentDetailsDialog and reset it
|
|
DocumentDetailsDialog docDetails = (DocumentDetailsDialog)FacesContext.getCurrentInstance().
|
|
getExternalContext().getSessionMap().get("DocumentDetailsDialog");
|
|
if (docDetails != null)
|
|
{
|
|
docDetails.reset();
|
|
}
|
|
|
|
// remember the document
|
|
setDocument(node);
|
|
|
|
// setup the dispatch context in case it is required
|
|
this.navigator.setupDispatchContext(node);
|
|
}
|
|
catch (InvalidNodeRefException refErr)
|
|
{
|
|
Utils.addErrorMessage(MessageFormat.format(Application.getMessage(
|
|
FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) );
|
|
throw new AbortProcessingException("Invalid node reference");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
setDocument(null);
|
|
}
|
|
|
|
// clear the UI state in preparation for finishing the next action
|
|
if (invalidate == true)
|
|
{
|
|
// use the context service to notify all registered beans
|
|
UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes the given node from the breadcrumb i.e. following a delete
|
|
*
|
|
* @param node The space to remove from the breadcrumb
|
|
*/
|
|
public void removeSpaceFromBreadcrumb(Node node)
|
|
{
|
|
List<IBreadcrumbHandler> location = navigator.getLocation();
|
|
IBreadcrumbHandler handler = location.get(location.size() - 1);
|
|
if (handler instanceof IRepoBreadcrumbHandler)
|
|
{
|
|
// see if the current breadcrumb location is our node
|
|
if ( ((IRepoBreadcrumbHandler)handler).getNodeRef().equals(node.getNodeRef()) == true )
|
|
{
|
|
location.remove(location.size() - 1);
|
|
|
|
// now work out which node to set the list to refresh against
|
|
if (location.size() != 0)
|
|
{
|
|
handler = location.get(location.size() - 1);
|
|
|
|
if (handler instanceof IRepoBreadcrumbHandler)
|
|
{
|
|
// change the current node Id
|
|
navigator.setCurrentNodeId(((IRepoBreadcrumbHandler)handler).getNodeRef().getId());
|
|
}
|
|
else
|
|
{
|
|
// if we don't have access to the NodeRef to go to next then go to the home space
|
|
navigator.processToolbarLocation(NavigationBean.LOCATION_HOME, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if there is no breadcrumb left go to the user's home space
|
|
navigator.processToolbarLocation(NavigationBean.LOCATION_HOME, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Support for refresh of lists via special case for an External Access URL.
|
|
* these URLs restart the JSF lifecycle but an old UIRichList is restored from
|
|
* the component tree - which needs clearing "late" in the lifecycle process.
|
|
*/
|
|
public void externalAccessRefresh()
|
|
{
|
|
this.externalForceRefresh = true;
|
|
}
|
|
|
|
/**
|
|
* Save the state of the panel that was expanded/collapsed
|
|
*/
|
|
public void expandPanel(ActionEvent event)
|
|
{
|
|
if (event instanceof ExpandedEvent)
|
|
{
|
|
String id = event.getComponent().getId();
|
|
this.panels.put(id, ((ExpandedEvent)event).State);
|
|
}
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Private helpers
|
|
|
|
/**
|
|
* Initialise default values from client configuration
|
|
*/
|
|
private void initFromClientConfig()
|
|
{
|
|
// TODO - review implications of these default values on dynamic/MT client: viewsConfig & browseViewMode, as well as page size content/spaces ...
|
|
ConfigService config = Application.getConfigService(FacesContext.getCurrentInstance());
|
|
|
|
this.viewsConfig = (ViewsConfigElement)config.getConfig("Views").
|
|
getConfigElement(ViewsConfigElement.CONFIG_ELEMENT_ID);
|
|
|
|
this.browseViewMode = this.viewsConfig.getDefaultView(PAGE_NAME_BROWSE);
|
|
int pageSize = this.viewsConfig.getDefaultPageSize(PAGE_NAME_BROWSE, this.browseViewMode);
|
|
setPageSizeContent(pageSize);
|
|
setPageSizeSpaces(pageSize);
|
|
}
|
|
|
|
/**
|
|
* @return the Set of NodeEventListeners registered against this bean
|
|
*/
|
|
private Set<NodeEventListener> getNodeEventListeners()
|
|
{
|
|
if ((this.nodeEventListeners == null) || (Application.isDynamicConfig(FacesContext.getCurrentInstance())))
|
|
{
|
|
Set<NodeEventListener> allNodeEventListeners = new HashSet<NodeEventListener>();
|
|
|
|
if (Application.isDynamicConfig(FacesContext.getCurrentInstance()) && (this.nodeEventListeners != null))
|
|
{
|
|
// for dynamic config, can add/remove node event listeners dynamically ...
|
|
// however, in case anyone is using public methods (add/removeNodeEventListener)
|
|
// we merge list here with list returned from the config
|
|
allNodeEventListeners.addAll(this.nodeEventListeners);
|
|
}
|
|
|
|
FacesContext fc = FacesContext.getCurrentInstance();
|
|
Config listenerConfig = Application.getConfigService(fc).getConfig("Node Event Listeners");
|
|
if (listenerConfig != null)
|
|
{
|
|
ConfigElement listenerElement = listenerConfig.getConfigElement("node-event-listeners");
|
|
if (listenerElement != null)
|
|
{
|
|
for (ConfigElement child : listenerElement.getChildren())
|
|
{
|
|
if (child.getName().equals("listener"))
|
|
{
|
|
// retrieved the JSF Managed Bean identified in the config
|
|
String listenerName = child.getValue().trim();
|
|
Object bean = FacesHelper.getManagedBean(fc, listenerName);
|
|
if (bean instanceof NodeEventListener)
|
|
{
|
|
allNodeEventListeners.add((NodeEventListener)bean);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Application.isDynamicConfig(FacesContext.getCurrentInstance()))
|
|
{
|
|
return allNodeEventListeners;
|
|
}
|
|
else
|
|
{
|
|
this.nodeEventListeners = allNodeEventListeners;
|
|
}
|
|
}
|
|
return this.nodeEventListeners;
|
|
}
|
|
|
|
/**
|
|
* Refresh the UI after a Space selection change. Adds the selected space to the breadcrumb
|
|
* location path and also updates the list components in the UI.
|
|
*
|
|
* @param ref NodeRef of the selected space
|
|
*/
|
|
public void updateUILocation(NodeRef ref)
|
|
{
|
|
// get the current breadcrumb location and append a new handler to it
|
|
// our handler know the ID of the selected node and the display label for it
|
|
List<IBreadcrumbHandler> location = this.navigator.getLocation();
|
|
if (location.size() != 0)
|
|
{
|
|
// attempt to find the ID - if it's already in the breadcrumb then we
|
|
// navigate directly to that node - rather than add duplication to the breadcrumb path
|
|
boolean foundNode = false;
|
|
for (int i=0; i<location.size(); i++)
|
|
{
|
|
IBreadcrumbHandler element = location.get(i);
|
|
if (element instanceof IRepoBreadcrumbHandler)
|
|
{
|
|
NodeRef nodeRef = ((IRepoBreadcrumbHandler)element).getNodeRef();
|
|
if (ref.equals(nodeRef) == true)
|
|
{
|
|
// TODO: we should be able to do this - but the UIBreadcrumb component modifies
|
|
// it's own internal value when clicked - then uses that from then on!
|
|
// the other ops are using the same List object and modding it directly.
|
|
//List<IBreadcrumbHandler> newLocation = new ArrayList<IBreadcrumbHandler>(i+1);
|
|
//newLocation.addAll(location.subList(0, i + 1));
|
|
//this.navigator.setLocation(newLocation);
|
|
// TODO: but instead for now we do this:
|
|
int count = location.size();
|
|
for (int n=i+1; n<count; n++)
|
|
{
|
|
location.remove(i+1);
|
|
}
|
|
|
|
foundNode = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add new node to the end of the existing breadcrumb
|
|
if (foundNode == false)
|
|
{
|
|
FacesContext context = FacesContext.getCurrentInstance();
|
|
String breadcrumbMode = Application.getClientConfig(context).getBreadcrumbMode();
|
|
|
|
if (ClientConfigElement.BREADCRUMB_LOCATION.equals(breadcrumbMode))
|
|
{
|
|
// if the breadcrumb is in "location" mode set the breadcrumb
|
|
// to the full path to the node
|
|
|
|
// TODO: check the end of the current breadcrumb, if the given
|
|
// node is a child then we can shortcut the build of the
|
|
// whole path.
|
|
|
|
Repository.setupBreadcrumbLocation(context, this.navigator, location, ref);
|
|
}
|
|
else
|
|
{
|
|
// if the breadcrum is in "path" mode just add the given item to the end
|
|
String name = Repository.getNameForNode(this.getNodeService(), ref);
|
|
location.add(new BrowseBreadcrumbHandler(ref, name));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// special case to add first item to the location
|
|
String name = Repository.getNameForNode(this.getNodeService(), ref);
|
|
location.add(new BrowseBreadcrumbHandler(ref, name));
|
|
}
|
|
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("Updated breadcrumb: " + location);
|
|
|
|
// set the current node Id ready for page refresh
|
|
this.navigator.setCurrentNodeId(ref.getId());
|
|
|
|
// set up the dispatch context for the navigation handler
|
|
this.navigator.setupDispatchContext(new Node(ref));
|
|
|
|
// inform any listeners that the current space has changed
|
|
UIContextService.getInstance(FacesContext.getCurrentInstance()).spaceChanged();
|
|
|
|
navigateBrowseScreen();
|
|
}
|
|
|
|
/**
|
|
* Invalidate list component state after an action which changes the UI context
|
|
*/
|
|
private void invalidateComponents()
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("Invalidating browse components...");
|
|
|
|
// clear the value for the list components - will cause re-bind to it's data and refresh
|
|
if (this.contentRichList != null)
|
|
{
|
|
this.contentRichList.setValue(null);
|
|
if (this.navigator.getSearchContext() != null)
|
|
{
|
|
// clear the sorting mode so the search results are displayed in default 'score' order
|
|
this.contentRichList.clearSort();
|
|
}
|
|
}
|
|
if (this.spacesRichList != null)
|
|
{
|
|
this.spacesRichList.setValue(null);
|
|
if (this.navigator.getSearchContext() != null)
|
|
{
|
|
// clear the sorting mode so the search results are displayed in default 'score' order
|
|
this.spacesRichList.clearSort();
|
|
}
|
|
}
|
|
|
|
// clear the storage of the last set of nodes
|
|
this.containerNodes = null;
|
|
this.contentNodes = null;
|
|
this.parentContainerNodes = null;
|
|
}
|
|
|
|
/**
|
|
* @return whether the current View ID is the "browse" screen
|
|
*/
|
|
private boolean isViewCurrent()
|
|
{
|
|
return (FacesContext.getCurrentInstance().getViewRoot().getViewId().equals(BROWSE_VIEW_ID));
|
|
}
|
|
|
|
/**
|
|
* Perform navigation to the browse screen if it is not already the current View
|
|
*/
|
|
private void navigateBrowseScreen()
|
|
{
|
|
String outcome = null;
|
|
|
|
if (isViewCurrent() == false)
|
|
{
|
|
outcome = "browse";
|
|
}
|
|
|
|
FacesContext fc = FacesContext.getCurrentInstance();
|
|
fc.getApplication().getNavigationHandler().handleNavigation(fc, null, outcome);
|
|
}
|
|
|
|
/**
|
|
* Event handler used when a file is being deleted, checks that the node
|
|
* does not have an associated working copy.
|
|
*
|
|
* @param event The event
|
|
*/
|
|
public void deleteFile(ActionEvent event)
|
|
{
|
|
setupContentAction(event);
|
|
|
|
UIActionLink link = (UIActionLink)event.getComponent();
|
|
Map<String, String> params = link.getParameterMap();
|
|
|
|
String ref = params.get("ref");
|
|
if (ref != null && ref.length() > 0)
|
|
{
|
|
NodeRef nodeRef = new NodeRef(ref);
|
|
|
|
NodeRef workingCopyNodeRef = getCheckOutCheckInService().getWorkingCopy(nodeRef);
|
|
|
|
if (workingCopyNodeRef != null)
|
|
{
|
|
// if node has a working copy setup error message and return
|
|
Utils.addErrorMessage(MessageFormat.format(Application.getMessage(
|
|
FacesContext.getCurrentInstance(), MSG_CANNOT_DELETE_NODE_HAS_WORKING_COPY),
|
|
new Object[] {getNodeService().getProperty(nodeRef, ContentModel.PROP_NAME)}));
|
|
return;
|
|
}
|
|
|
|
// if there isn't a working copy go to normal delete dialog
|
|
boolean hasMultipleParents = false;
|
|
boolean showDeleteAssocDialog = false;
|
|
|
|
// get type of node being deleted
|
|
Node node = this.getDocument();
|
|
QName type = node.getType();
|
|
TypeDefinition typeDef = this.dictionaryService.getType(type);
|
|
|
|
// determine if the node being delete has multiple parents
|
|
if (!type.equals(ContentModel.TYPE_MULTILINGUAL_CONTAINER) &&
|
|
!node.hasAspect(ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION) &&
|
|
!node.hasAspect(ContentModel.ASPECT_MULTILINGUAL_DOCUMENT) &&
|
|
!type.equals(ContentModel.TYPE_LINK) &&
|
|
!this.dictionaryService.isSubClass(typeDef.getName(), ContentModel.TYPE_LINK))
|
|
{
|
|
List<ChildAssociationRef> parents = this.nodeService.getParentAssocs(node.getNodeRef(),
|
|
ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
|
|
if (parents != null && parents.size() > 1)
|
|
{
|
|
hasMultipleParents = true;
|
|
}
|
|
}
|
|
|
|
// determine which delete dialog to display
|
|
if (this.navigator.getSearchContext() == null && hasMultipleParents)
|
|
{
|
|
// if we are not in a search and the node has multiple parents
|
|
// see if the current node has the primary parent association
|
|
NodeRef parentSpace = this.navigator.getCurrentNode().getNodeRef();
|
|
ChildAssociationRef assoc = this.nodeService.getPrimaryParent(node.getNodeRef());
|
|
|
|
// show delete assoc dialog if the current space is not the primary parent for the node
|
|
showDeleteAssocDialog = !parentSpace.equals(assoc.getParentRef());
|
|
}
|
|
|
|
// show the appropriate dialog
|
|
FacesContext fc = FacesContext.getCurrentInstance();
|
|
if (showDeleteAssocDialog)
|
|
{
|
|
fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "dialog:deleteFileAssoc");
|
|
}
|
|
else
|
|
{
|
|
final Map<String, String> dialogParams = new HashMap<String, String>(1);
|
|
dialogParams.put("hasMultipleParents", Boolean.toString(hasMultipleParents));
|
|
Application.getDialogManager().setupParameters(dialogParams);
|
|
fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "dialog:deleteFile");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles the deleteSpace action by deciding which delete dialog to display
|
|
*/
|
|
public void deleteSpace(ActionEvent event)
|
|
{
|
|
setupDeleteAction(event);
|
|
|
|
boolean hasMultipleParents = false;
|
|
boolean showDeleteAssocDialog = false;
|
|
|
|
// determine if the node being delete has multiple parents
|
|
Node node = this.getActionSpace();
|
|
List<ChildAssociationRef> parents = this.nodeService.getParentAssocs(node.getNodeRef(),
|
|
ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
|
|
if (parents != null && parents.size() > 1)
|
|
{
|
|
hasMultipleParents = true;
|
|
}
|
|
|
|
// determine which delete dialog to display
|
|
if (this.navigator.getSearchContext() == null && hasMultipleParents)
|
|
{
|
|
// if we are not in a search and the node has multiple parents
|
|
// see if the current node has the primary parent association
|
|
NodeRef parentSpace = this.navigator.getCurrentNode().getNodeRef();
|
|
ChildAssociationRef assoc = this.nodeService.getPrimaryParent(node.getNodeRef());
|
|
|
|
// show delete assoc dialog if the current space is not the primary parent for the node
|
|
showDeleteAssocDialog = !parentSpace.equals(assoc.getParentRef());
|
|
}
|
|
|
|
// show the appropriate dialog
|
|
FacesContext fc = FacesContext.getCurrentInstance();
|
|
if (showDeleteAssocDialog)
|
|
{
|
|
fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "dialog:deleteSpaceAssoc");
|
|
}
|
|
else
|
|
{
|
|
final Map<String, String> dialogParams = new HashMap<String, String>(1);
|
|
dialogParams.put("hasMultipleParents", Boolean.toString(hasMultipleParents));
|
|
Application.getDialogManager().setupParameters(dialogParams);
|
|
fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "dialog:deleteSpace");
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Inner classes
|
|
|
|
/**
|
|
* Class to handle breadcrumb interaction for Browse pages
|
|
*/
|
|
private class BrowseBreadcrumbHandler implements IRepoBreadcrumbHandler
|
|
{
|
|
private static final long serialVersionUID = 3833183653173016630L;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param NodeRef The NodeRef for this browse navigation element
|
|
* @param label Element label
|
|
*/
|
|
public BrowseBreadcrumbHandler(NodeRef nodeRef, String label)
|
|
{
|
|
this.label = label;
|
|
this.nodeRef = nodeRef;
|
|
}
|
|
|
|
/**
|
|
* @see java.lang.Object#toString()
|
|
*/
|
|
public String toString()
|
|
{
|
|
return this.label;
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.web.ui.common.component.IBreadcrumbHandler#navigationOutcome(org.alfresco.web.ui.common.component.UIBreadcrumb)
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public String navigationOutcome(UIBreadcrumb breadcrumb)
|
|
{
|
|
// All browse breadcrumb element relate to a Node Id - when selected we
|
|
// set the current node id
|
|
navigator.setCurrentNodeId(this.nodeRef.getId());
|
|
navigator.setLocation( (List)breadcrumb.getValue() );
|
|
|
|
// setup the dispatch context
|
|
navigator.setupDispatchContext(new Node(this.nodeRef));
|
|
|
|
// inform any listeners that the current space has changed
|
|
UIContextService.getInstance(FacesContext.getCurrentInstance()).spaceChanged();
|
|
|
|
// return to browse page if required
|
|
return (isViewCurrent() ? null : "browse");
|
|
}
|
|
|
|
public NodeRef getNodeRef()
|
|
{
|
|
return this.nodeRef;
|
|
}
|
|
|
|
private NodeRef nodeRef;
|
|
private String label;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// Private data
|
|
|
|
private static Log logger = LogFactory.getLog(BrowseBean.class);
|
|
|
|
/** Browse screen view ID */
|
|
public static final String BROWSE_VIEW_ID = "/jsp/browse/browse.jsp";
|
|
public static final String CATEGORY_VIEW_ID = "/jsp/browse/category-browse.jsp";
|
|
|
|
/** Small icon default name */
|
|
public static final String SPACE_SMALL_DEFAULT = "space_small";
|
|
|
|
private static final String VIEWMODE_DASHBOARD = "dashboard";
|
|
private static final String PAGE_NAME_BROWSE = "browse";
|
|
|
|
/** I18N messages */
|
|
private static final String MSG_DELETE_COMPANYROOT = "delete_companyroot_confirm";
|
|
public static final String MSG_SEARCH_MINIMUM = "search_minimum";
|
|
private static final String MSG_CANNOT_DELETE_NODE_HAS_WORKING_COPY = "cannot_delete_node_has_working_copy";
|
|
|
|
/** The NodeService to be used by the bean */
|
|
private transient NodeService nodeService;
|
|
|
|
/** The CheckOutCheckInService to be used by the bean */
|
|
private transient CheckOutCheckInService checkOutCheckInService;
|
|
|
|
/** The SearchService to be used by the bean */
|
|
private transient SearchService searchService;
|
|
|
|
/** The LockService to be used by the bean */
|
|
private transient LockService lockService;
|
|
|
|
/** The NavigationBean bean reference */
|
|
protected NavigationBean navigator;
|
|
|
|
/** The UserPreferencesBean to be used by the bean */
|
|
protected UserPreferencesBean userPreferencesBean;
|
|
|
|
/** The DictionaryService bean reference */
|
|
private transient DictionaryService dictionaryService;
|
|
|
|
/** The file folder service */
|
|
private transient FileFolderService fileFolderService;
|
|
|
|
/** The Multilingual Content Service */
|
|
private transient MultilingualContentService multilingualContentService;
|
|
|
|
/** Views configuration object */
|
|
protected ViewsConfigElement viewsConfig = null;
|
|
|
|
/** Listeners for Node events */
|
|
protected Set<NodeEventListener> nodeEventListeners = null;
|
|
|
|
/** Collapsable Panel state */
|
|
private Map<String, Boolean> panels = new HashMap<String, Boolean>(4, 1.0f);
|
|
|
|
/** Component references */
|
|
protected UIRichList spacesRichList;
|
|
protected UIRichList contentRichList;
|
|
private UIStatusMessage statusMessage;
|
|
|
|
/** Transient lists of container and content nodes for display */
|
|
protected List<Node> containerNodes = null;
|
|
protected List<Node> contentNodes = null;
|
|
protected List<Node> parentContainerNodes = null;
|
|
|
|
/** The current space and it's properties - if any */
|
|
protected Node actionSpace;
|
|
|
|
/** The current document */
|
|
protected Node document;
|
|
|
|
/** Special message to display when user deleting certain folders e.g. Company Home */
|
|
private String deleteMessage;
|
|
|
|
/** The current browse view mode - set to a well known IRichListRenderer identifier */
|
|
private String browseViewMode;
|
|
|
|
/** The current browse view page sizes */
|
|
private int pageSizeSpaces;
|
|
private int pageSizeContent;
|
|
private String pageSizeSpacesStr;
|
|
private String pageSizeContentStr;
|
|
|
|
/** True if current space has a dashboard (template) view available */
|
|
private boolean dashboardView;
|
|
|
|
private boolean externalForceRefresh = false;
|
|
} |