package com.epam.wilma.extras.shortcircuit; /*========================================================================== Copyright 2013-2017 EPAM Systems This file is part of Wilma. Wilma 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. Wilma 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 Wilma. If not, see <http://www.gnu.org/licenses/>. ===========================================================================*/ import com.epam.wilma.common.helper.FileFactory; import com.epam.wilma.common.helper.LogFilePathProvider; import com.epam.wilma.domain.http.WilmaHttpRequest; import com.epam.wilma.domain.http.WilmaHttpResponse; import com.epam.wilma.domain.stubconfig.parameter.ParameterList; import com.epam.wilma.webapp.config.servlet.stub.upload.helper.FileOutputStreamFactory; import org.apache.commons.codec.binary.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import javax.servlet.http.HttpServletResponse; import java.util.Calendar; import java.util.Map; /** * This class provides the Short Circuit Service calls. * * @author tkohegyi */ class ShortCircuitInterceptorCore { private static Map<String, ShortCircuitResponseInformation> shortCircuitMap = ShortCircuitChecker.getShortCircuitMap(); private final Logger logger = LoggerFactory.getLogger(ShortCircuitInterceptorCore.class); private final ShortCircuitResponseInformationFileHandler shortCircuitResponseInformationFileHandler = new ShortCircuitResponseInformationFileHandler(); @Autowired private LogFilePathProvider logFilePathProvider; @Autowired private FileFactory fileFactory; @Autowired private FileOutputStreamFactory fileOutputStreamFactory; /** * Method that generates the general hash code for a request in Short Circuit. * * @param wilmaHttpRequest is the request, that is the base of the hash code * @return with the generated hash code */ String generateKeyForMap(final WilmaHttpRequest wilmaHttpRequest) { String hashString = wilmaHttpRequest.getRequestLine() + " - " + wilmaHttpRequest.getBody().hashCode(); //ensure that it is ok for a header //CHECKSTYLE OFF - we must use "new String" here hashString = new String(Base64.encodeBase64(hashString.getBytes())); //CHECKSTYLE ON return hashString; } /** * Method that handles GET (all) and DELETE (all) methods on the actual Short Circuit Map. * * @param myMethod is expected as either GET or DELETE * @param httpServletResponse is the response object * @param path is the request path * @return with the response body (and with the updated httpServletResponse object */ String handleBasicCall(String myMethod, HttpServletResponse httpServletResponse, String path) { String response = null; if ("get".equalsIgnoreCase(myMethod)) { //list the map (circuits + get) response = getShortCircuitMap(httpServletResponse); } if ("delete".equalsIgnoreCase(myMethod)) { //first detect if we have basic path or we have a specified id in it int index = path.lastIndexOf("/"); String idStr = path.substring(index + 1); int id; try { id = Integer.parseInt(idStr); //remove a specific map entry String[] keySet = shortCircuitMap.keySet().toArray(new String[shortCircuitMap.size()]); if (keySet.length > id) { //we can delete it String entryKey = keySet[id]; shortCircuitMap.remove(entryKey); response = getShortCircuitMap(httpServletResponse); } else { //resource cannot be deleted response = "{ \"Cannot found specified entry in Short Circuit Cache\": \"" + id + "\" }"; httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND); } } catch (NumberFormatException e) { //it is not a problem, we should delete all //invalidate map (remove all from map) (circuits + delete) shortCircuitMap.clear(); response = getShortCircuitMap(httpServletResponse); } } return response; } /** * Method that handles request to save and load the Short Circuit Map. * * @param myMethod POST (for Save) and GET (for Load) and DELETE for a selected * @param folder is the folder to be used, or the identifier of the entry to be deleted * @param httpServletResponse is the response object * @return with the response body (and with the updated httpServletResponse object */ String handleComplexCall(String myMethod, String folder, HttpServletResponse httpServletResponse) { String response = null; if ("post".equalsIgnoreCase(myMethod)) { //save map (to files) (post + circuits?folder=...) String path = logFilePathProvider.getLogFilePath() + "/" + folder + "/"; response = shortCircuitResponseInformationFileHandler.savePreservedMessagesFromMap(path, fileFactory, fileOutputStreamFactory, httpServletResponse); } if ("get".equalsIgnoreCase(myMethod)) { //load map (from files) (get + circuits?folder=....) String path = logFilePathProvider.getLogFilePath() + "/" + folder; shortCircuitResponseInformationFileHandler.loadPreservedMessagesToMap(path); response = getShortCircuitMap(httpServletResponse); } return response; } /** * Method that is used to preserve the response when it arrives first time. * Note that only responses with 200 response code will be preserved. * * @param shortCircuitHashCode is the hash code used as key in the Short Circuit Map * @param wilmaHttpResponse is the response object * @param parameterList may contain the timeout value to be used for the entry */ void preserveResponse(String shortCircuitHashCode, WilmaHttpResponse wilmaHttpResponse, ParameterList parameterList) { long timeout; if (shortCircuitMap.containsKey(shortCircuitHashCode)) { //only if this needs to be handled //do we have the response already, or we need to catch it right now? ShortCircuitResponseInformation shortCircuitResponseInformation = shortCircuitMap.get(shortCircuitHashCode); if (shortCircuitResponseInformation == null) { //we need to store the response now //but first check if response code is 200, and content type is text based String contentType = wilmaHttpResponse.getHeader("Content-Type"); if (wilmaHttpResponse.getStatusCode() == HttpServletResponse.SC_OK && contentType != null && allowedContentType(contentType)) { String timeoutParameterName = "timeout"; if (parameterList != null && parameterList.get(timeoutParameterName) != null) { timeout = Long.valueOf(parameterList.get(timeoutParameterName)) + Calendar.getInstance().getTimeInMillis(); } else { timeout = Long.MAX_VALUE; //forever } shortCircuitResponseInformation = new ShortCircuitResponseInformation(wilmaHttpResponse, timeout, shortCircuitHashCode); shortCircuitMap.put(shortCircuitHashCode, shortCircuitResponseInformation); //CHECKSTYLE OFF - we must use "new String" here String decodedEntryKey = new String(Base64.decodeBase64(shortCircuitHashCode)); //make it human readable //CHECKSTYLE ON logger.info("ShortCircuit: Message captured for hashcode: " + decodedEntryKey); } } else { //we have response //take care about timeout timeout = Calendar.getInstance().getTimeInMillis(); if (timeout > shortCircuitResponseInformation.getTimeout()) { shortCircuitMap.remove(shortCircuitHashCode); //CHECKSTYLE OFF - we must use "new String" here String decodedEntryKey = new String(Base64.decodeBase64(shortCircuitHashCode)); //make it human readable //CHECKSTYLE ON logger.info("ShortCircuit: Timeout has happened for hashcode: " + decodedEntryKey); } } } } boolean allowedContentType(final String contentType) { return contentType.contains("text/plain") || contentType.contains("application/json") || contentType.contains("text/html") || contentType.contains("text/css"); } /** * List the actual status of the Short Circuit Map. * * @param httpServletResponse is the response object * @return with the response body (and with the updated httpServletResponse object */ private String getShortCircuitMap(HttpServletResponse httpServletResponse) { StringBuilder response = new StringBuilder("{\n \"shortCircuitCache\": [\n"); if (!shortCircuitMap.isEmpty()) { String[] keySet = shortCircuitMap.keySet().toArray(new String[shortCircuitMap.size()]); for (int i = 0; i < keySet.length; i++) { String entryKey = keySet[i]; ShortCircuitResponseInformation shortCircuitResponseInformation = shortCircuitMap.get(entryKey); boolean cached = shortCircuitResponseInformation != null; long usageCount = 0; if (shortCircuitResponseInformation != null) { usageCount = shortCircuitResponseInformation.getUsageCount(); } //CHECKSTYLE OFF - we must use "new String" here String decodedEntryKey = new String(Base64.decodeBase64(entryKey)); //make it human readable //CHECKSTYLE ON response.append(" { \"id\": \"").append(i) .append("\", \"hashCode\": \"").append(decodedEntryKey) .append("\", \"cached\": ").append(cached) .append(", \"usageCount\": ").append(usageCount) .append(" }"); if (i < keySet.length - 1) { response.append(","); } response.append("\n"); } } response.append(" ]\n}\n"); httpServletResponse.setStatus(HttpServletResponse.SC_OK); return response.toString(); } }