. AVM cut/copy item clipboard support

- AVM files and folders can be cut and copied to the clipboard
 - AVM files and folders can be pasted (copied or moved) around a sandbox
 - The usual "Copy of …" filename logic applies
 - Cut and Copy actions now appear for AVM nodes (as appropriate for user permissions)
 - Refactored logic for cut/copy into specific clipboard item classes - WorkspaceClipboardItem and AVMClipboardItem
. Fixed unreported issue where the DownloadContentServlet was unable to open content for AVM paths containing a space character
. Pager control border style alignment improvement

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@4943 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Kevin Roast
2007-01-26 16:25:03 +00:00
parent 7a187e6a9a
commit 2384aba9bc
9 changed files with 482 additions and 347 deletions

View File

@@ -19,6 +19,7 @@ package org.alfresco.web.app.servlet;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.SocketException; import java.net.SocketException;
import java.net.URLDecoder;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.Date; import java.util.Date;
@@ -138,7 +139,7 @@ public abstract class BaseDownloadContentServlet extends BaseServlet
// assume 'workspace' or other NodeRef based protocol for remaining URL elements // assume 'workspace' or other NodeRef based protocol for remaining URL elements
StoreRef storeRef = new StoreRef(t.nextToken(), t.nextToken()); StoreRef storeRef = new StoreRef(t.nextToken(), t.nextToken());
String id = t.nextToken(); String id = URLDecoder.decode(t.nextToken(), "UTF-8");
// build noderef from the appropriate URL elements // build noderef from the appropriate URL elements
nodeRef = new NodeRef(storeRef, id); nodeRef = new NodeRef(storeRef, id);

View File

@@ -16,7 +16,19 @@
*/ */
package org.alfresco.web.bean.clipboard; package org.alfresco.web.bean.clipboard;
import javax.faces.context.FacesContext;
import javax.transaction.UserTransaction;
import org.alfresco.repo.avm.AVMNodeConverter;
import org.alfresco.service.cmr.avm.AVMExistsException;
import org.alfresco.service.cmr.avm.AVMService;
import org.alfresco.service.cmr.model.FileExistsException;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.web.app.Application;
import org.alfresco.web.app.servlet.FacesHelper;
import org.alfresco.web.bean.repository.Repository;
import org.alfresco.web.bean.wcm.AVMBrowseBean;
/** /**
* Class representing an 'avm' store protocol clipboard item * Class representing an 'avm' store protocol clipboard item
@@ -25,6 +37,8 @@ import org.alfresco.service.cmr.repository.NodeRef;
*/ */
public class AVMClipboardItem extends AbstractClipboardItem public class AVMClipboardItem extends AbstractClipboardItem
{ {
private static final String AVM_PASTE_VIEW_ID = "/jsp/wcm/browse-sandbox.jsp";
/** /**
* @param ref * @param ref
* @param mode * @param mode
@@ -41,4 +55,128 @@ public class AVMClipboardItem extends AbstractClipboardItem
{ {
return false; return false;
} }
/**
* @see org.alfresco.web.bean.clipboard.ClipboardItem#canPasteToViewId(java.lang.String)
*/
public boolean canPasteToViewId(String viewId)
{
// TODO: add 'workspace' paste view when interstore copy/move is supported
return (AVM_PASTE_VIEW_ID.equals(viewId));
}
/**
* @see org.alfresco.web.bean.clipboard.ClipboardItem#paste(javax.faces.context.FacesContext, java.lang.String, int)
*/
public boolean paste(FacesContext fc, String viewId, int action) throws Throwable
{
if (AVM_PASTE_VIEW_ID.equals(viewId))
{
AVMBrowseBean avmBrowseBean = (AVMBrowseBean)FacesHelper.getManagedBean(fc, AVMBrowseBean.BEAN_NAME);
String destPath = avmBrowseBean.getCurrentPath();
NodeRef destRef = AVMNodeConverter.ToNodeRef(-1, destPath);
String sourcePath = AVMNodeConverter.ToAVMVersionPath(getNodeRef()).getSecond();
FileFolderService fileFolderService = getServiceRegistry().getFileFolderService();
AVMService avmService = getServiceRegistry().getAVMService();
// initial name to attempt the copy of the item with
String name = getName();
boolean operationComplete = false;
while (operationComplete == false)
{
UserTransaction tx = null;
try
{
// attempt each copy/paste in its own transaction
tx = Repository.getUserTransaction(fc);
tx.begin();
if (getMode() == ClipboardStatus.COPY)
{
// COPY operation
if (logger.isDebugEnabled())
logger.debug("Attempting to copy node path: " + sourcePath + " into path: " + destPath);
// copy the avm path
// first check that we are not attempting to copy a duplicate into the same parent
if (AVMNodeConverter.ExtendAVMPath(destPath, name).equals(sourcePath))
{
// manually change the name if this occurs
String copyOf = Application.getMessage(fc, MSG_COPY_OF);
name = copyOf + ' ' + name;
}
/*fileFolderService.copy(
getNodeRef(),
destRef,
name);*/
avmService.copy(-1, sourcePath, destPath, name);
// if we get here without an exception, the clipboard copy operation was successful
operationComplete = true;
}
else
{
// MOVE operation
if (logger.isDebugEnabled())
logger.debug("Attempting to move node path: " + sourcePath + " into path: " + destRef);
// move the avm path
/*fileFolderService.move(
getNodeRef(),
destRef,
name);*/
avmService.rename(AVMNodeConverter.SplitBase(sourcePath)[0], getName(),
destPath, name);
// if we get here without an exception, the clipboard move operation was successful
operationComplete = true;
}
}
catch (FileExistsException fileExistsErr)
{
if (getMode() != ClipboardStatus.COPY)
{
// we should not rename an item when it is being moved - so exit
throw fileExistsErr;
}
}
catch (AVMExistsException avmExistsErr)
{
if (getMode() != ClipboardStatus.COPY)
{
// we should not rename an item when it is being moved - so exit
throw avmExistsErr;
}
}
catch (Throwable e)
{
// some other type of exception occured - rollback and exit
throw e;
}
finally
{
// rollback if the operation didn't complete
if (operationComplete == false)
{
try { if (tx != null) {tx.rollback();} } catch (Exception tex) {}
String copyOf = Application.getMessage(fc, MSG_COPY_OF);
name = copyOf + ' ' + name;
}
else
{
// commit the transaction
tx.commit();
}
}
}
return operationComplete;
}
else
{
// TODO: support 'workspace' destination view...
return false;
}
}
} }

