/** * Copyright (C) 2015-2017 Philip Helger (www.helger.com) * philip[at]helger[dot]com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.helger.as2servlet; import java.io.File; import java.io.IOException; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.OverridingMethodsMustInvokeSuper; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.helger.as2lib.exception.OpenAS2Exception; import com.helger.as2lib.message.AS2Message; import com.helger.as2lib.processor.CNetAttribute; import com.helger.as2lib.processor.receiver.AS2ReceiverModule; import com.helger.as2lib.util.http.HTTPHelper; import com.helger.as2servlet.util.AS2OutputStreamCreatorHttpServletResponse; import com.helger.as2servlet.util.AS2ServletReceiverModule; import com.helger.as2servlet.util.AS2ServletSession; import com.helger.commons.annotation.OverrideOnDemand; import com.helger.commons.collection.ext.ICommonsList; import com.helger.commons.string.StringHelper; import com.helger.servlet.request.RequestHelper; /** * This is the main servlet that takes AS2 messages and processes them. This * servlet is configured to accept only POST requests. This class contains a lot * of methods that may be overridden. So simply subclass this class and * reference the subclasses class in your web.xml file. * * @author Philip Helger */ public class AS2ReceiveServlet extends HttpServlet { /** * The name of the Servlet's init-parameter from which the absolute path to * the configuration file is read. */ public static final String SERVLET_INIT_PARAM_AS2_SERVLET_CONFIG_FILENAME = "as2-servlet-config-filename"; private static final Logger s_aLogger = LoggerFactory.getLogger (AS2ReceiveServlet.class); private AS2ServletSession m_aSession; private AS2ReceiverModule m_aReceiver; /** * Get the AS2 configuration file to be used. By default this method reads it * from the Servlet init-param called * {@link #SERVLET_INIT_PARAM_AS2_SERVLET_CONFIG_FILENAME}. You may override * this method to use another way of retrieving the configuration file. <br> * Note: it must be a {@link File} because the configuration file allows for * "%home%" parameter substitution which uses the directory of the * configuration file as the base directory. * * @return The configuration file to be used. MUST not be <code>null</code>. * @throws ServletException * If no or an invalid configuration file was provided. */ @OverrideOnDemand @Nonnull protected File getConfigurationFile () throws ServletException { final String sConfigurationFilename = getServletConfig ().getInitParameter (SERVLET_INIT_PARAM_AS2_SERVLET_CONFIG_FILENAME); if (StringHelper.hasNoText (sConfigurationFilename)) throw new ServletException ("Servlet Init-Parameter '" + SERVLET_INIT_PARAM_AS2_SERVLET_CONFIG_FILENAME + "' is missing or empty!"); try { return new File (sConfigurationFilename).getCanonicalFile (); } catch (final IOException ex) { throw new ServletException ("Failed to get the canonical file from '" + sConfigurationFilename + "'", ex); } } /** * Create the AS2 session to be used based on the provided configuration file. * * @param aConfigurationFile * Configuration file to be used. Never <code>null</code>. * @return The created session. May not be <code>null</code>. * @throws OpenAS2Exception * In case something goes wrong when initializing the session * @throws ServletException * In case an overriding methods wants to throw a different exception */ @Nonnull @OverrideOnDemand protected AS2ServletSession createAS2ServletSession (@Nonnull final File aConfigurationFile) throws OpenAS2Exception, ServletException { return new AS2ServletSession (aConfigurationFile); } @Override public void init () throws ServletException { // Get configuration file final File aConfigurationFile = getConfigurationFile (); if (aConfigurationFile == null) throw new ServletException ("No configuration file provided!"); try { // Read the configuration from a file m_aSession = createAS2ServletSession (aConfigurationFile); // Don't start active modules to avoid connecting to a port! m_aReceiver = m_aSession.getMessageProcessor ().getModuleOfClass (AS2ServletReceiverModule.class); if (m_aReceiver == null) throw new ServletException ("Failed to retrieve AS2ReceiverModule which is a mandatory module!"); } catch (final OpenAS2Exception ex) { throw new ServletException ("Failed to init AS2 configuration", ex); } s_aLogger.info ("Successfully initialized AS2 configuration from file '" + aConfigurationFile.getAbsolutePath () + "'"); } /** * @return The AS2 session that was created in {@link #init()}. Never * <code>null</code>. * @throws IllegalStateException * In case initialization failed */ @Nonnull protected AS2ServletSession getSession () { if (m_aSession == null) throw new IllegalStateException ("This servlet was not initialized properly! No AS2 session is present."); return m_aSession; } /** * @return The AS2 receiver module that was created in {@link #init()}. Never * <code>null</code>. * @throws IllegalStateException * In case initialization failed */ @Nonnull protected AS2ReceiverModule getReceiverModule () { if (m_aReceiver == null) throw new IllegalStateException ("This servlet was not initialized properly! No receiver is present."); return m_aReceiver; } /** * Main handling method * * @param aHttpRequest * HTTP request * @param aHttpResponse * HTTP response * @param aMsgData * Message content * @param aMsg * AS2 message object * @param aResponseHandler * The response handler for sending back the MDN * @throws ServletException * In case of an error */ @OverrideOnDemand @OverridingMethodsMustInvokeSuper protected void handeIncomingMessage (@Nonnull final HttpServletRequest aHttpRequest, @Nonnull final HttpServletResponse aHttpResponse, @Nonnull final byte [] aMsgData, @Nonnull final AS2Message aMsg, @Nonnull final AS2OutputStreamCreatorHttpServletResponse aResponseHandler) throws ServletException { // Handle the incoming message, and return the MDN if necessary final String sClientInfo = aHttpRequest.getRemoteAddr () + ":" + aHttpRequest.getRemotePort (); // This call internally invokes the AS2ServletSBDModule getReceiverModule ().createHandler ().handleIncomingMessage (sClientInfo, aMsgData, aMsg, aResponseHandler); } @Override protected void doPost (@Nonnull final HttpServletRequest aHttpRequest, @Nonnull final HttpServletResponse aHttpResponse) throws ServletException, IOException { // Create empty message final AS2Message aMsg = new AS2Message (); aMsg.setAttribute (CNetAttribute.MA_SOURCE_IP, aHttpRequest.getRemoteAddr ()); aMsg.setAttribute (CNetAttribute.MA_SOURCE_PORT, Integer.toString (aHttpRequest.getRemotePort ())); aMsg.setAttribute (CNetAttribute.MA_DESTINATION_IP, aHttpRequest.getLocalAddr ()); aMsg.setAttribute (CNetAttribute.MA_DESTINATION_PORT, Integer.toString (aHttpRequest.getLocalPort ())); // Request type (e.g. "POST") aMsg.setAttribute (HTTPHelper.MA_HTTP_REQ_TYPE, aHttpRequest.getMethod ()); // Request URL (e.g. "/as2") aMsg.setAttribute (HTTPHelper.MA_HTTP_REQ_URL, aHttpRequest.getRequestURI ()); // Add all request headers to the AS2 message for (final Map.Entry <String, ICommonsList <String>> aEntry : RequestHelper.getRequestHeaderMap (aHttpRequest)) { final String sName = aEntry.getKey (); for (final String sValue : aEntry.getValue ()) aMsg.addHeader (sName, sValue); } // Build the handler that performs the response handling final AS2OutputStreamCreatorHttpServletResponse aResponseHandler = new AS2OutputStreamCreatorHttpServletResponse (aHttpResponse); // Read the S/MIME content final byte [] aMsgData = HTTPHelper.readHttpPayload (aHttpRequest.getInputStream (), aResponseHandler, aMsg); // Dump on demand if (HTTPHelper.isHTTPIncomingDumpEnabled ()) HTTPHelper.dumpIncomingHttpRequest (HTTPHelper.getAllHTTPHeaderLines (aMsg.getHeaders ()), aMsgData, aMsg); // Call main handling method handeIncomingMessage (aHttpRequest, aHttpResponse, aMsgData, aMsg, aResponseHandler); } }