org.extensiblecatalog.ncip.v2.responder.implprof1.NCIPServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.extensiblecatalog.ncip.v2.responder.implprof1.NCIPServlet.java

Source

/**
 * Copyright (c) 2010 eXtensible Catalog Organization
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the MIT/X11 license. The text of the license can be
 * found at http://www.opensource.org/licenses/mit-license.php.
 */

package org.extensiblecatalog.ncip.v2.responder.implprof1;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.log4j.Logger;
import org.extensiblecatalog.ncip.v2.common.*;
import org.extensiblecatalog.ncip.v2.service.*;

/**
 * This servlet implements an NCIP responder for HTTP and HTTPS transport according to
 * the NCIP Implementation Profile 1.
 */
public class NCIPServlet extends HttpServlet {

    private static final Logger LOG = Logger.getLogger(NCIPServlet.class);

    /**
     * Serial Id
     */
    private static final long serialVersionUID = -8518989441219684952L;

    /**
     * Whether to include stack traces in problem responses.
     */
    protected boolean includeStackTracesInProblemResponse = false;

    /**
     * The {@link Translator} instance used to translate network octets to instances of {@link NCIPInitiationData}
     * or {@link NCIPResponseData} for passing to the {@link NCIPService}.
     */
    protected Translator translator;

    /**
     * The {@link ServiceValidator} instance used to create {@link ServiceContext}s.
     */
    protected ServiceValidator serviceValidator;

    /**
     * The {@link MessageHandler} instance used to handle {@link NCIPInitiationData} objects representing incoming
     * NCIP initiation messages.
     */
    protected MessageHandler messageHandler;

    /**
     * The {@link StatisticsBean} instance used to report performance data.
     */
    protected StatisticsBean statisticsBean;

    // TODO: Add constructors for variations
    /**
     * Construct a new instance of this servlet with no {@link MessageHandler} or {@link Translator} set; these
     * must be set before any NCIP messages are received.
     */
    public NCIPServlet() {
        super();
    }

    public NCIPServlet(Properties properties) throws ToolkitException {

        super();

        this.includeStackTracesInProblemResponse = CoreConfigurationFactory.buildConfiguration(properties)
                .getIncludeStackTracesInProblemResponses();

        this.messageHandler = MessageHandlerFactory.buildMessageHandler(properties);

        this.translator = TranslatorFactory.buildTranslator(properties);

        this.serviceValidator = ServiceValidatorFactory.buildServiceValidator(properties);

        this.statisticsBean = StatisticsBeanFactory.buildStatisticsBean(properties);

    }

    public NCIPServlet(CoreConfiguration coreConfig) throws ToolkitException {

        super();
        this.includeStackTracesInProblemResponse = coreConfig.getIncludeStackTracesInProblemResponses();
        this.messageHandler = MessageHandlerFactory
                .buildMessageHandler(coreConfig.getMessageHandlerConfiguration());

        this.translator = TranslatorFactory.buildTranslator(coreConfig.getTranslatorConfiguration());

        this.serviceValidator = ServiceValidatorFactory
                .buildServiceValidator(coreConfig.getServiceValidatorConfiguration());

        this.statisticsBean = StatisticsBeanFactory
                .buildStatisticsBean(coreConfig.getStatisticsBeanConfiguration());

    }

    /**
     * Deprecated: Use {@link NCIPServlet(Properties)} or {@link NCIPServlet(CoreConfiguration)}.
     *
     * Construct a new instance of this servlet with the provided {@link MessageHandler}, {@link Translator},
     * {@link ServiceContext} and {@link StatisticsBean}.
     *
     * @param messageHandler the message handler for this responder instance
     * @param translator     the translator for this responder instance
     */
    @Deprecated
    public NCIPServlet(MessageHandler messageHandler, Translator translator, ServiceValidator serviceValidator,
            StatisticsBean statisticsBean) throws ToolkitException {
        super();
        this.includeStackTracesInProblemResponse = ConfigurationHelper.getCoreConfiguration()
                .getIncludeStackTracesInProblemResponses();
        this.messageHandler = messageHandler;
        this.translator = translator;
        this.serviceValidator = serviceValidator;
        this.statisticsBean = statisticsBean;
    }

    /**
     * Deprecated: Use {@link NCIPServlet(Properties)} or {@link NCIPServlet(CoreConfiguration)}.
     * Construct a new instance of this servlet with the provided {@link MessageHandler} and {@link Translator}.
     *
     * @param messageHandler the message handler for this responder instance
     * @param translator     the translator for this responder instance
     */
    @Deprecated
    public NCIPServlet(MessageHandler messageHandler, Translator translator, ServiceValidator serviceValidator)
            throws ToolkitException {
        super();
        this.includeStackTracesInProblemResponse = ConfigurationHelper.getCoreConfiguration()
                .getIncludeStackTracesInProblemResponses();
        this.messageHandler = messageHandler;
        this.translator = translator;
        this.serviceValidator = serviceValidator;
        this.statisticsBean = StatisticsBeanFactory.buildStatisticsBean();
    }

