/*
* $Id$
*
* Copyright 2007 Glencoe Software, Inc. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package ome.logic;
import java.util.List;
import ome.annotations.RolesAllowed;
import ome.api.IRepositoryInfo;
import ome.api.ServiceInterface;
import ome.conditions.InternalException;
import ome.conditions.ResourceError;
import ome.io.nio.OriginalFilesService;
import ome.io.nio.PixelsService;
import ome.io.nio.ThumbnailService;
import ome.tools.RepositoryTask;
import ome.util.SqlAction;
import org.apache.commons.io.FileSystemUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
/**
* Class implementation of the IRepositoryInfo service interface.
* <p>
* Stateless ome.logic to determine disk space utilization at the server's data
* image mount point, e.g. /OMERO See source code documentation for more.
* <p>
* Copyright 2007 Glencoe Software Inc. All rights reserved. Use is subject to
* license terms supplied in LICENSE.txt
* <p/>
*
* @author David L. Whitehurst <a
* href="mailto:david@glencoesoftware.com">david@glencoesoftware.com</a>
* @version $Revision$
* @since 3.0
* @see IRepositoryInfo
*/
@Transactional
public class RepositoryInfoImpl extends AbstractLevel2Service implements
IRepositoryInfo {
/**
* Time (2 minutes) between successive calls to
* {@link #sanityCheckRepository()} needed to trigger an actual call to
* {@link #getUsageFraction()}
*/
public final static long INITIAL_DELAY = 2 * 60 * 1000L;
/**
* Percentage (100.0 - 0.0) of disk use which will cause an exception during
* {@link #sanityCheckRepository()}
*/
public final static double CRITICAL_USAGE = 95.0;
/* The logger for this class. */
private transient static Logger log = LoggerFactory
.getLogger(RepositoryInfoImpl.class);
/* repository filesystem */
private transient String datadir;
/* The ROMIO thumbnail service. */
private transient ThumbnailService thumbnailService;
/* The ROMIO pixels service. */
private transient PixelsService pixelsService;
/* The ROMIO file service. */
private transient OriginalFilesService fileService;
/* JDBC operations for removedUnusedFiles */
private transient SqlAction sql;
// Static state
// =========================================================================
/**
* Time of the last true {@link #sanityCheckRepository()}
*/
private volatile long lastCheck = 0;
/**
* Cached value from the last true {@link #sanityCheckRepository()}
*/
private volatile double lastUsage = 0.0;
/**
* Lock-object around the call to the true {@link #sanityCheckRepository()}.
* Should be used to protected all sections of code which change
* {@link #lastCheck} and {@link #lastUsage}
*/
private final Object lastLock = new Object();
private static final String DEPRECATED =
"This UNSAFE method has been deprecated. Server side functionality " +
"has been REMOVED.";
/**
* Bean injection setter for ROMIO thumbnail service
* @param thumbnailService the thumbnail service
*/
public void setThumbnailService(ThumbnailService thumbnailService) {
getBeanHelper().throwIfAlreadySet(this.thumbnailService,
thumbnailService);
this.thumbnailService = thumbnailService;
}
/**
* Bean injection setter for ROMIO pixels service
* @param pixelsService the pixels service
*/
public void setPixelsService(PixelsService pixelsService) {
getBeanHelper().throwIfAlreadySet(this.pixelsService, pixelsService);
this.pixelsService = pixelsService;
}
/**
* Bean injection setter for ROMIO file service
* @param fileService the raw file service
*/
public void setFileService(OriginalFilesService fileService) {
getBeanHelper().throwIfAlreadySet(this.fileService, fileService);
this.fileService = fileService;
}
/**
* Bean injection setter for SQL operations
* @param sql the SQL action instance
*/
public void setSqlAction(SqlAction sql) {
getBeanHelper().throwIfAlreadySet(this.sql, sql);
this.sql = sql;
}
/*
* (non-Javadoc)
*
* @see ome.api.IRepositoryInfo#getFreeSpaceInKilobytes()
*/
@RolesAllowed("user")
public long getFreeSpaceInKilobytes() {
long result = 0L;
try {
result = FileSystemUtils.freeSpaceKb(datadir);
if (log.isInfoEnabled()) {
log.info("Total kilobytes free: " + result);
}
} catch (Throwable t) {
log.error("Error retrieving usage in KB.", t);
throw new ResourceError(t.getMessage());
}
return result;
}
/*
* (non-Javadoc)
*
* @see ome.api.IRepositoryInfo#getUsedSpaceInKilobytes()
*/
@RolesAllowed("user")
@Deprecated
public long getUsedSpaceInKilobytes() {
throw new InternalException(DEPRECATED);
}
/*
* (non-Javadoc)
*
* @see ome.api.IRepositoryInfo#getUsageFraction()
*/
@RolesAllowed("user")
@Deprecated
public double getUsageFraction() {
throw new InternalException(DEPRECATED);
}
/*
* (non-Javadoc)
*
* @see ome.logic.AbstractBean#getServiceInterface()
*/
public final Class<? extends ServiceInterface> getServiceInterface() {
return IRepositoryInfo.class;
}
/**
* Bean injection setter for data repository directory
* @param datadir the data repository directory
*/
public void setDatadir(String datadir) {
this.datadir = datadir;
}
/**
* Calculates based on the cached usage and the
* elapsed time whether or not a real
* {@link #sanityCheckRepository()} should be calculated.
* @return if the repository needs a sanity check
*/
public boolean needsSanityCheck() {
// Disabled in 4.0. See ticket:1230
// --------------------------------
// The getUsage() information is not being properly calculated
// and doing so in Java 1.5 requires undue hoop jumping.
if (true) {
return false;
}
long time = System.currentTimeMillis();
long elapsed = time - lastCheck;
// If the usage is within 5% of the critical usage
// return true regardless
if (lastUsage > CRITICAL_USAGE - 5.0) {
return true;
}
// Otherwise, scale the initial delay down based on how high our
// usage is, and if we've passed that value, then return true.
// For example, if the initial delay 2 minutes and our previous usage is
// 80 percent, then 100 - 80 / 100 = .2 * 2 minute = 24 seconds.
double scale = (100 - lastUsage) / 100;
if (elapsed > scale * INITIAL_DELAY) {
return true;
}
return false;
}
/**
* @see ome.api.IRepositoryInfo#sanityCheckRepository()
*/
@RolesAllowed("user")
public void sanityCheckRepository() throws InternalException {
if (needsSanityCheck()) {
synchronized (lastLock) {
// Check the time again, in case another thread
// updated the values.
if (needsSanityCheck()) {
try {
lastUsage = getUsageFraction() * 100;
} catch (Throwable t) {
log.error(
"Critical failure sanity checking repository.",
t);
throw new InternalException(
"Error in sanityCheckRepository(): "
+ t.getMessage());
}
lastCheck = System.currentTimeMillis();
}
}
}
if (lastUsage > CRITICAL_USAGE) {
throw new ResourceError(String.format(
"Server repository disk space usage (%s%%) exceeds %s%%",
lastUsage, CRITICAL_USAGE));
}
}
/*
* (non-Javadoc)
*
* @see ome.api.IRepository#removeUnusedFiles()
*/
@RolesAllowed("user")
public void removeUnusedFiles() {
RepositoryTask task = new RepositoryTask(sql);
// get ids for any objects marked as deleted
List<Long> files = task.getFileIds();
List<Long> pixels = task.getPixelIds();
List<Long> thumbs = task.getThumbnailIds();
// cleanup any files
if (files != null && files.size() > 0) {
log.info("Removing files: " + files);
fileService.removeFiles(files);
}
// cleanup any pixels
if (pixels != null && pixels.size() > 0) {
log.info("Removing pixels: " + pixels);
pixelsService.removePixels(pixels);
}
// cleanup any thumbnails
if (thumbs != null && thumbs.size() > 0) {
log.info("Removing thumbnails: " + thumbs);
thumbnailService.removeThumbnails(thumbs);
}
}
}