Merged V2.9 to HEAD

10531: Merged V2.2 to V2.9
      9928: Optimise & consolidate get web project user role (ETWOTWO-568)
      9962: Reverted rev 9902 of RuleServiceImpl
      9964: Fixed transaction read-only declaration
      9979: ETWOTWO-572: Allow OpenOffice to be called remotely
      9987: Second attempt at fixing ETWOTWO-438: versionable aspect and invite user
      10096: Fix for ETWOTWO-507 FSR Service Port
      10224: Fix for ETWOTWO-507 (inconsistent results with add and delete together)
      10225: Adding logging and making FSR work with absolute directories (ETWOTWO-70 and ETWOONE-81)
      10254: ALFCOM-242, ALFCOM-230,  ETWOTWO-437
      10283: Fixed deployment installer builder to use IJ v1.2.7
      10359: Add Display Group for deployment servers to JSF client (ETWOTWO-474)
   10536: MT - simple setup/system test
   10553: Hid domain objects completely within the UsageDeltaDAO


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@10613 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2008-09-01 13:56:46 +00:00
parent 15fcd07997
commit 480181d6ea
20 changed files with 1023 additions and 109 deletions

View File

@@ -83,6 +83,7 @@ public interface WCMAppModel
static final QName PROP_DEPLOYSERVERNAME = QName.createQName(NamespaceService.WCMAPP_MODEL_1_0_URI, "deployservername");
static final QName PROP_DEPLOYSERVERUSERNAME = QName.createQName(NamespaceService.WCMAPP_MODEL_1_0_URI, "deployserverusername");
static final QName PROP_DEPLOYSERVERPASSWORD = QName.createQName(NamespaceService.WCMAPP_MODEL_1_0_URI, "deployserverpassword");
static final QName PROP_DEPLOYSERVERGROUP = QName.createQName(NamespaceService.WCMAPP_MODEL_1_0_URI, "deployservergroup");
static final QName PROP_DEPLOYSERVERURL = QName.createQName(NamespaceService.WCMAPP_MODEL_1_0_URI, "deployserverurl");
static final QName PROP_DEPLOYSERVERTARGET = QName.createQName(NamespaceService.WCMAPP_MODEL_1_0_URI, "deployservertarget");
static final QName PROP_DEPLOYSOURCEPATH = QName.createQName(NamespaceService.WCMAPP_MODEL_1_0_URI, "deploysourcepath");

View File

@@ -41,7 +41,6 @@ import org.alfresco.service.cmr.avm.AVMExistsException;
import org.alfresco.service.cmr.avm.AVMNotFoundException;
import org.alfresco.service.cmr.avm.locking.AVMLock;
import org.alfresco.service.cmr.avm.locking.AVMLockingService;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
@@ -50,7 +49,7 @@ import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.util.MD5;
import org.alfresco.util.Pair;
import org.apache.commons.logging.Log;
@@ -642,19 +641,39 @@ public class AVMLockingServiceImpl implements AVMLockingService
}
// check for content manager role - we allow access to all managers within the same store
List<ChildAssociationRef> children = fNodeService.getChildAssocs(
webProjectRef, WCMAppModel.ASSOC_WEBUSER, RegexQNamePattern.MATCH_ALL);
for (ChildAssociationRef child : children)
// TODO as part of WCM refactor, consolidate with WebProject.getWebProjectUserRole
StringBuilder query = new StringBuilder(128);
query.append("+PARENT:\"").append(webProjectRef).append("\" ");
query.append("+TYPE:\"").append(WCMAppModel.TYPE_WEBUSER).append("\" ");
query.append("+@").append(NamespaceService.WCMAPP_MODEL_PREFIX).append("\\:username:\"");
query.append(user);
query.append("\"");
ResultSet resultSet = fSearchService.query(
new StoreRef(this.webProjectStore),
SearchService.LANGUAGE_LUCENE,
query.toString());
List<NodeRef> nodes = resultSet.getNodeRefs();
if (nodes.size() == 1)
{
NodeRef childRef = child.getChildRef();
if (fNodeService.getProperty(childRef, WCMAppModel.PROP_WEBUSERNAME).equals(user) &&
fNodeService.getProperty(childRef, WCMAppModel.PROP_WEBUSERROLE).equals(ROLE_CONTENT_MANAGER))
String userrole = (String)fNodeService.getProperty(nodes.get(0), WCMAppModel.PROP_WEBUSERROLE);
if (ROLE_CONTENT_MANAGER.equals(userrole))
{
if (logger.isDebugEnabled())
logger.debug(" GRANTED: Store match and user is ContentManager role in webproject.");
return true;
if (logger.isDebugEnabled())
{
logger.debug("GRANTED: Store match and user is ContentManager role in webproject.");
}
return true;
}
}
else if (nodes.size() == 0)
{
logger.warn("hasAccess: user role not found for " + user);
}
else
{
logger.warn("hasAccess: more than one user role found for " + user);
}
// finally check the owners of the lock against the specified authority
List<String> owners = lock.getOwners();

View File

