SpooledInformTask.java
/*
* This file is part of Waarp Project (named also Waarp or GG).
*
* Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
* tags. See the COPYRIGHT.txt in the distribution for a full listing of
* individual contributors.
*
* All Waarp Project is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Waarp 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* Waarp . If not, see <http://www.gnu.org/licenses/>.
*/
package org.waarp.openr66.context.task;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.waarp.common.digest.FilesystemBasedDigest;
import org.waarp.common.filemonitor.FileMonitor.FileItem;
import org.waarp.common.filemonitor.FileMonitor.FileMonitorInformation;
import org.waarp.common.json.JsonHandler;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;
import org.waarp.common.utility.ParametersChecker;
import org.waarp.common.utility.WaarpStringUtils;
import org.waarp.openr66.client.SpooledDirectoryTransfer;
import org.waarp.openr66.protocol.configuration.Configuration;
import org.waarp.openr66.protocol.configuration.Messages;
import org.waarp.openr66.protocol.exception.OpenR66ProtocolPacketException;
import org.waarp.openr66.protocol.localhandler.packet.BusinessRequestPacket;
import org.waarp.openr66.protocol.utils.ChannelUtils;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.util.Date;
import java.util.Set;
import java.util.TreeMap;
import static org.waarp.common.database.DbConstant.*;
/**
* Java Task for SpooledDirectory information to the Waarp Server
*/
public class SpooledInformTask extends AbstractExecJavaTask {
private static final String TD_TD = "</TD><TD>";
private static final String TH_TH = "</TH><TH>";
/**
* Internal Logger
*/
private static final WaarpLogger logger =
WaarpLoggerFactory.getLogger(SpooledInformTask.class);
static final TreeMap<String, SpooledInformation> spooledInformationMap =
new TreeMap<String, SpooledInformation>();
public static class SpooledInformation {
public final String host;
public FileMonitorInformation fileMonitorInformation;
public Date lastUpdate = new Date();
/**
* @param host
* @param fileMonitorInformation
*/
private SpooledInformation(final String host,
final FileMonitorInformation fileMonitorInformation) {
this.host = host;
this.fileMonitorInformation = fileMonitorInformation;
}
}
@Override
public final void run() {
if (callFromBusiness) {
// Business Request to validate?
String validated = SpooledDirectoryTransfer.PARTIALOK;
if (isToValidate) {
try {
final FileMonitorInformation fileMonitorInformation =
JsonHandler.mapper.readValue(fullarg,
FileMonitorInformation.class);
logger.info(
"Receive SpooledInform of size: " + fullarg.length() + " (" +
fileMonitorInformation.fileItems.size() + ", " +
(fileMonitorInformation.removedFileItems != null?
fileMonitorInformation.removedFileItems.size() : -1) + ')');
final String host = session.getAuth().getUser();
synchronized (spooledInformationMap) {
if (fileMonitorInformation.removedFileItems == null ||
fileMonitorInformation.removedFileItems.isEmpty()) {
final SpooledInformation old =
spooledInformationMap.put(fileMonitorInformation.name,
new SpooledInformation(host,
fileMonitorInformation));
if (old != null && old.fileMonitorInformation != null) {
if (old.fileMonitorInformation.directories != null) {
old.fileMonitorInformation.directories.clear();
}
if (old.fileMonitorInformation.fileItems != null) {
old.fileMonitorInformation.fileItems.clear();
}
old.fileMonitorInformation = null;
}
} else {
// partial update
final SpooledInformation update =
spooledInformationMap.get(fileMonitorInformation.name);
if (update == null) {
// Issue since update is not existing so full update is needed next time
spooledInformationMap.put(fileMonitorInformation.name,
new SpooledInformation(host,
fileMonitorInformation));
validated = SpooledDirectoryTransfer.NEEDFULL;
} else {
for (final String item : fileMonitorInformation.removedFileItems) {
update.fileMonitorInformation.fileItems.remove(item);
}
update.fileMonitorInformation.fileItems.putAll(
fileMonitorInformation.fileItems);
update.lastUpdate = new Date();
}
}
}
} catch (final JsonParseException e1) {
logger.warn("Cannot parse SpooledInformation: " + fullarg + ' ' +
e1.getMessage());
} catch (final JsonMappingException e1) {
logger.warn("Cannot parse SpooledInformation: " + fullarg + ' ' +
e1.getMessage());
} catch (final IOException e1) {
logger.warn("Cannot parse SpooledInformation: " + e1.getMessage());
}
final BusinessRequestPacket packet =
new BusinessRequestPacket(getClass().getName() + " informed", 0);
if (!validate(packet)) {
// No Validation therefore send the new packet back
try {
ChannelUtils.writeAbstractLocalPacket(
session.getLocalChannelReference(), packet, true);
} catch (final OpenR66ProtocolPacketException ignored) {
// nothing
}
}
status = 0;
}
finalValidate(validated);
} else {
// unallowed
logger.warn("SpooledInformTask not allowed as Java Task: " + fullarg);
invalid();
}
}
/**
* @param detailed
* @param status 1 for ok, -1 for ko, 0 for all
* @param uri
*
* @return the StringBuilder containing the HTML format as a Table of the
* current Spooled information
*/
public static StringBuilder buildSpooledTable(final boolean detailed,
final int status,
final String uri) {
final StringBuilder builder = beginSpooledTable(detailed, uri);
// get current information
synchronized (spooledInformationMap) {
final Set<String> names = spooledInformationMap.keySet();
for (final String name : names) {
// per Name
buildSpooledTableElement(detailed, status, builder, name);
}
}
endSpooledTable(builder);
return builder;
}
/**
* @param name
* @param uri
*
* @return the StringBuilder containing the HTML format as a Table of the
* current Spooled information
*/
public static StringBuilder buildSpooledUniqueTable(final String uri,
final String name) {
final StringBuilder builder = beginSpooledTable(false, uri);
// get current information
synchronized (spooledInformationMap) {
// per Name
final SpooledInformation inform =
buildSpooledTableElement(false, 0, builder, name);
endSpooledTable(builder);
builder.append("<BR>");
if (inform != null) {
buildSpooledTableFiles(builder, inform);
}
}
return builder;
}
/**
* @param builder
*/
private static void endSpooledTable(final StringBuilder builder) {
builder.append("</TBODY></TABLE></small>");
}
/**
* @param detailed
* @param uri
*
* @return the associated StringBuilder as temporary result
*/
private static StringBuilder beginSpooledTable(final boolean detailed,
final String uri) {
final StringBuilder builder = new StringBuilder();
builder.append(
"<small><TABLE class='table table-condensed table-bordered' BORDER=1><CAPTION><A HREF=");
builder.append(uri);
if (detailed) {
builder.append(
Messages.getString("SpooledInformTask.TitleDetailed")); //$NON-NLS-1$
} else {
builder.append(
Messages.getString("SpooledInformTask.TitleNormal")); //$NON-NLS-1$
}
// title first
builder.append("<THEAD><TR><TH>")
.append(Messages.getString("SpooledInformTask.0")) //$NON-NLS-1$
.append(TH_TH)
.append(Messages.getString("SpooledInformTask.1")) //$NON-NLS-1$
.append(TH_TH)
.append(Messages.getString("SpooledInformTask.2")) //$NON-NLS-1$
.append(TH_TH)
.append(Messages.getString("SpooledInformTask.3")) //$NON-NLS-1$
.append(TH_TH)
.append(Messages.getString("SpooledInformTask.4")) //$NON-NLS-1$
.append(TH_TH)
.append(Messages.getString("SpooledInformTask.5")) //$NON-NLS-1$
.append(TH_TH)
.append(Messages.getString("SpooledInformTask.6")) //$NON-NLS-1$
.append(TH_TH)
.append(Messages.getString("SpooledInformTask.7")) //$NON-NLS-1$
.append(TH_TH)
.append(Messages.getString("SpooledInformTask.8")) //$NON-NLS-1$
.append(TH_TH)
.append(Messages.getString("SpooledInformTask.9")) //$NON-NLS-1$
.append("</TH></TR></THEAD><TBODY>");
return builder;
}
/**
* @param detailed
* @param status
* @param builder
* @param name
*/
private static SpooledInformation buildSpooledTableElement(
final boolean detailed, final int status, final StringBuilder builder,
final String name) {
final SpooledInformation inform = spooledInformationMap.get(name);
if (inform == null) {
return null;
}
final long time = inform.lastUpdate.getTime() +
Configuration.configuration.getTimeoutCon();
final long curtime = System.currentTimeMillis();
if (time + Configuration.configuration.getTimeoutCon() < curtime) {
if (status > 0) {
return inform;
}
} else {
if (status < 0) {
return inform;
}
}
builder.append("<TR><TH>").append(name.replace(',', ' '))
.append("</TH><TD>").append(inform.host).append("</TD>");
if (time + Configuration.configuration.getTimeoutCon() < curtime) {
builder.append("<TD bgcolor=Red>");
} else if (time < curtime) {
builder.append("<TD bgcolor=Orange>");
} else {
builder.append("<TD bgcolor=LightGreen>");
}
final DateFormat dateFormat =
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.LONG);
builder.append(dateFormat.format(inform.lastUpdate)).append("</TD>");
if (inform.fileMonitorInformation != null) {
builder.append(
Messages.getString("SpooledInformTask.AllOk")) //$NON-NLS-1$
.append(inform.fileMonitorInformation.globalok).append(
Messages.getString("SpooledInformTask.AllError")) //$NON-NLS-1$
.append(inform.fileMonitorInformation.globalerror).append(
Messages.getString("SpooledInformTask.TodayOk")) //$NON-NLS-1$
.append(inform.fileMonitorInformation.todayok).append(
Messages.getString("SpooledInformTask.TodayError")) //$NON-NLS-1$
.append(inform.fileMonitorInformation.todayerror).append(TD_TD)
.append(inform.fileMonitorInformation.elapseTime).append(TD_TD)
.append(inform.fileMonitorInformation.stopFile).append(TD_TD)
.append(inform.fileMonitorInformation.statusFile).append(TD_TD)
.append(inform.fileMonitorInformation.scanSubDir).append("</TD>");
final StringBuilder dirs =
new StringBuilder("<ul class='list-unstyled'>");
for (final File dir : inform.fileMonitorInformation.directories) {
dirs.append("<li>").append(dir).append("</li>");
}
dirs.append("</ul>");
builder.append("<TD>").append(dirs).append(TD_TD);
if (detailed && inform.fileMonitorInformation.fileItems != null) {
buildSpooledTableFiles(builder, inform);
} else {
// simply print number of files
if (inform.fileMonitorInformation.fileItems != null) {
builder.append(inform.fileMonitorInformation.fileItems.size());
} else {
builder.append(0);
}
// Form GET to ensure encoding
builder.append(
"<FORM class='form-inline' name='DETAIL' method='GET' action='/SpooledDetailed.html'><input type=hidden name='name' value='")
.append(name).append(
"'/><INPUT type='submit' class='btn btn-info btn-sm' value='DETAIL'/></FORM>");
}
}
builder.append("</TD></TR>");
return inform;
}
/**
* @param builder
* @param inform
*/
private static void buildSpooledTableFiles(final StringBuilder builder,
final SpooledInformation inform) {
builder.append(
"<small><TABLE class='table table-condensed table-bordered' BORDER=1><THEAD><TR><TH>") //$NON-NLS-1$
.append(Messages.getString("SpooledInformTask.10")).append(TH_TH)
.append(Messages.getString("SpooledInformTask.11")) //$NON-NLS-2$
.append(TH_TH)
.append(Messages.getString("SpooledInformTask.12")) //$NON-NLS-1$
.append(TH_TH)
.append(Messages.getString("SpooledInformTask.13")) //$NON-NLS-1$
.append(TH_TH)
.append(Messages.getString("SpooledInformTask.14")) //$NON-NLS-1$
.append(TH_TH)
.append(Messages.getString("SpooledInformTask.15")) //$NON-NLS-1$
.append("</TH></TR></THEAD><TBODY>");
final DateFormat dateFormat =
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.LONG);
for (final FileItem fileItem : inform.fileMonitorInformation.fileItems.values()) {
builder.append("<TR><TD>").append(fileItem.file).append(TD_TD);
if (fileItem.hash != null) {
builder.append(FilesystemBasedDigest.getHex(fileItem.hash));
}
builder.append(TD_TD);
if (fileItem.lastTime > 0) {
builder.append(dateFormat.format(new Date(fileItem.lastTime)));
}
builder.append(TD_TD);
if (fileItem.timeUsed > 0) {
builder.append(dateFormat.format(new Date(fileItem.timeUsed)));
}
builder.append(TD_TD).append(fileItem.used).append(TD_TD)
.append(fileItem.specialId).append("</TD></TR>");
}
builder.append("</TBODY></TABLE></small>");
}
/**
* @param detailed
* @param status 1 for ok, -1 for ko, 0 for all
* @param uri
*
* @return the String containing the JSON format of the current Spooled
* information
*/
public static String buildSpooledJson(final boolean detailed,
final int status, final String uri) {
final ArrayNode array = JsonHandler.createArrayNode();
// get current information
synchronized (spooledInformationMap) {
final Set<String> names = spooledInformationMap.keySet();
for (final String name : names) {
// per Name
buildSpooledJsonElement(detailed, status, array, name, false);
}
}
return WaarpStringUtils.cleanJsonForHtml(JsonHandler.writeAsString(array));
}
/**
* @param name
* @param uri
*
* @return the String containing the JSON format of the current Spooled
* information
*/
public static String buildSpooledUniqueJson(final String uri,
final String name) {
final ArrayNode array = JsonHandler.createArrayNode();
// get current information
synchronized (spooledInformationMap) {
// per Name
buildSpooledJsonElement(true, 0, array, name, false);
}
return WaarpStringUtils.cleanJsonForHtml(JsonHandler.writeAsString(array));
}
/**
* For REST V2 service
*
* @param status if 0, get all Spooled, 1 active ones, -1 inactive ones
* @param name if null or empty, will get all Spooled
*
* @return the total number of valid "Entries" from request arguments of
* SpooledInformation
*/
public static int getSpooledJsonEntriesNumber(final int status,
final String name) {
int nb = 0;
if (ParametersChecker.isEmpty(name)) {
synchronized (spooledInformationMap) {
final Set<String> names = spooledInformationMap.keySet();
for (final String nameInternal : names) {
// per Name
if (checkValidEntrySpooled(status, nameInternal) != null) {
nb++;
}
}
}
} else {
synchronized (spooledInformationMap) {
// per Name
nb = checkValidEntrySpooled(status, name) != null? 1 : 0;
}
}
return nb;
}
/**
* For REST V2 service
*
* @param array the ArrayNode to fill with the results
* @param status if 0, get all Spooled, 1 active ones, -1 inactive ones
* @param name if null or empty, will get all Spooled
*
* @return the total number of "files" from request arguments of
* SpooledInformation
*/
public static int buildSpooledJson(final ArrayNode array, final int status,
final String name) {
int nb = 0;
if (ParametersChecker.isEmpty(name)) {
synchronized (spooledInformationMap) {
final Set<String> names = spooledInformationMap.keySet();
for (final String nameInternal : names) {
// per Name
nb +=
buildSpooledJsonElement(true, status, array, nameInternal, true);
}
}
} else {
synchronized (spooledInformationMap) {
// per Name
nb += buildSpooledJsonElement(true, status, array, name, true);
}
}
return nb;
}
/**
* Check if this entry is valid according to status and time
*
* @param status
* @param name
*
* @return the SpooledInformation if OK, else null
*/
private static SpooledInformation checkValidEntrySpooled(final int status,
final String name) {
final SpooledInformation inform = spooledInformationMap.get(name);
if (inform == null) {
return null;
}
final long time = inform.lastUpdate.getTime() +
Configuration.configuration.getTimeoutCon();
final long curtime = System.currentTimeMillis();
if (status != 0) {
if (time + Configuration.configuration.getTimeoutCon() < curtime) {
if (status < 0) {
return null;
}
} else if (status > 0) {
return null;
}
}
return inform;
}
/**
* @param detailed
* @param status
* @param array
* @param name
* @param strict
*
* @return number of files within
*/
private static int buildSpooledJsonElement(final boolean detailed,
final int status,
final ArrayNode array,
final String name,
final boolean strict) {
final SpooledInformation inform = checkValidEntrySpooled(status, name);
if (inform == null) {
return 0;
}
final long time = inform.lastUpdate.getTime() +
Configuration.configuration.getTimeoutCon();
final long curtime = System.currentTimeMillis();
int nb = 0;
if (array == null) {
if (inform.fileMonitorInformation != null &&
inform.fileMonitorInformation.fileItems != null) {
nb = inform.fileMonitorInformation.fileItems.size();
}
} else {
final ObjectNode elt = JsonHandler.createObjectNode();
elt.put("NAME", name.replace(',', ' '));
elt.put("HOST", inform.host);
if (strict) {
elt.put("LAST_UPDATE", inform.lastUpdate.getTime());
} else {
final String val;
if (time + Configuration.configuration.getTimeoutCon() < curtime) {
val = "bg-danger";
} else if (time < curtime) {
val = "bg-warning";
} else {
val = "bg-success";
}
elt.put("LAST_UPDATE", val + ' ' + inform.lastUpdate.getTime());
}
if (inform.fileMonitorInformation != null) {
elt.put("GLOBALOK", inform.fileMonitorInformation.globalok.get());
elt.put("GLOBALERROR", inform.fileMonitorInformation.globalerror.get());
elt.put("TODAYOK", inform.fileMonitorInformation.todayok.get());
elt.put("TODAYERROR", inform.fileMonitorInformation.todayerror.get());
elt.put("INTERVAL", inform.fileMonitorInformation.elapseTime);
elt.put("STOPFILE", inform.fileMonitorInformation.stopFile.getPath());
elt.put("STATUSFILE",
inform.fileMonitorInformation.statusFile.getPath());
elt.put("SUBDIRS", inform.fileMonitorInformation.scanSubDir);
final StringBuilder dirs = new StringBuilder();
final StringBuilder dirs2 = new StringBuilder();
int i = 0;
for (final File dir : inform.fileMonitorInformation.directories) {
i++;
dirs.append(dir).append('(').append(i).append(") ");
dirs2.append(dir).append(' ');
}
elt.put("DIRECTORIES", dirs.toString());
if (detailed && inform.fileMonitorInformation.fileItems != null) {
buildSpooledJsonFiles(elt, inform, dirs2.toString().split(" "),
strict);
} else {
// simply print number of files
if (inform.fileMonitorInformation.fileItems != null) {
elt.putArray("FILES")
.add(inform.fileMonitorInformation.fileItems.size());
} else {
elt.putArray("FILES").add(0);
}
}
nb = inform.fileMonitorInformation.fileItems.size();
}
array.add(elt);
}
return nb;
}
/**
* @param node
* @param inform
* @param dirs
*/
private static void buildSpooledJsonFiles(final ObjectNode node,
final SpooledInformation inform,
final String[] dirs,
final boolean strict) {
final ArrayNode array = node.putArray("FILES");
if (inform.fileMonitorInformation.fileItems.isEmpty()) {
array.add(0);
return;
}
if (!strict) {
final ArrayNode header = JsonHandler.createArrayNode();
header.add("FILE");
header.add("HASH");
header.add("LASTTIME");
header.add("USEDTIME");
header.add("USED");
header.add("ID");
array.add(header);
}
for (final FileItem fileItem : inform.fileMonitorInformation.fileItems.values()) {
final ObjectNode elt = JsonHandler.createObjectNode();
int i = 0;
String path = fileItem.file.getPath();
final String sep = path.lastIndexOf('/') >= 0? "/" : "\\";
for (final String dir : dirs) {
i++;
if (path.startsWith(dir + sep)) {
path = "(" + i + ')' + path.substring(dir.length());
break;
}
}
elt.put("FILE", path);
if (fileItem.hash != null) {
elt.put("HASH", FilesystemBasedDigest.getHex(fileItem.hash));
} else {
elt.putNull("HASH");
}
if (fileItem.lastTime > 0) {
elt.put("LASTTIME", fileItem.lastTime);
} else {
elt.putNull("LASTTIME");
}
if (fileItem.timeUsed > 0) {
elt.put("USEDTIME", fileItem.timeUsed);
} else {
elt.putNull("USEDTIME");
}
elt.put("USED", fileItem.used);
if (fileItem.specialId == ILLEGALVALUE) {
elt.put("ID", "");
} else {
elt.put("ID", Long.toString(fileItem.specialId));
}
array.add(elt);
}
}
}