Merged V2.1 to HEAD

6500: Office 2003 Add-ins: Fix for AWC-1505
   6501: Fix for AWC-1361
   6502: Fixes for locking issues regrading expired content
   6503: Fix for AR-1615
   6504: WCM-444, WCM-288, WCM-735, WCM-480
   6505: WCM-498 wasn't fully fixed
   6506: Fix for AWC-1462
   6507: Fix for WCM-741 (link validation report for staging sandbox can get stuck)
   6508: AR-1650: WS Unit tests fail
   6509: Fix for WCM-751 which also solves WCM-570 - also fixed issue to allow deletion of any "broken" webprojects created due to either of those bugs.
   6510: Fix for WCM-546 (workflow history panel should be expanded by default)
   6511: Fix AWC-1128
   6512: Fixes to several to a couple of bugs found under concurrent load.
   6513: Build fix for test using an invalid noderef (now stripped out as it does not exist)
   6514: Add support for the QueryFile transact request to the IPC$ named pipe handler. Fix for AR-1687.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@6740 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2007-09-10 23:30:53 +00:00
parent 1b272d6766
commit d0e64d06b4
14 changed files with 2599 additions and 41 deletions

View File

@@ -24,11 +24,15 @@
*/
package org.alfresco.filesys.smb.server;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.alfresco.filesys.server.filesys.FileInfo;
import org.alfresco.filesys.server.filesys.NetworkFile;
import org.alfresco.filesys.server.filesys.PathNotFoundException;
import org.alfresco.filesys.server.filesys.TooManyFilesException;
import org.alfresco.filesys.server.filesys.TreeConnection;
import org.alfresco.filesys.server.filesys.UnsupportedInfoLevelException;
import org.alfresco.filesys.smb.PacketType;
import org.alfresco.filesys.smb.SMBStatus;
import org.alfresco.filesys.smb.TransactionNames;
@@ -186,6 +190,12 @@ class IPCHandler
DCERPCHandler.processDCERPCRequest(sess, vc, tbuf, outPkt);
break;
// Query file information via handle
case PacketType.Trans2QueryFile:
procTrans2QueryFile(sess, vc, tbuf, outPkt);
break;
// Unknown command
default:
@@ -661,4 +671,134 @@ class IPCHandler
sess.sendResponseSMB(outPkt);
}
/**
* Process a transact2 query file information (via handle) request.
*
* @param sess SMBSrvSession
* @param vc VirtualCircuit
* @param tbuf Transaction request details
* @param outPkt SMBSrvPacket
* @exception java.io.IOException The exception description.
* @exception org.alfresco.aifs.smb.SMBSrvException SMB protocol exception
*/
protected static final void procTrans2QueryFile(SMBSrvSession sess, VirtualCircuit vc, SrvTransactBuffer tbuf, SMBSrvPacket outPkt)
throws java.io.IOException, SMBSrvException {
// Get the tree connection details
int treeId = tbuf.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasReadAccess() == false) {
// User does not have the required access rights
sess.sendErrorResponseSMB(SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Get the file id and query path information level
DataBuffer paramBuf = tbuf.getParameterBuffer();
int fid = paramBuf.getShort();
int infoLevl = paramBuf.getShort();
// Get the file details via the file id
NetworkFile netFile = conn.findFile(fid);
if (netFile == null) {
sess.sendErrorResponseSMB(SMBStatus.DOSInvalidHandle, SMBStatus.ErrDos);
return;
}
// Debug
if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_IPC))
logger.debug("IPC$ Query File - level=0x" + Integer.toHexString(infoLevl) + ", fid=" + fid + ", name=" + netFile.getFullName());
// Access the shared device disk interface
try {
// Set the return parameter count, so that the data area position can be calculated.
outPkt.setParameterCount(10);
// Pack the file information into the data area of the transaction reply
byte[] buf = outPkt.getBuffer();
int prmPos = DataPacker.longwordAlign(outPkt.getByteOffset());
int dataPos = prmPos + 4;
// Pack the return parametes, EA error offset
outPkt.setPosition(prmPos);
outPkt.packWord(0);
// Create a data buffer using the SMB packet. The response should always fit into a single
// reply packet.
DataBuffer replyBuf = new DataBuffer(buf, dataPos, buf.length - dataPos);
// Build the file information from the network file details
FileInfo fileInfo = new FileInfo(netFile.getName(), netFile.getFileSize(), netFile.getFileAttributes());
fileInfo.setAccessDateTime(netFile.getAccessDate());
fileInfo.setCreationDateTime(netFile.getCreationDate());
fileInfo.setModifyDateTime(netFile.getModifyDate());
fileInfo.setChangeDateTime(netFile.getModifyDate());
fileInfo.setFileId(netFile.getFileId());
// Pack the file information into the return data packet
int dataLen = QueryInfoPacker.packInfo(fileInfo, replyBuf, infoLevl, true);
// Check if any data was packed, if not then the information level is not supported
if (dataLen == 0) {
sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
SMBSrvTransPacket.initTransactReply(outPkt, 2, prmPos, dataLen, dataPos);
outPkt.setByteCount(replyBuf.getPosition() - outPkt.getByteOffset());
// Send the transact reply
sess.sendResponseSMB(outPkt);
}
catch (FileNotFoundException ex) {
// Requested file does not exist
sess.sendErrorResponseSMB(SMBStatus.NTObjectNotFound, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
catch (PathNotFoundException ex) {
// Requested path does not exist
sess.sendErrorResponseSMB(SMBStatus.NTObjectPathNotFound, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
catch (UnsupportedInfoLevelException ex) {
// Requested information level is not supported
sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
}
}

View File

@@ -178,6 +178,7 @@ public class LinkValidationAction extends ActionExecuterAbstractBase
}
catch (Throwable err)
{
// capture the error in the report
if (report != null)
{
report.setError(err);
@@ -187,6 +188,12 @@ public class LinkValidationAction extends ActionExecuterAbstractBase
report = new LinkValidationReport(storeName, webappName, err);
}
// set the monitor object as completed
if (monitor != null)
{
monitor.setDone(true);
}
logger.error("Link Validation Error: ", err);
}

View File

@@ -108,7 +108,7 @@ public class ComparePropertyValueEvaluatorTest extends BaseSpringTest
+ System.currentTimeMillis());
this.rootNodeRef = this.nodeService.getRootNode(this.testStoreRef);
this.nodeValue = new NodeRef(this.testStoreRef, "1234");
this.nodeValue = this.rootNodeRef;
this.beforeDateValue = new Date();
Thread.sleep(2000);

View File

@@ -41,9 +41,9 @@ public class AVMCrawlTestP extends AVMServiceTestBase
*/
public void testCrawl()
{
int n = 3; // Number of Threads.
int m = 10; // How many multiples of content to start with.
long runTime = 18000000; // 6 hours.
int n = 4; // Number of Threads.
int m = 2; // How many multiples of content to start with.
long runTime = 3600000; // 1 Hour. .
fService.purgeStore("main");
BulkLoader loader = new BulkLoader();
loader.setAvmService(fService);

View File

@@ -48,6 +48,7 @@ import org.alfresco.sandbox.SandboxConstants;
import org.alfresco.service.cmr.avm.AVMNodeDescriptor;
import org.alfresco.service.cmr.avm.AVMService;
import org.alfresco.service.cmr.avm.AVMStoreDescriptor;
import org.alfresco.service.cmr.avm.locking.AVMLock;
import org.alfresco.service.cmr.avm.locking.AVMLockingService;
import org.alfresco.service.cmr.avmsync.AVMSyncService;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
@@ -88,6 +89,7 @@ public class AVMExpiredContentProcessor
protected AVMService avmService;
protected AVMSyncService avmSyncService;
protected AVMService avmLockingAwareService;
protected AVMLockingService avmLockingService;
protected NodeService nodeService;
protected WorkflowService workflowService;
protected PersonService personService;
@@ -120,6 +122,11 @@ public class AVMExpiredContentProcessor
this.avmService = avmService;
}
public void setAvmLockingService(AVMLockingService avmLockingService)
{
this.avmLockingService = avmLockingService;
}
public void setAvmSyncService(AVMSyncService avmSyncService)
{
this.avmSyncService = avmSyncService;
@@ -321,35 +328,53 @@ public class AVMExpiredContentProcessor
if (expirationDate != null && expirationDate.before(now))
{
// get the map of expired content for the store
Map<String, List<String>> storeExpiredContent = this.expiredContent.get(storeName);
if (storeExpiredContent == null)
{
storeExpiredContent = new HashMap<String, List<String>>(4);
this.expiredContent.put(storeName, storeExpiredContent);
}
// get the list of expired content for the last modifier of the node
String modifier = node.getLastModifier();
List<String> userExpiredContent = storeExpiredContent.get(modifier);
if (userExpiredContent == null)
{
userExpiredContent = new ArrayList<String>(4);
storeExpiredContent.put(modifier, userExpiredContent);
}
// add the content to the user's list for the current store
userExpiredContent.add(nodePath);
// before doing anything else see whether the item is locked by any user,
// if it is then just log a warning messge and wait until the next time around
String[] splitPath = nodePath.split(":");
AVMLock lock = this.avmLockingService.getLock(storeName, splitPath[1]);
if (logger.isDebugEnabled())
logger.debug("Added " + nodePath + " to " + modifier + "'s list of expired content");
logger.debug("lock details for '" + nodePath + "': " + lock);
if (lock == null)
{
// get the map of expired content for the store
Map<String, List<String>> storeExpiredContent = this.expiredContent.get(storeName);
if (storeExpiredContent == null)
{
storeExpiredContent = new HashMap<String, List<String>>(4);
this.expiredContent.put(storeName, storeExpiredContent);
}
// reset the expiration date
this.avmService.setNodeProperty(nodePath, WCMAppModel.PROP_EXPIRATIONDATE,
new PropertyValue(DataTypeDefinition.DATETIME, null));
if (logger.isDebugEnabled())
logger.debug("Reset expiration date for: " + nodePath);
// get the list of expired content for the last modifier of the node
String modifier = node.getLastModifier();
List<String> userExpiredContent = storeExpiredContent.get(modifier);
if (userExpiredContent == null)
{
userExpiredContent = new ArrayList<String>(4);
storeExpiredContent.put(modifier, userExpiredContent);
}
// add the content to the user's list for the current store
userExpiredContent.add(nodePath);
if (logger.isDebugEnabled())
logger.debug("Added " + nodePath + " to " + modifier + "'s list of expired content");
// reset the expiration date
this.avmService.setNodeProperty(nodePath, WCMAppModel.PROP_EXPIRATIONDATE,
new PropertyValue(DataTypeDefinition.DATETIME, null));
if (logger.isDebugEnabled())
logger.debug("Reset expiration date for: " + nodePath);
}
else
{
if (logger.isWarnEnabled())
{
logger.warn("ignoring '" + nodePath + "', although it has expired, it's currently locked");
}
}
}
}
}

View File

@@ -708,14 +708,7 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi
{
return true;
}
try
{
return fAVMService.hasAspect(version, path, aspectTypeQName);
}
catch (AVMNotFoundException e)
{
throw new InvalidNodeRefException(nodeRef);
}
return fAVMService.hasAspect(version, path, aspectTypeQName);
}
private static QName [] fgBuiltinAspects = new QName[] { ContentModel.ASPECT_AUDITABLE,

View File

@@ -101,6 +101,21 @@ import org.alfresco.util.Pair;
*/
public class AVMServiceTest extends AVMServiceTestBase
{
public void testSpacesInStoreNames()
{
try
{
fService.createStore("I have spaces");
fService.createFile("I have spaces:/", "in my name.txt").close();
assertNotNull(fService.lookup(-1, "I have spaces:/in my name.txt"));
}
catch (Exception e)
{
e.printStackTrace();
fail();
}
}
public void testHeadPathsInLayers()
{
try

View File

@@ -291,11 +291,13 @@ public class AVMStoreImpl implements AVMStore, Serializable
}
VersionRoot versionRoot = new VersionRootImpl(this,
fRoot,
fNextVersionID,
fNextVersionID++,
System.currentTimeMillis(),
user,
tag,
description);
// Another embarassing flush needed.
AVMDAOs.Instance().fAVMNodeDAO.flush();
AVMDAOs.Instance().fVersionRootDAO.save(versionRoot);
for (AVMNode node : layeredNodes)
{
@@ -308,7 +310,6 @@ public class AVMStoreImpl implements AVMStore, Serializable
}
}
// Increment the version id.
fNextVersionID++;
return snapShotMap;
}
@@ -462,6 +463,8 @@ public class AVMStoreImpl implements AVMStore, Serializable
RawServices.Instance().getMimetypeService().guessMimetype(name),
-1,
"UTF-8"));
// Yet another flush.
AVMDAOs.Instance().fAVMNodeDAO.flush();
ContentWriter writer = createContentWriter(AVMNodeConverter.ExtendAVMPath(path, name));
writer.putContent(data);
}

