/*
* Copyright (C) 2012 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.services.jcr.impl.quota;
import org.exoplatform.commons.utils.PrivilegedSystemHelper;
import org.exoplatform.commons.utils.SecurityHelper;
import org.exoplatform.management.annotations.Managed;
import org.exoplatform.management.annotations.ManagedDescription;
import org.exoplatform.management.annotations.ManagedName;
import org.exoplatform.management.jmx.annotations.NameTemplate;
import org.exoplatform.management.jmx.annotations.Property;
import org.exoplatform.services.jcr.config.RepositoryEntry;
import org.exoplatform.services.jcr.config.WorkspaceEntry;
import org.exoplatform.services.jcr.core.WorkspaceContainerFacade;
import org.exoplatform.services.jcr.dataflow.persistent.ExtendedMandatoryItemsPersistenceListener;
import org.exoplatform.services.jcr.impl.backup.BackupException;
import org.exoplatform.services.jcr.impl.backup.Backupable;
import org.exoplatform.services.jcr.impl.backup.DataRestore;
import org.exoplatform.services.jcr.impl.backup.ResumeException;
import org.exoplatform.services.jcr.impl.backup.SuspendException;
import org.exoplatform.services.jcr.impl.backup.Suspendable;
import org.exoplatform.services.jcr.impl.backup.rdbms.DataRestoreContext;
import org.exoplatform.services.jcr.impl.core.JCRPath;
import org.exoplatform.services.jcr.impl.core.RepositoryImpl;
import org.exoplatform.services.jcr.impl.core.query.SearchManager;
import org.exoplatform.services.jcr.impl.dataflow.persistent.WorkspacePersistentDataManager;
import org.exoplatform.services.jcr.impl.util.io.DirectoryHelper;
import org.exoplatform.services.rpc.RPCException;
import org.exoplatform.services.rpc.RPCService;
import org.picocontainer.Startable;
import java.io.File;
import java.lang.reflect.Method;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.jcr.RepositoryException;
/**
* {@link WorkspaceQuotaManager} listens all changes performed in
* {@link WorkspacePersistentDataManager} and checks if some of them might
* exceed defined quota limit. If so, exception might be thrown or warning printing.
* Then all changes are devided into two part. First one should be applied synchronously.
* Another ones are put into changes log and to be passed to coordinator by timer.
* It is managed by {@link RepositoryQuotaManager}
*
* @author <a href="abazko@exoplatform.com">Anatoliy Bazko</a>
* @version $Id: WorkspaceQuotaManager.java 34360 2009-07-22 23:58:59Z tolusha $
*/
@Managed
@NameTemplate(@Property(key = "service", value = "WorkspaceQuotaManager"))
public class WorkspaceQuotaManager implements Startable, Backupable, Suspendable
{
/**
* Workspace name.
*/
protected final String wsName;
/**
* Repository name.
*/
protected final String rName;
/**
* Unique name.
*/
protected final String uniqueName;
/**
* Workspace container.
*/
protected final WorkspaceContainerFacade wsContainer;
/**
* {@link WorkspacePersistentDataManager}.
*/
protected final WorkspacePersistentDataManager dataManager;
/**
* Quota manager of repository level.
*/
protected final RepositoryQuotaManager repositoryQuotaManager;
/**
* Executor service per workspace to be able to suspend all tasks devoted current workspace..
*/
protected final QuotaExecutorService executor;
/**
* {@link QuotaPersister}
*/
protected final QuotaPersister quotaPersister;
/**
* {@link RPCService}
*/
protected final RPCService rpcService;
/**
* {@link ExtendedMandatoryItemsPersistenceListener} implementations.
*/
protected final ChangesListener changesListener;
/**
* Indicates if component suspended or not.
*/
protected final AtomicBoolean isSuspended = new AtomicBoolean();
/**
* Context;
*/
protected final WorkspaceQuotaContext context;
/**
* Helps calculate node data size.
*/
protected final CalculateNodeDataSizeTool calculateNodeDataSizeTool;
/**
* BaseWorkspaceQuotaManager constructor.
*/
public WorkspaceQuotaManager(RepositoryImpl repository, RepositoryQuotaManager rQuotaManager,
RepositoryEntry rEntry, WorkspaceEntry wsEntry, WorkspacePersistentDataManager dataManager)
{
this.rName = rEntry.getName();
this.wsName = wsEntry.getName();
this.uniqueName = "/" + rName + "/" + wsName;
this.wsContainer = repository.getWorkspaceContainer(wsName);
this.dataManager = dataManager;
this.repositoryQuotaManager = rQuotaManager;
this.quotaPersister = rQuotaManager.globalQuotaManager.quotaPersister;
this.rpcService = rQuotaManager.globalQuotaManager.rpcService;
this.executor = new QuotaExecutorService(uniqueName);
repositoryQuotaManager.registerWorkspaceQuotaManager(wsName, this);
this.context =
new WorkspaceQuotaContext(wsName, rName, uniqueName, dataManager, repository.getLocationFactory(), executor,
quotaPersister, rpcService, rQuotaManager.globalQuotaManager.exceededQuotaBehavior);
changesListener = new ChangesListener(this);
calculateNodeDataSizeTool = new CalculateNodeDataSizeTool(context);
dataManager.addItemPersistenceListener(changesListener);
}
/**
* @see QuotaManager#getNodeDataSize(String, String, String)
*/
@Managed
@ManagedDescription("Returns a node data size")
public long getNodeDataSize(@ManagedName("nodePath") String nodePath) throws QuotaManagerException
{
if (nodePath.equals(JCRPath.ROOT_PATH))
{
return getWorkspaceDataSize();
}
return quotaPersister.getNodeDataSize(rName, wsName, nodePath);
}
/**
* @see QuotaManager#getNodeQuota(String, String, String)
*/
@Managed
@ManagedDescription("Returns a node quota limit")
public long getNodeQuota(@ManagedName("nodePath") String nodePath) throws QuotaManagerException
{
if (nodePath.equals(JCRPath.ROOT_PATH))
{
return getWorkspaceQuota();
}
return quotaPersister.getNodeQuotaOrGroupOfNodesQuota(rName, wsName, nodePath);
}
/**
* @see QuotaManager#setNodeQuota(String, String, String, long, boolean)
*/
@Managed
@ManagedDescription("Sets a node quota limit")
public void setNodeQuota(@ManagedName("nodePath") String nodePath, @ManagedName("quotaLimit") long quotaLimit,
@ManagedName("asyncUpdate") boolean asyncUpdate) throws QuotaManagerException
{
if (nodePath.equals(JCRPath.ROOT_PATH))
{
setWorkspaceQuota(quotaLimit);
}
else
{
quotaPersister.setNodeQuota(rName, wsName, nodePath, quotaLimit, asyncUpdate);
}
}
/**
* @see QuotaManager#setGroupOfNodesQuota(String, String, String, long, boolean)
*/
@Managed
@ManagedDescription("Sets a quota limit for a bunch of nodes")
public void setGroupOfNodesQuota(@ManagedName("patternPath") String patternPath,
@ManagedName("quotaLimit") long quotaLimit, @ManagedName("asyncUpdate") boolean asyncUpdate)
throws QuotaManagerException
{
if (patternPath.equals(JCRPath.ROOT_PATH))
{
setWorkspaceQuota(quotaLimit);
}
else
{
quotaPersister.setGroupOfNodesQuota(rName, wsName, patternPath, quotaLimit, asyncUpdate);
}
}
/**
* @see QuotaManager#removeNodeQuota(String, String, String)
*/
@Managed
@ManagedDescription("Removes a quota limit for a node")
public void removeNodeQuota(@ManagedName("nodePath") String nodePath) throws QuotaManagerException
{
if (nodePath.equals(JCRPath.ROOT_PATH))
{
removeWorkspaceQuota();
}
else
{
quotaPersister.removeNodeQuotaAndDataSize(rName, wsName, nodePath);
}
}
/**
* @see QuotaManager#removeGroupOfNodesQuota(String, String, String)
*/
@Managed
@ManagedDescription("Removes a quota limit for a bunch of nodes")
public void removeGroupOfNodesQuota(@ManagedName("patternPath") String patternPath) throws QuotaManagerException
{
if (patternPath.equals(JCRPath.ROOT_PATH))
{
removeWorkspaceQuota();
}
else
{
quotaPersister.removeGroupOfNodesAndDataSize(rName, wsName, patternPath);
}
}
/**
* @see QuotaManager#setWorkspaceQuota(String, String, long)
*/
@Managed
@ManagedDescription("Sets workspace quota limit")
public void setWorkspaceQuota(long quotaLimit) throws QuotaManagerException
{
quotaPersister.setWorkspaceQuota(rName, wsName, quotaLimit);
}
/**
* @see QuotaManager#removeWorkspaceQuota(String, String)
*/
@Managed
@ManagedDescription("Removes workspace quota limit")
public void removeWorkspaceQuota() throws QuotaManagerException
{
quotaPersister.removeWorkspaceQuota(rName, wsName);
}
/**
* @see QuotaManager#getWorkspaceQuota(String, String)
*/
@Managed
@ManagedDescription("Returns workspace quota limit")
public long getWorkspaceQuota() throws QuotaManagerException
{
return quotaPersister.getWorkspaceQuota(rName, wsName);
}
/**
* @see QuotaManager#getWorkspaceDataSize(String, String)
*/
@Managed
@ManagedDescription("Returns a size of the Workspace")
public long getWorkspaceDataSize() throws QuotaManagerException
{
return quotaPersister.getWorkspaceDataSize(rName, wsName);
}
/**
* @see QuotaManager#getWorkspaceIndexSize(String, String)
*/
@Managed
@ManagedDescription("Returns a size of the index")
public long getWorkspaceIndexSize() throws QuotaManagerException
{
try
{
return SecurityHelper.doPrivilegedExceptionAction(new PrivilegedExceptionAction<Long>()
{
public Long run() throws Exception
{
Method getIndexDirMethod = SearchManager.class.getDeclaredMethod("getIndexDirectory");
getIndexDirMethod.setAccessible(true);
// get all instances, since for system workspace we have 2 SearchManager
List<SearchManager> searchers = wsContainer.getComponentInstancesOfType(SearchManager.class);
long size = 0;
for (SearchManager searchManager : searchers)
{
File indexDir = (File)getIndexDirMethod.invoke(searchManager);
size += DirectoryHelper.getSize(indexDir);
}
return size;
}
});
}
catch (PrivilegedActionException e)
{
throw new QuotaManagerException(e.getMessage(), e);
}
}
/**
* Calculates node data size by asking directly respective {@link WorkspacePersistentDataManager}.
*/
public long getNodeDataSizeDirectly(final String nodePath) throws QuotaManagerException
{
try
{
return SecurityHelper.doPrivilegedExceptionAction(new PrivilegedExceptionAction<Long>()
{
public Long run() throws Exception
{
if (nodePath.equals(JCRPath.ROOT_PATH))
{
return getWorkspaceDataSizeDirectly();
}
return calculateNodeDataSizeTool.getNodeDataSizeDirectly(nodePath);
}
});
}
catch (PrivilegedActionException e)
{
throw new QuotaManagerException(e.getMessage(), e);
}
}
/**
* Calculate workspace data size by asking directly respective {@link WorkspacePersistentDataManager}.
*/
public long getWorkspaceDataSizeDirectly() throws QuotaManagerException
{
try
{
return dataManager.getWorkspaceDataSize();
}
catch (RepositoryException e)
{
throw new QuotaManagerException(e.getMessage(), e);
}
}
/**
* {@inheritDoc}
*/
public void start()
{
boolean isCoordinator;
try
{
isCoordinator = rpcService.isCoordinator();
}
catch (RPCException e)
{
throw new IllegalStateException(e.getMessage(), e);
}
if (isCoordinator)
{
try
{
quotaPersister.getWorkspaceDataSize(rName, wsName);
}
catch (UnknownDataSizeException e)
{
calculateWorkspaceDataSize();
}
}
}
/**
* {@inheritDoc}
*/
public void stop()
{
executor.shutdownNow();
repositoryQuotaManager.unregisterWorkspaceQuotaManager(wsName);
changesListener.destroy();
dataManager.removeItemPersistenceListener(changesListener);
}
/**
* Getter for {@link #context}.
*/
protected WorkspaceQuotaContext getContext()
{
return context;
}
// ======================================> Backup & Suspend
/**
* {@inheritDoc}
*/
public void backup(File storageDir) throws BackupException
{
WorkspaceQuotaRestore wqr = new WorkspaceQuotaRestore(this, storageDir);
wqr.backup();
}
/**
* {@inheritDoc}
*/
public void clean() throws BackupException
{
File storageDir = new File(PrivilegedSystemHelper.getProperty("java.io.tmpdir"));
WorkspaceQuotaRestore wqr = new WorkspaceQuotaRestore(this, storageDir);
wqr.clean();
}
/**
* {@inheritDoc}
*/
public DataRestore getDataRestorer(DataRestoreContext context) throws BackupException
{
return new WorkspaceQuotaRestore(this, context);
}
/**
* {@inheritDoc}
*/
public void suspend() throws SuspendException
{
executor.suspend();
isSuspended.set(true);
}
/**
* {@inheritDoc}
*/
public void resume() throws ResumeException
{
executor.resume();
isSuspended.set(false);
}
/**
* {@inheritDoc}
*/
public boolean isSuspended()
{
return isSuspended.get();
}
/**
* {@inheritDoc}
*/
public int getPriority()
{
return Suspendable.PRIORITY_LOW;
}
/**
*
* @throws QuotaManagerException
*/
protected void pushAllChangesToCoordinator() throws QuotaManagerException
{
try
{
changesListener.pushAllChangesToCoordinator();
}
catch (SecurityException e)
{
throw new QuotaManagerException("Can't push changes to coordinator", e);
}
catch (RPCException e)
{
throw new QuotaManagerException("Can't push changes to coordinator", e);
}
}
/**
* Calculates and accumulates workspace data size.
*/
private void calculateWorkspaceDataSize()
{
long dataSize;
try
{
dataSize = getWorkspaceDataSizeDirectly();
}
catch (QuotaManagerException e1)
{
throw new IllegalStateException("Can't calculate workspace data size", e1);
}
ChangesItem changesItem = new ChangesItem();
changesItem.updateWorkspaceChangedSize(dataSize);
Runnable task = new ApplyPersistedChangesTask(context, changesItem);
task.run();
}
}