/*
* Copyright (C) 2015
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.cleverbus.admin.web.msg;
import static org.springframework.util.StringUtils.hasText;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import org.cleverbus.api.entity.Message;
import org.cleverbus.common.log.Log;
import org.cleverbus.modules.ExternalSystemEnum;
import org.cleverbus.spi.msg.MessageService;
import org.apache.commons.lang.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
/**
* Controller that encapsulates actions around messages.
*
* @author <a href="mailto:petr.juza@cleverlance.com">Petr Juza</a>
* @author <a href="mailto:tomas.hanus@cleverlance.com">Tomas Hanus</a>
*/
@Controller
@RequestMapping("/messages")
public class MessageController {
@Autowired
private MessageService messageService;
@Autowired
private MessageLogParser messageLogParser;
@RequestMapping(value = "/{msgId}", method = RequestMethod.GET)
public String getMsgDetailByMsgId(@PathVariable("msgId") Long msgId, Model model) {
Message msg = messageService.findEagerMessageById(msgId);
model.addAttribute("requestMsgId", msgId);
if (msg != null) {
// pretty-print envelope
if (StringUtils.isNotEmpty(msg.getEnvelope())) {
msg.setEnvelope(prettyPrintXML(msg.getEnvelope()));
}
// pretty-print payload
if (StringUtils.isNotEmpty(msg.getPayload())) {
msg.setPayload(prettyPrintXML(msg.getPayload()));
}
model.addAttribute("msg", msg);
}
return "msg";
}
@RequestMapping(value = "/{msgId}/log", method = RequestMethod.GET)
public String getLogOfMsgByMsgId(@PathVariable("msgId") Long msgId, Model model) {
Message msg = messageService.findMessageById(msgId);
model.addAttribute("requestMsgId", msgId);
if (msg != null) {
String correlationId = msg.getCorrelationId();
model.addAttribute("correlationId", correlationId);
List<String> logLines = new ArrayList<String>();
try {
long start = System.currentTimeMillis();
SortedSet<LocalDate> logDates = getMsgDates(msg);
logDates.add(new LocalDate()); // adding today just in case
Log.debug("Starts searching log for correlationId = " + correlationId);
for (LocalDate logDate : logDates) {
logLines.addAll(messageLogParser.getLogLines(correlationId, logDate.toDate()));
}
Log.debug("Finished searching log in " + (System.currentTimeMillis() - start) + " ms.");
model.addAttribute("log", StringUtils.join(logLines, "\n"));
} catch (IOException ex) {
model.addAttribute("logErr", "Error occurred during reading log file: " + ex.getMessage());
}
}
return "msgLog";
}
/**
* Creates a set of message dates, to search logs for these dates.
* This set contains just the dates of the following timestamps:
* <ul>
* <li>source system timestamp</li>
* <li>received timestamp</li>
* <li>start processing timestamp</li>
* <li>last update timestamp</li>
* </ul>
* It's a Set, so that only distinct dates will be included.
* It's a SortedSet, so that the dates are in their natural order.
* The elements are LocalDate (without time),
* so that the dates will be in the correct order
* and the set will correctly not include duplicates.
*
* @param msg the message to find dates for
* @return a set of dates
*/
private SortedSet<LocalDate> getMsgDates(Message msg) {
TreeSet<LocalDate> logDates = new TreeSet<LocalDate>();
Date[] msgDates = new Date[]{
msg.getMsgTimestamp(),
msg.getReceiveTimestamp(),
msg.getStartProcessTimestamp(),
msg.getLastUpdateTimestamp()
};
for (Date msgDate : msgDates) {
if (msgDate != null) {
logDates.add(new LocalDate(msgDate));
}
}
return logDates;
}
@RequestMapping(value = "/searchForm", method = RequestMethod.GET)
public String getSearchForm(@RequestParam(value = "source", required = false) ExternalSystemEnum sourceSystem,
@RequestParam(value = "correlation", required = false) String correlationId,
@ModelAttribute("model") ModelMap model) {
addExternalSystemsIntoModel(model);
if (hasText(correlationId)) {
Message msg = messageService.findMessageByCorrelationId(correlationId,
sourceSystem == ExternalSystemEnum.UNDEFINED ? null : sourceSystem);
if (msg != null) {
return "redirect:/web/admin/messages/" + msg.getMsgId();
} else {
model.addAttribute("resultFailed", Boolean.TRUE);
return "msg_search";
}
}
model.put("source", sourceSystem);
model.put("correlation", correlationId);
return "msg_search";
}
private void addExternalSystemsIntoModel(ModelMap model) {
Map<String, String> systemsMap = new LinkedHashMap<String, String>();
for (ExternalSystemEnum systemEnum : ExternalSystemEnum.values()) {
systemsMap.put(systemEnum.name(), systemEnum.name());
}
model.addAttribute("systemsMap", systemsMap);
}
@RequestMapping(value = "/searchForm", method = RequestMethod.POST)
public String searchMessage(@RequestParam("sourceSystem") ExternalSystemEnum sourceSystem,
@RequestParam("correlationId") String correlationId,
@ModelAttribute("model") ModelMap model) {
Message msg = messageService.findMessageByCorrelationId(correlationId,
sourceSystem == ExternalSystemEnum.UNDEFINED ? null : sourceSystem);
if (msg != null) {
return "redirect:/web/admin/messages/" + msg.getMsgId();
} else {
addExternalSystemsIntoModel(model);
model.addAttribute("resultFailed", Boolean.TRUE);
return "msg_search";
}
}
@RequestMapping(value = "/messagesByContent", method = { RequestMethod.GET, RequestMethod.POST })
public String showMessagesByContent(@RequestParam(value = "substring", required = false) String substring,
@ModelAttribute("model") ModelMap model) {
if (StringUtils.isNotBlank(substring)) {
List<Message> messageList = messageService.findMessagesByContent(substring);
if (!messageList.isEmpty()) {
model.addAttribute("messageList", messageList);
}
model.put("substring", substring);
}
return "msgByContent";
}
/**
* Converts input XML to "nice" XML.
*
* @param original the original XML
* @return pretty XML
*/
static String prettyPrintXML(String original) {
try {
OutputFormat format = OutputFormat.createPrettyPrint();
Document document = DocumentHelper.parseText(original);
StringWriter sw = new StringWriter();
XMLWriter writer = new XMLWriter(sw, format);
writer.write(document);
return sw.toString();
} catch (Exception exc) {
Log.debug("Error pretty-printing XML: ", exc);
return original;
}
}
}