View File

@@ -0,0 +1,379 @@
/*
* Copyright (C) 2005-2007 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.node;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.repository.datatype.TypeConversionException;
import org.alfresco.service.namespace.QName;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* A method interceptor to clean up node ref properties as they are passed in and out of the node service. For
* getProperty and getProperies calls invalid node refs are removed from the returned set (they appear to have be
* cleaned up). For setProperty and setProperties calls invalid node refs are removed and thus not set. It only
* considers properties of type d:noderef.
*
* @author andyh
*/
public class NodeRefPropertyMethodInterceptor implements MethodInterceptor
{
private static Log logger = LogFactory.getLog(NodeRefPropertyMethodInterceptor.class);
private boolean filterOnGet = true;
private boolean filterOnSet = true;
private DictionaryService dictionaryService;
private NodeService nodeService;
public boolean isFilterOnGet()
{
return filterOnGet;
}
public void setFilterOnGet(boolean filterOnGet)
{
this.filterOnGet = filterOnGet;
}
public boolean isFilterOnSet()
{
return filterOnSet;
}
public void setFilterOnSet(boolean filterOnSet)
{
this.filterOnSet = filterOnSet;
}
public void setDictionaryService(DictionaryService dictionaryService)
{
this.dictionaryService = dictionaryService;
}
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
@SuppressWarnings("unchecked")
public Object invoke(MethodInvocation invocation) throws Throwable
{
String methodName = invocation.getMethod().getName();
// We are going to change the method arguments as we proceed - so we keep them to set the references back at the
// end
// Not sure if there would be any side effect but we guard against it in any case.
// Audit for example will see the correct values on exit
// org.springframework.aop.framework.ReflectiveMethodInvocation does not do any special wrapping and this is
// fine
Object[] args = invocation.getArguments();
Object[] in = new Object[args.length];
System.arraycopy(args, 0, in, 0, args.length);
invocation.getStaticPart();
try
{
if (methodName.equals("addAspect"))
{
if (filterOnSet)
{
NodeRef nodeRef = (NodeRef) args[0];
QName aspectType = (QName) args[1];
Map<QName, Serializable> newProperties = (Map<QName, Serializable>) args[2];
if (newProperties == null)
{
args[2] = newProperties;
return invocation.proceed();
}
else
{
Map<QName, Serializable> convertedProperties = new HashMap<QName, Serializable>(newProperties.size() * 2);
for (Map.Entry<QName, Serializable> entry : newProperties.entrySet())
{
QName propertyQName = entry.getKey();
Serializable value = entry.getValue();
value = getValue(propertyQName, value);
convertedProperties.put(propertyQName, value);
}
args[2] = convertedProperties;
return invocation.proceed();
}
}
else
{
return invocation.proceed();
}
}
else if (methodName.equals("createNode") & (args.length == 5))
{
if (filterOnSet)
{
NodeRef parentRef = (NodeRef) args[0];
QName assocTypeQName = (QName) args[1];
QName assocQName = (QName) args[2];
QName nodeTypeQName = (QName) args[3];
Map<QName, Serializable> newProperties = (Map<QName, Serializable>) args[4];
if (newProperties == null)
{
args[4] = newProperties;
return invocation.proceed();
}
else
{
Map<QName, Serializable> convertedProperties = new HashMap<QName, Serializable>(newProperties.size() * 2);
for (Map.Entry<QName, Serializable> entry : newProperties.entrySet())
{
QName propertyQName = entry.getKey();
Serializable value = entry.getValue();
value = getValue(propertyQName, value);
convertedProperties.put(propertyQName, value);
}
args[4] = newProperties;
return invocation.proceed();
}
}
else
{
return invocation.proceed();
}
}
else if (methodName.equals("getProperty"))
{
if (filterOnGet)
{
NodeRef nodeRef = (NodeRef) args[0];
QName propertyQName = (QName) args[1];
Serializable value = (Serializable) invocation.proceed();
return getValue(propertyQName, value);
}
else
{
return invocation.proceed();
}
}
else if (methodName.equals("getProperties"))
{
if (filterOnGet)
{
NodeRef nodeRef = (NodeRef) args[0];
Map<QName, Serializable> properties = (Map<QName, Serializable>) invocation.proceed();
Map<QName, Serializable> convertedProperties = new HashMap<QName, Serializable>(properties.size() * 2);
for (Map.Entry<QName, Serializable> entry : properties.entrySet())
{
QName propertyQName = entry.getKey();
Serializable value = entry.getValue();
Serializable convertedValue = getValue(propertyQName, value);
convertedProperties.put(propertyQName, convertedValue);
}
return convertedProperties;
}
else
{
return invocation.proceed();
}
}
else if (methodName.equals("setProperties"))
{
if (filterOnSet)
{
NodeRef nodeRef = (NodeRef) args[0];
Map<QName, Serializable> newProperties = (Map<QName, Serializable>) args[1];
Map<QName, Serializable> convertedProperties = new HashMap<QName, Serializable>(newProperties.size() * 2);
for (Map.Entry<QName, Serializable> entry : newProperties.entrySet())
{
QName propertyQName = entry.getKey();
Serializable value = entry.getValue();
value = getValue(propertyQName, value);
convertedProperties.put(propertyQName, value);
}
args[1] = convertedProperties;
return invocation.proceed();
}
else
{
return invocation.proceed();
}
}
else if (methodName.equals("setProperty"))
{
if (filterOnSet)
{
NodeRef nodeRef = (NodeRef) args[0];
QName propertyQName = (QName) args[1];
Serializable value = (Serializable) args[2];
value = getValue(propertyQName, value);
args[2] = value;
return invocation.proceed();
}
else
{
return invocation.proceed();
}
}
else
{
return invocation.proceed();
}
}
finally
{
System.arraycopy(in, 0, args, 0, in.length);
}
}
/**
* Remove unknown node ref values Remove unknowen categories - the node will be removed if it does exist and it is
* not a category
*
* @param propertyQName
* @param inboundValue
* @return
*/
private Serializable getValue(QName propertyQName, Serializable inboundValue)
{
PropertyDefinition propertyDef = this.dictionaryService.getProperty(propertyQName);
if (propertyDef == null)
{
return inboundValue;
}
else
{
if ((propertyDef.getDataType().getName().equals(DataTypeDefinition.NODE_REF)) || (propertyDef.getDataType().getName().equals(DataTypeDefinition.CATEGORY)))
{
if (inboundValue instanceof Collection)
{
Collection in = (Collection) inboundValue;
ArrayList<NodeRef> out = new ArrayList<NodeRef>(in.size());
for (Object o : in)
{
Serializable value = (Serializable) o;
if (value == null)
{
out.add(null);
}
else
{
try
{
NodeRef test = DefaultTypeConverter.INSTANCE.convert(NodeRef.class, value);
if (nodeService.exists(test))
{
if (propertyDef.getDataType().getName().equals(DataTypeDefinition.CATEGORY))
{
QName type = nodeService.getType(test);
if (dictionaryService.isSubClass(type, ContentModel.TYPE_CATEGORY))
{
out.add(test);
}
}
else
{
out.add(test);
}
}
}
catch (TypeConversionException e)
{
// Catch and continue
}
}
}
return out;
}
else
{
if (inboundValue == null)
{
return inboundValue;
}
else
{
try
{
NodeRef test = DefaultTypeConverter.INSTANCE.convert(NodeRef.class, inboundValue);
if (nodeService.exists(test))
{
if (propertyDef.getDataType().getName().equals(DataTypeDefinition.CATEGORY))
{
QName type = nodeService.getType(test);
if (dictionaryService.isSubClass(type, ContentModel.TYPE_CATEGORY))
{
return inboundValue;
}
else
{
return null;
}
}
else
{
return inboundValue;
}
}
else
{
return null;
}
}
catch (TypeConversionException e)
{
return null;
}
}
}
}
else
{
return inboundValue;
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,97 @@
<model name="test:nodeservice"
xmlns="http://www.alfresco.org/model/dictionary/1.0">
<description>Test Model for NodeService tests</description>
<author>Alfresco</author>
<published>2005-06-05</published>
<version>0.1</version>
<imports>
<import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d"/>
<import uri="http://www.alfresco.org/model/system/1.0" prefix="sys"/>
<import uri="http://www.alfresco.org/model/content/1.0" prefix="cm"/>
</imports>
<namespaces>
<namespace uri="http://www.alfresco.org/test/NodeRefTestModel"
prefix="test"/>
</namespaces>
<types>
<type name="test:testType">
<title>Content</title>
<parent>cm:content</parent>
<archive>false</archive>
<properties>
<property name="test:category1">
<type>d:category</type>
<mandatory>false</mandatory>
<multiple>false</multiple>
</property>
<property name="test:categories1">
<type>d:category</type>
<mandatory>false</mandatory>
<multiple>true</multiple>
</property>
<property name="test:noderef1">
<type>d:noderef</type>
<mandatory>false</mandatory>
<multiple>false</multiple>
</property>
<property name="test:noderefs1">
<type>d:noderef</type>
<mandatory>false</mandatory>
<multiple>true</multiple>
</property>
</properties>
</type>
</types>
<aspects>
<aspect name="test:singleCategory">
<title>Single Category</title>
<properties>
<property name="test:category">
<type>d:category</type>
<mandatory>false</mandatory>
<multiple>false</multiple>
</property>
</properties>
</aspect>
<aspect name="test:multipleCategories">
<title>Multiple Categories</title>
<properties>
<property name="test:categories">
<type>d:category</type>
<mandatory>false</mandatory>
<multiple>true</multiple>
</property>
</properties>
</aspect>
<aspect name="test:singleNodeRef">
<title>Single Node Ref</title>
<properties>
<property name="test:noderef">
<type>d:noderef</type>
<mandatory>false</mandatory>
<multiple>false</multiple>
</property>
</properties>
</aspect>
<aspect name="test:multipleNodeRefs">
<title>Mutliple Node Refs</title>
<properties>
<property name="test:noderefs">
<type>d:noderef</type>
<mandatory>false</mandatory>
<multiple>true</multiple>
</property>
</properties>
</aspect>
</aspects>
</model>

View File

@@ -183,4 +183,15 @@ public class AVMLock implements Serializable
{
return fWebProject;
}
public String toString()
{
StringBuilder buffer = new StringBuilder();
buffer.append(" (webproject=").append(this.fWebProject);
buffer.append(" store=").append(this.fStore);
buffer.append(" path=").append(this.fPath);
buffer.append(" type=").append(this.fType);
buffer.append(" owners=").append(this.fOwners).append(")");
return buffer.toString();
}
}