@@ -0,0 +1,240 @@
/*
* 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.content.transform;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import net.sf.jooreports.converter.DocumentFamily;
import net.sf.jooreports.converter.DocumentFormat;
import net.sf.jooreports.converter.DocumentFormatRegistry;
import net.sf.jooreports.converter.XmlDocumentFormatRegistry;
import net.sf.jooreports.openoffice.connection.OpenOfficeConnection;
import net.sf.jooreports.openoffice.connection.OpenOfficeException;
import net.sf.jooreports.openoffice.converter.StreamOpenOfficeDocumentConverter;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.transform.AbstractContentTransformer;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.util.PropertyCheck;
import org.alfresco.util.TempFileProvider;
import org.springframework.core.io.DefaultResourceLoader;
/**
* Makes use of the {@link http://sourceforge.net/projects/joott/JOOConverter} library to
* perform OpenOffice-drive conversions.
*
* @author Derek Hulley
* @author Juan David Zuluaga Arboleda
* @author Jared Ottley
*
*/
public class RemoteOpenOfficeContentTransformer extends AbstractContentTransformer
{
private OpenOfficeConnection connection;
private StreamOpenOfficeDocumentConverter converter;
private String documentFormatsConfiguration;
private DocumentFormatRegistry formatRegistry;
public RemoteOpenOfficeContentTransformer()
{
}
public void setConnection(OpenOfficeConnection connection)
{
this.connection = connection;
}
/**
* Set a non-default location from which to load the document format mappings.
*
* @param path a resource location supporting the <b>file:</b> or <b>classpath:</b> prefixes
*/
public void setDocumentFormatsConfiguration(String path)
{
this.documentFormatsConfiguration = path;
}
public boolean isConnected()
{
return connection.isConnected();
}
@Override
public void register()
{
PropertyCheck.mandatory("OpenOfficeContentTransformer", "connection", connection);
// load the document conversion configuration
if (documentFormatsConfiguration != null)
{
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
try
{
InputStream is = resourceLoader.getResource(documentFormatsConfiguration).getInputStream();
formatRegistry = new XmlDocumentFormatRegistry(is);
}
catch (IOException e)
{
throw new AlfrescoRuntimeException(
"Unable to load document formats configuration file: " + documentFormatsConfiguration);
}
}
else
{
formatRegistry = new XmlDocumentFormatRegistry();
}
// set up the converter
converter = new StreamOpenOfficeDocumentConverter(connection);
// Register
super.register();
}
/**
* @see DocumentFormatRegistry
*/
public double getReliability(String sourceMimetype, String targetMimetype)
{
if (!isConnected())
{
// The connection management is must take care of this
return 0.0;
}
// there are some conversions that fail, despite the converter believing them possible
if (targetMimetype.equals(MimetypeMap.MIMETYPE_XHTML))
{
return 0.0;
}
else if (targetMimetype.equals(MimetypeMap.MIMETYPE_WORDPERFECT))
{
return 0.0;
}
MimetypeService mimetypeService = getMimetypeService();
String sourceExtension = mimetypeService.getExtension(sourceMimetype);
String targetExtension = mimetypeService.getExtension(targetMimetype);
// query the registry for the source format
DocumentFormat sourceFormat = formatRegistry.getFormatByFileExtension(sourceExtension);
if (sourceFormat == null)
{
// no document format
return 0.0;
}
// query the registry for the target format
DocumentFormat targetFormat = formatRegistry.getFormatByFileExtension(targetExtension);
if (targetFormat == null)
{
// no document format
return 0.0;
}
// get the family of the target document
DocumentFamily sourceFamily = sourceFormat.getFamily();
// does the format support the conversion
if (!targetFormat.isExportableFrom(sourceFamily))
{
// unable to export from source family of documents to the target format
return 0.0;
}
else
{
return 1.0;
}
}
protected void transformInternal(
ContentReader reader,
ContentWriter writer,
Map<String, Object> options) throws Exception
{
String sourceMimetype = getMimetype(reader);
String targetMimetype = getMimetype(writer);
MimetypeService mimetypeService = getMimetypeService();
String sourceExtension = mimetypeService.getExtension(sourceMimetype);
String targetExtension = mimetypeService.getExtension(targetMimetype);
// query the registry for the source format
DocumentFormat sourceFormat = formatRegistry.getFormatByFileExtension(sourceExtension);
if (sourceFormat == null)
{
// source format is not recognised
throw new ContentIOException("No OpenOffice document format for source extension: " + sourceExtension);
}
// query the registry for the target format
DocumentFormat targetFormat = formatRegistry.getFormatByFileExtension(targetExtension);
if (targetFormat == null)
{
// target format is not recognised
throw new ContentIOException("No OpenOffice document format for target extension: " + targetExtension);
}
// get the family of the target document
DocumentFamily sourceFamily = sourceFormat.getFamily();
// does the format support the conversion
if (!targetFormat.isExportableFrom(sourceFamily))
{
throw new ContentIOException(
"OpenOffice conversion not supported: \n" +
" reader: " + reader + "\n" +
" writer: " + writer);
}
// create temporary files to convert from and to
File tempFromFile = TempFileProvider.createTempFile(
"OpenOfficeContentTransformer-source-",
"." + sourceExtension);
File tempToFile = TempFileProvider.createTempFile(
"OpenOfficeContentTransformer-target-",
"." + targetExtension);
// download the content from the source reader
reader.getContent(tempFromFile);
try
{
converter.convert(tempFromFile, sourceFormat, tempToFile, targetFormat);
// conversion success
}
catch (OpenOfficeException e)
{
throw new ContentIOException("OpenOffice server conversion failed: \n" +
" reader: " + reader + "\n" +
" writer: " + writer + "\n" +
" from file: " + tempFromFile + "\n" +
" to file: " + tempToFile,
e);
}
// upload the temp output to the writer given us
writer.putContent(tempToFile);
}
}

View File

