. 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.UnsupportedEncodingException;
import java.net.SocketException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.MessageFormat;
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
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
nodeRef = new NodeRef(storeRef, id);

View File

@@ -16,7 +16,19 @@
*/
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.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
@@ -25,6 +37,8 @@ import org.alfresco.service.cmr.repository.NodeRef;
*/
public class AVMClipboardItem extends AbstractClipboardItem
{
private static final String AVM_PASTE_VIEW_ID = "/jsp/wcm/browse-sandbox.jsp";
/**
* @param ref
* @param mode
@@ -41,4 +55,128 @@ public class AVMClipboardItem extends AbstractClipboardItem
{
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;
import java.util.List;
import javax.faces.context.FacesContext;
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.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.web.bean.repository.Node;
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.
@@ -33,6 +39,24 @@ import org.alfresco.web.bean.repository.Repository;
*/
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
*
@@ -54,7 +78,8 @@ abstract class AbstractClipboardItem implements ClipboardItem
{
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;
}
@@ -63,7 +88,7 @@ abstract class AbstractClipboardItem implements ClipboardItem
{
if (this.type == null)
{
this.type = getNodeService().getType(this.ref);
this.type = getServiceRegistry().getNodeService().getType(this.ref);
}
return this.type;
}
@@ -72,7 +97,8 @@ abstract class AbstractClipboardItem implements ClipboardItem
{
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;
}
@@ -114,16 +140,33 @@ abstract class AbstractClipboardItem implements ClipboardItem
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 ClipboardStatus mode;
// cached values
private String name;
private QName type;
private String icon;
protected boolean checkExists(String name, NodeRef parent)
{
QueryParameterDefinition[] params = new QueryParameterDefinition[1];
params[0] = new QueryParameterDefImpl(
ContentModel.PROP_NAME,
getServiceRegistry().getDictionaryService().getDataType(
DataTypeDefinition.TEXT),
true,
name);
// execute the query
List<NodeRef> nodeRefs = getServiceRegistry().getSearchService().selectNodes(
parent,
XPATH_QUERY_NODE_MATCH,
params,
getServiceRegistry().getNamespaceService(),
false);
return (nodeRefs.size() != 0);
}
}

View File

@@ -16,36 +16,17 @@
*/
package org.alfresco.web.bean.clipboard;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.faces.context.FacesContext;
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.NodeService;
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.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.component.UIActionLink;
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;
/**
* 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
*/
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
/**
* @param nodeService The NodeService to set.
*/
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.
* @return Returns a list representing the items on the user clipboard.
*/
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)
{
@@ -185,30 +141,30 @@ public class ClipboardBean
{
// paste all
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);
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
}
else
{
// single paste operation
ClipboardItem item = this.items.get(index);
performClipboardOperation(item, action);
if (item.getMode() == ClipboardStatus.CUT)
if (performClipboardOperation(item, action) == true)
{
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 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
{
NodeRef destRef = new NodeRef(Repository.getStoreRef(), this.navigator.getCurrentNodeId());
FacesContext fc = FacesContext.getCurrentInstance();
DictionaryService dd = Repository.getServiceRegistry(
FacesContext.getCurrentInstance()).getDictionaryService();
// 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 = 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)
// test the current JSF view to see if the clipboard item can paste to it
if (logger.isDebugEnabled())
logger.debug("Clipboard destintation View Id: " + fc.getViewRoot().getViewId());
if (item.canPasteToViewId(fc.getViewRoot().getViewId()) == false)
{
// copy as link was specifically requested by the user
String linkTo = Application.getMessage(FacesContext.getCurrentInstance(), MSG_LINK_TO);
name = linkTo + ' ' + name;
// early exit if we cannot support this view as a paste location
if (logger.isDebugEnabled())
logger.debug("Clipboard Item: " + item.getNodeRef() + " not suitable for paste to current View Id.");
return false;
}
boolean operationComplete = false;
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);
return item.paste(fc, fc.getViewRoot().getViewId(), action);
}
/**
@@ -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 org.alfresco.model.ApplicationModel;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
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.
@@ -46,4 +41,8 @@ public interface ClipboardItem
public NodeRef getNodeRef();
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;
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.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
@@ -25,6 +46,11 @@ import org.alfresco.service.cmr.repository.NodeRef;
*/
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 mode
@@ -41,4 +67,214 @@ public class WorkspaceClipboardItem extends AbstractClipboardItem
{
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 static final String BEAN_NAME = "AVMBrowseBean";
private static final Log LOGGER = LogFactory.getLog(AVMBrowseBean.class);
private static final String MSG_REVERT_SUCCESS = "revert_success";