View File

@@ -16,15 +16,21 @@
*/ */
package org.alfresco.web.bean.clipboard; package org.alfresco.web.bean.clipboard;
import java.util.List;
import javax.faces.context.FacesContext; import javax.faces.context.FacesContext;
import org.alfresco.model.ApplicationModel; import org.alfresco.model.ApplicationModel;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.repo.search.QueryParameterDefImpl;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.search.QueryParameterDefinition;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.alfresco.web.bean.repository.Node;
import org.alfresco.web.bean.repository.Repository; import org.alfresco.web.bean.repository.Repository;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/** /**
* Base class representing a single item added to the clipboard. * Base class representing a single item added to the clipboard.
@@ -33,6 +39,24 @@ import org.alfresco.web.bean.repository.Repository;
*/ */
abstract class AbstractClipboardItem implements ClipboardItem abstract class AbstractClipboardItem implements ClipboardItem
{ {
protected static Log logger = LogFactory.getLog(ClipboardBean.class);
protected static final String MSG_COPY_OF = "copy_of";
/** Shallow search for nodes with a name pattern */
private static final String XPATH_QUERY_NODE_MATCH = "./*[like(@cm:name, $cm:name, false)]";
private ServiceRegistry services = null;
protected NodeRef ref;
protected ClipboardStatus mode;
// cached values
private String name;
private QName type;
private String icon;
/** /**
* Constructor * Constructor
* *
@@ -54,7 +78,8 @@ abstract class AbstractClipboardItem implements ClipboardItem
{ {
if (this.name == null) if (this.name == null)
{ {
this.name = (String)getNodeService().getProperty(this.ref, ContentModel.PROP_NAME); this.name = (String)getServiceRegistry().getNodeService().getProperty(
this.ref, ContentModel.PROP_NAME);
} }
return this.name; return this.name;
} }
@@ -63,7 +88,7 @@ abstract class AbstractClipboardItem implements ClipboardItem
{ {
if (this.type == null) if (this.type == null)
{ {
this.type = getNodeService().getType(this.ref); this.type = getServiceRegistry().getNodeService().getType(this.ref);
} }
return this.type; return this.type;
} }
@@ -72,7 +97,8 @@ abstract class AbstractClipboardItem implements ClipboardItem
{ {
if (this.icon == null) if (this.icon == null)
{ {
this.icon = (String)getNodeService().getProperty(this.ref, ApplicationModel.PROP_ICON); this.icon = (String)getServiceRegistry().getNodeService().getProperty(
this.ref, ApplicationModel.PROP_ICON);
} }
return this.icon; return this.icon;
} }
@@ -114,16 +140,33 @@ abstract class AbstractClipboardItem implements ClipboardItem
return ref.hashCode(); return ref.hashCode();
} }
protected static NodeService getNodeService() protected ServiceRegistry getServiceRegistry()
{ {
return Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getNodeService(); if (services == null)
{
services = Repository.getServiceRegistry(FacesContext.getCurrentInstance());
}
return services;
} }
protected NodeRef ref; protected boolean checkExists(String name, NodeRef parent)
protected ClipboardStatus mode; {
QueryParameterDefinition[] params = new QueryParameterDefinition[1];
params[0] = new QueryParameterDefImpl(
ContentModel.PROP_NAME,
getServiceRegistry().getDictionaryService().getDataType(
DataTypeDefinition.TEXT),
true,
name);
// cached values // execute the query
private String name; List<NodeRef> nodeRefs = getServiceRegistry().getSearchService().selectNodes(
private QName type; parent,
private String icon; XPATH_QUERY_NODE_MATCH,
params,
getServiceRegistry().getNamespaceService(),
false);
return (nodeRefs.size() != 0);
}
} }

View File

@@ -16,36 +16,17 @@
*/ */
package org.alfresco.web.bean.clipboard; package org.alfresco.web.bean.clipboard;
import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.faces.context.FacesContext; import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent; import javax.faces.event.ActionEvent;
import javax.transaction.UserTransaction;
import org.alfresco.model.ApplicationModel;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.search.QueryParameterDefImpl;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.model.FileExistsException;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.CopyService;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.QueryParameterDefinition;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.namespace.QName;
import org.alfresco.web.app.Application; import org.alfresco.web.app.Application;
import org.alfresco.web.app.context.UIContextService; import org.alfresco.web.app.context.UIContextService;
import org.alfresco.web.bean.NavigationBean;
import org.alfresco.web.bean.repository.Repository;
import org.alfresco.web.ui.common.Utils; import org.alfresco.web.ui.common.Utils;
import org.alfresco.web.ui.common.component.UIActionLink; import org.alfresco.web.ui.common.component.UIActionLink;
import org.alfresco.web.ui.repo.component.shelf.UIClipboardShelfItem; import org.alfresco.web.ui.repo.component.shelf.UIClipboardShelfItem;
@@ -53,54 +34,29 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
/** /**
* Bean backing the Clipboard shelf functionality.
* <p>
* The clipboard bean is responsible for processing Cut/Copy requests to the clipboard
* and for executing the various Paste calls available to the user.
*
* @author Kevin Roast * @author Kevin Roast
*/ */
public class ClipboardBean public class ClipboardBean
{ {
private static Log logger = LogFactory.getLog(ClipboardBean.class);
/** I18N messages */
private static final String MSG_ERROR_PASTE = "error_paste";
/** Current state of the clipboard items */
private List<ClipboardItem> items = new ArrayList<ClipboardItem>(4);
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
// Bean property getters and setters // Bean property getters and setters
/** /**
* @param nodeService The NodeService to set. * @return Returns a list representing the items on the user clipboard.
*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/**
* @param fileFolderService The FileFolderService to set.
*/
public void setFileFolderService(FileFolderService fileFolderService)
{
this.fileFolderService = fileFolderService;
}
/**
* @param copyService The CopyService to set.
*/
public void setCopyService(CopyService copyService)
{
this.copyService = copyService;
}
/**
* @param searchService The SearchService to set.
*/
public void setSearchService(SearchService searchService)
{
this.searchService = searchService;
}
/**
* @param navigator The NavigationBean to set.
*/
public void setNavigator(NavigationBean navigator)
{
this.navigator = navigator;
}
/**
* @return Returns the clipboard items.
*/ */
public List<ClipboardItem> getItems() public List<ClipboardItem> getItems()
{ {
@@ -108,7 +64,7 @@ public class ClipboardBean
} }
/** /**
* @param items The clipboard items to set. * @param items List representing the items on the user clipboard.
*/ */
public void setItems(List<ClipboardItem> items) public void setItems(List<ClipboardItem> items)
{ {
@@ -185,30 +141,30 @@ public class ClipboardBean
{ {
// paste all // paste all
for (int i=0; i<this.items.size(); i++) for (int i=0; i<this.items.size(); i++)
{
performClipboardOperation(this.items.get(i), action);
}
// remove the cut operation item from the clipboard
List<ClipboardItem> newItems = new ArrayList<ClipboardItem>(this.items.size());
for (int i=0; i<this.items.size(); i++)
{ {
ClipboardItem item = this.items.get(i); ClipboardItem item = this.items.get(i);
if (item.getMode() != ClipboardStatus.CUT) if (performClipboardOperation(item, action) == true)
{ {
newItems.add(item); // if cut operation then remove item from the clipboard
if (item.getMode() == ClipboardStatus.CUT)
{
this.items.remove(i);
}
} }
} }
setItems(newItems);
// TODO: after a paste all - remove items from the clipboard...? or not. ask linton // TODO: after a paste all - remove items from the clipboard...? or not. ask linton
} }
else else
{ {
// single paste operation // single paste operation
ClipboardItem item = this.items.get(index); ClipboardItem item = this.items.get(index);
performClipboardOperation(item, action); if (performClipboardOperation(item, action) == true)
if (item.getMode() == ClipboardStatus.CUT)
{ {
this.items.remove(index); // if cut operation then remove item from the clipboard
if (item.getMode() == ClipboardStatus.CUT)
{
this.items.remove(index);
}
} }
} }
@@ -227,214 +183,26 @@ public class ClipboardBean
* *
* @param item the ClipboardItem * @param item the ClipboardItem
* @param action the clipboard action to perform (see UIClipboardShelfItem) * @param action the clipboard action to perform (see UIClipboardShelfItem)
*
* @return true on successful operation
*/ */
private void performClipboardOperation(ClipboardItem item, int action) private boolean performClipboardOperation(ClipboardItem item, int action)
throws Throwable throws Throwable
{ {
NodeRef destRef = new NodeRef(Repository.getStoreRef(), this.navigator.getCurrentNodeId()); FacesContext fc = FacesContext.getCurrentInstance();
DictionaryService dd = Repository.getServiceRegistry( // test the current JSF view to see if the clipboard item can paste to it
FacesContext.getCurrentInstance()).getDictionaryService(); if (logger.isDebugEnabled())
logger.debug("Clipboard destintation View Id: " + fc.getViewRoot().getViewId());
// TODO: Should we be using primary parent here? if (item.canPasteToViewId(fc.getViewRoot().getViewId()) == false)
// We are assuming that the item exists in only a single parent and that the source for
// the clipboard operation (e.g. the source folder) is specifically that parent node.
// So does not allow for more than one possible parent node - or for linked objects!
// This code should be refactored to use a parent ID when appropriate.
ChildAssociationRef assocRef = this.nodeService.getPrimaryParent(item.getNodeRef());
// initial name to attempt the copy of the item with
String name = item.getName();
if (action == UIClipboardShelfItem.ACTION_PASTE_LINK)
{ {
// copy as link was specifically requested by the user // early exit if we cannot support this view as a paste location
String linkTo = Application.getMessage(FacesContext.getCurrentInstance(), MSG_LINK_TO); if (logger.isDebugEnabled())
name = linkTo + ' ' + name; logger.debug("Clipboard Item: " + item.getNodeRef() + " not suitable for paste to current View Id.");
return false;
} }
boolean operationComplete = false; return item.paste(fc, fc.getViewRoot().getViewId(), action);
while (operationComplete == false)
{
UserTransaction tx = null;
try
{
// attempt each copy/paste in its own transaction
tx = Repository.getUserTransaction(FacesContext.getCurrentInstance());
tx.begin();
if (item.getMode() == ClipboardStatus.COPY)
{
if (action == UIClipboardShelfItem.ACTION_PASTE_LINK)
{
// LINK operation
if (logger.isDebugEnabled())
logger.debug("Attempting to link node ID: " + item.getId() + " into node ID: " + destRef.getId());
// we create a special Link Object node that has a property to reference the original
// create the node using the nodeService (can only use FileFolderService for content)
if (checkExists(name + ".lnk", destRef) == false)
{
Map<QName, Serializable> props = new HashMap<QName, Serializable>(2, 1.0f);
props.put(ContentModel.PROP_NAME, name + ".lnk");
props.put(ContentModel.PROP_LINK_DESTINATION, item.getNodeRef());
if (dd.isSubClass(item.getType(), ContentModel.TYPE_CONTENT))
{
// create File Link node
ChildAssociationRef childRef = this.nodeService.createNode(
destRef,
ContentModel.ASSOC_CONTAINS,
assocRef.getQName(),
ApplicationModel.TYPE_FILELINK,
props);
// apply the titled aspect - title and description
Map<QName, Serializable> titledProps = new HashMap<QName, Serializable>(2, 1.0f);
titledProps.put(ContentModel.PROP_TITLE, name);
titledProps.put(ContentModel.PROP_DESCRIPTION, name);
this.nodeService.addAspect(childRef.getChildRef(), ContentModel.ASPECT_TITLED, titledProps);
}
else
{
// create Folder link node
ChildAssociationRef childRef = this.nodeService.createNode(
destRef,
ContentModel.ASSOC_CONTAINS,
assocRef.getQName(),
ApplicationModel.TYPE_FOLDERLINK,
props);
// apply the uifacets aspect - icon, title and description props
Map<QName, Serializable> uiFacetsProps = new HashMap<QName, Serializable>(4, 1.0f);
uiFacetsProps.put(ApplicationModel.PROP_ICON, "space-icon-link");
uiFacetsProps.put(ContentModel.PROP_TITLE, name);
uiFacetsProps.put(ContentModel.PROP_DESCRIPTION, name);
this.nodeService.addAspect(childRef.getChildRef(), ApplicationModel.ASPECT_UIFACETS, uiFacetsProps);
}
// if we get here without an exception, the clipboard link operation was successful
operationComplete = true;
}
}
else
{
// COPY operation
if (logger.isDebugEnabled())
logger.debug("Attempting to copy node ID: " + item.getId() + " into node ID: " + destRef.getId());
if (dd.isSubClass(item.getType(), ContentModel.TYPE_CONTENT) ||
dd.isSubClass(item.getType(), ContentModel.TYPE_FOLDER))
{
// copy the file/folder
// first check that we are not attempting to copy a duplicate into the same parent
if (destRef.equals(assocRef.getParentRef()) && name.equals(item.getName()))
{
// manually change the name if this occurs
String copyOf = Application.getMessage(FacesContext.getCurrentInstance(), MSG_COPY_OF);
name = copyOf + ' ' + name;
}
this.fileFolderService.copy(
item.getNodeRef(),
destRef,
name);
}
else
{
// copy the node
if (checkExists(name, destRef) == false)
{
this.copyService.copy(
item.getNodeRef(),
destRef,
ContentModel.ASSOC_CONTAINS,
assocRef.getQName(),
true);
}
}
// if we get here without an exception, the clipboard copy operation was successful
operationComplete = true;
}
}
else
{
// MOVE operation
if (logger.isDebugEnabled())
logger.debug("Attempting to move node ID: " + item.getId() + " into node ID: " + destRef.getId());
if (dd.isSubClass(item.getType(), ContentModel.TYPE_CONTENT) ||
dd.isSubClass(item.getType(), ContentModel.TYPE_FOLDER))
{
// move the file/folder
this.fileFolderService.move(
item.getNodeRef(),
destRef,
name);
}
else
{
// move the node
this.nodeService.moveNode(
item.getNodeRef(),
destRef,
ContentModel.ASSOC_CONTAINS,
assocRef.getQName());
}
// if we get here without an exception, the clipboard move operation was successful
operationComplete = true;
}
}
catch (FileExistsException fileExistsErr)
{
if (item.getMode() != ClipboardStatus.COPY)
{
// we should not rename an item when it is being moved - so exit
throw fileExistsErr;
}
}
catch (Throwable e)
{
// some other type of exception occured - rollback and exit
throw e;
}
finally
{
// rollback if the operation didn't complete
if (operationComplete == false)
{
try { if (tx != null) {tx.rollback();} } catch (Exception tex) {}
String copyOf = Application.getMessage(FacesContext.getCurrentInstance(), MSG_COPY_OF);
name = copyOf + ' ' + name;
}
else
{
// commit the transaction
tx.commit();
}
}
}
}
private boolean checkExists(String name, NodeRef parent)
{
ServiceRegistry services = Repository.getServiceRegistry(FacesContext.getCurrentInstance());
QueryParameterDefinition[] params = new QueryParameterDefinition[1];
params[0] = new QueryParameterDefImpl(
ContentModel.PROP_NAME,
services.getDictionaryService().getDataType(
DataTypeDefinition.TEXT),
true,
name);
// execute the query
List<NodeRef> nodeRefs = searchService.selectNodes(
parent,
XPATH_QUERY_NODE_MATCH,
params,
services.getNamespaceService(),
false);
return (nodeRefs.size() != 0);
} }
/** /**
@@ -481,36 +249,4 @@ public class ClipboardBean
} }
} }
} }
// ------------------------------------------------------------------------------
// Private data
private static Log logger = LogFactory.getLog(ClipboardBean.class);
/** I18N messages */
private static final String MSG_ERROR_PASTE = "error_paste";
private static final String MSG_COPY_OF = "copy_of";
private static final String MSG_LINK_TO = "link_to";
/** Shallow search for nodes with a name pattern */
private static final String XPATH_QUERY_NODE_MATCH = "./*[like(@cm:name, $cm:name, false)]";
/** The NodeService to be used by the bean */
protected NodeService nodeService;
/** The FileFolderService to be used by the bean */
protected FileFolderService fileFolderService;
/** The CopyService to be used by the bean */
protected CopyService copyService;
/** The SearchService to be used by the bean */
protected SearchService searchService;
/** The NavigationBean reference */
protected NavigationBean navigator;
/** Current state of the clipboard items */
private List<ClipboardItem> items = new ArrayList<ClipboardItem>(4);
} }

View File

@@ -18,13 +18,8 @@ package org.alfresco.web.bean.clipboard;
import javax.faces.context.FacesContext; import javax.faces.context.FacesContext;
import org.alfresco.model.ApplicationModel;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.alfresco.web.bean.repository.Node;
import org.alfresco.web.bean.repository.Repository;
/** /**
* Simple class representing a single item added to the clipboard. * Simple class representing a single item added to the clipboard.
@@ -46,4 +41,8 @@ public interface ClipboardItem
public NodeRef getNodeRef(); public NodeRef getNodeRef();
public boolean supportsLink(); public boolean supportsLink();
public boolean canPasteToViewId(String viewId);
public boolean paste(FacesContext fc, String viewId, int action) throws Throwable;
} }

View File

@@ -16,7 +16,28 @@
*/ */
package org.alfresco.web.bean.clipboard; package org.alfresco.web.bean.clipboard;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import javax.faces.context.FacesContext;
import javax.transaction.UserTransaction;
import org.alfresco.model.ApplicationModel;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.model.FileExistsException;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.CopyService;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
import org.alfresco.web.app.Application;
import org.alfresco.web.app.servlet.FacesHelper;
import org.alfresco.web.bean.NavigationBean;
import org.alfresco.web.bean.repository.Repository;
import org.alfresco.web.ui.repo.component.shelf.UIClipboardShelfItem;
/** /**
* Class representing a 'workspace' store protocol clipboard item * Class representing a 'workspace' store protocol clipboard item
@@ -25,6 +46,11 @@ import org.alfresco.service.cmr.repository.NodeRef;
*/ */
public class WorkspaceClipboardItem extends AbstractClipboardItem public class WorkspaceClipboardItem extends AbstractClipboardItem
{ {
private static final String WORKSPACE_PASTE_VIEW_ID = "/jsp/browse/browse.jsp";
private static final String MSG_LINK_TO = "link_to";
/** /**
* @param ref * @param ref
* @param mode * @param mode
@@ -41,4 +67,214 @@ public class WorkspaceClipboardItem extends AbstractClipboardItem
{ {
return true; return true;
} }
/**
* @see org.alfresco.web.bean.clipboard.ClipboardItem#canPasteToViewId(java.lang.String)
*/
public boolean canPasteToViewId(String viewId)
{
// TODO: add 'avm' paste view when interstore copy/move is supported
return (WORKSPACE_PASTE_VIEW_ID.equals(viewId));
}
/**
* @see org.alfresco.web.bean.clipboard.ClipboardItem#paste(javax.faces.context.FacesContext, java.lang.String, int)
*/
public boolean paste(FacesContext fc, String viewId, int action)
throws Throwable
{
if (WORKSPACE_PASTE_VIEW_ID.equals(viewId))
{
NavigationBean navigator = (NavigationBean)FacesHelper.getManagedBean(fc, NavigationBean.BEAN_NAME);
NodeRef destRef = new NodeRef(Repository.getStoreRef(), navigator.getCurrentNodeId());
DictionaryService dd = getServiceRegistry().getDictionaryService();
NodeService nodeService = getServiceRegistry().getNodeService();
FileFolderService fileFolderService = getServiceRegistry().getFileFolderService();
CopyService copyService = getServiceRegistry().getCopyService();
// TODO: Should we be using primary parent here?
// We are assuming that the item exists in only a single parent and that the source for
// the clipboard operation (e.g. the source folder) is specifically that parent node.
// So does not allow for more than one possible parent node - or for linked objects!
// This code should be refactored to use a parent ID when appropriate.
ChildAssociationRef assocRef = nodeService.getPrimaryParent(getNodeRef());
// initial name to attempt the copy of the item with
String name = getName();
if (action == UIClipboardShelfItem.ACTION_PASTE_LINK)
{
// copy as link was specifically requested by the user
String linkTo = Application.getMessage(fc, MSG_LINK_TO);
name = linkTo + ' ' + name;
}
boolean operationComplete = false;
while (operationComplete == false)
{
UserTransaction tx = null;
try
{
// attempt each copy/paste in its own transaction
tx = Repository.getUserTransaction(fc);
tx.begin();
if (getMode() == ClipboardStatus.COPY)
{
if (action == UIClipboardShelfItem.ACTION_PASTE_LINK)
{
// LINK operation
if (logger.isDebugEnabled())
logger.debug("Attempting to link node ID: " + getId() + " into node ID: " + destRef.getId());
// we create a special Link Object node that has a property to reference the original
// create the node using the nodeService (can only use FileFolderService for content)
if (checkExists(name + ".lnk", destRef) == false)
{
Map<QName, Serializable> props = new HashMap<QName, Serializable>(2, 1.0f);
props.put(ContentModel.PROP_NAME, name + ".lnk");
props.put(ContentModel.PROP_LINK_DESTINATION, getNodeRef());
if (dd.isSubClass(getType(), ContentModel.TYPE_CONTENT))
{
// create File Link node
ChildAssociationRef childRef = nodeService.createNode(
destRef,
ContentModel.ASSOC_CONTAINS,
assocRef.getQName(),
ApplicationModel.TYPE_FILELINK,
props);
// apply the titled aspect - title and description
Map<QName, Serializable> titledProps = new HashMap<QName, Serializable>(2, 1.0f);
titledProps.put(ContentModel.PROP_TITLE, name);
titledProps.put(ContentModel.PROP_DESCRIPTION, name);
nodeService.addAspect(childRef.getChildRef(), ContentModel.ASPECT_TITLED, titledProps);
}
else
{
// create Folder link node
ChildAssociationRef childRef = nodeService.createNode(
destRef,
ContentModel.ASSOC_CONTAINS,
assocRef.getQName(),
ApplicationModel.TYPE_FOLDERLINK,
props);
// apply the uifacets aspect - icon, title and description props
Map<QName, Serializable> uiFacetsProps = new HashMap<QName, Serializable>(4, 1.0f);
uiFacetsProps.put(ApplicationModel.PROP_ICON, "space-icon-link");
uiFacetsProps.put(ContentModel.PROP_TITLE, name);
uiFacetsProps.put(ContentModel.PROP_DESCRIPTION, name);
nodeService.addAspect(childRef.getChildRef(), ApplicationModel.ASPECT_UIFACETS, uiFacetsProps);
}
// if we get here without an exception, the clipboard link operation was successful
operationComplete = true;
}
}
else
{
// COPY operation
if (logger.isDebugEnabled())
logger.debug("Attempting to copy node ID: " + getId() + " into node ID: " + destRef.getId());
if (dd.isSubClass(getType(), ContentModel.TYPE_CONTENT) ||
dd.isSubClass(getType(), ContentModel.TYPE_FOLDER))
{
// copy the file/folder
// first check that we are not attempting to copy a duplicate into the same parent
if (destRef.equals(assocRef.getParentRef()) && name.equals(getName()))
{
// manually change the name if this occurs
String copyOf = Application.getMessage(fc, MSG_COPY_OF);
name = copyOf + ' ' + name;
}
fileFolderService.copy(
getNodeRef(),
destRef,
name);
}
else
{
// copy the node
if (checkExists(name, destRef) == false)
{
copyService.copy(
getNodeRef(),
destRef,
ContentModel.ASSOC_CONTAINS,
assocRef.getQName(),
true);
}
}
// if we get here without an exception, the clipboard copy operation was successful
operationComplete = true;
}
}
else
{
// MOVE operation
if (logger.isDebugEnabled())
logger.debug("Attempting to move node ID: " + getId() + " into node ID: " + destRef.getId());
if (dd.isSubClass(getType(), ContentModel.TYPE_CONTENT) ||
dd.isSubClass(getType(), ContentModel.TYPE_FOLDER))
{
// move the file/folder
fileFolderService.move(
getNodeRef(),
destRef,
name);
}
else
{
// move the node
nodeService.moveNode(
getNodeRef(),
destRef,
ContentModel.ASSOC_CONTAINS,
assocRef.getQName());
}
// if we get here without an exception, the clipboard move operation was successful
operationComplete = true;
}
}
catch (FileExistsException fileExistsErr)
{
if (getMode() != ClipboardStatus.COPY)
{
// we should not rename an item when it is being moved - so exit
throw fileExistsErr;
}
}
catch (Throwable e)
{
// some other type of exception occured - rollback and exit
throw e;
}
finally
{
// rollback if the operation didn't complete
if (operationComplete == false)
{
try { if (tx != null) {tx.rollback();} } catch (Exception tex) {}
String copyOf = Application.getMessage(fc, MSG_COPY_OF);
name = copyOf + ' ' + name;
}
else
{
// commit the transaction
tx.commit();
}
}
}
return operationComplete;
}
else
{
// TODO: support 'avm' destination view...
return false;
}
}
} }

View File

@@ -77,6 +77,8 @@ import org.apache.commons.logging.LogFactory;
*/ */
public class AVMBrowseBean implements IContextListener public class AVMBrowseBean implements IContextListener
{ {
public static final String BEAN_NAME = "AVMBrowseBean";
private static final Log LOGGER = LogFactory.getLog(AVMBrowseBean.class); private static final Log LOGGER = LogFactory.getLog(AVMBrowseBean.class);
private static final String MSG_REVERT_SUCCESS = "revert_success"; private static final String MSG_REVERT_SUCCESS = "revert_success";

View File

@@ -237,31 +237,11 @@
<managed-bean> <managed-bean>
<description> <description>
The bean that holds a users Clipboard state. The bean that manages a users Clipboard state.
</description> </description>
<managed-bean-name>ClipboardBean</managed-bean-name> <managed-bean-name>ClipboardBean</managed-bean-name>
<managed-bean-class>org.alfresco.web.bean.clipboard.ClipboardBean</managed-bean-class> <managed-bean-class>org.alfresco.web.bean.clipboard.ClipboardBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope> <managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>navigator</property-name>
<value>#{NavigationBean}</value>
</managed-property>
<managed-property>
<property-name>nodeService</property-name>
<value>#{NodeService}</value>
</managed-property>
<managed-property>
<property-name>fileFolderService</property-name>
<value>#{FileFolderService}</value>
</managed-property>
<managed-property>
<property-name>copyService</property-name>
<value>#{CopyService}</value>
</managed-property>
<managed-property>
<property-name>searchService</property-name>
<value>#{SearchService}</value>
</managed-property>
</managed-bean> </managed-bean>
<managed-bean> <managed-bean>

View File

@@ -435,7 +435,7 @@ a.topToolbarLinkHighlight, a.topToolbarLinkHighlight:link, a.topToolbarLinkHighl
.pager .pager
{ {
padding: 6px 4px 3px 4px; padding: 3px;
border: 1px dotted #cccccc; border: 1px dotted #cccccc;
} }