package uws.service; /* * This file is part of UWSLibrary. * * UWSLibrary 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. * * UWSLibrary 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 UWSLibrary. If not, see <http://www.gnu.org/licenses/>. * * Copyright 2012-2016 - UDS/Centre de DonnĂ©es astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import uws.AcceptHeader; import uws.UWSException; import uws.UWSExceptionFactory; import uws.UWSToolBox; import uws.job.ErrorSummary; import uws.job.JobList; import uws.job.JobThread; import uws.job.Result; import uws.job.UWSJob; import uws.job.parameters.DestructionTimeController; import uws.job.parameters.DestructionTimeController.DateField; import uws.job.parameters.ExecutionDurationController; import uws.job.parameters.InputParamController; import uws.job.parameters.UWSParameters; import uws.job.serializer.JSONSerializer; import uws.job.serializer.UWSSerializer; import uws.job.serializer.XMLSerializer; import uws.job.user.JobOwner; import uws.service.actions.UWSAction; import uws.service.backup.UWSBackupManager; import uws.service.error.DefaultUWSErrorWriter; import uws.service.error.ServiceErrorWriter; import uws.service.file.LocalUWSFileManager; import uws.service.file.UWSFileManager; import uws.service.log.DefaultUWSLog; import uws.service.log.UWSLog; import uws.service.log.UWSLog.LogLevel; import uws.service.request.RequestParser; import uws.service.request.UWSRequestParser; import uws.service.request.UploadFile; /** * <p> * This servlet lets initialize and manage a web service implementing the UWS pattern. * That's to say all methods and functionalities of a UWS are already implemented. All you have to * do is to initialize and maybe to customize your UWS. * </p> * * <h3>UWS Definition</h3> * <p> * To create a such servlet, you have to extend this class. Once done, only two functions must be * implemented: {@link #createJob(HttpServletRequest, JobOwner)} and {@link #initUWS()}. * </p> * <p> * The first one will be called by the library each time a job must be created. All the job parameters * given by the user and the identity of the user are given in the arguments. You just have to return * an appropriate instance of a job using all these information. * </p> * <p> * {@link #initUWS()} must contain at least one line: the creation of a job list. For instance: * </p> * <code> * addJobList(new JobList<MyJob>("jlName")); * </code> * <p>The below code show an example of usage of this class:</p> * <pre> * public class MyUWSServlet extends UWSServlet { * * // Initialize the UWS service by creating at least one job list. * public void initUWS() throws UWSException { * addJobList(new JobList("jobList")); * } * * // Create the job process corresponding to the job to execute ; generally, the process identification can be merely done by checking the job list name. * public JobThread createJobThread(UWSJob job) throws UWSException { * if (job.getJobList().getName().equals("jobList")) * return new MyJobThread(job); * else * throw new UWSException("Impossible to create a job inside the jobs list \"" + job.getJobList().getName() + "\" !"); * } * } * </pre> * <p> * The name and the description of the UWS may be specified in the web.xml file as init-param of the servlet: * <code>name</code> and <code>description</code>. The other way is to directly set the corresponding * attributes: {@link #name} and {@link #description}. * </p> * * <p><i>Note: * If any error occurs while the initialization or the creation of a {@link UWSServlet} instance, a {@link ServletException} * will be thrown with a basic message dedicated to the service users. This basic and non-informative message is * obviously not intended to the administrator which will be able to get the reason of the failure * (with a stack trace when available) in the log files. * </i></p> * * <h3>UWS customization</h3> * <p> * As for the classic HTTP servlets, this servlet has one method for each method of the implemented protocol. * Thus, you have one function for the "add job" action, another one for the "get job list" action, ... * These functions are: * </p> * <ul> * <li>{@link #doAddJob(UWSUrl, HttpServletRequest, HttpServletResponse, JobOwner)}</li> * <li>{@link #doDestroyJob(UWSUrl, HttpServletRequest, HttpServletResponse, JobOwner)}</li> * <li>{@link #doGetJobParam(UWSUrl, HttpServletRequest, HttpServletResponse, JobOwner)}</li> * <li>{@link #doJobSummary(UWSUrl, HttpServletRequest, HttpServletResponse, JobOwner)}</li> * <li>{@link #doListJob(UWSUrl, HttpServletRequest, HttpServletResponse, JobOwner)}</li> * <li>{@link #doSetJobParam(UWSUrl, HttpServletRequest, HttpServletResponse, JobOwner)}</li> * </ul> * <p> * They are all already implemented following their definition in the IVOA document. However, * if needed, they can be overridden in order to do your own actions. * </p> * <p> * Besides, the classic HTTP servlet methods (i.e. {@link #doGet(HttpServletRequest, HttpServletResponse)}, {@link #doPost(HttpServletRequest, HttpServletResponse)}, ...) * are called normally if none of the UWS actions match to the received HTTP request. * So, they can be overridden as in any HTTP servlet. * </p> * * @author Grégory Mantelet (CDS;ARI) * @version 4.2 (02/2016) */ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory { private static final long serialVersionUID = 1L; /** Name of this UWS. */ protected String name = null; /** Description of this UWS. */ protected String description = null; /** List of all managed jobs lists. <i>(it is a LinkedHashMap so that jobs lists are ordered by insertion)</i> */ private Map<String,JobList> mapJobLists; /** List of available serializers. */ private Map<String,UWSSerializer> serializers; /** The MIME type of the default serialization format. */ protected String defaultSerializer = null; /** The object to use to extract the user ID from the received request. */ protected UserIdentifier userIdentifier = null; /** The "interpreter" of UWS URLs. */ private UWSUrl urlInterpreter = null; /** List of all expected additional parameters. */ protected final ArrayList<String> expectedAdditionalParams = new ArrayList<String>(10); /** List the controllers of all the input parameters. See {@link UWSParameters} and {@link InputParamController} for more details. */ protected final HashMap<String,InputParamController> inputParamControllers = new HashMap<String,InputParamController>(10); /** Lets managing all UWS files (i.e. log, result, backup, ...). */ private UWSFileManager fileManager = null; /** Lets saving and/or restoring the whole UWS. */ protected UWSBackupManager backupManager; /** Lets logging info/debug/warnings/errors about this UWS. */ protected UWSLog logger; /** Lets extract all parameters from an HTTP request, whatever is its content-type. * @since 4.1*/ protected RequestParser requestParser; /** Lets writing/formatting any exception/throwable in a HttpServletResponse. */ protected ServiceErrorWriter errorWriter; @Override public final void init(ServletConfig config) throws ServletException{ super.init(config); } @Override public final void init() throws ServletException{ final String INIT_ERROR_MSG = "UWS initialization ERROR! Contact the administrator of the service to figure out the failure."; // Set the general information about this UWS: name = getServletConfig().getInitParameter("name"); description = getServletConfig().getInitParameter("description"); // Set the file manager to use: try{ fileManager = createFileManager(); if (fileManager == null) throw new ServletException(INIT_ERROR_MSG); }catch(UWSException ue){ throw new ServletException(INIT_ERROR_MSG, ue); } // Set the logger: logger = new DefaultUWSLog(this); errorWriter = new DefaultUWSErrorWriter(logger); // Set the request parser: try{ requestParser = createRequestParser(fileManager); }catch(UWSException ue){ logger.logUWS(LogLevel.FATAL, null, "INIT", "Can't create a request parser!", ue); throw new ServletException(INIT_ERROR_MSG, ue); } // Initialize the list of jobs: mapJobLists = new LinkedHashMap<String,JobList>(); // Initialize the list of available serializers: serializers = new HashMap<String,UWSSerializer>(); addSerializer(new XMLSerializer()); addSerializer(new JSONSerializer()); try{ // Initialize the service: initUWS(); // Log the successful initialization: logger.logUWS(LogLevel.INFO, this, "INIT", "UWS successfully initialized.", null); }catch(UWSException ue){ logger.logUWS(LogLevel.FATAL, null, "INIT", "Can't execute the custom initialization of this UWS service (UWSServlet.initUWS())!", ue); throw new ServletException(INIT_ERROR_MSG); } } public abstract void initUWS() throws UWSException; @Override public void destroy(){ // Backup all jobs: /* Jobs are backuped now so that running jobs are set back to the PENDING phase in the backup. * Indeed, the "stopAll" operation of the ExecutionManager may fail and would set the phase to ERROR * for the wrong reason. */ if (backupManager != null){ // save all jobs: backupManager.setEnabled(true); backupManager.saveAll(); // stop the automatic backup, if there is one: backupManager.setEnabled(false); } // Stop all jobs and stop watching for the jobs' destruction: for(JobList jl : mapJobLists.values()){ jl.getExecutionManager().stopAll(); jl.getDestructionManager().stop(); } // Just in case that previous clean "stop"s did not work, try again an interruption for all running threads: /* note: timers are not part of this ThreadGroup and so, they won't be affected by this function call. */ JobThread.tg.interrupt(); // Log the service is stopped: if (logger != null) logger.logUWS(LogLevel.INFO, this, "STOP", "UWS Service \"" + getName() + "\" stopped!", null); // Default destroy function: super.destroy(); } public UWSFileManager createFileManager() throws UWSException{ UWSFileManager fm = null; String rootPath = getServletConfig().getInitParameter("rootDirectory"); if (rootPath == null || rootPath.trim().isEmpty()) rootPath = ((name != null && !name.trim().isEmpty()) ? name.replaceAll("/", "_") : "") + "_files"; if (rootPath.startsWith("/")) fm = new LocalUWSFileManager(new File(rootPath)); else fm = new LocalUWSFileManager(new File(getServletContext().getRealPath("/" + rootPath))); return fm; } @Override public UWSFileManager getFileManager(){ return fileManager; } @Override public RequestParser getRequestParser(){ return requestParser; } @Override public final void service(ServletRequest req, ServletResponse resp) throws ServletException, IOException{ super.service(req, resp); } protected static String lastRequestID = null; protected synchronized String generateRequestID(final HttpServletRequest request){ String id; do{ id = System.currentTimeMillis() + ""; }while(lastRequestID != null && lastRequestID.startsWith(id)); lastRequestID = id; return id; } @Override protected final void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ String uwsAction = null; JobOwner user = null; // Generate a unique ID for this request execution (for log purpose only): final String reqID = generateRequestID(req); req.setAttribute(UWS.REQ_ATTRIBUTE_ID, reqID); // Extract all parameters: try{ req.setAttribute(UWS.REQ_ATTRIBUTE_PARAMETERS, requestParser.parse(req)); }catch(UWSException ue){ logger.log(LogLevel.WARNING, "REQUEST_PARSER", "Can not extract the HTTP request parameters!", ue); } // Log the reception of the request: logger.logHttp(LogLevel.INFO, req, reqID, null, null); try{ String method = req.getMethod(); // Create a URL interpreter if needed: if (urlInterpreter == null) setUrlInterpreter(new UWSUrl(req)); // Update the UWS URL interpreter: UWSUrl requestUrl = new UWSUrl(this.urlInterpreter); requestUrl.load(req); // Identify the user: user = UWSToolBox.getUser(req, userIdentifier); // Set the character encoding: resp.setCharacterEncoding(UWSToolBox.DEFAULT_CHAR_ENCODING); // METHOD GET: if (method.equals("GET")){ // HOME PAGE: if (!requestUrl.hasJobList()){ uwsAction = UWSAction.HOME_PAGE; writeHomePage(requestUrl, req, resp, user); }// LIST JOBS: else if (requestUrl.hasJobList() && !requestUrl.hasJob()){ uwsAction = UWSAction.LIST_JOBS; doListJob(requestUrl, req, resp, user); }// JOB SUMMARY: else if (requestUrl.hasJobList() && requestUrl.hasJob() && !requestUrl.hasAttribute()){ uwsAction = UWSAction.JOB_SUMMARY; doJobSummary(requestUrl, req, resp, user); }// GET JOB PARAMETER: else if (requestUrl.hasJobList() && requestUrl.hasJobList() && requestUrl.hasAttribute()){ uwsAction = UWSAction.GET_JOB_PARAM; doGetJobParam(requestUrl, req, resp, user); }else throw new UWSException(UWSException.NOT_IMPLEMENTED, "Unknown UWS action!"); }// METHOD POST: else if (method.equals("POST")){ // HOME PAGE: if (!requestUrl.hasJobList()){ uwsAction = UWSAction.HOME_PAGE; writeHomePage(requestUrl, req, resp, user); }// ADD JOB: else if (requestUrl.hasJobList() && !requestUrl.hasJob()){ uwsAction = UWSAction.ADD_JOB; doAddJob(requestUrl, req, resp, user); }// SET JOB's UWS STANDARD PARAMETER else if (requestUrl.hasJobList() && requestUrl.hasJob() && requestUrl.getAttributes().length == 1 && requestUrl.getAttributes()[0].toLowerCase().matches(UWSParameters.UWS_RW_PARAMETERS_REGEXP) && UWSToolBox.hasParameter(requestUrl.getAttributes()[0], req, false)){ uwsAction = UWSAction.SET_UWS_PARAMETER; doSetUWSParameter(requestUrl, req, resp, user); }// DESTROY JOB: else if (requestUrl.hasJobList() && requestUrl.hasJob() && UWSToolBox.hasParameter(UWSJob.PARAM_ACTION, UWSJob.ACTION_DELETE, req, false)){ uwsAction = UWSAction.DESTROY_JOB; doDestroyJob(requestUrl, req, resp, user); }// SET JOB PARAMETER: else if (requestUrl.hasJobList() && requestUrl.hasJob() && (!requestUrl.hasAttribute() || requestUrl.getAttributes().length == 1 && requestUrl.getAttributes()[0].equalsIgnoreCase(UWSJob.PARAM_PARAMETERS)) && UWSToolBox.getNbParameters(req) > 0){ uwsAction = UWSAction.SET_JOB_PARAM; doSetJobParam(requestUrl, req, resp, user); }else throw new UWSException(UWSException.NOT_IMPLEMENTED, "Unknown UWS action!"); }// METHOD PUT: else if (method.equals("PUT")){ // SET JOB PARAMETER: if (requestUrl.hasJobList() && requestUrl.hasJob() && requestUrl.getAttributes().length >= 2 && requestUrl.getAttributes()[0].equalsIgnoreCase(UWSJob.PARAM_PARAMETERS)){ uwsAction = UWSAction.SET_JOB_PARAM; if (!UWSToolBox.hasParameter(requestUrl.getAttributes()[1], req, false)) throw new UWSException(UWSException.BAD_REQUEST, "Wrong parameter name in the PUT request! Expected: " + requestUrl.getAttributes()[1]); doSetJobParam(requestUrl, req, resp, user); }// SET JOB's UWS STANDARD PARAMETER else if (requestUrl.hasJobList() && requestUrl.hasJob() && requestUrl.getAttributes().length == 1 && requestUrl.getAttributes()[0].toLowerCase().matches(UWSParameters.UWS_RW_PARAMETERS_REGEXP) && UWSToolBox.hasParameter(requestUrl.getAttributes()[0], req, false)){ uwsAction = UWSAction.SET_UWS_PARAMETER; doSetUWSParameter(requestUrl, req, resp, user); }else throw new UWSException(UWSException.NOT_IMPLEMENTED, "Unknown UWS action!"); }// METHOD DELETE: else if (method.equals("DELETE")){ // DESTROY JOB: if (requestUrl.hasJobList() && requestUrl.hasJob() && req.getMethod().equalsIgnoreCase("delete")){ uwsAction = UWSAction.DESTROY_JOB; doDestroyJob(requestUrl, req, resp, user); }else throw new UWSException(UWSException.NOT_IMPLEMENTED, "Unknown UWS action!"); }// ELSE ERROR: else throw new UWSException(UWSException.NOT_IMPLEMENTED, "Unknown UWS action!"); resp.flushBuffer(); // Log the successful execution of the action: logger.logHttp(LogLevel.INFO, resp, reqID, user, "UWS action \"" + uwsAction + "\" successfully executed.", null); }catch(IOException ioe){ /* * Any IOException thrown while writing the HTTP response is generally caused by a client abortion (intentional or timeout) * or by a connection closed with the client for another reason. * Consequently, a such error should not be considered as a real error from the server or the library: the request is * canceled, and so the response is not expected. It is anyway not possible any more to send it (header and/or body) totally * or partially. * Nothing can solve this error. So the "error" is just reported as a simple information and theoretically the action * executed when this error has been thrown is already stopped. */ logger.logHttp(LogLevel.INFO, resp, reqID, user, "HTTP request aborted or connection with the client closed => the UWS action \"" + uwsAction + "\" has stopped and the body of the HTTP response can not have been partially or completely written!", null); }catch(UWSException ex){ /* * Any known/"expected" UWS exception is logged but also returned to the HTTP client in an error document. * Since the error is known, it is supposed to have already been logged with a full stack trace. Thus, there * is no need to log again its stack trace...just its message is logged. * Besides, this error may also be just a redirection and not a true error. In such case, the error message * is not logged. */ sendError(ex, req, reqID, user, uwsAction, resp); }catch(IllegalStateException ise){ /* * Any IllegalStateException that reaches this point, is supposed coming from a HttpServletResponse operation which * has to reset the response buffer (e.g. resetBuffer(), sendRedirect(), sendError()). * If this exception happens, the library tried to rewrite the HTTP response body with a message or a result, * while this body has already been partially sent to the client. It is then no longer possible to change its content. * Consequently, the error is logged as FATAL and a message will be appended at the end of the already submitted response * to alert the HTTP client that an error occurs and the response should not be considered as complete and reliable. */ // Write the error in the response and return the appropriate HTTP status code: errorWriter.writeError(ise, resp, req, reqID, user, uwsAction); // Log the error: getLogger().logHttp(LogLevel.FATAL, resp, reqID, user, "HTTP response already partially committed => the UWS action \"" + uwsAction + "\" has stopped and the body of the HTTP response can not have been partially or completely written!", (ise.getCause() != null) ? ise.getCause() : ise); }catch(Throwable t){ /* * Any other error is considered as unexpected if it reaches this point. Consequently, it has not yet been logged. * So its stack trace will be fully logged, and an appropriate message will be returned to the HTTP client. The * returned document should contain not too technical information which would be useless for the user. */ sendError(t, req, reqID, user, uwsAction, resp); }finally{ // Free resources about uploaded files ; only unused files will be deleted: UWSToolBox.deleteUploads(req); } } /* *********** */ /* UWS ACTIONS */ /* *********** */ protected void writeHomePage(UWSUrl requestUrl, HttpServletRequest req, HttpServletResponse resp, JobOwner user) throws UWSException, ServletException, IOException{ UWSSerializer serializer = getSerializer(req.getHeader("Accept")); resp.setContentType(serializer.getMimeType()); resp.setCharacterEncoding(UWSToolBox.DEFAULT_CHAR_ENCODING); String serialization; try{ serialization = serializer.getUWS(this); }catch(Exception e){ if (!(e instanceof UWSException)){ getLogger().logUWS(LogLevel.ERROR, requestUrl, "SERIALIZE", "Can't display the default home page, due to a serialization error!", e); throw new UWSException(UWSException.NO_CONTENT, e, "No home page available for this UWS service!"); }else throw (UWSException)e; } if (serialization != null){ PrintWriter output = resp.getWriter(); output.print(serialization); output.flush(); }else throw new UWSException(UWSException.NO_CONTENT, "No home page available for this UWS service."); } protected void doListJob(UWSUrl requestUrl, HttpServletRequest req, HttpServletResponse resp, JobOwner user) throws UWSException, ServletException, IOException{ // Get the jobs list: JobList jobsList = getJobList(requestUrl.getJobListName()); // Write the jobs list: UWSSerializer serializer = getSerializer(req.getHeader("Accept")); resp.setContentType(serializer.getMimeType()); resp.setCharacterEncoding(UWSToolBox.DEFAULT_CHAR_ENCODING); try{ jobsList.serialize(resp.getOutputStream(), serializer, user); }catch(Exception e){ if (!(e instanceof UWSException)){ getLogger().logUWS(LogLevel.ERROR, requestUrl, "SERIALIZE", "Can not serialize the jobs list \"" + jobsList.getName() + "\"!", e); throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, e, "Can not format properly the jobs list \"" + jobsList.getName() + "\"!"); }else throw (UWSException)e; } } protected void doAddJob(UWSUrl requestUrl, HttpServletRequest req, HttpServletResponse resp, JobOwner user) throws UWSException, ServletException, IOException{ // Get the jobs list: JobList jobsList = getJobList(requestUrl.getJobListName()); // Forbids the job creation if the user has not the WRITE permission for the specified jobs list: if (user != null && !user.hasWritePermission(jobsList)) throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.writePermissionDenied(user, true, jobsList.getName())); // Create the job: UWSJob newJob = createJob(req, user); // Add it to the jobs list: if (jobsList.addNewJob(newJob) != null){ // Start the job if the phase parameter was provided with the "RUN" value: if (UWSToolBox.hasParameter(UWSJob.PARAM_PHASE, UWSJob.PHASE_RUN, req, false)) newJob.start(); // Make a redirection to the added job: redirect(requestUrl.jobSummary(jobsList.getName(), newJob.getJobId()).getRequestURL(), req, user, UWSAction.ADD_JOB, resp); }else throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Unable to add the new job " + newJob.getJobId() + " to the jobs list " + jobsList.getName() + ". (ID already used = " + (jobsList.getJob(newJob.getJobId()) != null) + ")"); } protected void doSetUWSParameter(UWSUrl requestUrl, HttpServletRequest req, HttpServletResponse resp, JobOwner user) throws UWSException, ServletException, IOException{ // Get the job: UWSJob job = getJob(requestUrl); // Forbids the action if the user has not the WRITE permission for the specified job: if (user != null && !user.hasWritePermission(job)) throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.writePermissionDenied(user, true, job.getJobId())); String name = requestUrl.getAttributes()[0]; job.addOrUpdateParameter(name, UWSToolBox.getParameter(name, req, false), user); // Make a redirection to the job: redirect(requestUrl.jobSummary(requestUrl.getJobListName(), job.getJobId()).getRequestURL(), req, user, getName(), resp); } protected void doDestroyJob(UWSUrl requestUrl, HttpServletRequest req, HttpServletResponse resp, JobOwner user) throws UWSException, ServletException, IOException{ // Get the jobs list: JobList jobsList = getJobList(requestUrl.getJobListName()); // Destroy the job: try{ jobsList.destroyJob(requestUrl.getJobId(), user); }catch(UWSException ue){ getLogger().logUWS(LogLevel.ERROR, requestUrl, "DESTROY_JOB", "Can not destroy the job \"" + requestUrl.getJobId() + "\"!", ue); throw ue; } // Make a redirection to the jobs list: redirect(requestUrl.listJobs(jobsList.getName()).getRequestURL(), req, user, UWSAction.DESTROY_JOB, resp); } protected void doJobSummary(UWSUrl requestUrl, HttpServletRequest req, HttpServletResponse resp, JobOwner user) throws UWSException, ServletException, IOException{ // Get the job: UWSJob job = getJob(requestUrl); // Write the job summary: UWSSerializer serializer = getSerializer(req.getHeader("Accept")); resp.setContentType(serializer.getMimeType()); resp.setCharacterEncoding(UWSToolBox.DEFAULT_CHAR_ENCODING); try{ job.serialize(resp.getOutputStream(), serializer, user); }catch(Exception e){ if (!(e instanceof UWSException)){ getLogger().logUWS(LogLevel.ERROR, requestUrl, "SERIALIZE", "Can not serialize the job \"" + job.getJobId() + "\"!", e); throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, e, "Can not format properly the job \"" + job.getJobId() + "\"!"); }else throw (UWSException)e; } } protected void doGetJobParam(UWSUrl requestUrl, HttpServletRequest req, HttpServletResponse resp, JobOwner user) throws UWSException, ServletException, IOException{ // Get the job: UWSJob job = getJob(requestUrl, user); String[] attributes = requestUrl.getAttributes(); // RESULT CASE: Display the content of the selected result: if (attributes[0].equalsIgnoreCase(UWSJob.PARAM_RESULTS) && attributes.length > 1){ Result result = job.getResult(attributes[1]); if (result == null) throw new UWSException(UWSException.NOT_FOUND, "No result identified with \"" + attributes[1] + "\" in the job \"" + job.getJobId() + "\"!"); else if (result.getHref() != null && !result.getHref().trim().isEmpty() && !result.getHref().equalsIgnoreCase(req.getRequestURL().toString())) redirect(result.getHref(), req, user, UWSAction.GET_JOB_PARAM, resp); else{ InputStream input = null; try{ input = getFileManager().getResultInput(result, job); UWSToolBox.write(input, result.getMimeType(), result.getSize(), resp); }catch(IOException ioe){ getLogger().logUWS(LogLevel.ERROR, result, "GET_RESULT", "Can not read the content of the result \"" + result.getId() + "\" of the job \"" + job.getJobId() + "\"!", ioe); throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, ioe, "Can not read the content of the result " + result.getId() + " (job ID: " + job.getJobId() + ")."); }finally{ if (input != null) input.close(); } } }// ERROR DETAILS CASE: Display the full stack trace of the error: else if (attributes[0].equalsIgnoreCase(UWSJob.PARAM_ERROR_SUMMARY) && attributes.length > 1 && attributes[1].equalsIgnoreCase("details")){ ErrorSummary error = job.getErrorSummary(); if (error == null) throw new UWSException(UWSException.NOT_FOUND, "No error summary for the job \"" + job.getJobId() + "\"!"); else{ InputStream input = null; try{ input = getFileManager().getErrorInput(error, job); UWSToolBox.write(input, errorWriter.getErrorDetailsMIMEType(), getFileManager().getErrorSize(error, job), resp); }catch(IOException ioe){ getLogger().logUWS(LogLevel.ERROR, error, "GET_ERROR", "Can not read the details of the error summary of the job \"" + job.getJobId() + "\"!", ioe); throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, ioe, "Can not read the error details (job ID: " + job.getJobId() + ")."); }finally{ if (input != null) input.close(); } } }// REFERENCE FILE: Display the content of the uploaded file or redirect to the URL (if it is a URL): else if (attributes[0].equalsIgnoreCase(UWSJob.PARAM_PARAMETERS) && attributes.length > 1 && job.getAdditionalParameterValue(attributes[1]) != null && job.getAdditionalParameterValue(attributes[1]) instanceof UploadFile){ UploadFile upl = (UploadFile)job.getAdditionalParameterValue(attributes[1]); if (upl.getLocation().matches("^http(s)?://")) redirect(upl.getLocation(), req, user, getName(), resp); else{ InputStream input = null; try{ input = getFileManager().getUploadInput(upl); UWSToolBox.write(input, upl.mimeType, upl.length, resp); }catch(IOException ioe){ getLogger().logUWS(LogLevel.ERROR, upl, "GET_PARAMETER", "Can not read the content of the uploaded file \"" + upl.paramName + "\" of the job \"" + job.getJobId() + "\"!", ioe); throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, ioe, "Can not read the content of the uploaded file " + upl.paramName + " (job ID: " + job.getJobId() + ")."); }finally{ if (input != null) input.close(); } } }// DEFAULT CASE: Display the serialization of the selected UWS object: else{ // Write the value/content of the selected attribute: UWSSerializer serializer = getSerializer(req.getHeader("Accept")); String uwsField = attributes[0]; boolean jobSerialization = false; // Set the content type: if (uwsField == null || uwsField.trim().isEmpty() || (attributes.length <= 1 && (uwsField.equalsIgnoreCase(UWSJob.PARAM_ERROR_SUMMARY) || uwsField.equalsIgnoreCase(UWSJob.PARAM_RESULTS) || uwsField.equalsIgnoreCase(UWSJob.PARAM_PARAMETERS)))){ resp.setContentType(serializer.getMimeType()); jobSerialization = true; }else resp.setContentType("text/plain"); // Set the character encoding: resp.setCharacterEncoding(UWSToolBox.DEFAULT_CHAR_ENCODING); // Serialize the selected attribute: try{ job.serialize(resp.getOutputStream(), attributes, serializer); }catch(Exception e){ if (!(e instanceof UWSException)){ String errorMsgPart = (jobSerialization ? "the job \"" + job.getJobId() + "\"" : "the parameter " + uwsField + " of the job \"" + job.getJobId() + "\""); getLogger().logUWS(LogLevel.ERROR, requestUrl, "SERIALIZE", "Can not serialize " + errorMsgPart + "!", e); throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, e, "Can not format properly " + errorMsgPart + "!"); }else throw (UWSException)e; } } } protected void doSetJobParam(UWSUrl requestUrl, HttpServletRequest req, HttpServletResponse resp, JobOwner user) throws UWSException, ServletException, IOException{ // Get the job: UWSJob job = getJob(requestUrl); UWSParameters params = getFactory().createUWSParameters(req); // Update the job parameters: job.addOrUpdateParameters(params, user); // Make a redirection to the job: redirect(requestUrl.jobSummary(requestUrl.getJobListName(), job.getJobId()).getRequestURL(), req, user, UWSAction.SET_JOB_PARAM, resp); } public UWSJob getJob(UWSUrl requestUrl) throws UWSException{ return getJob(requestUrl, null); } public UWSJob getJob(UWSUrl requestUrl, JobOwner user) throws UWSException{ // Get the job ID: String jobId = requestUrl.getJobId(); UWSJob job = null; if (jobId != null){ // Get the jobs list: JobList jobsList = getJobList(requestUrl.getJobListName()); // Get the job: job = jobsList.getJob(jobId, user); if (job == null) throw new UWSException(UWSException.NOT_FOUND, "Incorrect job ID! The job \"" + jobId + "\" does not exist in the jobs list \"" + jobsList.getName() + "\"."); }else throw new UWSException(UWSException.BAD_REQUEST, "Missing job ID!"); return job; } /* ************ */ /* JOB CREATION */ /* ************ */ @Override public final UWSFactory getFactory(){ return this; } @Override public UWSJob createJob(HttpServletRequest request, JobOwner user) throws UWSException{ return new UWSJob(user, createUWSParameters(request)); } @Override public UWSJob createJob(final String jobID, final JobOwner owner, final UWSParameters params, final long quote, final long startTime, final long endTime, final List<Result> results, final ErrorSummary error) throws UWSException{ return new UWSJob(jobID, owner, params, quote, startTime, endTime, results, error); } @Override public UWSParameters createUWSParameters(final Map<String,Object> params) throws UWSException{ return new UWSParameters(params, expectedAdditionalParams, inputParamControllers); } @Override public UWSParameters createUWSParameters(final HttpServletRequest req) throws UWSException{ return new UWSParameters(req, expectedAdditionalParams, inputParamControllers); } @Override public RequestParser createRequestParser(final UWSFileManager fileManager) throws UWSException{ return new UWSRequestParser(fileManager); } /* ****************************** */ /* REDIRECTION & ERROR MANAGEMENT */ /* ****************************** */ /** * <p>Sends a redirection (with the HTTP status code 303) to the given URL/URI into the given response.</p> * * @param url The redirection URL/URI. * @param request The {@link HttpServletRequest} which may be used to make a redirection. * @param response The {@link HttpServletResponse} which must contain all information to make a redirection. * * @throws IOException If there is an error during the redirection. * @throws UWSException If there is any other error. */ public void redirect(String url, HttpServletRequest request, JobOwner user, String uwsAction, HttpServletResponse response) throws ServletException, IOException{ response.setStatus(HttpServletResponse.SC_SEE_OTHER); response.setContentType(request.getContentType()); response.setCharacterEncoding(UWSToolBox.DEFAULT_CHAR_ENCODING); response.setHeader("Location", url); response.flushBuffer(); } /** * <p> * Fills the response with the given error. The HTTP status code is set in function of the error code of the given UWSException. * If the error code is {@link UWSException#SEE_OTHER} this method calls {@link #redirect(String, HttpServletRequest, JobOwner, String, HttpServletResponse)}. * Otherwise the function {@link HttpServletResponse#sendError(int, String)} is called. * </p> * * @param error The error to send/display. * @param request The request which has caused the given error <i>(not used by default)</i>. * @param reqID ID of the request. * @param user The user which executes the given request. * @param uwsAction The UWS action corresponding to the given request. * @param response The response in which the error must be published. * * @throws IOException If there is an error when calling {@link #redirect(String, HttpServletRequest, JobOwner, String, HttpServletResponse)} or {@link HttpServletResponse#sendError(int, String)}. * @throws UWSException If there is an error when calling {@link #redirect(String, HttpServletRequest, JobOwner, String, HttpServletResponse)}. * * @see #redirect(String, HttpServletRequest, JobOwner, String, HttpServletResponse) * @see #sendError(Throwable, HttpServletRequest, String, JobOwner, String, HttpServletResponse) */ public final void sendError(UWSException error, HttpServletRequest request, String reqID, JobOwner user, String uwsAction, HttpServletResponse response) throws ServletException{ if (error.getHttpErrorCode() == UWSException.SEE_OTHER){ // Log the redirection, if any: logger.logHttp(LogLevel.INFO, response, reqID, user, "HTTP " + UWSException.SEE_OTHER + " [Redirection toward " + error.getMessage() + "] - Action \"" + uwsAction + "\" successfully executed.", null); // Apply the redirection: try{ redirect(error.getMessage(), request, user, uwsAction, response); }catch(IOException ioe){ logger.logHttp(LogLevel.FATAL, request, reqID, "Can not redirect the response toward " + error.getMessage(), error); throw new ServletException("Can not redirect the response! You should notify the administrator of the service (FATAL-" + reqID + "). However, while waiting a correction of this problem, you can manually go toward " + error.getMessage() + "."); } }else sendError((Exception)error, request, reqID, user, uwsAction, response); } /** * <p> * Fills the response with the given error. * The stack trace of the error is printed on the standard output and then the function * {@link HttpServletResponse#sendError(int, String)} is called with the HTTP status code is {@link UWSException#INTERNAL_SERVER_ERROR} * and the message of the given exception. * </p> * * * @param error The error to send/display. * @param request The request which has caused the given error <i>(not used by default)</i>. * @param reqID ID of the request. * @param user The user which executes the given request. * @param uwsAction The UWS action corresponding to the given request. * @param response The response in which the error must be published. * * @throws IOException If there is an error when calling {@link HttpServletResponse#sendError(int, String)}. * @throws UWSException * * @see ServiceErrorWriter#writeError(Throwable, HttpServletResponse, HttpServletRequest, String, JobOwner, String) */ public final void sendError(Throwable error, HttpServletRequest request, String reqID, JobOwner user, String uwsAction, HttpServletResponse response) throws ServletException{ // Write the error in the response and return the appropriate HTTP status code: errorWriter.writeError(error, response, request, reqID, user, uwsAction); // Log the error: logger.logHttp(LogLevel.ERROR, response, reqID, user, "Can not complete the UWS action \"" + uwsAction + "\", because: " + error.getMessage(), error); } /* ************** */ /* LOG MANAGEMENT */ /* ************** */ @Override public UWSLog getLogger(){ return logger; } /** * Gets the object used to write/format any error in a HttpServletResponse. * * @return The error writer/formatter. */ public final ServiceErrorWriter getErrorWriter(){ return errorWriter; } /** * <p>Sets the object used to write/format any error in a HttpServletResponse.</p> * * <p><i><u>Note:</u> Nothing is done if the given writer is NULL !</i></p> * * @param errorWriter The new error writer/formatter. */ public final void setErrorWriter(ServiceErrorWriter errorWriter){ if (errorWriter != null) this.errorWriter = errorWriter; } /* **************** */ /* INPUT PARAMETERS */ /* **************** */ /** * Adds the name of an additional parameter which must be identified without taking into account its case * and then stored with the case of the given name. * * @param paramName Name of an additional parameter. */ public final void addExpectedAdditionalParameter(final String paramName){ if (paramName != null && !paramName.trim().isEmpty()) expectedAdditionalParams.add(paramName); } /** * Gets the number of additional parameters which must be identified with no case sensitivity. * * @return Number of expected additional parameters. */ public final int getNbExpectedAdditionalParameters(){ return expectedAdditionalParams.size(); } /** * Gets the names of the expected additional parameters. These parameters are identified with no case sensitivity * and stored in the given case. * * @return Names of the expected additional parameters. */ public final ArrayList<String> getExpectedAdditionalParameters(){ return expectedAdditionalParams; } /** * Gets an iterator on the names of the expected additional parameters. * * @return An iterator on the names of the expected additional parameters. */ public final Iterator<String> expectedAdditionalParametersIterator(){ return expectedAdditionalParams.iterator(); } /** * Removes the name of an expected additional parameter. * This parameter will never be identify specifically and so, it will be stored in the same case as * in the initial Map or HttpServletRequest. * * @param paramName Name of an additional parameter. */ public final void removeExpectedAdditionalParam(final String paramName){ if (paramName != null && !paramName.trim().isEmpty()) expectedAdditionalParams.remove(paramName); } /** * Gets the list of all UWS input parameter controllers. * @return All parameter controllers. */ public final Map<String,InputParamController> getInputParamControllers(){ return inputParamControllers; } /** * Gets an iterator on the list of all UWS input parameter controllers. * @return An iterator on all parameter controllers. */ public final Iterator<Map.Entry<String,InputParamController>> getInputParamControllersIterator(){ return inputParamControllers.entrySet().iterator(); } /** * Gets the controller of the specified UWS input parameter. * @param inputParamName Name of the parameter whose the controller must be returned. * @return The corresponding controller or <i>null</i> if there is none. */ public final InputParamController getInputParamController(final String inputParamName){ return (inputParamName == null) ? null : inputParamControllers.get(inputParamName); } /** * Sets the controller of the specified input UWS job parameter. * * @param paramName Name of the parameter with which the given controller will be associated. * @param controller An input parameter controller. * * @return The former controller associated with the specified parameter * or <i>null</i> if there is no controller before this call * or if the given parameter name is <i>null</i> or an empty string. */ public final InputParamController setInputParamController(final String paramName, final InputParamController controller){ if (paramName == null || paramName.trim().isEmpty()) return null; if (controller == null) return inputParamControllers.remove(paramName); else return inputParamControllers.put(paramName, controller); } /** * Removes the controller of the specified input UWS job parameter. * * @param paramName Name of the parameter whose the controller must be removed. * * @return The removed controller * or <i>null</i> if there were no controller * or if the given name is <i>null</i> or an empty string. */ public final InputParamController removeInputParamController(final String paramName){ return (paramName == null) ? null : inputParamControllers.remove(paramName); } /** * <p>Lets configuring the execution duration default and maximum value.</p> * * <p><i><u>note:</u> A new controller is created if needed. * Otherwise the current one (if it is an instance of {@link DestructionTimeController}) is updated.</i></p> * * @param defaultDuration Default duration between the start and the end of the execution of a job. * @param maxDuration Maximum duration between the start and the end of the execution of a job that a user can set when creating/initializing a job. * @param allowModif <i>true</i> to allow the modification of this parameter after its initialization, <i>false</i> otherwise. * * @see ExecutionDurationController */ public final void configureExecution(final long defaultDuration, final long maxDuration, final boolean allowModif){ InputParamController controller = inputParamControllers.get(UWSJob.PARAM_EXECUTION_DURATION); // Configures the controller: if (controller != null && controller instanceof ExecutionDurationController){ ExecutionDurationController durationController = (ExecutionDurationController)controller; durationController.setMaxExecutionDuration(maxDuration); durationController.setDefaultExecutionDuration(defaultDuration); durationController.allowModification(allowModif); }// Or creates a new one, if it does not exist: else inputParamControllers.put(UWSJob.PARAM_EXECUTION_DURATION, new ExecutionDurationController(defaultDuration, maxDuration, allowModif)); } /** * <p>Lets configuring the destruction time default and maximum value.</p> * * <p><i><u>note:</u> A new controller is created if needed. * Otherwise the current one (if it is an instance of {@link ExecutionDurationController}) is updated.</i></p> * * @param defaultTime Default time since the job creation and its destruction. * @param defaultTimeUnit Unit of the default time (i.e. minutes, days, ...). * @param maxTime Maximum time since the job creation and its destruction that a user can set when creating/initializing a job. * @param maxTimeUnit Unit of the maximum time (i.e. minutes, days, ...). * @param allowModif <i>true</i> to allow the modification of this parameter after its initialization, <i>false</i> otherwise. * * @see DestructionTimeController */ public final void configureDestruction(final int defaultTime, final DateField defaultTimeUnit, final int maxTime, final DateField maxTimeUnit, final boolean allowModif){ InputParamController controller = inputParamControllers.get(UWSJob.PARAM_DESTRUCTION_TIME); // Cast the controller or built a new DestructionTimeController, if it does not exist: DestructionTimeController destructionController; if (controller == null || !(controller instanceof DestructionTimeController)){ destructionController = new DestructionTimeController(); inputParamControllers.put(UWSJob.PARAM_DESTRUCTION_TIME, controller); }else destructionController = (DestructionTimeController)controller; // Configure the controller: destructionController.setMaxDestructionInterval(maxTime, maxTimeUnit); destructionController.setDefaultDestructionInterval(defaultTime, defaultTimeUnit); destructionController.allowModification(allowModif); } /* ************************* */ /* GENERAL GETTERS & SETTERS */ /* ************************* */ /** * @return The name. */ @Override public final String getName(){ return name; } /** * @return The description. */ @Override public final String getDescription(){ return description; } /* ******************** */ /* JOBS LIST MANAGEMENT */ /* ******************** */ @Override public final Iterator<JobList> iterator(){ return mapJobLists.values().iterator(); } @Override public JobList getJobList(String name) throws UWSException{ if (name != null) name = name.trim(); if (name == null || name.length() == 0) throw new UWSException(UWSException.BAD_REQUEST, "Missing job list name!"); else if (!mapJobLists.containsKey(name)) throw new UWSException(UWSException.NOT_FOUND, "Incorrect job list name ! The jobs list \"" + name + "\" does not exist."); else return mapJobLists.get(name); } @Override public final int getNbJobList(){ return mapJobLists.size(); } @Override public final boolean addJobList(JobList jl){ if (jl == null) return false; else if (mapJobLists.containsKey(jl.getName())) return false; try{ jl.setUWS(this); mapJobLists.put(jl.getName(), jl); }catch(IllegalStateException ise){ logger.logUWS(LogLevel.ERROR, jl, "ADD_JOB_LIST", "The jobs list \"" + jl.getName() + "\" can not be added into the UWS " + getName() + ": it may already be associated with one!", ise); return false; } return true; } @Override public final boolean destroyJobList(String name){ return destroyJobList(mapJobLists.get(name)); } /** * Destroys the given jobs list. * * @param jl The jobs list to destroy. * * @return <i>true</i> if the given jobs list has been destroyed, <i>false</i> otherwise. * * @see JobList#clear() * @see JobList#setUWS(UWS) */ public boolean destroyJobList(JobList jl){ if (jl == null) return false; jl = mapJobLists.remove(jl.getName()); if (jl != null){ try{ jl.clear(); jl.setUWS(null); }catch(IllegalStateException ise){ logger.logUWS(LogLevel.WARNING, jl, "DESTROY_JOB_LIST", "Impossible to erase completely the association between the jobs list \"" + jl.getName() + "\" and the UWS \"" + getName() + "\"!", ise); } } return jl != null; } /* **************************** */ /* JOB SERIALIZATION MANAGEMENT */ /* **************************** */ /** * <p>Adds a serializer to this UWS</p> * <p><b><u>WARNING:</u> If there is already a serializer with the same MIME type (see {@link UWSSerializer#getMimeType()}) in this UWS , * it should be replaced by the given one !</b></p> * * @param serializer The serializer to add. * @return <i>true</i> if the serializer has been successfully added, <i>false</i> otherwise. */ public final boolean addSerializer(UWSSerializer serializer){ if (serializer != null){ serializers.put(serializer.getMimeType(), serializer); if (serializers.size() == 1) defaultSerializer = serializer.getMimeType(); return true; } return false; } /** * <p>Gets the serializer whose the MIME type is the same as the given one.</p> * * <p><i><u>Note:</u> If this UWS has no corresponding serializer, its default one will be returned !</i></p> * * @param mimeTypes The MIME type of the searched serializer (may be more than one MIME types * - comma separated ; see the format of the Accept header of a HTTP-Request). * * @return The corresponding serializer * or the default serializer of this UWS if no corresponding serializer has been found. * * @throws UWSException If there is no corresponding serializer AND if the default serializer of this UWS can not be found. * * @see AcceptHeader#AcceptHeader(String) * @see AcceptHeader#getOrderedMimeTypes() */ @Override public final UWSSerializer getSerializer(String mimeTypes) throws UWSException{ UWSSerializer choosenSerializer = null; if (mimeTypes != null){ // Parse the given MIME types list: AcceptHeader accept = new AcceptHeader(mimeTypes); ArrayList<String> lstMimeTypes = accept.getOrderedMimeTypes(); // Try each of them and stop at the first which match with an existing serializer: for(int i = 0; choosenSerializer == null && i < lstMimeTypes.size(); i++) choosenSerializer = serializers.get(lstMimeTypes.get(i)); } // If no serializer has been found for each given mime type, return the default one: if (choosenSerializer == null){ choosenSerializer = serializers.get(defaultSerializer); if (choosenSerializer == null) throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Missing UWS serializer for the MIME types: " + mimeTypes + " (given MIME types) and " + defaultSerializer + " (default serializer MIME type)" + "!"); } return choosenSerializer; } /** * Removes the serializer whose the MIME type is the same as the given one. * * @param mimeType MIME type of the serializer to remove. * @return The removed serializer * or <i>null</i> if no corresponding serializer has been found. */ public final UWSSerializer removeSerializer(String mimeType){ return serializers.remove(mimeType); } /** * Gets the URL of the XSLT style-sheet that the XML serializer of this UWS is using. * * @return The used XSLT URL. */ public final String getXsltURL(){ XMLSerializer serializer = (XMLSerializer)serializers.get(UWSSerializer.MIME_TYPE_XML); if (serializer != null) return serializer.getXSLTPath(); return null; } /** * Sets the URL of the XSLT style-sheet that the XML serializer of this UWS must use. * * @param xsltPath The new XSLT URL. * * @return <i>true</i> if the given path/url has been successfully set, <i>false</i> otherwise. */ public final boolean setXsltURL(String xsltPath){ XMLSerializer serializer = (XMLSerializer)serializers.get(UWSSerializer.MIME_TYPE_XML); if (serializer != null){ serializer.setXSLTPath(xsltPath); return true; } return false; } /* *************** */ /* USER IDENTIFIER */ /* *************** */ /** * <p>Gets the object which lets extracting the user ID from a HTTP request.</p> * <p><i><u>note:</u>If the returned user identifier is NULL, no job should have an owner.</i></p> * * @return The used UserIdentifier (MAY BE NULL). */ @Override public final UserIdentifier getUserIdentifier(){ return userIdentifier; } /** * Sets the object which lets extracting the use ID from a received request. * * @param identifier The UserIdentifier to use (may be <i>null</i>). */ public final void setUserIdentifier(UserIdentifier identifier){ this.userIdentifier = identifier; } /* ******************* */ /* UWS URL INTERPRETER */ /* ******************* */ /** * Gets the UWS URL interpreter of this UWS. * * @return Its UWS URL interpreter. */ @Override public final UWSUrl getUrlInterpreter(){ return urlInterpreter; } /** * Sets the UWS URL interpreter to use in this UWS. * * @param urlInterpreter Its new UWS URL interpreter (may be <i>null</i>. In this case, it will be created from the next request ; see {@link #service(HttpServletRequest, HttpServletResponse)}). */ public final void setUrlInterpreter(UWSUrl urlInterpreter){ this.urlInterpreter = urlInterpreter; if (name == null && urlInterpreter != null) name = urlInterpreter.getUWSName(); } /* ************** */ /* BACKUP MANAGER */ /* ************** */ /** * <p>Gets its backup manager.</p> * * @return Its backup manager. */ @Override public final UWSBackupManager getBackupManager(){ return backupManager; } }