@@ -877,7 +877,7 @@ public class DeploymentServiceImpl implements DeploymentService
}
report.add(event);
String storeName = srcPath.substring(0, srcPath.indexOf(':'));
System.out.println(storeName);
if (version < 0)
{
version = fAVMService.createSnapshot(storeName, null, null).get(storeName);
@@ -908,15 +908,28 @@ public class DeploymentServiceImpl implements DeploymentService
callback.eventOccurred(event);
}
if (service != null)
if (service != null && ticket != null)
{
// TODO MER - Consider what happens if abort throws an exception itself, then we loose e?
service.abort(ticket);
}
throw new AVMException("Deployment to: " + target + " failed.", e);
}
}
/**
* deployDirectoryPush
* Compares the source and destination listings and updates report with update events required to make
* dest similar to src.
* @param service
* @param ticket
* @param report
* @param callbacks
* @param version
* @param srcPath
* @param dstPath
* @param matcher
*/
private void deployDirectoryPush(DeploymentReceiverService service, String ticket,
DeploymentReport report, List<DeploymentCallback> callbacks,
int version,
@@ -926,9 +939,12 @@ public class DeploymentServiceImpl implements DeploymentService
List<FileDescriptor> dstListing = service.getListing(ticket, dstPath);
Iterator<AVMNodeDescriptor> srcIter = srcListing.values().iterator();
Iterator<FileDescriptor> dstIter = dstListing.iterator();
// Here with two sorted directory listings
AVMNodeDescriptor src = null;
FileDescriptor dst = null;
while (srcIter.hasNext() || dstIter.hasNext())
// Step through both directory listings
while (srcIter.hasNext() || dstIter.hasNext() || src != null || dst != null)
{
if (src == null)
{
@@ -944,7 +960,11 @@ public class DeploymentServiceImpl implements DeploymentService
dst = dstIter.next();
}
}
// This means no entry on src so delete.
if (fgLogger.isDebugEnabled())
{
fgLogger.debug("comparing src:" + src + " dst:"+ dst);
}
// This means no entry on src so delete what is on dst.
if (src == null)
{
String newDstPath = extendPath(dstPath, dst.getName());
@@ -980,9 +1000,12 @@ public class DeploymentServiceImpl implements DeploymentService
src = null;
continue;
}
// Here with src and dst containing something
int diff = src.getName().compareToIgnoreCase(dst.getName());
if (diff < 0)
{
// src is less than dst - must be new content in src
if (!excluded(matcher, src.getPath(), null))
{
copy(service, ticket, report, callbacks, version, src, dstPath, matcher);
@@ -992,6 +1015,7 @@ public class DeploymentServiceImpl implements DeploymentService
}
if (diff == 0)
{
// src and dst have same file name
if (src.getGuid().equals(dst.getGUID()))
{
src = null;

View File

@@ -22,7 +22,7 @@
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.usage;
package org.alfresco.repo.domain;
import org.alfresco.repo.domain.Node;

View File

@@ -22,17 +22,21 @@
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.usage.hibernate;
package org.alfresco.repo.domain.hibernate;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.alfresco.repo.domain.Node;
import org.alfresco.repo.domain.UsageDelta;
import org.alfresco.repo.node.db.NodeDaoService;
import org.alfresco.repo.transaction.TransactionalDao;
import org.alfresco.repo.usage.UsageDelta;
import org.alfresco.repo.usage.UsageDeltaDAO;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.util.GUID;
import org.alfresco.util.ParameterCheck;
import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.orm.hibernate3.HibernateCallback;
@@ -44,13 +48,19 @@ import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
*/
public class HibernateUsageDeltaDAO extends HibernateDaoSupport implements UsageDeltaDAO, TransactionalDao
{
private static final String QUERY_GET_DELTAS = "usage.GetDeltas";
// private static final String QUERY_GET_DELTAS = "usage.GetDeltas";
private static final String QUERY_GET_TOTAL_DELTA_SIZE = "usage.GetTotalDeltaSize";
private static final String QUERY_GET_USAGE_DELTA_NODES = "usage.GetUsageDeltaNodes";
private static final String QUERY_DELETE_DELTAS_FOR_NODE = "usage.DeleteUsageDeltasForNode";
/** a uuid identifying this unique instance */
private final String uuid;
private NodeDaoService nodeDaoService;
public void setNodeDaoService(NodeDaoService nodeDaoService)
{
this.nodeDaoService = nodeDaoService;
}
/**
*
@@ -119,34 +129,48 @@ public class HibernateUsageDeltaDAO extends HibernateDaoSupport implements Usage
{
getSession().flush();
}
private Node getNodeNotNull(NodeRef nodeRef) throws InvalidNodeRefException
{
ParameterCheck.mandatory("nodeRef", nodeRef);
Node unchecked = nodeDaoService.getNode(nodeRef);
if (unchecked == null)
{
throw new InvalidNodeRefException("Node does not exist: " + nodeRef, nodeRef);
}
return unchecked;
}
public int deleteDeltas(NodeRef nodeRef)
{
Node node = getNodeNotNull(nodeRef);
return deleteDeltas(node.getId());
}
@SuppressWarnings("unchecked")
public int deleteDeltas(final Node node)
public int deleteDeltas(final Long nodeId)
{
HibernateCallback callback = new HibernateCallback()
{
public Object doInHibernate(Session session)
{
Query query = session.getNamedQuery(QUERY_GET_DELTAS);
query.setParameter("node", node);
return query.list();
Query query = session.getNamedQuery(QUERY_DELETE_DELTAS_FOR_NODE);
query.setParameter("nodeId", nodeId);
return query.executeUpdate();
}
};
// execute
List<UsageDelta> queryResults = (List<UsageDelta>)getHibernateTemplate().execute(callback);
Integer delCount = (Integer) getHibernateTemplate().execute(callback);
for (UsageDelta usageDelta : queryResults)
{
getHibernateTemplate().delete(usageDelta);
}
return queryResults.size();
return delCount.intValue();
}
@SuppressWarnings("unchecked")
public long getTotalDeltaSize(final Node node)
public long getTotalDeltaSize(NodeRef nodeRef)
{
final Node node = getNodeNotNull(nodeRef);
HibernateCallback callback = new HibernateCallback()
{
public Object doInHibernate(Session session)
@@ -163,14 +187,21 @@ public class HibernateUsageDeltaDAO extends HibernateDaoSupport implements Usage
return (queryResult == null ? 0 : queryResult);
}
public void insertDelta(UsageDelta deltaInfo)
public void insertDelta(NodeRef usageNodeRef, long deltaSize)
{
Node node = getNodeNotNull(usageNodeRef);
UsageDelta delta = new UsageDeltaImpl();
// delta properties
delta.setNode(node);
delta.setDeltaSize(deltaSize);
// Save
getSession().save(deltaInfo);
getSession().save(delta);
}
@SuppressWarnings("unchecked")
public Set<Node> getUsageDeltaNodes()
public Set<NodeRef> getUsageDeltaNodes()
{
HibernateCallback callback = new HibernateCallback()
{
@@ -183,10 +214,10 @@ public class HibernateUsageDeltaDAO extends HibernateDaoSupport implements Usage
};
// execute read-only tx
List<Node> queryResults = (List<Node>)getHibernateTemplate().execute(callback);
Set<Node> results = new HashSet<Node>(queryResults.size());
Set<NodeRef> results = new HashSet<NodeRef>(queryResults.size(), 1.0F);
for (Node node : queryResults)
{
results.add(node);
results.add(node.getNodeRef());
}
return results;
}

View File

@@ -16,8 +16,8 @@
<!-- The Usage Delta -->
<class name="org.alfresco.repo.usage.hibernate.UsageDeltaImpl"
proxy="org.alfresco.repo.usage.UsageDelta"
<class name="org.alfresco.repo.domain.hibernate.UsageDeltaImpl"
proxy="org.alfresco.repo.domain.UsageDelta"
table="alf_usage_delta"
dynamic-update="false"
dynamic-insert="false"
@@ -52,7 +52,7 @@
select
sum(deltaSize)
from
org.alfresco.repo.usage.hibernate.UsageDeltaImpl as usage_delta
org.alfresco.repo.domain.hibernate.UsageDeltaImpl as usage_delta
where
usage_delta.node = :node
</query>
@@ -63,7 +63,7 @@
select
distinct usage_delta.node
from
org.alfresco.repo.usage.hibernate.UsageDeltaImpl as usage_delta
org.alfresco.repo.domain.hibernate.UsageDeltaImpl as usage_delta
</query>
<!-- Get usage deltas for a node -->
@@ -71,9 +71,17 @@
select
usage_delta
from
org.alfresco.repo.usage.hibernate.UsageDeltaImpl as usage_delta
org.alfresco.repo.domain.hibernate.UsageDeltaImpl as usage_delta
where
usage_delta.node = :node
</query>
<query name="usage.DeleteUsageDeltasForNode">
delete
from
org.alfresco.repo.domain.hibernate.UsageDeltaImpl as usage
where
usage.node.id = :nodeId
</query>
</hibernate-mapping>

View File

@@ -22,10 +22,10 @@
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.usage.hibernate;
package org.alfresco.repo.domain.hibernate;
import org.alfresco.repo.domain.Node;
import org.alfresco.repo.usage.UsageDelta;
import org.alfresco.repo.domain.UsageDelta;
/**
* Usage Delta Implementation

View File

@@ -684,7 +684,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
{
logger.debug("Deleting usage deltas of node (if any)" + node.getId());
}
usageDeltaDao.deleteDeltas(node);
usageDeltaDao.deleteDeltas(node.getId());
// update the node status
NodeRef nodeRef = node.getNodeRef();

View File

@@ -123,7 +123,7 @@ public class MissingContentReindexComponent extends AbstractReindexComponent
return null;
}
};
transactionService.getRetryingTransactionHelper().doInTransaction(reindexWork);
transactionService.getRetryingTransactionHelper().doInTransaction(reindexWork, true);
count++;
// check if we have to break out
if (isShuttingDown())

View File

@@ -354,13 +354,8 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService
public List<Rule> getRules(NodeRef nodeRef, boolean includeInherited, String ruleTypeName)
{
List<Rule> rules = new ArrayList<Rule>();
// Extra check of CONSUMER permission was added to rule selection,
// to prevent Access Denied Exception due to the bug:
// https://issues.alfresco.com/browse/ETWOTWO-438
if (this.runtimeNodeService.exists(nodeRef) == true && checkNodeType(nodeRef) == true &&
permissionService.hasPermission(nodeRef, PermissionService.CONSUMER) == AccessStatus.ALLOWED)
if (this.runtimeNodeService.exists(nodeRef) == true && checkNodeType(nodeRef) == true)
{
if (includeInherited == true && this.runtimeNodeService.hasAspect(nodeRef, RuleModel.ASPECT_IGNORE_INHERITED_RULES) == false)
{
@@ -375,7 +370,12 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService
}
}
if (this.runtimeNodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES) == true)
// Extra check of CONSUMER permission was added to rule selection,
// to prevent Access Denied Exception due to the bug:
// https://issues.alfresco.com/browse/ETWOTWO-438
if (this.runtimeNodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES) == true &&
permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.ALLOWED)
{
NodeRef ruleFolder = getSavedRuleFolderRef(nodeRef);
if (ruleFolder != null)

View File

@@ -0,0 +1,566 @@
/*
* Copyright (C) 2005-2008 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.tenant;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ApplicationModel;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
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.CategoryService;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.security.OwnableService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.ApplicationContextHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
public class MultiTDemoSystemTest extends TestCase
{
private static Log logger = LogFactory.getLog(MultiTDemoSystemTest.class);
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
private NodeService nodeService;
private AuthenticationService authenticationService;
private PersonService personService;
private SearchService searchService;
private ContentService contentService;
private PermissionService permissionService;
private OwnableService ownableService;
private TenantAdminService tenantAdminService;
private TenantService tenantService;
private AuthorityService authorityService;
private CategoryService categoryService;
public static final String TEST_TENANT_DOMAIN1 = "yyy.com";
public static final String TEST_TENANT_DOMAIN2 = "zzz.com";
private static List<String> tenants;
static {
tenants = new ArrayList<String>(2);
tenants.add(TEST_TENANT_DOMAIN1);
tenants.add(TEST_TENANT_DOMAIN2);
}
public static final String ROOT_DIR = "./tenantstores";
public static final String TEST_ADMIN_BASENAME = "admin";
public static final String TEST_ADMIN_PASSWORD = "admin";
public static final String TEST_USER1 = "alice";
public static final String TEST_USER2 = "bob";
public static final String TEST_USER3 = "eve";
public static StoreRef SPACES_STORE = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");
public MultiTDemoSystemTest()
{
}
@Override
protected void setUp() throws Exception
{
super.setUp();
nodeService = (NodeService) ctx.getBean("NodeService");
authenticationService = (AuthenticationService) ctx.getBean("AuthenticationService");
tenantAdminService = (TenantAdminService) ctx.getBean("tenantAdminService");
tenantService = (TenantService) ctx.getBean("tenantService");
personService = (PersonService) ctx.getBean("PersonService");
searchService = (SearchService) ctx.getBean("SearchService");
contentService = (ContentService) ctx.getBean("ContentService");
permissionService = (PermissionService) ctx.getBean("PermissionService");
ownableService = (OwnableService) ctx.getBean("OwnableService");
authorityService = (AuthorityService) ctx.getBean("AuthorityService");
categoryService = (CategoryService) ctx.getBean("CategoryService");
AuthenticationUtil.setCurrentUser(AuthenticationUtil.getSystemUserName()); // force, to clear real user from previous test (runAs issue ?)
}
@Override
protected void tearDown() throws Exception
{
super.tearDown();
}
public void testCreateTenants() throws Throwable
{
logger.info("Create tenants");
try
{
for (final String tenantDomain : tenants)
{
// create tenants (if not already created)
AuthenticationUtil.runAs(new RunAsWork<Object>()
{
public Object doWork() throws Exception
{
if (! tenantAdminService.existsTenant(tenantDomain))
{
//tenantAdminService.createTenant(tenantDomain, TEST_ADMIN_PASSWORD.toCharArray(), ROOT_DIR + "/" + tenantDomain);
tenantAdminService.createTenant(tenantDomain, TEST_ADMIN_PASSWORD.toCharArray(), null); // use default root dir
logger.info("Created tenant " + tenantDomain);
}
return null;
}
}, AuthenticationUtil.getSystemUserName());
}
}
catch (Throwable t)
{
StringWriter stackTrace = new StringWriter();
t.printStackTrace(new PrintWriter(stackTrace));
System.err.println(stackTrace.toString());
throw t;
}
}
public void testCreateUsers() throws Throwable
{
logger.info("Create demo users");
try
{
for (final String tenantDomain : tenants)
{
String tenantAdminName = tenantService.getDomainUser(TenantService.ADMIN_BASENAME, tenantDomain);
AuthenticationUtil.runAs(new RunAsWork<Object>()
{
public Object doWork() throws Exception
{
createUser(TEST_USER1, tenantDomain, "welcome");
createUser(TEST_USER2, tenantDomain, "welcome");
if (tenantDomain.equals(TEST_TENANT_DOMAIN2))
{
createUser(TEST_USER3, tenantDomain, "welcome");
}
return null;
}
}, tenantAdminName);
}
}
catch (Throwable t)
{
StringWriter stackTrace = new StringWriter();
t.printStackTrace(new PrintWriter(stackTrace));
System.err.println(stackTrace.toString());
throw t;
}
}
public void testCreateGroups()
{
logger.info("Create demo groups");
for (final String tenantDomain : tenants)
{
String tenantAdminName = tenantService.getDomainUser("admin", tenantDomain);
AuthenticationUtil.runAs(new RunAsWork<Object>()
{
public Object doWork() throws Exception
{
createGroup("GrpA-"+tenantDomain, null);
createGroup("SubGrpA-"+tenantDomain, "GrpA-"+tenantDomain);
createGroup("GrpB-"+tenantDomain, null);
createGroup("SubGrpB-"+tenantDomain, "GrpB-"+tenantDomain);
if (tenantDomain.equals(TEST_TENANT_DOMAIN2))
{
createGroup("GrpC-"+tenantDomain, null);
createGroup("SubGrpC-"+tenantDomain, "GrpC-"+tenantDomain);
}
return null;
}
}, tenantAdminName);
}
}
public void testCreateCategories()
{
logger.info("Create demo categories");
for (final String tenantDomain : tenants)
{
String tenantAdminName = tenantService.getDomainUser(TenantService.ADMIN_BASENAME, tenantDomain);
AuthenticationUtil.runAs(new RunAsWork<Object>()
{
public Object doWork() throws Exception
{
NodeRef catRef = createCategory(SPACES_STORE, null, "CatA", "CatA-"+tenantDomain);
createCategory(SPACES_STORE, catRef, "SubCatA", "SubCatA-"+tenantDomain); // ignore return
catRef = createCategory(SPACES_STORE, null, "CatB", "CatB-"+tenantDomain);
createCategory(SPACES_STORE, catRef, "SubCatB", "SubCatB-"+tenantDomain); // ignore return
if (tenantDomain.equals(TEST_TENANT_DOMAIN2))
{
catRef = createCategory(SPACES_STORE, null, "CatC", "CatC-"+tenantDomain);
createCategory(SPACES_STORE, catRef, "SubCatC", "SubCatC-"+tenantDomain); // ignore return
}
return null;
}
}, tenantAdminName);
}
}
public void testCreateFolders()
{
logger.info("Create demo folders");
List<String> users = new ArrayList<String>(3);
users.add(TEST_USER1);
users.add(TEST_USER2);
users.add(TEST_USER3);
for (final String tenantDomain : tenants)
{
for (String baseUserName : users)
{
if ((! baseUserName.equals(TEST_USER3)) || (tenantDomain.equals(TEST_TENANT_DOMAIN2)))
{
final String tenantUserName = tenantService.getDomainUser(baseUserName, tenantDomain);
AuthenticationUtil.runAs(new RunAsWork<Object>()
{
public Object doWork() throws Exception
{
NodeRef homeSpaceRef = getHomeSpaceFolderNode(tenantUserName);
NodeRef folderRef = createFolderNode(homeSpaceRef, "myfolder1");
createFolderNode(folderRef, "mysubfolder1"); // ignore return
folderRef = createFolderNode(homeSpaceRef, "myfolder2");
createFolderNode(folderRef, "mysubfolder2"); // ignore return
if (tenantDomain.equals(TEST_TENANT_DOMAIN2))
{
folderRef = createFolderNode(homeSpaceRef, "myfolder3");
createFolderNode(folderRef, "mysubfolder3"); // ignore return
}
return null;
}
}, tenantUserName);
}
}
}
}
public void testCreateUserContent()
{
logger.info("Create demo content");
List<String> users = new ArrayList<String>(3);
users.add(TEST_USER1);
users.add(TEST_USER2);
users.add(TEST_USER3);
for (final String tenantDomain : tenants)
{
for (String baseUserName : users)
{
if ((! baseUserName.equals(TEST_USER3)) || (tenantDomain.equals(TEST_TENANT_DOMAIN2)))
{
final String tenantUserName = tenantService.getDomainUser(baseUserName, tenantDomain);
AuthenticationUtil.runAs(new RunAsWork<Object>()
{
public Object doWork() throws Exception
{
NodeRef homeSpaceRef = getHomeSpaceFolderNode(tenantUserName);
addTextContent(homeSpaceRef, tenantUserName+" quick brown fox.txt", "The quick brown fox jumps over the lazy dog (tenant " + tenantDomain + ")");
if (tenantDomain.equals(TEST_TENANT_DOMAIN2))
{
addTextContent(homeSpaceRef, tenantUserName+" quick brown fox ANO.txt", "The quick brown fox jumps over the lazy dog ANO (tenant " + tenantDomain + ")");
}
return null;
}
}, tenantUserName);
}
}
}
}
private void createGroup(String shortName, String parentShortName)
{
// create new Group using authority Service
String groupName = this.authorityService.getName(AuthorityType.GROUP, shortName);
if (this.authorityService.authorityExists(groupName) == false)
{
String parentGroupName = null;
if (parentShortName != null)
{
parentGroupName = this.authorityService.getName(AuthorityType.GROUP, parentShortName);
if (this.authorityService.authorityExists(parentGroupName) == false)
{
logger.warn("Parent group does not exist: " + parentShortName);
return;
}
}
this.authorityService.createAuthority(AuthorityType.GROUP, parentGroupName, shortName);
}
else
{
logger.warn("Group already exists: " + shortName);
}
}
private void createUser(String baseUserName, String tenantDomain, String password)
{
String userName = tenantService.getDomainUser(baseUserName, tenantDomain);
if (! this.authenticationService.authenticationExists(userName))
{
NodeRef baseHomeFolder = getUserHomesNodeRef(SPACES_STORE);
// Create the users home folder
NodeRef homeFolder = createHomeSpaceFolderNode(
baseHomeFolder,
baseUserName,
userName);
// Create the authentication
this.authenticationService.createAuthentication(userName, password.toCharArray());
// Create the person
Map<QName, Serializable> personProperties = new HashMap<QName, Serializable>();
personProperties.put(ContentModel.PROP_USERNAME, userName);
personProperties.put(ContentModel.PROP_HOMEFOLDER, homeFolder);
personProperties.put(ContentModel.PROP_FIRSTNAME, baseUserName);
personProperties.put(ContentModel.PROP_LASTNAME, baseUserName+"-"+tenantDomain); // add domain suffix here for demo only
personProperties.put(ContentModel.PROP_EMAIL, userName);
NodeRef newPerson = this.personService.createPerson(personProperties);
// ensure the user can access their own Person object
this.permissionService.setPermission(newPerson, userName, permissionService.getAllPermission(), true);
logger.info("Created user " + userName);
}
}
private NodeRef getUserHomesNodeRef(StoreRef storeRef)
{
// get the users' home location
String path = "/app:company_home/app:user_homes";
ResultSet rs = this.searchService.query(storeRef, SearchService.LANGUAGE_XPATH, path);
NodeRef usersHomeNodeRef = null;
if (rs.length() == 0)
{
throw new AlfrescoRuntimeException("Cannot find user homes location: " + path);
}
else
{
usersHomeNodeRef = rs.getNodeRef(0);
}
return usersHomeNodeRef;
}
private NodeRef createFolderNode(NodeRef parentFolderNodeRef, String nameValue)
{
if (nameValue != null)
{
Map<QName, Serializable> folderProps = new HashMap<QName, Serializable>();
folderProps.put(ContentModel.PROP_NAME, nameValue);
return this.nodeService.createNode(
parentFolderNodeRef,
ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, nameValue),
ContentModel.TYPE_FOLDER,
folderProps).getChildRef();
}
return null;
}
private NodeRef createCategory(StoreRef storeRef, NodeRef parentCategoryRef, String name, String description)
{
// create category using categoryservice
NodeRef ref;
if (parentCategoryRef == null)
{
ref = this.categoryService.createRootCategory(storeRef, ContentModel.ASPECT_GEN_CLASSIFIABLE, name);
}
else
{
ref = categoryService.createCategory(parentCategoryRef, name);
}
// apply the titled aspect - for description
Map<QName, Serializable> titledProps = new HashMap<QName, Serializable>(1, 1.0f);
titledProps.put(ContentModel.PROP_DESCRIPTION, description);
this.nodeService.addAspect(ref, ContentModel.ASPECT_TITLED, titledProps);
return ref;
}
private NodeRef createHomeSpaceFolderNode(NodeRef folderNodeRef, String spaceName, String userName)
{
if (spaceName != null)
{
Map<QName, Serializable> folderProps = new HashMap<QName, Serializable>();
folderProps.put(ContentModel.PROP_NAME, spaceName);
NodeRef nodeRef = this.nodeService.createNode(
folderNodeRef,
ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, spaceName),
ContentModel.TYPE_FOLDER,
folderProps).getChildRef();
// apply the uifacets aspect - icon and title props
Map<QName, Serializable> uiFacetsProps = new HashMap<QName, Serializable>(3);
uiFacetsProps.put(ApplicationModel.PROP_ICON, "space-icon-default");
uiFacetsProps.put(ContentModel.PROP_TITLE, spaceName);
this.nodeService.addAspect(nodeRef, ApplicationModel.ASPECT_UIFACETS, uiFacetsProps);
setupHomeSpacePermissions(nodeRef, userName);
return nodeRef;
}
return null;
}
private void setupHomeSpacePermissions(NodeRef homeSpaceRef, String userName)
{
// Admin Authority has full permissions by default (automatic - set in the permission config)
// give full permissions to the new user
this.permissionService.setPermission(homeSpaceRef, userName, permissionService.getAllPermission(), true);
// by default other users will only have GUEST access to the space contents
String permission = "Consumer";
if (permission != null && permission.length() != 0)
{
this.permissionService.setPermission(homeSpaceRef, permissionService.getAllAuthorities(), permission, true);
}
// the new user is the OWNER of their own space and always has full permissions
this.ownableService.setOwner(homeSpaceRef, userName);
this.permissionService.setPermission(homeSpaceRef, permissionService.getOwnerAuthority(), permissionService.getAllPermission(), true);
// now detach (if we did this first we could not set any permissions!)
this.permissionService.setInheritParentPermissions(homeSpaceRef, false);
}
private NodeRef getHomeSpaceFolderNode(String userName)
{
return (NodeRef)this.nodeService.getProperty(personService.getPerson(userName), ContentModel.PROP_HOMEFOLDER);
}
private void addTextContent(NodeRef spaceRef, String name, String textData)
{
Map<QName, Serializable> contentProps = new HashMap<QName, Serializable>();
contentProps.put(ContentModel.PROP_NAME, name);
ChildAssociationRef association = nodeService.createNode(spaceRef,
ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name),
ContentModel.TYPE_CONTENT,
contentProps);
NodeRef content = association.getChildRef();
// add titled aspect (for Web Client display)
Map<QName, Serializable> titledProps = new HashMap<QName, Serializable>();
titledProps.put(ContentModel.PROP_TITLE, name);
titledProps.put(ContentModel.PROP_DESCRIPTION, name);
this.nodeService.addAspect(content, ContentModel.ASPECT_TITLED, titledProps);
ContentWriter writer = contentService.getWriter(content, ContentModel.PROP_CONTENT, true);
writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
writer.setEncoding("UTF-8");
writer.putContent(textData);
}
// comment-in to run from command line
public static void main(String args[])
{
System.out.println(new Date());
junit.textui.TestRunner.run(MultiTDemoSystemTest.class);
System.out.println(new Date());
}
}

View File

@@ -47,30 +47,69 @@ public interface TenantService extends TenantBaseService
public static final String ADMIN_BASENAME = "admin";
/**
* @return the reference <b>with</b> the tenant-specific ID attached
*/
public NodeRef getName(NodeRef nodeRef);
/**
* @return the reference <b>with</b> the tenant-specific ID attached
*/
public NodeRef getName(NodeRef inNodeRef, NodeRef nodeRef);
/**
* @return the reference <b>with</b> the tenant-specific ID attached
*/
public StoreRef getName(StoreRef storeRef);
/**
* @return the reference <b>with</b> the tenant-specific ID attached
*/
public ChildAssociationRef getName(ChildAssociationRef childAssocRef);
/**
* @return the reference <b>with</b> the tenant-specific ID attached
*/
public StoreRef getName(String username, StoreRef storeRef);
/**
* @return the reference <b>with</b> the tenant-specific ID attached
*/
public QName getName(NodeRef inNodeRef, QName name);
/**
* @return the reference <b>with</b> the tenant-specific ID attached
*/
public String getName(String name);
/**
* @return the reference <b>without</b> the tenant-specific ID attached
*/
public QName getBaseName(QName name, boolean forceIfNonTenant);
/**
* @return the reference <b>without</b> the tenant-specific ID attached
*/
public NodeRef getBaseName(NodeRef nodeRef);
/**
* @return the reference <b>without</b> the tenant-specific ID attached
*/
public StoreRef getBaseName(StoreRef storeRef);
/**
* @return the reference <b>without</b> the tenant-specific ID attached
*/
public ChildAssociationRef getBaseName(ChildAssociationRef childAssocRef);
/**
* @return the reference <b>without</b> the tenant-specific ID attached
*/
public String getBaseName(String name);
/**
* @return the reference <b>without</b> the tenant-specific ID attached
*/
public String getBaseName(String name, boolean forceIfNonTenant);
public String getBaseNameUser(String name);

View File

@@ -26,7 +26,7 @@ package org.alfresco.repo.usage;
import java.util.Set;
import org.alfresco.repo.domain.Node;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* The interface to persist usage delta information.
@@ -37,20 +37,21 @@ public interface UsageDeltaDAO
/**
* Create a usage delta entry.
*
* @param deltaInfo
* @param deltaSize the size change
*/
public void insertDelta(UsageDelta deltaInfo);
public void insertDelta(NodeRef usageNodeRef, long deltaSize);
/**
* Get the total delta size for a node.
*
* @param node
* @return sum of delta sizes (in bytes) - can be +ve or -ve
* @param nodeRef the node reference
* @return sum of delta sizes (in bytes) - can be +ve or -ve
*/
public long getTotalDeltaSize(Node node);
public long getTotalDeltaSize(NodeRef usageNodeRef);
public Set<NodeRef> getUsageDeltaNodes();
public Set<Node> getUsageDeltaNodes();
public int deleteDeltas(NodeRef nodeRef);
public int deleteDeltas(Node node);
public int deleteDeltas(Long nodeId);
}

View File

@@ -24,80 +24,43 @@
*/
package org.alfresco.repo.usage;
import java.util.HashSet;
import java.util.Set;
import org.alfresco.repo.domain.Node;
import org.alfresco.repo.node.db.NodeDaoService;
import org.alfresco.repo.usage.hibernate.UsageDeltaImpl;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.usage.UsageService;
import org.alfresco.util.ParameterCheck;
/**
* The implementation of the UsageService for tracking usages.
*
* @author Jan Vonka
* @since 2.9
*/
public class UsageServiceImpl implements UsageService
{
private UsageDeltaDAO usageDeltaDao;
private NodeDaoService nodeDaoService;
public void setUsageDeltaDao(UsageDeltaDAO usageDeltaDao)
{
this.usageDeltaDao = usageDeltaDao;
}
public void setNodeDaoService(NodeDaoService nodeDaoService)
{
this.nodeDaoService = nodeDaoService;
}
public void insertDelta(NodeRef usageNodeRef, long deltaSize)
{
UsageDelta delta = new UsageDeltaImpl();
// delta properties
delta.setNode(getNodeNotNull(usageNodeRef));
delta.setDeltaSize(deltaSize);
usageDeltaDao.insertDelta(delta);
usageDeltaDao.insertDelta(usageNodeRef, deltaSize);
}
public long getTotalDeltaSize(NodeRef usageNodeRef)
{
return usageDeltaDao.getTotalDeltaSize(getNodeNotNull(usageNodeRef));
return usageDeltaDao.getTotalDeltaSize(usageNodeRef);
}
public Set<NodeRef> getUsageDeltaNodes()
{
Set<Node> nodes = usageDeltaDao.getUsageDeltaNodes();
// convert nodes to nodeRefs (tenant-specific)
Set<NodeRef> results = new HashSet<NodeRef>(nodes.size());
for (Node node : nodes)
{
results.add(node.getNodeRef());
}
return results;
return usageDeltaDao.getUsageDeltaNodes();
}
public int deleteDeltas(NodeRef usageNodeRef)
{
return usageDeltaDao.deleteDeltas(getNodeNotNull(usageNodeRef));
}
private Node getNodeNotNull(NodeRef nodeRef) throws InvalidNodeRefException
{
ParameterCheck.mandatory("nodeRef", nodeRef);
Node unchecked = nodeDaoService.getNode(nodeRef);
if (unchecked == null)
{
throw new InvalidNodeRefException("Node does not exist: " + nodeRef, nodeRef);
}
return unchecked;
return usageDeltaDao.deleteDeltas(usageNodeRef);
}
}

View File

@@ -33,6 +33,8 @@ import org.alfresco.service.cmr.repository.NodeRef;
/**
* The public API by which applications can create usage delta entries.
*
* @author Jan Vonka
* @since 2.9
*/
@PublicService
public interface UsageService