View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  package org.waarp.openr66.protocol.http;
21  
22  import io.netty.buffer.ByteBuf;
23  import io.netty.buffer.Unpooled;
24  import io.netty.channel.ChannelFuture;
25  import io.netty.channel.ChannelFutureListener;
26  import io.netty.channel.ChannelHandlerContext;
27  import io.netty.channel.SimpleChannelInboundHandler;
28  import io.netty.channel.group.ChannelGroup;
29  import io.netty.handler.codec.http.DefaultFullHttpResponse;
30  import io.netty.handler.codec.http.FullHttpRequest;
31  import io.netty.handler.codec.http.FullHttpResponse;
32  import io.netty.handler.codec.http.HttpHeaderNames;
33  import io.netty.handler.codec.http.HttpHeaderValues;
34  import io.netty.handler.codec.http.HttpMethod;
35  import io.netty.handler.codec.http.HttpResponseStatus;
36  import io.netty.handler.codec.http.HttpUtil;
37  import io.netty.handler.codec.http.HttpVersion;
38  import io.netty.handler.codec.http.QueryStringDecoder;
39  import io.netty.handler.codec.http.cookie.Cookie;
40  import io.netty.handler.codec.http.cookie.DefaultCookie;
41  import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
42  import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
43  import io.netty.handler.traffic.TrafficCounter;
44  import org.waarp.common.database.DbAdmin;
45  import org.waarp.common.database.exception.WaarpDatabaseNoConnectionException;
46  import org.waarp.common.database.exception.WaarpDatabaseSqlException;
47  import org.waarp.common.exception.FileTransferException;
48  import org.waarp.common.exception.InvalidArgumentException;
49  import org.waarp.common.logging.SysErrLogger;
50  import org.waarp.common.logging.WaarpLogger;
51  import org.waarp.common.logging.WaarpLoggerFactory;
52  import org.waarp.common.utility.ParametersChecker;
53  import org.waarp.common.utility.WaarpStringUtils;
54  import org.waarp.gateway.kernel.http.HttpWriteCacheEnable;
55  import org.waarp.openr66.context.ErrorCode;
56  import org.waarp.openr66.context.R66Session;
57  import org.waarp.openr66.context.task.SpooledInformTask;
58  import org.waarp.openr66.dao.DAOFactory;
59  import org.waarp.openr66.dao.Filter;
60  import org.waarp.openr66.dao.TransferDAO;
61  import org.waarp.openr66.dao.database.DBTransferDAO;
62  import org.waarp.openr66.dao.exception.DAOConnectionException;
63  import org.waarp.openr66.database.data.DbTaskRunner;
64  import org.waarp.openr66.database.data.DbTaskRunner.TASKSTEP;
65  import org.waarp.openr66.pojo.Transfer;
66  import org.waarp.openr66.pojo.UpdatedInfo;
67  import org.waarp.openr66.protocol.configuration.Configuration;
68  import org.waarp.openr66.protocol.configuration.Messages;
69  import org.waarp.openr66.protocol.exception.OpenR66Exception;
70  import org.waarp.openr66.protocol.exception.OpenR66ExceptionTrappedFactory;
71  import org.waarp.openr66.protocol.exception.OpenR66ProtocolBusinessNoWriteBackException;
72  import org.waarp.openr66.protocol.localhandler.LocalChannelReference;
73  
74  import java.io.IOException;
75  import java.sql.Timestamp;
76  import java.util.ArrayList;
77  import java.util.Date;
78  import java.util.List;
79  import java.util.Map;
80  import java.util.Map.Entry;
81  import java.util.Set;
82  
83  /**
84   * Handler for HTTP information support
85   */
86  public class HttpFormattedHandler
87      extends SimpleChannelInboundHandler<FullHttpRequest> {
88    private static final String OPEN_R66_WEB_ERROR = "OpenR66 Web Error {}";
89    /**
90     * Internal Logger
91     */
92    private static final WaarpLogger logger =
93        WaarpLoggerFactory.getLogger(HttpFormattedHandler.class);
94  
95    private static final String MONITORING_HEADER_HTML = "monitoring_header.html";
96    private static final String MONITORING_END_HTML = "monitoring_end.html";
97  
98    private enum REQUEST {
99      index("index.html"), active(MONITORING_HEADER_HTML, MONITORING_END_HTML),
100     error(MONITORING_HEADER_HTML, MONITORING_END_HTML),
101     done(MONITORING_HEADER_HTML, MONITORING_END_HTML),
102     all(MONITORING_HEADER_HTML, MONITORING_END_HTML),
103     status(MONITORING_HEADER_HTML, MONITORING_END_HTML), statusxml(""),
104     statusjson("");
105 
106     private static final String MONITOR = "monitor/";
107     private final String header;
108     private final String end;
109 
110     /**
111      * Constructor for a unique file
112      *
113      * @param uniquefile
114      */
115     REQUEST(final String uniquefile) {
116       header = uniquefile;
117       end = uniquefile;
118     }
119 
120     /**
121      * @param header
122      * @param end
123      */
124     REQUEST(final String header, final String end) {
125       this.header = header;
126       this.end = end;
127     }
128 
129     /**
130      * Reader for a unique file
131      *
132      * @return the content of the unique file
133      */
134     public final String readFileUnique(final HttpFormattedHandler handler) {
135       return handler.readFileHeader(
136           Configuration.configuration.getHttpBasePath() + MONITOR + header);
137     }
138 
139     public final String readHeader(final HttpFormattedHandler handler) {
140       return handler.readFileHeader(
141           Configuration.configuration.getHttpBasePath() + MONITOR + header);
142     }
143 
144     public final String readEnd() {
145       return WaarpStringUtils.readFile(
146           Configuration.configuration.getHttpBasePath() + MONITOR + end);
147     }
148   }
149 
150   protected enum REPLACEMENT {
151     XXXHOSTIDXXX, XXXLOCACTIVEXXX, XXXNETACTIVEXXX, XXXBANDWIDTHXXX, XXXDATEXXX,
152     XXXLANGXXX
153   }
154 
155   public static final int LIMITROW = 60; // better if it can be divided by 4
156   private static final String I18NEXT = "i18next";
157 
158   private final R66Session authentHttp = new R66Session(false);
159 
160   protected String lang = Messages.getSlocale();
161 
162   protected FullHttpRequest request;
163 
164   protected final StringBuilder responseContent = new StringBuilder();
165 
166   protected HttpResponseStatus status;
167 
168   protected String uriRequest;
169 
170   private static final String sINFO = "INFO";
171   private static final String sNB = "NB";
172   private static final String sDETAIL = "DETAIL";
173 
174   protected boolean isCurrentRequestXml;
175   protected boolean isCurrentRequestJson;
176 
177   protected Map<String, List<String>> params;
178 
179   private String readFileHeader(final String filename) {
180     final String value;
181     try {
182       value = WaarpStringUtils.readFileException(filename);
183     } catch (final FileTransferException e) {
184       logger.error("Error while trying to read: " + filename, e);
185       return "";
186     }
187     final StringBuilder builder = new StringBuilder(value);
188 
189     WaarpStringUtils.replace(builder, REPLACEMENT.XXXDATEXXX.toString(),
190                              new Date().toString());
191     WaarpStringUtils.replace(builder, REPLACEMENT.XXXLOCACTIVEXXX.toString(),
192                              Integer.toString(
193                                  Configuration.configuration.getLocalTransaction()
194                                                             .getNumberLocalChannel()));
195     WaarpStringUtils.replace(builder, REPLACEMENT.XXXNETACTIVEXXX.toString(),
196                              Integer.toString(DbAdmin.getNbConnection()));
197     WaarpStringUtils.replace(builder, REPLACEMENT.XXXHOSTIDXXX.toString(),
198                              Configuration.configuration.getHostId());
199     final TrafficCounter trafficCounter =
200         Configuration.configuration.getGlobalTrafficShapingHandler()
201                                    .trafficCounter();
202     WaarpStringUtils.replace(builder, REPLACEMENT.XXXBANDWIDTHXXX.toString(),
203                              "IN:" +
204                              trafficCounter.lastReadThroughput() / 131072 +
205                              "Mbits&nbsp;&nbsp;OUT:" +
206                              trafficCounter.lastWriteThroughput() / 131072 +
207                              "Mbits");
208     WaarpStringUtils.replace(builder, REPLACEMENT.XXXLANGXXX.toString(), lang);
209     return builder.toString();
210   }
211 
212   protected final String getTrimValue(final String varname) {
213     String value;
214     try {
215       value = params.get(varname).get(0).trim();
216     } catch (final NullPointerException e) {
217       return null;
218     }
219     if (value.isEmpty()) {
220       value = null;
221     }
222     return value;
223   }
224 
225   @Override
226   protected void channelRead0(final ChannelHandlerContext ctx,
227                               final FullHttpRequest msg) {
228     isCurrentRequestXml = false;
229     isCurrentRequestJson = false;
230     status = HttpResponseStatus.OK;
231     final FullHttpRequest httpRequest = this.request = msg;
232     QueryStringDecoder queryStringDecoder =
233         new QueryStringDecoder(httpRequest.uri());
234     uriRequest = queryStringDecoder.path();
235     logger.debug("Msg: {}", uriRequest);
236     if (uriRequest.contains("gre/") || uriRequest.contains("img/") ||
237         uriRequest.contains("res/") || uriRequest.contains("favicon.ico")) {
238       HttpWriteCacheEnable.writeFile(httpRequest, ctx,
239                                      Configuration.configuration.getHttpBasePath() +
240                                      uriRequest, "XYZR66NOSESSION");
241       return;
242     }
243 
244     char cval = 'z';
245     long nb = LIMITROW;
246     // check the URI
247     if ("/active".equalsIgnoreCase(uriRequest)) {
248       cval = '0';
249     } else if ("/error".equalsIgnoreCase(uriRequest)) {
250       cval = '1';
251     } else if ("/done".equalsIgnoreCase(uriRequest)) {
252       cval = '2';
253     } else if ("/all".equalsIgnoreCase(uriRequest)) {
254       cval = '3';
255     } else if ("/status".equalsIgnoreCase(uriRequest)) {
256       cval = '4';
257     } else if ("/statusxml".equalsIgnoreCase(uriRequest)) {
258       cval = '5';
259       nb = 0; // since it could be the default or setup by request
260       isCurrentRequestXml = true;
261     } else if (uriRequest.toLowerCase().startsWith("/spooled")) {
262       cval = '6';
263     } else if ("/statusjson".equalsIgnoreCase(uriRequest)) {
264       cval = '7';
265       nb = 0; // since it could be the default or setup by request
266       isCurrentRequestJson = true;
267     }
268     // Get the params according to get or post
269     if (httpRequest.method() == HttpMethod.GET) {
270       params = queryStringDecoder.parameters();
271     } else if (httpRequest.method() == HttpMethod.POST) {
272       final ByteBuf content = httpRequest.content();
273       if (content.isReadable()) {
274         final String param = content.toString(WaarpStringUtils.UTF8);
275         queryStringDecoder = new QueryStringDecoder("/?" + param);
276       } else {
277         responseContent.append(REQUEST.index.readFileUnique(this));
278         writeResponse(ctx);
279         return;
280       }
281       params = queryStringDecoder.parameters();
282       boolean invalidEntry = false;
283       for (final Entry<String, List<String>> paramCheck : params.entrySet()) {
284         try {
285           ParametersChecker.checkSanityString(paramCheck.getValue().toArray(
286               ParametersChecker.ZERO_ARRAY_STRING));
287         } catch (final InvalidArgumentException e) {
288           logger.error(
289               "Arguments incompatible with Security: " + paramCheck.getKey() +
290               ": {}", e.getMessage());
291           invalidEntry = true;
292         }
293       }
294       if (invalidEntry) {
295         for (final Entry<String, List<String>> paramCheck : params.entrySet()) {
296           paramCheck.getValue().clear();
297         }
298         params.clear();
299         logger.error("No parameter validated since security issue found");
300       }
301     }
302     boolean getMenu = cval == 'z';
303     boolean extraBoolean = false;
304     if (!params.isEmpty()) {
305       // if not uri, from get or post
306       if (getMenu) {
307         final String info = getTrimValue(sINFO);
308         if (info != null) {
309           getMenu = false;
310           cval = info.charAt(0);
311         } else {
312           getMenu = true;
313         }
314       }
315       // search the nb param
316       final String snb = getTrimValue(sNB);
317       if (snb != null) {
318         try {
319           nb = Long.parseLong(snb);
320         } catch (final Exception ignored) {
321           SysErrLogger.FAKE_LOGGER.ignoreLog(ignored);
322         }
323       }
324       // search the detail param
325       final String sdetail = getTrimValue(sDETAIL);
326       if (sdetail != null) {
327         try {
328           if (Integer.parseInt(sdetail) > 0) {
329             extraBoolean = true;
330           }
331         } catch (final Exception ignored) {
332           SysErrLogger.FAKE_LOGGER.ignoreLog(ignored);
333         }
334       }
335       final String langarg = getTrimValue("setLng");
336       if (ParametersChecker.isNotEmpty(langarg)) {
337         lang = langarg;
338       }
339     }
340     if (getMenu) {
341       responseContent.append(REQUEST.index.readFileUnique(this));
342     } else {
343       // Use value 0=Active 1=Error 2=Done 3=All
344       switch (cval) {
345         case '0':
346           active(ctx, (int) nb);
347           break;
348         case '1':
349           error(ctx, (int) nb);
350           break;
351         case '2':
352           done(ctx, (int) nb);
353           break;
354         case '3':
355           all(ctx, (int) nb);
356           break;
357         case '4':
358           status(ctx);
359           break;
360         case '5':
361           statusxml(ctx, nb, extraBoolean);
362           break;
363         case '6':
364           String name = null;
365           if (params.containsKey("name")) {
366             name = getTrimValue("name");
367           }
368           int istatus = 0;
369           if (params.containsKey("status")) {
370             final String statusNew = getTrimValue("status");
371             try {
372               istatus = Integer.parseInt(statusNew);
373             } catch (final NumberFormatException e1) {
374               istatus = 0;
375             }
376           }
377           if (uriRequest.toLowerCase().startsWith("/spooleddetail")) {
378             extraBoolean = true;
379           }
380           spooled(extraBoolean, name, istatus);
381           break;
382         case '7':
383           statusjson(ctx, nb, extraBoolean);
384           break;
385         default:
386           responseContent.append(REQUEST.index.readFileUnique(this));
387       }
388     }
389     writeResponse(ctx);
390   }
391 
392   /**
393    * Add all runners from preparedStatement for type
394    *
395    * @param type
396    * @param nb
397    *
398    * @throws WaarpDatabaseNoConnectionException
399    * @throws WaarpDatabaseSqlException
400    */
401   private void addRunners(final List<Transfer> transfers, final String type,
402                           final int nb) {
403     responseContent.append(
404                        "<style>td{font-size: 8pt;}</style><table border=\"2\">")
405                    .append("<tr><td>").append(type).append("</td>")
406                    .append(DbTaskRunner.headerHtml()).append("</tr>\r\n");
407 
408     int i = 0;
409     for (final Transfer transfer : transfers) {
410       final DbTaskRunner taskRunner = new DbTaskRunner(transfer);
411       responseContent.append("<tr><td>")
412                      .append(taskRunner.isSender()? "S" : "R").append("</td>");
413       final LocalChannelReference lcr =
414           Configuration.configuration.getLocalTransaction()
415                                      .getFromRequest(taskRunner.getKey());
416       responseContent.append(taskRunner.toHtml(getAuthentHttp(), lcr != null?
417           Messages.getString("HttpSslHandler.Active") :
418           Messages.getString("HttpSslHandler.NotActive"))).append("</tr>\r\n");
419       if (nb > 0) {
420         i++;
421         if (i >= nb) {
422           break;
423         }
424       }
425     }
426     responseContent.append("</table><br>\r\n");
427   }
428 
429   /**
430    * print all active transfers
431    *
432    * @param ctx
433    * @param nb
434    */
435   private void active(final ChannelHandlerContext ctx, final int nb) {
436     responseContent.append(REQUEST.active.readHeader(this));
437 
438     TransferDAO transferAccess = null;
439     final List<Filter> filters = new ArrayList<Filter>();
440     List<Transfer> transfers;
441     try {
442       transferAccess = DAOFactory.getInstance().getTransferDAO();
443 
444       // Find Status = RUNNING transfer
445       filters.add(new Filter(DBTransferDAO.STEP_STATUS_FIELD, "=",
446                              ErrorCode.Running.getCode()));
447       filters.add(DbTaskRunner.getOwnerFilter());
448       transfers =
449           transferAccess.find(filters, DBTransferDAO.TRANSFER_START_FIELD,
450                               false, nb);
451       addRunners(transfers, ErrorCode.Running.getMesg(), nb);
452 
453       transfers.clear();
454       filters.clear();
455       // Find UpdatedInfo = INTERUPTED transfer
456       filters.add(new Filter(DBTransferDAO.UPDATED_INFO_FIELD, "=",
457                              UpdatedInfo.INTERRUPTED.ordinal()));
458       filters.add(DbTaskRunner.getOwnerFilter());
459       filters.add(new Filter(DBTransferDAO.TRANSFER_START_FIELD, "<",
460                              new Timestamp(System.currentTimeMillis())));
461       transfers =
462           transferAccess.find(filters, DBTransferDAO.TRANSFER_START_FIELD,
463                               false, nb);
464       addRunners(transfers, UpdatedInfo.INTERRUPTED.name(), nb);
465 
466       transfers.clear();
467       filters.clear();
468       // Find UpdatedInfo = TOSUBMIT transfer
469       filters.add(new Filter(DBTransferDAO.UPDATED_INFO_FIELD, "=",
470                              UpdatedInfo.TOSUBMIT.ordinal()));
471       filters.add(DbTaskRunner.getOwnerFilter());
472       filters.add(new Filter(DBTransferDAO.TRANSFER_START_FIELD, "<",
473                              new Timestamp(System.currentTimeMillis())));
474       transfers =
475           transferAccess.find(filters, DBTransferDAO.TRANSFER_START_FIELD,
476                               false, nb);
477       addRunners(transfers, UpdatedInfo.TOSUBMIT.name(), nb);
478 
479       transfers.clear();
480       filters.clear();
481       // Find Status = INITOK transfer
482       filters.add(new Filter(DBTransferDAO.STEP_STATUS_FIELD, "=",
483                              ErrorCode.InitOk.getCode()));
484       filters.add(DbTaskRunner.getOwnerFilter());
485       transfers =
486           transferAccess.find(filters, DBTransferDAO.TRANSFER_START_FIELD,
487                               false, nb);
488       addRunners(transfers, ErrorCode.InitOk.getMesg(), nb);
489 
490       transfers.clear();
491       filters.clear();
492       // Find Status = PREPROCESSINGOK transfer
493       filters.add(new Filter(DBTransferDAO.STEP_STATUS_FIELD, "=",
494                              ErrorCode.PreProcessingOk.getCode()));
495       filters.add(DbTaskRunner.getOwnerFilter());
496       transfers =
497           transferAccess.find(filters, DBTransferDAO.TRANSFER_START_FIELD,
498                               false, nb);
499       addRunners(transfers, ErrorCode.PreProcessingOk.getMesg(), nb);
500 
501       transfers.clear();
502       filters.clear();
503       // Find Status = TRANSFEROK transfer
504       filters.add(new Filter(DBTransferDAO.STEP_STATUS_FIELD, "=",
505                              ErrorCode.TransferOk.getCode()));
506       filters.add(DbTaskRunner.getOwnerFilter());
507       transfers =
508           transferAccess.find(filters, DBTransferDAO.TRANSFER_START_FIELD,
509                               false, nb);
510       addRunners(transfers, ErrorCode.TransferOk.getMesg(), nb);
511 
512       transfers.clear();
513       filters.clear();
514       // Find Status = POSTPROCESSING transfer
515       filters.add(new Filter(DBTransferDAO.STEP_STATUS_FIELD, "=",
516                              ErrorCode.PostProcessingOk.getCode()));
517       filters.add(DbTaskRunner.getOwnerFilter());
518       transfers =
519           transferAccess.find(filters, DBTransferDAO.TRANSFER_START_FIELD,
520                               false, nb);
521       addRunners(transfers, ErrorCode.PostProcessingOk.getMesg(), nb);
522     } catch (final DAOConnectionException e) {
523       logger.warn(OPEN_R66_WEB_ERROR, e.getMessage());
524       sendError(ctx, HttpResponseStatus.SERVICE_UNAVAILABLE);
525     } finally {
526       DAOFactory.closeDAO(transferAccess);
527     }
528     responseContent.append(REQUEST.active.readEnd());
529   }
530 
531   /**
532    * print all transfers in error
533    *
534    * @param ctx
535    * @param nb
536    */
537   private void error(final ChannelHandlerContext ctx, final int nb) {
538     responseContent.append(REQUEST.error.readHeader(this));
539     TransferDAO transferAccess = null;
540     final List<Filter> filters = new ArrayList<Filter>();
541     List<Transfer> transfers;
542     try {
543       transferAccess = DAOFactory.getInstance().getTransferDAO();
544 
545       // Find UpdatedInfo = INERROR transfer
546       filters.add(new Filter(DBTransferDAO.UPDATED_INFO_FIELD, "=",
547                              UpdatedInfo.INERROR.ordinal()));
548       filters.add(DbTaskRunner.getOwnerFilter());
549       filters.add(new Filter(DBTransferDAO.TRANSFER_START_FIELD, "<",
550                              new Timestamp(System.currentTimeMillis())));
551       transfers =
552           transferAccess.find(filters, DBTransferDAO.TRANSFER_START_FIELD,
553                               false, nb / 2);
554       addRunners(transfers, UpdatedInfo.INERROR.name(), nb / 2);
555 
556       transfers.clear();
557       filters.clear();
558       // Find UpdatedInfo = INTERUPTED transfer
559       filters.add(new Filter(DBTransferDAO.UPDATED_INFO_FIELD, "=",
560                              UpdatedInfo.INTERRUPTED.ordinal()));
561       filters.add(DbTaskRunner.getOwnerFilter());
562       filters.add(new Filter(DBTransferDAO.TRANSFER_START_FIELD, "<",
563                              new Timestamp(System.currentTimeMillis())));
564       transfers =
565           transferAccess.find(filters, DBTransferDAO.TRANSFER_START_FIELD,
566                               false, nb / 2);
567       addRunners(transfers, UpdatedInfo.INTERRUPTED.name(), nb / 2);
568 
569       transfers.clear();
570       filters.clear();
571       // Find GlobalStep = ERRORTASK transfer
572       filters.add(new Filter(DBTransferDAO.GLOBAL_STEP_FIELD, "=",
573                              Transfer.TASKSTEP.ERRORTASK.ordinal()));
574       filters.add(DbTaskRunner.getOwnerFilter());
575       filters.add(new Filter(DBTransferDAO.TRANSFER_START_FIELD, "<",
576                              new Timestamp(System.currentTimeMillis())));
577       transfers =
578           transferAccess.find(filters, DBTransferDAO.TRANSFER_START_FIELD,
579                               false, nb / 4);
580       addRunners(transfers, TASKSTEP.ERRORTASK.name(), nb / 4);
581     } catch (final DAOConnectionException e) {
582       logger.warn(OPEN_R66_WEB_ERROR, e.getMessage());
583       sendError(ctx, HttpResponseStatus.SERVICE_UNAVAILABLE);
584       return;
585     } finally {
586       DAOFactory.closeDAO(transferAccess);
587     }
588     responseContent.append(REQUEST.error.readEnd());
589   }
590 
591   /**
592    * Print all done transfers
593    *
594    * @param ctx
595    * @param nb
596    */
597   private void done(final ChannelHandlerContext ctx, final int nb) {
598     responseContent.append(REQUEST.done.readHeader(this));
599 
600     TransferDAO transferAccess = null;
601     final List<Transfer> transfers;
602     try {
603       transferAccess = DAOFactory.getInstance().getTransferDAO();
604 
605       final List<Filter> filters = new ArrayList<Filter>();
606       filters.add(new Filter(DBTransferDAO.STEP_STATUS_FIELD, "=",
607                              ErrorCode.CompleteOk.getCode()));
608       filters.add(DbTaskRunner.getOwnerFilter());
609 
610       transfers =
611           transferAccess.find(filters, DBTransferDAO.TRANSFER_START_FIELD,
612                               false, nb);
613       addRunners(transfers, "ALL RUNNERS: " + nb, nb);
614     } catch (final DAOConnectionException e) {
615       logger.warn(OPEN_R66_WEB_ERROR, e.getMessage());
616       sendError(ctx, HttpResponseStatus.SERVICE_UNAVAILABLE);
617       return;
618     } finally {
619       DAOFactory.closeDAO(transferAccess);
620     }
621     responseContent.append(REQUEST.done.readEnd());
622   }
623 
624   /**
625    * Print all nb last transfers
626    *
627    * @param ctx
628    * @param nb
629    */
630   private void all(final ChannelHandlerContext ctx, final int nb) {
631     responseContent.append(REQUEST.all.readHeader(this));
632 
633     TransferDAO transferAccess = null;
634     final List<Transfer> transfers;
635     try {
636       transferAccess = DAOFactory.getInstance().getTransferDAO();
637 
638       final List<Filter> filters = new ArrayList<Filter>();
639       filters.add(DbTaskRunner.getOwnerFilter());
640 
641       transfers =
642           transferAccess.find(filters, DBTransferDAO.TRANSFER_START_FIELD,
643                               false, nb);
644       addRunners(transfers, "ALL RUNNERS: " + nb, nb);
645     } catch (final DAOConnectionException e) {
646       logger.warn(OPEN_R66_WEB_ERROR, e.getMessage());
647       sendError(ctx, HttpResponseStatus.SERVICE_UNAVAILABLE);
648       return;
649     } finally {
650       DAOFactory.closeDAO(transferAccess);
651     }
652     responseContent.append(REQUEST.all.readEnd());
653   }
654 
655   /**
656    * print only status
657    *
658    * @param ctx
659    */
660   private void status(final ChannelHandlerContext ctx) {
661     responseContent.append(REQUEST.status.readHeader(this));
662 
663     TransferDAO transferAccess = null;
664     final List<Filter> filters = new ArrayList<Filter>();
665     List<Transfer> transfers;
666     try {
667       transferAccess = DAOFactory.getInstance().getTransferDAO();
668 
669       // Find UpdatedInfo = INERROR transfer
670       filters.add(new Filter(DBTransferDAO.UPDATED_INFO_FIELD, "=",
671                              UpdatedInfo.INERROR.ordinal()));
672       filters.add(DbTaskRunner.getOwnerFilter());
673 
674       transfers =
675           transferAccess.find(filters, DBTransferDAO.TRANSFER_START_FIELD,
676                               false, 1);
677       if (!transfers.isEmpty()) {
678         responseContent.append("<p>Some Transfers are in ERROR</p><br>");
679         status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
680       }
681 
682       filters.clear();
683       transfers.clear();
684       // Find UpdatedInfo = INTERUPTED transfer
685       filters.add(new Filter(DBTransferDAO.UPDATED_INFO_FIELD, "=",
686                              UpdatedInfo.INTERRUPTED.ordinal()));
687       filters.add(DbTaskRunner.getOwnerFilter());
688 
689       transfers =
690           transferAccess.find(filters, DBTransferDAO.TRANSFER_START_FIELD,
691                               false, 1);
692       if (!transfers.isEmpty()) {
693         responseContent.append("<p>Some Transfers are INTERRUPTED</p><br>");
694         status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
695       }
696       filters.clear();
697       transfers.clear();
698 
699       // Find GLOBAL_STEP = Error transfer
700       filters.add(new Filter(DBTransferDAO.GLOBAL_STEP_FIELD, "=",
701                              Transfer.TASKSTEP.ERRORTASK.ordinal()));
702       filters.add(DbTaskRunner.getOwnerFilter());
703 
704       transfers =
705           transferAccess.find(filters, DBTransferDAO.TRANSFER_START_FIELD,
706                               false, 1);
707       if (!transfers.isEmpty()) {
708         responseContent.append("<p>Some Transfers are in ERRORTASK</p><br>");
709         status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
710       }
711     } catch (final DAOConnectionException e) {
712       logger.warn(OPEN_R66_WEB_ERROR, e.getMessage());
713       sendError(ctx, HttpResponseStatus.SERVICE_UNAVAILABLE);
714       return;
715     } finally {
716       DAOFactory.closeDAO(transferAccess);
717     }
718     if (status != HttpResponseStatus.INTERNAL_SERVER_ERROR) {
719       responseContent.append("<p>No problem is found in Transfers</p><br>");
720     }
721     responseContent.append(REQUEST.status.readEnd());
722   }
723 
724   /**
725    * print only status
726    *
727    * @param ctx
728    * @param nb
729    */
730   protected final void statusxml(final ChannelHandlerContext ctx, final long nb,
731                                  final boolean detail) {
732     Configuration.configuration.getMonitoring().run(nb, detail);
733     responseContent.append(
734         Configuration.configuration.getMonitoring().exportXml(detail));
735   }
736 
737   /**
738    * print only status
739    *
740    * @param ctx
741    * @param nb
742    */
743   protected final void statusjson(final ChannelHandlerContext ctx,
744                                   final long nb, final boolean detail) {
745     Configuration.configuration.getMonitoring().run(nb, detail);
746     responseContent.append(
747         Configuration.configuration.getMonitoring().exportJson(detail));
748   }
749 
750   private void spooled(final boolean detail, final String name,
751                        final int istatus) {
752     responseContent.append(REQUEST.status.readHeader(this)).append(
753                        "<p><table border='0' cellpadding='0' cellspacing='0' >").append(
754                        "<tr style='background-image:url(gre/gresm.png);background-repeat:repeat-x;background-position:left top;'><td class='col_MenuHaut'>")
755                    .append(
756                        "<a data-i18n='menu2.sous-menu4a' href='Spooled.html' style='display:block;width:100%;height:100%;line-height:15px;'>")
757                    .append(
758                        "SPOOLED DIRECTORY no detail</a></td><td></td><td><img src='gre/gre11.png' height='15' width='1' style='border: none; display: block;' alt='' /></td>")
759                    .append(
760                        "<td></td><td class='col_MenuHaut'><a data-i18n='menu2.sous-menu4c' href='Spooled.html?status=-1' style='display:block;width:100%;height:100%;line-height:15px;'>")
761                    .append(
762                        "SPOOLED DIRECTORY no detail KO</a></td><td></td><td><img src='gre/gre11.png' height='15' width='1' style='border: none; display: block;' alt='' /></td>")
763                    .append(
764                        "<td></td><td class='col_MenuHaut'><a data-i18n='menu2.sous-menu4d' href='Spooled.html?status=1' style='display:block;width:100%;height:100%;line-height:15px;'>")
765                    .append(
766                        "SPOOLED DIRECTORY no detail OK</a></td></tr><tr style='background-image:url(gre/gre11.png);background-repeat:repeat-x;background-position:left top;'>")
767                    .append(
768                        "<td><img src='gre/gre11.png' height='1' width='100%' style='border: none; display: block;' alt='' /></td></tr>")
769                    .append(
770                        "<tr style='background-image:url(gre/gresm.png);background-repeat:repeat-x;background-position:left top;'>")
771                    .append(
772                        "<td class='col_MenuHaut'><a data-i18n='menu2.sous-menu4b' href='SpooledDetailed.html' style='display:block;width:100%;height:100%;line-height:15px;'>")
773                    .append(
774                        "SPOOLED DIRECTORY detailed</a></td><td></td><td><img src='gre/gre11.png' height='15' width='1' style='border: none; display: block;' alt='' /></td>")
775                    .append(
776                        "<td></td><td class='col_MenuHaut'><a data-i18n='menu2.sous-menu4e' href='SpooledDetailed.html?status=-1' style='display:block;width:100%;height:100%;line-height:15px;'>")
777                    .append(
778                        "SPOOLED DIRECTORY detailed KO</a></td><td></td><td><img src='gre/gre11.png' height='15' width='1' style='border: none; display: block;' alt='' /></td>")
779                    .append(
780                        "<td></td><td class='col_MenuHaut'><a data-i18n='menu2.sous-menu4f' href='SpooledDetailed.html?status=1' style='display:block;width:100%;height:100%;line-height:15px;'>")
781                    .append(
782                        "SPOOLED DIRECTORY detailed OK</a></td></tr></table></p>");
783     String uri;
784     if (detail) {
785       uri = "SpooledDetailed.html";
786     } else {
787       uri = "Spooled.html";
788     }
789     if (ParametersChecker.isNotEmpty(name)) {
790       // name is specified
791       uri = request.uri();
792       if (istatus != 0) {
793         uri += "&status=" + istatus;
794       }
795       responseContent.append(
796           SpooledInformTask.buildSpooledUniqueTable(uri, name));
797     } else {
798       if (istatus != 0) {
799         uri += "&status=" + istatus;
800       }
801       responseContent.append(
802           SpooledInformTask.buildSpooledTable(detail, istatus, uri));
803     }
804     responseContent.append(REQUEST.status.readEnd());
805   }
806 
807   /**
808    * Write the response
809    *
810    * @param ctx
811    */
812   protected final void writeResponse(final ChannelHandlerContext ctx) {
813     // Convert the response content to a ByteBuf.
814     final ByteBuf buf = Unpooled.copiedBuffer(responseContent.toString(),
815                                               WaarpStringUtils.UTF8);
816     responseContent.setLength(0);
817     // Decide whether to close the connection or not.
818     final boolean keepAlive = HttpUtil.isKeepAlive(request);
819     final boolean close = HttpHeaderValues.CLOSE.contentEqualsIgnoreCase(
820         request.headers().get(HttpHeaderNames.CONNECTION)) || !keepAlive;
821 
822     // Build the response object.
823     final FullHttpResponse response =
824         new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, buf);
825     response.headers().add(HttpHeaderNames.CONTENT_LENGTH,
826                            response.content().readableBytes());
827     if (isCurrentRequestXml) {
828       response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/xml");
829     } else if (isCurrentRequestJson) {
830       response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json");
831     } else {
832       response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html");
833     }
834     if (keepAlive) {
835       response.headers()
836               .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
837     }
838     if (!close) {
839       // There's no need to add 'Content-Length' header
840       // if this is the last response.
841       response.headers().set(HttpHeaderNames.CONTENT_LENGTH,
842                              String.valueOf(buf.readableBytes()));
843     }
844 
845     final String cookieString = request.headers().get(HttpHeaderNames.COOKIE);
846     if (cookieString != null) {
847       final Set<Cookie> cookies = ServerCookieDecoder.LAX.decode(cookieString);
848       boolean i18nextFound = false;
849       if (!cookies.isEmpty()) {
850         // Reset the cookies if necessary.
851         for (final Cookie cookie : cookies) {
852           if (cookie.name().equalsIgnoreCase(I18NEXT)) {
853             i18nextFound = true;
854             cookie.setValue(lang);
855             response.headers().add(HttpHeaderNames.SET_COOKIE,
856                                    ServerCookieEncoder.LAX.encode(cookie));
857           } else {
858             response.headers().add(HttpHeaderNames.SET_COOKIE,
859                                    ServerCookieEncoder.LAX.encode(cookie));
860           }
861         }
862         if (!i18nextFound) {
863           final Cookie cookie = new DefaultCookie(I18NEXT, lang);
864           response.headers().add(HttpHeaderNames.SET_COOKIE,
865                                  ServerCookieEncoder.LAX.encode(cookie));
866         }
867       }
868       if (!i18nextFound) {
869         final Cookie cookie = new DefaultCookie(I18NEXT, lang);
870         response.headers().add(HttpHeaderNames.SET_COOKIE,
871                                ServerCookieEncoder.LAX.encode(cookie));
872       }
873     }
874 
875     // Write the response.
876     final ChannelFuture future = ctx.writeAndFlush(response);
877     // Close the connection after the write operation is done if necessary.
878     if (close) {
879       future.addListener(ChannelFutureListener.CLOSE);
880     }
881   }
882 
883   /**
884    * Send an error and close
885    *
886    * @param ctx
887    * @param status
888    */
889   protected final void sendError(final ChannelHandlerContext ctx,
890                                  final HttpResponseStatus status) {
891     responseContent.setLength(0);
892     responseContent.append(REQUEST.error.readHeader(this))
893                    .append("OpenR66 Web Failure: ").append(status)
894                    .append(REQUEST.error.readEnd());
895     final ByteBuf buf = Unpooled.copiedBuffer(responseContent.toString(),
896                                               WaarpStringUtils.UTF8);
897     final FullHttpResponse response =
898         new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, buf);
899     response.headers().add(HttpHeaderNames.CONTENT_LENGTH,
900                            response.content().readableBytes());
901     response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html");
902     responseContent.setLength(0);
903     // Close the connection as soon as the error message is sent.
904     ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
905   }
906 
907   @Override
908   public void exceptionCaught(final ChannelHandlerContext ctx,
909                               final Throwable cause) {
910     final OpenR66Exception exception =
911         OpenR66ExceptionTrappedFactory.getExceptionFromTrappedException(
912             ctx.channel(), cause);
913     if (exception != null) {
914       if (!(exception instanceof OpenR66ProtocolBusinessNoWriteBackException)) {
915         if (cause instanceof IOException) {
916           // Nothing to do
917           return;
918         }
919         logger.warn("Exception in HttpHandler {}", exception.getMessage());
920       }
921       if (ctx.channel().isActive()) {
922         sendError(ctx, HttpResponseStatus.BAD_REQUEST);
923       }
924     }
925   }
926 
927   @Override
928   public void channelInactive(final ChannelHandlerContext ctx)
929       throws Exception {
930     super.channelInactive(ctx);
931     logger.debug("Closed");
932   }
933 
934   @Override
935   public void channelActive(final ChannelHandlerContext ctx) throws Exception {
936     logger.debug("Connected");
937     getAuthentHttp().getAuth().specialNoSessionAuth(false,
938                                                     Configuration.configuration.getHostId());
939     super.channelActive(ctx);
940     final ChannelGroup group =
941         Configuration.configuration.getHttpChannelGroup();
942     if (group != null) {
943       group.add(ctx.channel());
944     }
945   }
946 
947   /**
948    * @return the authentHttp
949    */
950   public R66Session getAuthentHttp() {
951     return authentHttp;
952   }
953 }