package uws.service.backup; /* * 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,2014 - UDS/Centre de DonnĂ©es astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.text.ParseException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Timer; import java.util.TimerTask; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import org.json.JSONWriter; import org.json.Json4Uws; import uws.ISO8601Format; import uws.UWSException; import uws.UWSToolBox; import uws.job.ErrorSummary; import uws.job.ErrorType; import uws.job.JobList; import uws.job.Result; import uws.job.UWSJob; import uws.job.parameters.UWSParameters; import uws.job.user.JobOwner; import uws.service.UWS; import uws.service.file.UWSFileManager; import uws.service.log.UWSLog; import uws.service.log.UWSLog.LogLevel; import uws.service.request.UploadFile; /** * <p>Default implementation of the interface {@link UWSBackupManager}.</p> * * <p> * With this class, a UWS can be saved and restored easily thanks to {@link #saveAll()} and {@link #restoreAll()}. * It is saved in JSON and in one or several files in function of the backup mode: * </p> * <ul> * <li><u>by user</u>: one file for each user. The file contains all the information about the user and all the jobs he owns.</li> * <li><u>in one file</u>: one file to describe all users and all the jobs.</li> * </ul> * * <p>The backup frequency can also be changed and may have 2 special values:</p> * <ul> * <li><u>{@link #AT_USER_ACTION} (=0)</u>: only the jobs of the user which has just created, destroyed, executed or stopped a job are saved. This frequency is possible only if the backup mode is <u>by user</u>.</li> * <li><u>{@link #MANUAL} (=-1)</u>: you must call yourself the function {@link #saveAll()} to save the UWS.</li> * </ul> * <p>Another positive value will be considered as the frequency (in milliseconds) of the automatic backup (= {@link #saveAll()}).</p> * * @author Grégory Mantelet (CDS;ARI) * @version 4.1 (12/2014) */ public class DefaultUWSBackupManager implements UWSBackupManager { /** Special frequency to mean that this manager wait a user action (create, update, start, abort, destruction) to save the jobs of this user. */ public static final long AT_USER_ACTION = 0; /** Special frequency to mean that this manager will NOT save automatically the UWS. The function {@link #saveAll()} MUST be explicitly called. */ public static final long MANUAL = -1; /** Default backup frequency. 60000ms = 60s = 1min */ public static final long DEFAULT_FREQUENCY = 60000; /** Date of the last restoration. */ protected Date lastRestoration = null; /** Date of the last backup. */ protected Date lastBackup = null; /** The UWS to restore/save. */ protected final UWS uws; /** Tells whether the backup (and particularly the automatic one) of the associated UWS is enabled or not. */ protected boolean enabled = true; /** Backup mode: one file by user or one file for all jobs and users. */ protected final boolean byUser; /** Backup frequency (in milliseconds). */ protected long backupFreq = AT_USER_ACTION; /** Timer which saves the backup each <i>backupFreq</i> milliseconds. */ protected Timer timAutoBackup = null; /** * Builds a backup manager in the mode "auto": one file for all users and all jobs, and the backup * is done all minutes (see {@link #DEFAULT_FREQUENCY}. * * @param uws The UWS to save/restore. * * @see #DefaultUWSBackupManager(UWS, long) */ public DefaultUWSBackupManager(final UWS uws){ this(uws, DEFAULT_FREQUENCY); } /** * <p>Builds a backup manager in the mode "auto" or "manual": one file for all users and all jobs, and the backup * is done at the given frequency.</p> * * <p>If the given frequency is 0 or negative (see {@link #MANUAL}), the backup will not be done automatically. You must manually * save the UWS thanks to the function {@link #saveAll()}.</p> * * <p>If the given frequency is positive, the backup will be done automatically at the given frequency.</p> * * @param uws The UWS to save/restore. * @param frequency The backup frequency (in ms ; MUST BE positive and different from 0. If negative or 0, the frequency will be automatically set to {@link #DEFAULT_FREQUENCY}). */ public DefaultUWSBackupManager(final UWS uws, final long frequency){ this.uws = uws; this.byUser = false; this.backupFreq = (frequency <= 0) ? MANUAL : frequency; if (backupFreq > 0){ timAutoBackup = new Timer(); timAutoBackup.scheduleAtFixedRate(new TimerTask(){ @Override public void run(){ saveAll(); } }, backupFreq, backupFreq); } } /** * Builds a backup manager in the given mode: "by user" (one file for each user and the backup is done at each user action) * or not (one file for all users and all jobs and the backup is done all minutes (see {@link #DEFAULT_FREQUENCY})). * * @param uws The UWS to save/restore. * @param byUser Backup mode. * * @throws UWSException If the user identification is disabled (that's to say, if the given UWS has no UserIdentifier) while the parameter <i>byUser</i> is <i>true</i>. * * @see #DefaultUWSBackupManager(UWS, boolean, long) */ public DefaultUWSBackupManager(final UWS uws, final boolean byUser) throws UWSException{ this(uws, byUser, byUser ? AT_USER_ACTION : DEFAULT_FREQUENCY); } /** * Builds a backup manager in the given mode and with the given frequency. * * @param uws The UWS to save/restore. * @param byUser Backup mode (<i>true</i> means one file for each user and <i>false</i>, one file for all users and jobs). * @param frequency Backup frequency ({@link #AT_USER_ACTION}, {@link #MANUAL}, {@link #DEFAULT_FREQUENCY}, or a positive value). * * @throws UWSException If the user identification is disabled (that's to say, if the given UWS has no UserIdentifier) while the parameter <i>byUser</i> is <i>true</i>. */ public DefaultUWSBackupManager(final UWS uws, final boolean byUser, final long frequency) throws UWSException{ this.uws = uws; this.byUser = byUser; this.backupFreq = frequency; if (byUser && uws.getUserIdentifier() == null) throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Impossible to save/restore a UWS by user if the user identification is disabled (no UserIdentifier is set to the UWS)!"); if (backupFreq == AT_USER_ACTION && !byUser) backupFreq = MANUAL; else if (backupFreq > 0){ timAutoBackup = new Timer(); timAutoBackup.scheduleAtFixedRate(new TimerTask(){ @Override public void run(){ saveAll(); } }, backupFreq, backupFreq); }else if (backupFreq < 0) backupFreq = MANUAL; } /** * Tells whether this backup manager is enabled or not. * * @return <i>true</i> if the backup is enabled, <i>false</i> otherwise. */ public final boolean isEnabled(){ return enabled; } @Override public final void setEnabled(boolean enabled){ this.enabled = enabled; if (backupFreq > 0){ if (this.enabled){ if (timAutoBackup == null){ timAutoBackup = new Timer(); timAutoBackup.scheduleAtFixedRate(new TimerTask(){ @Override public void run(){ saveAll(); } }, backupFreq, backupFreq); } }else{ if (timAutoBackup != null){ timAutoBackup.cancel(); timAutoBackup = null; } } } } /** * Gets the backup frequency. * * @return The backup frequency (in milliseconds). */ public final long getBackupFreq(){ return backupFreq; } /** * <p>Sets the backup frequency.</p> * * <p> * <i><u>note 1:</u> A negative frequency will be interpreted as "manual".. * that's to say you will have to call yourself the {@link #saveAll()} method to save the UWS. * </i></p> * <p> * <i><u>note 2:</u> Nothing will be done if the given frequency is {@link #AT_USER_ACTION} although the current backup mode is "by user". * </i></p> * * @param freq The new backup frequency (in milliseconds) ({@link #AT_USER_ACTION}, {@link #MANUAL}, {@link #DEFAULT_FREQUENCY} or any other positive value). */ public final void setBackupFreq(long freq){ if (freq < 0) freq = MANUAL; else if (freq == AT_USER_ACTION && !byUser) return; this.backupFreq = freq; if (timAutoBackup != null){ timAutoBackup.cancel(); timAutoBackup = null; } if (enabled && backupFreq > 0){ timAutoBackup = new Timer(); timAutoBackup.scheduleAtFixedRate(new TimerTask(){ @Override public void run(){ saveAll(); } }, 0, backupFreq); } } /** * Gets the date of the last restoration * * @return The date of the last restoration (MAY BE NULL). */ public final Date getLastRestoration(){ return lastRestoration; } /** * Gets the date of the last backup. * * @return The date of the last backup (MAY BE NULL). */ public final Date getLastBackup(){ return lastBackup; } /** * Gets the logger of its UWS, or the default one if it is unknown. * * @return A logger. * * @see UWS#getLogger() * @see UWSToolBox#getDefaultLogger() */ public UWSLog getLogger(){ if (uws.getLogger() != null) return uws.getLogger(); else return UWSToolBox.getDefaultLogger(); } /* ************ */ /* SAVE METHODS */ /* ************ */ @Override public int[] saveAll(){ if (!enabled) return null; int nbSavedJobs = 0, nbSavedOwners = 0; int nbJobs = 0, nbOwners = 0; // List all users of this UWS: HashMap<String,JobOwner> users = new HashMap<String,JobOwner>(); for(JobList jl : uws){ Iterator<JobOwner> it = jl.getUsers(); while(it.hasNext()){ JobOwner owner = it.next(); users.put(owner.getID(), owner); } } // "byUser" => 1 file par user => call saveOwner(user, true) for each user: if (byUser){ int[] saveReport; for(JobOwner user : users.values()){ nbOwners++; saveReport = saveOwner(user, true); if (saveReport != null && saveReport.length == 2){ nbSavedJobs += saveReport[0]; nbJobs += saveReport[1]; nbSavedOwners++; } } }// Otherwise: 1 file for all users and all jobs: else{ UWSFileManager fileManager = uws.getFileManager(); PrintWriter writer = null; try{ // Create a writer toward the backup file: writer = new PrintWriter(fileManager.getBackupOutput()); JSONWriter out = new JSONWriter(writer); // JSON structure: { date: ..., users: [...], jobs: [...] } out.object(); // Write the backup date: out.key("date").value((new Date()).toString()); // Write all users: out.key("users").array(); for(JobOwner user : users.values()){ nbOwners++; try{ out.value(getJSONUser(user)); nbSavedOwners++; }catch(JSONException je){ getLogger().logUWS(LogLevel.ERROR, user, "BACKUP", "Unexpected JSON error while saving the user '" + user.getID() + "'!", je); } } out.endArray(); writer.flush(); // Write all jobs: out.key("jobs").array(); for(JobList jl : uws){ for(UWSJob job : jl){ nbJobs++; try{ out.value(getJSONJob(job, jl.getName())); nbSavedJobs++; writer.flush(); }catch(UWSException ue){ getLogger().logUWS(LogLevel.ERROR, job, "BACKUP", "Unexpected UWS error while saving the job '" + job.getJobId() + "'!", ue); }catch(JSONException je){ getLogger().logUWS(LogLevel.ERROR, job, "BACKUP", "Unexpected JSON error while saving the job '" + job.getJobId() + "'!", je); } } } out.endArray(); // End the general structure: out.endObject(); }catch(JSONException je){ getLogger().logUWS(LogLevel.ERROR, null, "BACKUP", "Unexpected JSON error while saving the whole UWS !", je); }catch(IOException ie){ getLogger().logUWS(LogLevel.ERROR, null, "BACKUP", "Unexpected IO error while saving the whole UWS !", ie); }finally{ // Close the writer: if (writer != null) writer.close(); } } // Build the report and log it: int[] report = new int[]{nbSavedJobs,nbJobs,nbSavedOwners,nbOwners}; getLogger().logUWS(LogLevel.INFO, report, "BACKUPED", "UWS Service \"" + uws.getName() + "\" backuped!", null); lastBackup = new Date(); return report; } @Override public int[] saveOwner(JobOwner user){ if (!enabled) return null; return saveOwner(user, false); } protected int[] saveOwner(JobOwner user, boolean fromSaveAll){ if (!enabled) return null; // DO NOTHING if the "save" order does not come from saveAll(): if (!fromSaveAll && backupFreq != AT_USER_ACTION) return new int[]{-1,-1}; UWSFileManager fileManager = uws.getFileManager(); int[] saveReport = new int[]{0,0}; PrintWriter writer = null; try{ // Create a writer toward the backup file: writer = new PrintWriter(fileManager.getBackupOutput(user)); JSONWriter out = new JSONWriter(writer); // JSON structure: { date: ..., user: {}, jobs: [...] } out.object(); // Write the backup date: out.key("date").value(ISO8601Format.format(new Date())); // Write the description of the user: out.key("user").value(getJSONUser(user)); writer.flush(); // Write all its jobs: out.key("jobs").array(); for(JobList jl : uws){ Iterator<UWSJob> it = jl.getJobs(user); while(it.hasNext()){ saveReport[1]++; try{ out.value(getJSONJob(it.next(), jl.getName())); saveReport[0]++; writer.flush(); }catch(JSONException je){ getLogger().logUWS(LogLevel.ERROR, null, "BACKUP", "Unexpected JSON error while saving the " + saveReport[1] + "-th job of the job list '" + jl.getName() + "' owned by the user '" + user.getID() + "'!", je); }catch(UWSException ue){ getLogger().logUWS(LogLevel.ERROR, null, "BACKUP", "Unexpected UWS error while saving the " + saveReport[1] + "-th job of the job list '" + jl.getName() + "' owned by the user '" + user.getID() + "'!", ue); } } } out.endArray(); // End the general structure: out.endObject(); // Log the "save" report: getLogger().logUWS(LogLevel.INFO, saveReport, "BACKUPED", "UWS backuped!", null); lastBackup = new Date(); return saveReport; }catch(IOException ie){ getLogger().logUWS(LogLevel.ERROR, null, "BACKUP", "Unexpected IO error while saving the jobs of user '" + user.getID() + "'!", ie); }catch(JSONException je){ getLogger().logUWS(LogLevel.ERROR, null, "BACKUP", "Unexpected JSON error while saving the jobs of user '" + user.getID() + "'!", je); }finally{ // Close the writer: if (writer != null) writer.close(); } return null; } /** * <p>Serializes the given user into a JSON object.</p> * * <pre> * { * "id": "...", * "pseudo": "...", * ... * } * </pre> * <p> * <i><u>note</u>: * the last suspension points mean that other user data may be added into this JSON object. * These other user data to save MUST BE given by {@link JobOwner#getDataToSave()}. * </i></p> * * @param user The user to save. * * @return Its JSON representation. * * @throws JSONException If there is an error while building the JSON object. */ protected JSONObject getJSONUser(final JobOwner user) throws JSONException{ JSONObject jsonUser = new JSONObject(); jsonUser.put("id", user.getID()); jsonUser.put("pseudo", user.getPseudo()); if (user.getDataToSave() != null){ Iterator<Map.Entry<String,Object>> itUserData = user.getDataToSave().entrySet().iterator(); while(itUserData.hasNext()){ Map.Entry<String,Object> userData = itUserData.next(); jsonUser.put(userData.getKey(), userData.getValue()); } } return jsonUser; } /** * <p>Serializes the given job into a JSON object.</p> * * <p> * <i><u>note</u>: * the structure of the returned JSON object is decided by {@link Json4Uws#getJson(UWSJob)}. * Only one attribute is added: "jobListName". * </i></p> * * @param job The job to save. * @param jlName Name of the jobs list containing the given job. * * @return The JSON representation of the given job. * * @throws UWSException If there is an error while getting job parameters and serializing them. * @throws JSONException If there is an error while building the JSON object. */ protected JSONObject getJSONJob(final UWSJob job, final String jlName) throws UWSException, JSONException{ JSONObject jsonJob = Json4Uws.getJson(job); // Re-Build the parameters map, by separating the uploads and the "normal" parameters: JSONArray uploads = new JSONArray(); JSONObject params = new JSONObject(); Object val; for(String name : job.getAdditionalParameters()){ // get the raw value: val = job.getAdditionalParameterValue(name); // if an array, build a JSON array of strings: if (val != null && val.getClass().isArray()){ JSONArray array = new JSONArray(); for(Object o : (Object[])val){ if (o != null) array.put(o.toString()); } params.put(name, array); }else if (val != null && val instanceof UploadFile) uploads.put(getUploadJson((UploadFile)val)); // otherwise, just put the value: else if (val != null) params.put(name, val); } // Add the parameters and the uploads inside the JSON representation of the job: jsonJob.put(UWSJob.PARAM_PARAMETERS, params); jsonJob.put("uwsUploads", uploads); // Add the job owner: jsonJob.put(UWSJob.PARAM_OWNER, (job != null && job.getOwner() != null) ? job.getOwner().getID() : null); // Add the name of the job list owning the given job: jsonJob.put("jobListName", jlName); return jsonJob; } /** * Get the JSON representation of the given {@link UploadFile}. * * @param upl The uploaded file to serialize in JSON. * * @return Its JSON representation. * * @throws JSONException If there is an error while building the JSON object. * * @since 4.1 */ protected JSONObject getUploadJson(final UploadFile upl) throws JSONException{ if (upl == null) return null; JSONObject o = new JSONObject(); o.put("paramName", upl.paramName); o.put("fileName", upl.fileName); o.put("location", upl.getLocation()); o.put("mime", upl.mimeType); o.put("lenght", upl.length); return o; } /* ******************* */ /* RESTORATION METHODS */ /* ******************* */ @Override public int[] restoreAll(){ // Removes all current jobs from the UWS before restoring it from files: for(JobList jl : uws) jl.clear(); int nbRestoredJobs = 0, nbRestoredUsers = 0; int nbJobs = 0, nbUsers = 0; boolean userIdentificationEnabled = (uws.getUserIdentifier() != null); UWSFileManager fileManager = uws.getFileManager(); Iterator<InputStream> itInput; // Get the list of the input streams (on all the backup files to read): if (byUser){ if (!userIdentificationEnabled){ getLogger().logUWS(LogLevel.ERROR, null, "RESTORATION", "Impossible to restore a UWS by user if the user identification is disabled (that's to say, the UWS has no UserIdentifier)!", null); return null; }else itInput = fileManager.getAllUserBackupInputs(); }else{ try{ itInput = new SingleInputIterator(fileManager.getBackupInput()); }catch(IOException ioe){ getLogger().logUWS(LogLevel.ERROR, null, "RESTORATION", "Restoration of the UWS " + uws.getName() + " failed because an unexpected IO error has occured.", ioe); return null; } } // For each backup file... while(itInput.hasNext()){ InputStream inputStream = itInput.next(); if (inputStream == null) continue; // Create the JSON reader: JSONTokener in = new JSONTokener(new InputStreamReader(inputStream)); HashMap<String,JobOwner> users = new HashMap<String,JobOwner>(); String key; JSONObject object = null; try{ // Reads progressively the general structure (which is theoretically a JSON object): JSONObjectReader itKeys = new JSONObjectReader(in, getLogger()); while(itKeys.hasNext()){ // name of the current attribute: key = itKeys.next(); if (key == null) break; // key=DATE: if (key.equalsIgnoreCase("date")) itKeys.getValue(); // key=USER (note: this key exists only in the backup file of a specified user): else if (key.equalsIgnoreCase("user")){ nbUsers++; try{ // the value is supposed to be a JSON object: object = itKeys.getJSONObject(); if (object == null){ nbUsers--; continue; } if (userIdentificationEnabled){ // build the corresponding instance of DefaultJobOwner: JobOwner user = getUser(object); if (user != null){ users.put(user.getID(), user); nbRestoredUsers++; } } }catch(UWSException ue){ getLogger().logUWS(LogLevel.ERROR, object, "RESTORATION", "A job owner can not be restored!", ue); //break; // Because, the key "user" is found ONLY in the backup file of a user. If the user can not be restored, its jobs won't be ! } }// key=USERS (note: this key exists only in the backup file of the whole UWS): else if (key.equalsIgnoreCase("users")){ // the value is supposed to be an array of JSON objects: Iterator<JSONObject> it = itKeys.getArrayReader(); while(it.hasNext()){ nbUsers++; try{ // get the JSON object corresponding to the current user: object = it.next(); if (object == null){ nbUsers--; continue; } if (userIdentificationEnabled){ // build the corresponding instance of DefaultJobOwner: JobOwner user = getUser(object); if (user != null){ users.put(user.getID(), user); nbRestoredUsers++; } } }catch(UWSException ue){ getLogger().logUWS(LogLevel.ERROR, object, "RESTORATION", "The " + nbUsers + "-th user can not be restored!", ue); } } }// JOBS: else if (key.equalsIgnoreCase("jobs")){ // the value is supposed to be an array of JSON objects: Iterator<JSONObject> it = itKeys.getArrayReader(); while(it.hasNext()){ nbJobs++; try{ // get the JSON object corresponding to the current job: object = it.next(); if (object == null){ nbJobs--; continue; } // build the corresponding instance of UWSJob: if (restoreJob(object, users)) nbRestoredJobs++; }catch(UWSException ue){ getLogger().logUWS(LogLevel.ERROR, object, "RESTORATION", "The " + nbJobs + "-th job can not be restored!", ue); } } }// any other key is ignore but with a warning message: else getLogger().logUWS(LogLevel.WARNING, null, "RESTORATION", "Key '" + key + "' ignored because unknown! The UWS may be not completely restored.", null); } }catch(JSONException je){ getLogger().logUWS(LogLevel.ERROR, null, "RESTORATION", "Incorrect JSON format for a UWS backup file!", je); return null; }catch(Exception e){ getLogger().logUWS(LogLevel.ERROR, null, "RESTORATION", "Unexpected error while restoring the UWS!", e); return null; }finally{ // Close the reader: try{ inputStream.close(); }catch(IOException ioe){ getLogger().logUWS(LogLevel.ERROR, null, "RESTORATION", "Can not close the input stream opened on a user backup file!", ioe); } // Set the last restoration date: lastRestoration = new Date(); } } if (!userIdentificationEnabled && nbUsers > 0) getLogger().logUWS(LogLevel.WARNING, null, "RESTORATION", nbUsers + " job owners have not been restored because the user identification is disabled in this UWS! => Jobs of these users have not been restored.", null); // Build the restoration report and log it: int[] report = new int[]{nbRestoredJobs,nbJobs,nbRestoredUsers,nbUsers}; getLogger().logUWS(LogLevel.INFO, report, "RESTORED", "UWS restored!", null); return report; } /** * Builds the instance of {@link JobOwner} corresponding to the given JSON object. * * @param json JSON representation of the user to build. * * @return The corresponding instance of {@link JobOwner} or <i>null</i> if the given object is empty. * * @throws UWSException If the "id" parameter is missing (a user MUST have an id ; warning: the case sensitivity is enabled only for this attribute). * * @see JobOwner#restoreData(Map) */ protected JobOwner getUser(final JSONObject json) throws UWSException{ if (json == null || json.length() == 0) return null; // Fetch all user data: String ID = null, pseudo = null; String[] keys = JSONObject.getNames(json); Map<String,Object> userData = new HashMap<String,Object>(keys.length - 2); for(String key : keys){ try{ if (key.equalsIgnoreCase("id")) ID = json.getString(key); else if (key.equalsIgnoreCase("pseudo")) pseudo = json.getString(key); else userData.put(key, json.getString(key)); }catch(JSONException je){ getLogger().logUWS(LogLevel.WARNING, null, "RESTORATION", "Incorrect JSON format for the serialization of the user \"" + ID + "\"! The restoration of this job may be incomplete.", je); } } // Check that the ID exists: if (ID == null || ID.trim().isEmpty()) throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, null, "Impossible to restore a user from the backup file(s): no ID has been found!"); return uws.getUserIdentifier().restoreUser(ID, pseudo, userData); } /** * Builds the job corresponding to the given JSON object and then restore it in the UWS. * * @param json The JSON representation of the job to restore. * @param users The list of all fetched users. * * @return <i>true</i> if the corresponding job has been successfully restored, <i>false</i> otherwise. * * @throws UWSException If the job ID or the job list name is missing, * or if the job list name is incorrect, * or if there is an error with "parameters", "error" and "results". */ protected boolean restoreJob(final JSONObject json, Map<String,JobOwner> users) throws UWSException{ if (json == null || json.length() == 0) return false; String jobListName = null, jobId = null, ownerID = null, tmp; //Date destruction=null; long quote = UWSJob.UNLIMITED_DURATION, /*duration = UWSJob.UNLIMITED_DURATION, */startTime = -1, endTime = -1; HashMap<String,Object> inputParams = new HashMap<String,Object>(10); //Map<String, Object> params = null; ArrayList<Result> results = null; ErrorSummary error = null; JSONArray uploads = null; String[] keys = JSONObject.getNames(json); for(String key : keys){ try{ // key=JOB_LIST_NAME: if (key.equalsIgnoreCase("jobListName")) jobListName = json.getString(key); // key=JOB_ID: else if (key.equalsIgnoreCase(UWSJob.PARAM_JOB_ID)) jobId = json.getString(key); // key=PHASE: else if (key.equalsIgnoreCase(UWSJob.PARAM_PHASE)) ; // key=OWNER: else if (key.equalsIgnoreCase(UWSJob.PARAM_OWNER)) ownerID = json.getString(key); // key=RUN_ID: else if (key.equalsIgnoreCase(UWSJob.PARAM_RUN_ID)){ String runId = json.getString(key); inputParams.put(UWSJob.PARAM_RUN_ID, runId); }// key=QUOTE: else if (key.equalsIgnoreCase(UWSJob.PARAM_QUOTE)) quote = json.getLong(key); // key=EXECUTION_DURATION: else if (key.equalsIgnoreCase(UWSJob.PARAM_EXECUTION_DURATION)){ long duration = json.getLong(key); inputParams.put(UWSJob.PARAM_EXECUTION_DURATION, duration); }// key=DESTRUCTION: else if (key.equalsIgnoreCase(UWSJob.PARAM_DESTRUCTION_TIME)){ try{ tmp = json.getString(key); inputParams.put(UWSJob.PARAM_DESTRUCTION_TIME, ISO8601Format.parseToDate(tmp)); }catch(ParseException pe){ getLogger().logUWS(LogLevel.ERROR, json, "RESTORATION", "Incorrect date format for the '" + key + "' parameter!", pe); } }// key=START_TIME: else if (key.equalsIgnoreCase(UWSJob.PARAM_START_TIME)){ tmp = json.getString(key); try{ Date d = ISO8601Format.parseToDate(tmp); startTime = d.getTime(); }catch(ParseException pe){ getLogger().logUWS(LogLevel.ERROR, json, "RESTORATION", "Incorrect date format for the '" + key + "' parameter!", pe); } }// key=END_TIME: else if (key.equalsIgnoreCase(UWSJob.PARAM_END_TIME)){ tmp = json.getString(key); try{ Date d = ISO8601Format.parseToDate(tmp); endTime = d.getTime(); }catch(ParseException pe){ getLogger().logUWS(LogLevel.ERROR, json, "RESTORATION", "Incorrect date format for the '" + key + "' parameter!", pe); } }// key=PARAMETERS: else if (key.equalsIgnoreCase(UWSJob.PARAM_PARAMETERS)) inputParams.put(UWSJob.PARAM_PARAMETERS, getParameters(json.getJSONObject(key))); // key=uwsUploads: else if (key.equalsIgnoreCase("uwsUploads")) uploads = json.getJSONArray(key); // key=RESULTS: else if (key.equalsIgnoreCase(UWSJob.PARAM_RESULTS)) results = getResults(json.getJSONArray(key)); // key=ERROR: else if (key.equalsIgnoreCase(UWSJob.PARAM_ERROR_SUMMARY)){ error = getError(json.getJSONObject(key)); }// Ignore any other key but with a warning message: else getLogger().logUWS(LogLevel.WARNING, json, "RESTORATION", "The job attribute '" + key + "' has been ignored because unknown! A job may be not completely restored!", null); }catch(JSONException je){ getLogger().logUWS(LogLevel.ERROR, json, "RESTORATION", "Incorrect JSON format for a job serialization (attribute: \"" + key + "\")!", je); } } // Re-Build all the uploaded files' pointers for this job: if (uploads != null){ @SuppressWarnings("unchecked") Map<String,Object> params = (Map<String,Object>)(inputParams.get(UWSJob.PARAM_PARAMETERS)); UploadFile upl; try{ for(int i = 0; i < uploads.length(); i++){ upl = getUploadFile(uploads.getJSONObject(i));; if (upl != null) params.put(upl.paramName, upl); } }catch(JSONException je){ getLogger().logUWS(LogLevel.ERROR, json, "RESTORATION", "Incorrect JSON format for the serialization of the job \"" + jobId + "\" (attribute: \"uwsUploads\")!", je); } } // The job list name is REQUIRED: if (jobListName == null || jobListName.isEmpty()) getLogger().logUWS(LogLevel.ERROR, json, "RESTORATION", "Missing job list name! => Can not restore the job " + jobId + "!", null); // The job list name MUST correspond to an existing job list: else if (uws.getJobList(jobListName) == null) getLogger().logUWS(LogLevel.ERROR, json, "RESTORATION", "No job list named " + jobListName + "! => Can not restore the job " + jobId + "!", null); // The job ID is REQUIRED: else if (jobId == null || jobId.isEmpty()) getLogger().logUWS(LogLevel.ERROR, json, "RESTORATION", "Missing job ID! => Can not restore a job!", null); // Otherwise: the job can be created and restored: else{ // Search the job owner: JobOwner owner = users.get(ownerID); // If the specified user is unknown, display a warning and create the job without owner: if (ownerID != null && !ownerID.isEmpty() && owner == null){ getLogger().logUWS(LogLevel.ERROR, json, "RESTORATION", "Unknown job owner: " + ownerID + "! => Can not restore the job " + jobId + "!", null); return false; } // Build the UWSParameters object: UWSParameters uwsParams; try{ uwsParams = uws.getFactory().createUWSParameters(inputParams); }catch(UWSException ue){ getLogger().logUWS(LogLevel.ERROR, json, "RESTORATION", "Error with at least one of the UWS parameters to restore!", ue); return false; } // Create the job: UWSJob job = uws.getFactory().createJob(jobId, owner, uwsParams, quote, startTime, endTime, results, error); // Restore other job params if needed: restoreOtherJobParams(json, job); // Restore it: return (uws.getJobList(jobListName).addNewJob(job) != null); } return false; } /** * <p> * Restores other job parameters, either from the given JSON object or from the parameters map of the given job. * The job is supposed to be updated after the call of this function. * </p> * * <p><i><u>note:</u> By default, this function does nothing ! It is called by {@link #restoreJob(JSONObject, Map)} * just after the default restoration from the given JSON and just before to add the job in its dedicated jobs list.</i></p> * * @param json JSON backup of the given job. * @param job Default restoration of the job. * * @throws UWSException If there is an error while restoring other job parameters. * * @see #restoreJob(JSONObject, Map) */ protected void restoreOtherJobParams(final JSONObject json, final UWSJob job) throws UWSException{ ; } /** * Builds the list of parameters corresponding to the given JSON object. * * @param obj The JSON representation of a parameters list. * * @return The corresponding list of parameters * or <i>null</i> if the given object is empty. */ protected Map<String,Object> getParameters(final JSONObject obj){ if (obj == null || obj.length() == 0) return null; HashMap<String,Object> params = new HashMap<String,Object>(obj.length()); String[] names = JSONObject.getNames(obj); for(String n : names){ try{ params.put(n, obj.get(n)); }catch(JSONException je){ getLogger().logUWS(LogLevel.ERROR, obj, "RESTORATION", "Incorrect JSON format for the serialization of the parameter '" + n + "'!", je); } } return params; } /** * Build the upload file corresponding to the given JSON object. * * @param obj The JSON representation of the {@link UploadFile} to get. * * @return The corresponding {@link UploadFile}. * * @since 4.1 */ protected UploadFile getUploadFile(final JSONObject obj){ try{ UploadFile upl = new UploadFile(obj.getString("paramName"), (obj.has("fileName") ? obj.getString("fileName") : null), obj.getString("location"), uws.getFileManager()); if (obj.has("mime")) upl.mimeType = obj.getString("mime"); try{ if (obj.has("length")) upl.length = Long.parseLong(obj.getString("length")); }catch(NumberFormatException ex){} return upl; }catch(JSONException je){ getLogger().logUWS(LogLevel.ERROR, obj, "RESTORATION", "Incorrect JSON format for the serialization of an uploaded file!", je); return null; } } /** * Builds the list of results corresponding to the given JSON array. * * @param array The JSON representation of the results to restore. * * @return The corresponding list of results * or <i>null</i> if the array is empty. * * @throws UWSException If there is an error while restoring one of the result. * * @see #getResult(JSONObject) */ protected ArrayList<Result> getResults(final JSONArray array) throws UWSException{ if (array == null || array.length() == 0) return null; ArrayList<Result> results = new ArrayList<Result>(array.length()); for(int i = 0; i < array.length(); i++){ try{ Result r = getResult(array.getJSONObject(i)); if (r != null) results.add(r); }catch(JSONException je){ getLogger().logUWS(LogLevel.ERROR, array, "RESTORATION", "Incorrect JSON format for the serialization of the " + (i + 1) + "-th result!", je); } } return results; } /** * Builds the result corresponding to the given JSON object. * * @param obj The JSON representation of the result to restore. * * @return The corresponding result or <i>null</i> if the given object is empty. * * @throws JSONException If there is an error while reading the JSON. * @throws UWSException */ protected Result getResult(final JSONObject obj) throws JSONException, UWSException{ if (obj == null || obj.length() == 0) return null; String id = null, type = null, href = null, mime = null; boolean redirection = false; long size = -1; String[] names = JSONObject.getNames(obj); for(String n : names){ if (n.equalsIgnoreCase("id")) id = obj.getString(n); else if (n.equalsIgnoreCase("type")) type = obj.getString(n); else if (n.equalsIgnoreCase("href")) href = obj.getString(n); else if (n.equalsIgnoreCase("mime")) mime = obj.getString(n); else if (n.equalsIgnoreCase("redirection")) redirection = obj.getBoolean(n); else if (n.equalsIgnoreCase("size")) size = obj.getLong(n); else getLogger().logUWS(LogLevel.WARNING, obj, "RESTORATION", "The result parameter '" + n + "' has been ignored because unknown! A result may be not completely restored!", null); } if (id == null){ getLogger().logUWS(LogLevel.ERROR, obj, "RESTORATION", "Missing result ID! => A result can not be restored!", null); return null; }else{ Result r = new Result(id, type, href, redirection); r.setMimeType(mime); r.setSize(size); return r; } } /** * Builds the error summary corresponding to the given JSON object. * * @param obj The JSON representation of the error summary to restore. * * @return The corresponding error summary or <i>null</i> if the given object is empty. * * @throws UWSException */ protected ErrorSummary getError(final JSONObject obj) throws UWSException{ if (obj == null || obj.length() == 0) return null; String type = null, message = null, details = null; String[] names = JSONObject.getNames(obj); for(String n : names){ try{ if (n.equalsIgnoreCase("type")) type = obj.getString(n); else if (n.equalsIgnoreCase("detailsRef")) details = obj.getString(n); else if (n.equalsIgnoreCase("hasDetail")) ;//hasDetail = obj.getBoolean(n); else if (n.equalsIgnoreCase("message")) message = obj.getString(n); else getLogger().logUWS(LogLevel.WARNING, obj, "RESTORATION", "The error attribute '" + n + "' has been ignored because unknown! => An error summary may be not completely restored!", null); }catch(JSONException je){ getLogger().logUWS(LogLevel.ERROR, obj, "RESTORATION", "Incorrect JSON format for an error serialization!", je); } } if (message != null) return new ErrorSummary(message, ErrorType.valueOf(type.toUpperCase()), details); else return null; } /* **************** */ /* USEFUL ITERATORS */ /* **************** */ /** * Lets reading a JSON object from a {@link JSONTokener} (that's to say directly from a file), * as an iterator which returns all the keys. The value of each key can be fetched thanks to * the different available getters (i.e. {@link #getJSONArray()}, {@link #getJSONObject()}, * {@link #getValue()}, {@link #getString()}, ...). * * @author Grégory Mantelet (CDS) * @version 05/2012 */ protected final static class JSONObjectReader implements Iterator<String> { private final UWSLog logger; private final JSONTokener input; private String nextKey = null; private boolean valueGot = false; private boolean endReached = false; public JSONObjectReader(final JSONTokener input, final UWSLog log) throws JSONException{ this.input = input; this.logger = log; if (input.nextClean() != '{') throw input.syntaxError("A JSONObject text must begin with '{'"); if (input.nextClean() == '}') endReached = true; else input.back(); } private void readNext() throws JSONException{ if (nextKey != null){ nextKey = null; if (!prepareNextPair()){ nextKey = null; return; } } char c = input.nextClean(); switch(c){ case 0: throw input.syntaxError("A JSONObject text must end with '}'"); case '}': endReached = true; return; default: input.back(); nextKey = input.nextValue().toString(); } /* * The key is followed by ':'. We will also tolerate '=' or '=>'. */ c = input.nextClean(); if (c == '='){ if (input.next() != '>'){ input.back(); } }else if (c != ':'){ throw input.syntaxError("Expected a ':' after a key"); } } private boolean prepareNextPair() throws JSONException{ if (!valueGot) skipValue(); /* * Pairs are separated by ','. We will also tolerate ';'. */ switch(input.nextClean()){ case ';': case ',': if (input.nextClean() == '}'){ endReached = true; return false; } input.back(); break; case '}': endReached = true; return false; default: throw input.syntaxError("Expected a ',' or '}'"); } return true; } private void skipValue() throws JSONException{ valueGot = true; input.nextValue(); } public JSONObject getJSONObject() throws JSONException{ valueGot = true; return new JSONObject(input); } public JSONArray getJSONArray() throws JSONException{ valueGot = true; return new JSONArray(input); } public String getString() throws JSONException{ valueGot = true; return input.nextValue().toString(); } public Object getValue() throws JSONException{ valueGot = true; return input.nextValue(); } public JSONArrayReader getArrayReader() throws JSONException{ valueGot = true; return new JSONArrayReader(input, logger); } public JSONObjectReader getObjectReader() throws JSONException{ valueGot = true; return new JSONObjectReader(input, logger); } @Override public boolean hasNext(){ return !endReached; } @Override public String next() throws NoSuchElementException{ if (endReached) throw new NoSuchElementException(); try{ readNext(); return nextKey; }catch(JSONException je){ logger.logUWS(LogLevel.ERROR, null, "RESTORATION", "Incorrect JSON format in an object!", je); endReached = true; return null; } } @Override public void remove() throws UnsupportedOperationException{ throw new UnsupportedOperationException(); } } /** * Lets reading a JSON array from a {@link JSONTokener} (that's to directly from a file) * as an iterator <b>which returns all items which MUST BE {@link JSONObject}s</b>. * * @author Grégory Mantelet (CDS) * @version 05/2012 */ protected final static class JSONArrayReader implements Iterator<JSONObject> { private final UWSLog logger; private final JSONTokener input; private final char closeToken; private boolean endReached = false; private JSONObject nextObj = null; public JSONArrayReader(final JSONTokener input, final UWSLog log) throws JSONException{ this.input = input; this.logger = log; char c = input.nextClean(); switch(c){ case '[': closeToken = ']'; break; case '(': closeToken = ')'; break; default: endReached = true; throw input.syntaxError("A JSONArray text must start with '['"); } readNext(); } protected void readNext() throws JSONException{ nextObj = null; while(nextObj == null && !endReached){ // Read the JSON object: char c = input.nextClean(); if (c != ',' && c != ';' && c != ']' && c != ')'){ input.back(); nextObj = (JSONObject)input.nextValue(); c = input.nextClean(); } // Ensures the next character is allowed (',' or ']'): switch(c){ case ';': case ',': if (input.nextClean() == ']'){ endReached = true; return; } input.back(); break; case ']': case ')': endReached = true; if (closeToken != c){ throw input.syntaxError("Expected a '" + new Character(closeToken) + "'"); } return; default: endReached = true; throw input.syntaxError("Expected a ',' or ']'"); } } } @Override public boolean hasNext(){ return (nextObj != null); } @Override public JSONObject next() throws NoSuchElementException{ if (nextObj == null && endReached) throw new NoSuchElementException(); JSONObject obj = nextObj; try{ readNext(); }catch(JSONException je){ logger.logUWS(LogLevel.ERROR, null, "RESTORATION", "Incorrect JSON format in an Array!", je); endReached = true; nextObj = null; } return obj; } @Override public void remove() throws UnsupportedOperationException{ throw new UnsupportedOperationException(); } } /** * An iterator of input streams with ONLY ONE input stream. * * @author Grégory Mantelet (CDS) * @version 05/2012 * * @see DefaultUWSBackupManager#restoreAll() */ protected final static class SingleInputIterator implements Iterator<InputStream> { private InputStream input; public SingleInputIterator(final InputStream input){ this.input = input; } @Override public boolean hasNext(){ return (input != null); } @Override public InputStream next() throws NoSuchElementException{ if (input == null) throw new NoSuchElementException(); else{ InputStream in = input; input = null; return in; } } @Override public void remove() throws UnsupportedOperationException{ throw new UnsupportedOperationException(); } } }