Newer
Older
package uws.service.file;
/*
* 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/>.
Grégory Mantelet
committed
* Copyright 2012-2018 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
* Astronomisches Rechen Institut (ARI)
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import uws.UWSException;
import uws.UWSToolBox;
import uws.job.ErrorSummary;
import uws.job.Result;
import uws.job.UWSJob;
import uws.job.user.JobOwner;
import uws.service.file.io.OutputStreamWithCloseAction;
import uws.service.file.io.RotateFileAction;
import uws.service.log.UWSLog.LogLevel;
import uws.service.request.UploadFile;
Grégory Mantelet
committed
* All UWS files are stored in the local machine into the specified directory.
Grégory Mantelet
committed
* The name of the log file, the result files and the backup files may be
* customised by overriding the following functions:
* {@link #getLogFileName(uws.service.log.UWSLog.LogLevel, String)},
* {@link #getResultFileName(Result, UWSJob)},
* {@link #getBackupFileName(JobOwner)} and {@link #getBackupFileName()}.
Grégory Mantelet
committed
* By default, results and backups are grouped by owner/user and owners/users
* are grouped thanks to {@link DefaultOwnerGroupIdentifier}. By using the
* appropriate constructor, you can change these default behaviours.
* <p>
Grégory Mantelet
committed
* A log file rotation is set by default so that avoiding a too big log file
* after several months/years of use. By default the rotation is done every
* month on the 1st at 6am. This frequency can be changed easily thanks to the
* function {@link #setLogRotationFreq(String)}.
* </p>
* @author Grégory Mantelet (CDS;ARI)
Grégory Mantelet
committed
* @version 4.4 (08/2018)
*/
public class LocalUWSFileManager implements UWSFileManager {
/** Format to use to format dates. */
private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
/** Default name of the log file. */
gmantele
committed
protected static final String DEFAULT_LOG_FILE_NAME = "service.log";
/** Default name of the general UWS backup file. */
gmantele
committed
protected static final String DEFAULT_BACKUP_FILE_NAME = "service.backup";
/** Directory in which all files managed by this class will be written and read. */
protected final File rootDirectory;
Grégory Mantelet
committed
/** Directory in which temporary files (e.g. uploads) should be stored.
* @since 4.4 */
protected File tmpDirectory = new File(System.getProperty("java.io.tmpdir"));
/** Output toward the service log file. */
protected PrintWriter logOutput = null;
/** Frequency at which the log file must be "rotated" (the file is renamed with the date of its first write and a new log file is created).
* Thus, too big log files can be avoided. */
protected EventFrequency logRotation = new EventFrequency("D 0 0"); // Log file rotation every day at midnight.
/** Indicate whether a directory must be used to gather all jobs, results and errors related to one identified user.
* If FALSE, all jobs, results and errors will be in only one directory, whoever owns them. */
protected final boolean oneDirectoryForEachUser;
/** Gather user directories, set by set. At the end, several user group directories may be created.
* This option is considered only if {@link #oneDirectoryForEachUser} is TRUE. */
protected final boolean groupUserDirectories;
/** Object giving the policy about how to group user directories. */
protected final OwnerGroupIdentifier ownerGroupId;
/**
* <p>Builds a {@link UWSFileManager} which manages all UWS files in the given directory.</p>
* <p>
* There will be one directory for each owner ID and owner directories will be grouped
* thanks to {@link DefaultOwnerGroupIdentifier}.
* </p>
* @param root UWS root directory.
*
* @throws NullPointerException If the given root directory is <i>null</i>.
* @throws UWSException If the given file is not a directory or has not the READ and WRITE permissions.
* @see #LocalUWSFileManager(File, boolean, boolean, OwnerGroupIdentifier)
*/
gmantele
committed
public LocalUWSFileManager(final File root) throws UWSException{
this(root, true, true, null);
}
/**
* <p>Builds a {@link UWSFileManager} which manages all UWS files in the given directory.</p>
* <p>
* If, according to the third parameter, the owner directories must be grouped,
* the {@link DefaultOwnerGroupIdentifier} will be used.
* </p>
* @param root UWS root directory.
* @param oneDirectoryForEachUser <i>true</i> to create one directory for each owner ID, <i>false</i> otherwise.
* @param groupUserDirectories <i>true</i> to group user directories, <i>false</i> otherwise.
* <i><u>note:</u> this value is ignored if the previous parameter is false.</i>
*
* @throws NullPointerException If the given root directory is <i>null</i>.
* @throws UWSException If the given file is not a directory or has not the READ and WRITE permissions.
* @see #LocalUWSFileManager(File, boolean, boolean, OwnerGroupIdentifier)
*/
gmantele
committed
public LocalUWSFileManager(final File root, final boolean oneDirectoryForEachUser, final boolean groupUserDirectories) throws UWSException{
this(root, oneDirectoryForEachUser, groupUserDirectories, null);
}
/**
* Builds a {@link UWSFileManager} which manages all UWS files in the given directory.
* @param root UWS root directory.
* @param oneDirectoryForEachUser <i>true</i> to create one directory for each owner ID, <i>false</i> otherwise.
* @param groupUserDirectories <i>true</i> to group user directories, <i>false</i> otherwise.
* <i><u>note:</u> this value is ignored if the previous parameter is false.</i>
* @param ownerGroupIdentifier The "function" to use to identify the group of a job owner.
* <i><ul>
* <li><u>note 1:</u> this value is ignored if one of the two previous parameters is false.</li>
* <li><u>note 2:</u> if this value is null but the previous parameters are true,
* {@link DefaultOwnerGroupIdentifier} will be chosen as default group identifier.</li>
* </ul></i>
*
* @throws NullPointerException If the given root directory is <i>null</i>.
* @throws UWSException If the given file is not a directory or has not the READ and WRITE permissions.
gmantele
committed
public LocalUWSFileManager(final File root, final boolean oneDirectoryForEachUser, final boolean groupUserDirectories, final OwnerGroupIdentifier ownerGroupIdentifier) throws UWSException{
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
if (root == null)
throw new NullPointerException("Missing root directory ! Impossible to create a LocalUWSFileManager.");
else if (!root.exists()){
if (!root.mkdirs())
throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "The given root directory does not exist and can not be created automatically !");
}else if (!root.isDirectory())
throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "The root directory of a UWSFileManager must be a DIRECTORY !");
else if (!root.canRead())
throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Missing READ permission for the root directory of a UWSFileManager !");
else if (!root.canWrite())
throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Missing WRITE permission for the root directory of a UWSFileManager !");
rootDirectory = root;
this.oneDirectoryForEachUser = oneDirectoryForEachUser;
if (this.oneDirectoryForEachUser){
this.groupUserDirectories = groupUserDirectories;
if (this.groupUserDirectories){
if (ownerGroupIdentifier != null)
this.ownerGroupId = ownerGroupIdentifier;
else
this.ownerGroupId = new DefaultOwnerGroupIdentifier();
}else
this.ownerGroupId = null;
}else{
this.groupUserDirectories = false;
this.ownerGroupId = null;
}
}
/**
* Gets the directory of the given owner.
* @param owner A job owner.
* @return Its directory.
*/
public File getOwnerDirectory(final JobOwner owner){
if (!oneDirectoryForEachUser || owner == null || owner.getID() == null || owner.getID().trim().isEmpty())
return rootDirectory;
File ownerDir = rootDirectory;
if (groupUserDirectories){
String ownerGroup = ownerGroupId.getOwnerGroup(owner);
if (ownerGroup != null)
ownerDir = new File(rootDirectory, ownerGroup);
}
ownerDir = new File(ownerDir, owner.getID().replaceAll(Pattern.quote(File.separator), "_"));
return ownerDir;
}
/**
* Removes the owner directory if there is no more file in it (except the backup file which is no more required).
* @param owner The user whose the directory must be removed.
* @throws IOException If there is an error while removing the owner directory.
*/
gmantele
committed
protected void cleanOwnerDirectory(final JobOwner owner) throws IOException{
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// Remove the owner directory if empty or if only the owner backup file exists:
if (owner != null && oneDirectoryForEachUser){
File ownerDir = getOwnerDirectory(owner);
String[] dirContent = ownerDir.list();
// if empty...
if (dirContent.length <= 1){
// delete the owner backup file it it exists:
if (dirContent.length == 1 && dirContent[0].equals(getBackupFileName(owner))){
(new File(rootDirectory, getBackupFileName(owner))).delete();
dirContent = ownerDir.list();
}
// if empty (so, if the owner backup file does not exist any more)...
if (dirContent.length == 0){
// delete the owner directory !
if (ownerDir.delete() && groupUserDirectories){
// if the user group directory is also empty now, delete it:
File userGroupDir = ownerDir.getParentFile();
if (userGroupDir.list().length == 0)
userGroupDir.delete();
}
}
}
}
}
/* ******************* */
/* LOG FILE MANAGEMENT */
/* ******************* */
/**
* Get the frequency of the log file rotation
* in a human readable way.
* @return A human readable frequency of the log file rotation.
*/
public final String getLogRotationFreq(){
return logRotation.toString();
}
* <p>Set the frequency at which a rotation of the log file must be done.</p>
* <p>
* "rotation" means here, to close the currently used log file, to rename it so that suffixing it
* with the date at which the first log has been written in it, and to create a new log file.
* </p>
* <p>The frequency string must respect the following syntax:</p>
* <ul>
* <li>'D' hh mm : daily schedule at hh:mm</li>
* <li>'W' dd hh mm : weekly schedule at the given day of the week (1:sunday, 2:monday, ..., 7:saturday) at hh:mm</li>
* <li>'M' dd hh mm : monthly schedule at the given day of the month at hh:mm</li>
* <li>'h' mm : hourly schedule at the given minute</li>
* <li>'m' : scheduled every minute (for completness :-))</li>
* </ul>
* <p><i>Where: hh = integer between 0 and 23, mm = integer between 0 and 59, dd (for 'W') = integer between 1 and 7 (1:sunday, 2:monday, ..., 7:saturday),
* dd (for 'M') = integer between 1 and 31.</i></p>
* <p><i><b>Warning:</b>
* The frequency type is case sensitive! Then you should particularly pay attention at the case
* when using the frequency types 'M' (monthly) and 'm' (every minute).
* </p>
* <p>
* Parsing errors are not thrown but "resolved" silently. The "solution" depends of the error.
* 2 cases of errors are considered:
* </p>
* <ul>
* <li><b>Frequency type mismatch:</b> It happens when the first character is not one of the expected (D, W, M, h, m).
* That means: bad case (i.e. 'd' rather than 'D'), another character.
* In this case, the frequency will be: <b>daily at 00:00</b>.</li>
* <li><b>Parameter(s) missing or incorrect:</b> With the "daily" frequency ('D'), at least 2 parameters must be provided ;
* 3 for "weekly" ('W') and "monthly" ('M') ; only 1 for "hourly" ('h') ; none for "every minute" ('m').
* This number of parameters is a minimum: only the n first parameters will be considered while
* the others will be ignored.
* If this minimum number of parameters is not respected or if a parameter value is incorrect,
* <b>all parameters will be set to their default value</b>
* (which is 0 for all parameter except dd for which it is 1).</li>
* </ul>
* <p>Examples:</p>
* <ul>
* <li><i>"" or NULL</i> = every day at 00:00</li>
* <li><i>"D 06 30" or "D 6 30"</i> = every day at 06:30</li>
* <li><i>"D 24 30"</i> = every day at 00:00, because hh must respect the rule: 0 ≤ hh ≤ 23</li>
* <li><i>"d 06 30" or "T 06 30"</i> = every day at 00:00, because the frequency type "d" (lower case of "D") or "T" do not exist</li>
* <li><i>"W 2 6 30"</i> = every week on Tuesday at 06:30</li>
* <li><i>"W 8 06 30"</i> = every week on Sunday at 00:00, because with 'W' dd must respect the rule: 1 ≤ dd ≤ 7</li>
* <li><i>"M 2 6 30"</i> = every month on the 2nd at 06:30</li>
* <li><i>"M 32 6 30"</i> = every month on the 1st at 00:00, because with 'M' dd must respect the rule: 1 ≤ dd ≤ 31</li>
* <li><i>"M 5 6 30 12"</i> = every month on the 5th at 06:30, because at least 3 parameters are expected and so considered: "12" and eventual other parameters are ignored</li>
* </ul>
* @param interval Interval between two log rotations.
public final void setLogRotationFreq(final String interval){
logRotation = new EventFrequency(interval);
}
/**
* <p>Gets the name of the UWS log file.</p>
* <p>By default: {@link #DEFAULT_LOG_FILE_NAME}.</p>
* @param level Level of the message to log (DEBUG, INFO, WARNING, ERROR, FATAL).
* @param context Context of the message to log (UWS, HTTP, THREAD, JOB, ...).
* @return The name of the UWS log file.
*/
protected String getLogFileName(final LogLevel level, final String context){
return DEFAULT_LOG_FILE_NAME;
}
/**
* Gets the UWS log file.
* @param level Level of the message to log (DEBUG, INFO, WARNING, ERROR, FATAL).
* @param context Context of the message to log (UWS, HTTP, THREAD, JOB, ...).
* @return The UWS log file.
* @see #getLogFileName(uws.service.log.UWSLog.LogLevel, String)
protected File getLogFile(final LogLevel level, final String context){
return new File(rootDirectory, getLogFileName(level, context));
}
@Override
public InputStream getLogInput(final LogLevel level, final String context) throws IOException{
File logFile = getLogFile(level, context);
if (logFile.exists())
return new FileInputStream(logFile);
else
return null;
}
@Override
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
public synchronized PrintWriter getLogOutput(final LogLevel level, final String context) throws IOException{
// If a file rotation is needed...
if (logOutput != null && logRotation != null && logRotation.isTimeElapsed()){
// ...Close the output stream:
logOutput.close();
logOutput = null;
// ...Rename this log file:
// get the file:
File logFile = getLogFile(level, context);
// and its name:
String logFileName = logFile.getName();
// separate the file name from the extension:
String fileExt = "";
int indFileExt = logFileName.lastIndexOf('.');
if (indFileExt >= 0){
fileExt = logFileName.substring(indFileExt);
logFileName = logFileName.substring(0, indFileExt);
}
// build the new file name and rename the log file:
logFile.renameTo(new File(logFile.getParentFile(), logFileName + "_" + logRotation.getEventID() + fileExt));
}
// If the log output is not yet set or if a file rotation has been done...
if (logOutput == null){
// ...Create the output:
File logFile = getLogFile(level, context);
createParentDir(logFile);
logOutput = new PrintWriter(new FileOutputStream(logFile, true), true);
// ...Write a log header:
printLogHeader(logOutput);
// ...Set the date of the next rotation:
if (logRotation != null)
logRotation.nextEvent();
return logOutput;
}
/**
* Print a header into the log file so that separating older log messages to the new ones.
*/
protected void printLogHeader(final PrintWriter out){
String msgHeader = "########################################### LOG STARTS " + dateFormat.format(new Date()) + " (file rotation: " + logRotation + ") ###########################################";
StringBuffer buf = new StringBuffer("");
gmantele
committed
for(int i = 0; i < msgHeader.length(); i++)
buf.append('#');
String separator = buf.toString();
out.println(separator);
out.println(msgHeader);
out.println(separator);
out.flush();
}
/* ************************* */
/* UPLOADED FILES MANAGEMENT */
/* ************************* */
/**
* Create a File instance from the given upload file description.
* This function is able to deal with location as URI and as file path.
* @param upload Description of an uploaded file.
* @return The corresponding File object.
* @since 4.1
*/
protected final File getFile(final UploadFile upload){
if (upload.getLocation().startsWith("file:")){
try{
return new File(new URI(upload.getLocation()));
}catch(URISyntaxException use){
return new File(upload.getLocation());
}
}else
return new File(upload.getLocation());
}
Grégory Mantelet
committed
@Override
public File getTmpDirectory(){
return tmpDirectory;
}
@Override
public boolean setTmpDirectory(final File newTmpDir){
if (newTmpDir == null || !newTmpDir.exists() || !newTmpDir.isDirectory() || !newTmpDir.canRead() || !newTmpDir.canWrite())
return false;
else{
tmpDirectory = newTmpDir;
return true;
}
}
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
@Override
public InputStream getUploadInput(final UploadFile upload) throws IOException{
// Check the source file:
File source = getFile(upload);
if (!source.exists())
throw new FileNotFoundException("The uploaded file submitted with the parameter \"" + upload.paramName + "\" can not be found any more on the server!");
// Return the stream:
return new FileInputStream(source);
}
@Override
public InputStream openURI(final URI uri) throws UnsupportedURIProtocolException, IOException{
String scheme = uri.getScheme();
if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("ftp"))
return uri.toURL().openStream();
else
throw new UnsupportedURIProtocolException(uri);
}
@Override
public void deleteUpload(final UploadFile upload) throws IOException{
File f = getFile(upload);
if (!f.exists())
return;
else if (f.isDirectory())
throw new IOException("Incorrect location! An uploaded file must be a regular file, not a directory. (file location: \"" + f.getPath() + "\")");
else{
try{
if (!f.delete())
throw new IOException("Can not delete the file!");
}catch(SecurityException se){
throw new IOException("Unexpected permission restriction on the uploaded file \"" + f.getPath() + "\" => can not delete it!");
}
}
}
@Override
public String moveUpload(final UploadFile upload, final UWSJob destination) throws IOException{
// Check the source file:
File source = getFile(upload);
if (!source.exists())
throw new FileNotFoundException("The uploaded file submitted with the parameter \"" + upload.paramName + "\" can not be found any more on the server!");
// Build the final location (in the owner directory, under the name "UPLOAD_{job-id}_{param-name}":
File ownerDir = getOwnerDirectory(destination.getOwner());
File copy = new File(ownerDir, "UPLOAD_" + destination.getJobId() + "_" + upload.paramName);
OutputStream output = null;
InputStream input = null;
boolean done = false;
try{
// open the input and output:
input = new BufferedInputStream(getUploadInput(upload));
output = new BufferedOutputStream(new FileOutputStream(copy));
// proceed to the copy:
byte[] buffer = new byte[2048];
int len;
while((len = input.read(buffer)) > 0)
output.write(buffer, 0, len);
output.flush();
output.close();
output = null;
// close the input and delete the source file:
input.close();
input = null;
source.delete();
// return the new location:
done = true;
return copy.toURI().toString();
}finally{
if (output != null){
try{
output.close();
}catch(IOException ioe){
}
}
if (input != null){
try{
input.close();
}catch(IOException ioe){
}
}
// In case of problem, the copy must be deleted:
if (!done && copy.exists()){
try{
copy.delete();
}catch(SecurityException ioe){
}
}
}
}
/* *********************** */
/* RESULT FILES MANAGEMENT */
/* *********************** */
/**
* <p>Gets the name of the file in which the given result is/must be written.</p>
* <p>By default: jobID + "_" + resultID + "." + {@link UWSToolBox#getFileExtension(String) getFileExtension(resultMIMEType)}</p>
* <p><i><u>note:</u> there is no file extension if the MIME type of the result is unknown !</i></p>
* @param result The result whose the file name is asked.
* @param job The job which owns the given result.
* @return Name of the file corresponding to the given result.
* @see UWSToolBox#getFileExtension(String)
*/
protected String getResultFileName(final Result result, final UWSJob job){
gmantele
committed
String fileName = job.getJobId() + "_";
if (result != null && result.getId() != null && !result.getId().trim().isEmpty())
fileName += result.getId();
else
fileName += Result.DEFAULT_RESULT_NAME;
String fileExt = UWSToolBox.getFileExtension(result.getMimeType());
gmantele
committed
fileExt = (fileExt == null) ? "" : ("." + fileExt);
fileName += fileExt;
return fileName;
}
/**
* Gets the file corresponding to the given result.
* @param result The result whose the file is asked.
* @param job The job which owns the given result.
* @return The file corresponding to the given result.
* @see #getOwnerDirectory(JobOwner)
* @see #getResultFileName(Result, UWSJob)
*/
protected File getResultFile(final Result result, final UWSJob job){
File ownerDir = getOwnerDirectory(job.getOwner());
return new File(ownerDir, getResultFileName(result, job));
}
@Override
gmantele
committed
public InputStream getResultInput(Result result, UWSJob job) throws IOException{
File resultFile = getResultFile(result, job);
return resultFile.exists() ? new FileInputStream(resultFile) : null;
}
@Override
gmantele
committed
public OutputStream getResultOutput(Result result, UWSJob job) throws IOException{
File resultFile = getResultFile(result, job);
createParentDir(resultFile);
return new FileOutputStream(resultFile);
}
@Override
gmantele
committed
public long getResultSize(Result result, UWSJob job) throws IOException{
File resultFile = getResultFile(result, job);
if (resultFile == null || !resultFile.exists())
return -1;
else
return resultFile.length();
}
@Override
gmantele
committed
public boolean deleteResult(Result result, UWSJob job) throws IOException{
boolean deleted = getResultFile(result, job).delete();
if (deleted)
cleanOwnerDirectory(job.getOwner());
return deleted;
}
/* ********************** */
/* ERROR FILES MANAGEMENT */
/* ********************** */
/**
* <p>Gets the name of the file in which the described error is/must be written.</p>
* <p>By default: jobID + "_ERROR.log"</p>
* @param error The description of the error whose the file name is asked.
* @param job The job which owns the given error.
* @return Name of the file corresponding to the described error.
*/
gmantele
committed
protected String getErrorFileName(final ErrorSummary error, final UWSJob job){
return job.getJobId() + "_ERROR.log";
}
/**
* Gets the file corresponding to the described error.
* @param error The error whose the file is asked.
* @param job The job which owns the given error.
* @return The file corresponding to the described error.
* @see #getOwnerDirectory(JobOwner)
* @see #getErrorFileName(ErrorSummary, UWSJob)
*/
protected File getErrorFile(final ErrorSummary error, final UWSJob job){
File ownerDir = getOwnerDirectory(job.getOwner());
return new File(ownerDir, getErrorFileName(error, job));
}
@Override
gmantele
committed
public InputStream getErrorInput(ErrorSummary error, UWSJob job) throws IOException{
File errorFile = getErrorFile(error, job);
return errorFile.exists() ? new FileInputStream(errorFile) : null;
}
@Override
gmantele
committed
public OutputStream getErrorOutput(ErrorSummary error, UWSJob job) throws IOException{
File errorFile = getErrorFile(error, job);
createParentDir(errorFile);
return new FileOutputStream(errorFile);
}
@Override
gmantele
committed
public long getErrorSize(ErrorSummary error, UWSJob job) throws IOException{
File errorFile = getErrorFile(error, job);
if (errorFile == null || !errorFile.exists())
return -1;
else
return errorFile.length();
}
@Override
gmantele
committed
public boolean deleteError(ErrorSummary error, UWSJob job) throws IOException{
boolean deleted = getErrorFile(error, job).delete();
if (deleted)
cleanOwnerDirectory(job.getOwner());
return deleted;
}
/* *********************** */
/* BACKUP FILES MANAGEMENT */
/* *********************** */
/**
* <p>Gets the name of the backup file of the given job owner (~ UWS user).</p>
* <p>By default: ownerID + ".backup"</p>
* @param owner The job owner whose the name of the backup file is asked.
* @return The name of the backup file of the given owner.
* @throws IllegalArgumentException If the given owner is <i>null</i> or an empty string.
*/
gmantele
committed
protected String getBackupFileName(final JobOwner owner) throws IllegalArgumentException{
if (owner == null || owner.getID() == null || owner.getID().trim().isEmpty())
throw new IllegalArgumentException("Missing owner! Can not get the backup file of an unknown owner.");
return owner.getID().replaceAll(Pattern.quote(File.separator), "_") + ".backup";
}
@Override
gmantele
committed
public InputStream getBackupInput(JobOwner owner) throws IllegalArgumentException, IOException{
File backupFile = new File(getOwnerDirectory(owner), getBackupFileName(owner));
return backupFile.exists() ? new FileInputStream(backupFile) : null;
}
@Override
public Iterator<InputStream> getAllUserBackupInputs(){
return new LocalAllUserBackupInputs(this);
}
@Override
gmantele
committed
public OutputStream getBackupOutput(JobOwner owner) throws IllegalArgumentException, IOException{
File backupFile = new File(getOwnerDirectory(owner), getBackupFileName(owner));
Grégory Mantelet
committed
File tempBackupFile = new File(getOwnerDirectory(owner), getBackupFileName(owner) + ".temp-" + System.currentTimeMillis());
createParentDir(backupFile);
Grégory Mantelet
committed
return new OutputStreamWithCloseAction(new FileOutputStream(tempBackupFile), new RotateFileAction(tempBackupFile, backupFile));
}
/**
* <p>Gets the name of the UWS general backup file.</p>
* <p>By default: {@link #DEFAULT_BACKUP_FILE_NAME}</p>
* @return The name of the UWS general backup file.
*/
gmantele
committed
protected String getBackupFileName(){
return DEFAULT_BACKUP_FILE_NAME;
}
@Override
gmantele
committed
public InputStream getBackupInput() throws IOException{
File backupFile = new File(rootDirectory, getBackupFileName());
return backupFile.exists() ? new FileInputStream(backupFile) : null;
}
@Override
gmantele
committed
public OutputStream getBackupOutput() throws IOException{
File backupFile = new File(rootDirectory, getBackupFileName());
File tempBackupFile = new File(rootDirectory, getBackupFileName() + ".temp-" + System.currentTimeMillis());
createParentDir(backupFile);
return new OutputStreamWithCloseAction(new FileOutputStream(tempBackupFile), new RotateFileAction(tempBackupFile, backupFile));
}
/* ************** */
/* TOOL FUNCTIONS */
/* ************** */
/**
* Creates the parent directory(ies) if it(they) does/do not exist.
* @param f The file whose the parent directory must exist after the call of this function.
* @return <i>true</i> if the parent directory now exists, <i>false</i> otherwise.
*/
protected boolean createParentDir(final File f){
if (!f.getParentFile().exists())
return f.getParentFile().mkdirs();
else
return true;
}
/**
* Lets iterating on all user backup files.
* The {@link #next()} function creates and returns the {@link InputStream} for the next backup file.
* @author Grégory Mantelet (CDS)
* @version 05/2012
*/
protected class LocalAllUserBackupInputs implements Iterator<InputStream> {
private final LocalUWSFileManager fileManager;
private Iterator<File> itBackupFiles;
private final FileFilter dirFilter = new DirectoryFilter();
private final OwnerFileFilter ownerFileFilter = new OwnerFileFilter();
public LocalAllUserBackupInputs(final LocalUWSFileManager fm){
fileManager = fm;
itBackupFiles = loadAllBackupFiles().iterator();
}
private List<File> loadAllBackupFiles(){
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
ArrayList<File> backupFiles = new ArrayList<File>();
// If there must be 1 directory by user:
if (fileManager.oneDirectoryForEachUser){
File[] dir0 = fileManager.rootDirectory.listFiles(dirFilter);
// If user directories must be grouped (so, we have the list of all user groups):
if (fileManager.groupUserDirectories){
for(File groupDir : dir0){
File[] dir1 = groupDir.listFiles(dirFilter);
for(File userDir : dir1)
addOwnerBackupFiles(backupFiles, userDir, userDir.getName());
}
}// Otherwise: We have already all user directories:
else{
for(File userDir : dir0)
addOwnerBackupFiles(backupFiles, userDir, userDir.getName());
}
}// Otherwise: Get all backup files into the root directory:
else
addOwnerBackupFiles(backupFiles, fileManager.rootDirectory, null);
return backupFiles;
}
private void addOwnerBackupFiles(final ArrayList<File> files, final File rootDirectory, final String ownerID){
ownerFileFilter.setOwnerID(ownerID);
File[] backups = rootDirectory.listFiles(ownerFileFilter);
for(File f : backups)
files.add(f);
}
@Override
gmantele
committed
public boolean hasNext(){
return itBackupFiles != null && itBackupFiles.hasNext();
}
/**
* If the file whose the input stream must be created and returned does not exist
* or has not the READ permission, <i>null</i> will be returned.
* @see java.util.Iterator#next()
*/
@Override
gmantele
committed
public InputStream next() throws NoSuchElementException{
if (itBackupFiles == null)
throw new NoSuchElementException();
gmantele
committed
try{
File f = itBackupFiles.next();
if (!itBackupFiles.hasNext())
itBackupFiles = null;
return (f == null || !f.exists()) ? null : new FileInputStream(f);
gmantele
committed
}catch(FileNotFoundException e){
return null;
}
}
@Override
gmantele
committed
public void remove(){
throw new UnsupportedOperationException();
}
}
/**
* Filter which lets returning only the directories.
* @author Grégory Mantelet (CDS)
* @version 05/2012
*/
protected final static class DirectoryFilter implements FileFilter {
@Override
gmantele
committed
public boolean accept(File f){
return f != null && f.isDirectory();
}
}
/**
* Filter which lets returning only the backup file(s) of the specified user/owner.
* @author Gr&ecaute;gory Mantelet (CDS)
* @version 05/2012
*/
protected final class OwnerFileFilter implements FileFilter {
protected String ownerID = null;
gmantele
committed
/**
* Sets the ID of the user whose the backup file must be returned.
* If <i>null</i>, all the found backup files will be returned EXCEPT the backup file for the whole UWS.
* @param ownerID ID of the user whose the backup file must be returned. (MAY BE NULL)
*/
public void setOwnerID(final String ownerID){
this.ownerID = ownerID;
}
gmantele
committed
gmantele
committed
public boolean accept(File f){
if (f == null || f.isDirectory())
return false;
else if (ownerID == null || ownerID.trim().isEmpty())
return f.getName().endsWith(".backup") && !f.getName().equalsIgnoreCase(getBackupFileName());
else
gmantele
committed
return f.getName().equalsIgnoreCase(ownerID + ".backup");