package tap.resource;
/*
* This file is part of TAPLibrary.
*
* TAPLibrary 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 3 of the License, or
* (at your option) any later version.
*
* TAPLibrary 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 TAPLibrary. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2012-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
* Astronomisches Rechen Institut (ARI)
*/
import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import tap.ServiceConnection;
import tap.TAPException;
import tap.TAPJob;
import uws.UWSException;
import uws.job.JobList;
import uws.job.UWSJob;
import uws.job.manager.AbstractQueuedExecutionManager;
import uws.job.manager.QueuedExecutionManager;
import uws.service.UWSService;
import uws.service.backup.UWSBackupManager;
import uws.service.log.UWSLog;
import uws.service.log.UWSLog.LogLevel;
/**
* <p>Asynchronous resource of a TAP service.</p>
*
* <p>
* Requests sent to this resource are ADQL queries (plus some execution parameters) to execute asynchronously.
* Results and/or errors of the execution are stored on the server side and can be fetched by the user whenever he wants.
* </p>
*
* <p>
* This resource is actually another VO service: a UWS (Universal Worker Service pattern).
* That's why all requests sent to this resource are actually forwarded to an instance of {@link UWSService}.
* All the behavior of UWS described by the IVOA is already fully implemented by this implementation.
* </p>
*
* <p>This resource is also representing the only jobs' list of this UWS service.</p>
*
* <p>The UWS service is created and configured at the creation of this resource. Here are the list of the most important configured elements:</p>
* <ul>
* <li><b>User identification:</b> the user identifier is the same as the one used by the TAP service. It is provided by the given {@link ServiceConnection}.</li>
* <li><b>Jobs' lists:</b> the /async resource of TAP contains only one jobs' list. Its name is "async" and is accessed directly when requesting the /async resource.</li>
* <li><b>Job execution management:</b> an execution manager is created at the creation of this resource. It is queuing jobs when a maximum number of asynchronous jobs
* is already running. This maximum is provided by the TAP service description: {@link ServiceConnection#getNbMaxAsyncJobs()}. Jobs are also queued if no more DB
* connection is available ; when connection(s) will be available, this resource will be notified by {@link #freeConnectionAvailable()} so that the execution manager
* can be refreshed.</li>
* <li><b>Backup and Restoration:</b> UWS jobs can be saved at any defined moment. It is particularly useful when an grave error occurs and merely when the service must be restarted.
* Then, at the creation of this resource, the jobs are restored. Thus, the restart has been transparent for the users: they did not lose any job
* (except those at the origin of the grave error maybe).</li>
* <li><b>Error logging:</b> the created {@link UWSService} instance is using the same logger as the TAP service. It is also provided by the given {@link ServiceConnection} object at creation.</li>
* </ul>
*
* @author Grégory Mantelet (CDS;ARI)
* @version 2.0 (04/2015)
*
* @see UWSService
*/
public class ASync implements TAPResource {
/** Name of this TAP resource. */
public static final String RESOURCE_NAME = "async";
/** Description of the TAP service owning this resource. */
protected final ServiceConnection service;
/** UWS service represented by this TAP resource. */
protected final UWSService uws;
/** The only jobs' list managed by the inner UWS service. This resource represent the UWS but also this jobs' list. */
protected final JobList jobList;
/**
* Build an Asynchronous Resource of a TAP service.
*
* @param service Description of the TAP service which will own this resource.
*
* @throws TAPException If any error occurs while creating a UWS service or its backup manager.
* @throws UWSException If any error occurs while setting a new execution manager to the recent inner UWS service,
* or while restoring a UWS backup.
*/
public ASync(final ServiceConnection service) throws UWSException, TAPException{
this.service = service;
uws = service.getFactory().createUWS();
if (uws.getUserIdentifier() == null)
uws.setUserIdentifier(service.getUserIdentifier());
if (uws.getJobList(getName()) == null){
jobList = new JobList(getName());
uws.addJobList(jobList);
jobList.setExecutionManager(new AsyncExecutionManager(service.getLogger(), service.getNbMaxAsyncJobs()));
}else
jobList = uws.getJobList(getName());
if (uws.getBackupManager() == null)
uws.setBackupManager(service.getFactory().createUWSBackupManager(uws));
UWSBackupManager backupManager = uws.getBackupManager();
if (backupManager != null){
backupManager.setEnabled(false);
int[] report = uws.getBackupManager().restoreAll();
String errorMsg = null;
if (report == null || report.length == 0)
errorMsg = "GRAVE error while the restoration of the asynchronous jobs!";
else if (report.length < 4)
errorMsg = "Incorrect restoration report format! => Impossible to know the restoration status!";
else if (report[0] != report[1])
errorMsg = "FAILED restoration of the asynchronous jobs: " + report[0] + " on " + report[1] + " restored!";
else
backupManager.setEnabled(true);
if (errorMsg != null){
errorMsg += " => Backup disabled.";
service.getLogger().logTAP(LogLevel.FATAL, null, "ASYNC_INIT", errorMsg, null);
throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, errorMsg);
}
}
}
/**
* <p>Notify this TAP resource that free DB connection(s) is(are) now available.
* It means that the execution manager should be refreshed in order to execute one or more queued jobs.</p>
*
* <p><i>Note:
* This function has no effect if there is no execution manager.
* </i></p>
*/
public void freeConnectionAvailable(){
if (jobList.getExecutionManager() != null)
jobList.getExecutionManager().refresh();
}
@Override
public String getName(){
return RESOURCE_NAME;
}
@Override
public void setTAPBaseURL(final String baseURL){
;
}
/**
* Get the UWS behind this TAP resource.
*
* @return The inner UWS used by this TAP resource.
*/
public final UWSService getUWS(){
return uws;
}
@Override
public void init(final ServletConfig config) throws ServletException{
;
}
@Override
public void destroy(){
if (uws != null)
uws.destroy();
}
@Override
public boolean executeResource(final HttpServletRequest request, final HttpServletResponse response) throws IOException, TAPException{
try{
// Ensure the service is currently available:
if (!service.isAvailable())
throw new TAPException("Can not execute a query: this TAP service is not available! " + service.getAvailability(), UWSException.SERVICE_UNAVAILABLE);
// Forward the request to the UWS service:
return uws.executeRequest(request, response);
}catch(UWSException ue){
service.getLogger().logTAP(LogLevel.FATAL, null, null, "Error while executing the /async resource.", ue);
throw new TAPException(ue);
}
}
/**
* An execution manager which queues jobs when too many asynchronous jobs are running or
* when no more DB connection is available for the moment.
*
* @author Grégory Mantelet (CDS;ARI)
* @version 2.0 (02/2015)
* @since 2.0
*/
private class AsyncExecutionManager extends AbstractQueuedExecutionManager {
/** The maximum number of running jobs. */
protected int nbMaxRunningJobs = QueuedExecutionManager.NO_QUEUE;
/**
* Build a queuing execution manager.
*
* @param logger Logger to use.
* @param maxRunningJobs Maximum number of asynchronous jobs that can run in the same time.
*/
public AsyncExecutionManager(UWSLog logger, int maxRunningJobs){
super(logger);
nbMaxRunningJobs = (maxRunningJobs <= 0) ? QueuedExecutionManager.NO_QUEUE : maxRunningJobs;
}
@Override
public boolean isReadyForExecution(final UWSJob jobToExecute){
if (!hasQueue())
return ((TAPJob)jobToExecute).isReadyForExecution();
else
return (runningJobs.size() < nbMaxRunningJobs) && ((TAPJob)jobToExecute).isReadyForExecution();
}
}
}