    /**
     * Set the {@link MessageHandler} for this responder instance
     *
     * @param messageHandler the message handler
     */
    public void setMessageHandler(MessageHandler messageHandler) {

        this.messageHandler = messageHandler;

    }

    /**
     * Set the {@link Translator} for this responder instance
     *
     * @param translator the translator
     */
    public void setTranslator(Translator translator) {

        this.translator = translator;

    }

    public void setServiceValidator(ServiceValidator serviceValidator) {

        this.serviceValidator = serviceValidator;

    }

    /**
     * Set the {@link StatisticsBean} for this responder instance
     *
     * @param statisticsBean the statistics bean
     */
    public void setStatisticsBean(StatisticsBean statisticsBean) {
        this.statisticsBean = statisticsBean;
    }

    /**
     * Set the includeStackTracesInProblemResponse flag.
     * @param includeStackTracesInProblemResponse
     */
    public void setIncludeStackTracesInProblemResponse(boolean includeStackTracesInProblemResponse) {

        this.includeStackTracesInProblemResponse = includeStackTracesInProblemResponse;

    }

    /**
     * Initialize the servlet
     * @param config
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {

        super.init(config);
        String appName = ConfigurationHelper.getAppName(config.getServletContext());

        try {

            if (messageHandler == null) {

                messageHandler = MessageHandlerFactory.buildMessageHandler(appName);

            }

            if (translator == null) {

                translator = TranslatorFactory.buildTranslator(appName);

            }

            if (statisticsBean == null) {

                statisticsBean = StatisticsBeanFactory.buildStatisticsBean(appName);

            }

            if (serviceValidator == null) {

                serviceValidator = ServiceValidatorFactory.buildServiceValidator(appName);

            }

            includeStackTracesInProblemResponse = ConfigurationHelper.getCoreConfiguration()
                    .getIncludeStackTracesInProblemResponses();

        } catch (ToolkitException e) {

            throw new ServletException("Exception during init method:", e);

        }

    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException {

        long respTotalStartTime = System.currentTimeMillis();
        String serviceName = "Unknown";

        try {

            response.setContentType("application/xml; charset=\"utf-8\"");

            // Note: Statements that might throw exceptions are wrapped in individual try/catch blocks, allowing us
            // to provide very specific error messages.

            ServletInputStream inputStream = null;
            try {

                inputStream = request.getInputStream();

            } catch (IOException e) {

                returnException(response, "Exception getting ServletInputStream from the HttpServletRequest.", e);

            }

            ServiceContext serviceContext = null;
            try {

                serviceContext = serviceValidator.getInitialServiceContext();

            } catch (ToolkitException e) {

                returnException(response, "Exception creating initial service context.", e);

            }

            NCIPInitiationData initiationData = null;

            try {

                initiationData = translator.createInitiationData(serviceContext, inputStream);

            } catch (ServiceException e) {

                returnException(response,
                        "Exception creating the NCIPInitiationData object from the servlet's input stream.", e);

            } catch (ValidationException e) {

                returnValidationProblem(response, e);

            }

            long initPerfSvcStartTime = System.currentTimeMillis();

            NCIPResponseData responseData = messageHandler.performService(initiationData, serviceContext);

            long initPerfSvcEndTime = System.currentTimeMillis();

            serviceName = ServiceHelper.getServiceName(initiationData);

            statisticsBean.record(initPerfSvcStartTime, initPerfSvcEndTime,
                    StatisticsBean.RESPONDER_PERFORM_SERVICE_LABELS, serviceName);

            InputStream responseMsgInputStream = null;
            try {

                responseMsgInputStream = translator.createResponseMessageStream(serviceContext, responseData);

            } catch (ServiceException e) {

                returnException(response, "Exception creating the InputStream from the NCIPResponseData object.",
                        e);

            } catch (ValidationException e) {

                returnException(response, "Exception creating the InputStream from the NCIPResponseData object.",
                        e);

            }

            int bytesAvailable = 0;
            if (responseMsgInputStream != null) {

                try {

                    bytesAvailable = responseMsgInputStream.available();

                } catch (IOException e) {

                    returnException(response,
                            "Exception getting the count of available bytes from the response message's InputStream.",
                            e);

                }

            }

            int bytesRead = 0;
            if (bytesAvailable != 0) {

                byte[] responseMsgBytes = new byte[bytesAvailable];

                try {

                    bytesRead = responseMsgInputStream.read(responseMsgBytes, 0, bytesAvailable);

                } catch (IOException e) {

                    returnException(response, "Exception reading bytes from the response message's InputStream.",
                            e);

                }

                if (bytesRead != bytesAvailable) {

                    returnProblem(response, "Bytes read from the response message's InputStream (" + bytesRead
                            + ") are not the same as the number available (" + bytesAvailable + ").");

                } else {

                    response.setContentLength(responseMsgBytes.length);

                    // TODO: What about a try/finally that closes the output stream - is that needed?

                    ServletOutputStream outputStream = null;
                    try {

                        outputStream = response.getOutputStream();

                    } catch (IOException e) {

                        returnException(response, "Exception getting the HttpServletResponse's OutputStream.", e);

                    }

                    try {

                        outputStream.write(responseMsgBytes);

                    } catch (IOException e) {

                        returnException(response,
                                "Exception writing the NCIP response message to the HttpServletResponse's OutputStream.",
                                e);

                    }

                    try {

                        outputStream.flush();

                    } catch (IOException e) {

                        returnException(response, "Exception flushing the HttpServletResponse's OutputStream.", e);

                    }

                }

            }

        } catch (Exception e) {

            returnException(response, "Uncaught exception.", e);

        }

        long respTotalEndTime = System.currentTimeMillis();

        statisticsBean.record(respTotalStartTime, respTotalEndTime, StatisticsBean.RESPONDER_TOTAL_LABELS,
                serviceName);

    }

    protected void returnException(HttpServletResponse response, String msg, Throwable e) throws ServletException {
        if (includeStackTracesInProblemResponse) {
            returnProblem(response, msg + System.getProperty("line.separator") + "Stacktrace from NCIP responder:"
                    + System.getProperty("line.separator") + ServiceHelper.convertExceptionToString(e));
        } else {
            returnProblem(response, msg);
        }
    }

    // TODO: Test this
    protected void returnValidationProblem(HttpServletResponse response, ValidationException validationException)
            throws ServletException {

        StringBuffer sb = new StringBuffer();
        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
                // TODO: The version, namespace, etc. ought to come from ServiceContext
                .append("<ns1:NCIPMessage ns1:version=\"http://www.niso.org/ncip/v2_0/imp1/xsd/ncip_v2_0.xsd\"")
                .append(" xmlns:ns1=\"http://www.niso.org/2008/ncip\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"")
                .append(" xsi:schemaLocation=\"http://www.niso.org/2008/ncip ncip_v2_0.xsd\">\n");

        // This hand-marshaling may seem like a bad idea, but we may've entered this method because the marshaling is failing
        List<Problem> problemsList = validationException.getProblems();
        for (Problem p : problemsList) {

            sb.append("  <ns1:Problem>\n");
            ProblemType pt = p.getProblemType();
            if (pt != null) {

                sb.append("    <ns1:ProblemType");
                String scheme = p.getProblemType().getScheme();
                if (scheme != null && !scheme.isEmpty()) {
                    sb.append(" ns1:Scheme=\"").append(scheme).append("\"");
                }
                sb.append(">").append(p.getProblemType().getValue()).append("</ns1:ProblemType>\n");

            }

            String pd = p.getProblemDetail();
            if (pd != null && !pd.isEmpty()) {

                sb.append("    <ns1:ProblemDetail>").append(StringEscapeUtils.escapeXml(pd))
                        .append("</ns1:ProblemDetail>\\n");

            }

            sb.append("  </ns1:Problem>\n");

        }

        sb.append("</ns1:NCIPMessage>");

        byte[] problemMsgBytes = sb.toString().getBytes();

        response.setContentLength(problemMsgBytes.length);

        try {

            ServletOutputStream outputStream = response.getOutputStream();
            outputStream.write(problemMsgBytes);
            outputStream.flush();

        } catch (IOException e) {

            throw new ServletException("Exception writing Problem response.", e);

        }

    }

    /**
     * Create a valid version 2 NCIPMessage response indicating a "Temporary Processing Failure", without
     * relying on any facilities of the Toolkit. This method is to be used when handling exceptions that
     * indicate that Toolkit services such as
     * {@link ServiceHelper#generateProblems(org.extensiblecatalog.ncip.v2.service.ProblemType, String, String, String)} ()}
     * may fail.
     * @param response the HttpServletResponse object to use
     * @param detail the text message to include in the ProblemDetail element
     * @throws ServletException if there is an IOException writing to the response object's output stream
     */
    protected void returnProblem(HttpServletResponse response, String detail) throws ServletException {
        String problemMsg = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
                // TODO: The version, namespace, etc. ought to come from ServiceContext
                + "<ns1:NCIPMessage ns1:version=\"http://www.niso.org/ncip/v2_0/imp1/xsd/ncip_v2_0.xsd\""
                + " xmlns:ns1=\"http://www.niso.org/2008/ncip\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
                + " xsi:schemaLocation=\"http://www.niso.org/2008/ncip ncip_v2_0.xsd\">\n" + "  <ns1:Problem>\n"
                + "    <ns1:ProblemType ns1:Scheme=\"http://www.niso.org/ncip/v1_0/schemes/processingerrortype/generalprocessingerror.scm\">Temporary Processing Failure</ns1:ProblemType>\n"
                + "    <ns1:ProblemDetail>" + StringEscapeUtils.escapeXml(detail) + "</ns1:ProblemDetail>\n"
                + "  </ns1:Problem>\n" + "</ns1:NCIPMessage>";

        byte[] problemMsgBytes = problemMsg.getBytes();

        response.setContentLength(problemMsgBytes.length);

        try {

            ServletOutputStream outputStream = response.getOutputStream();
            outputStream.write(problemMsgBytes);
            outputStream.flush();

        } catch (IOException e) {

            throw new ServletException("Exception writing Problem response.", e);

        }

    }
}