Development of NETCONF RPCs for tr-069 adapter to
[oam/tr069-adapter.git] / acs / cpe / src / main / java / org / commscope / tr069adapter / acs / cpe / CPEManagementService.java
index 6f07e36..a94d981 100644 (file)
-/*\r
- * ============LICENSE_START========================================================================\r
- * ONAP : tr-069-adapter\r
- * =================================================================================================\r
- * Copyright (C) 2020 CommScope Inc Intellectual Property.\r
- * =================================================================================================\r
- * This tr-069-adapter software file is distributed by CommScope Inc under the Apache License,\r
- * Version 2.0 (the "License"); you may not use this file except in compliance with the License. You\r
- * may obtain a copy of the License at\r
- *\r
- * http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,\r
- * either express or implied. See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- * ===============LICENSE_END=======================================================================\r
- */\r
-\r
-package org.commscope.tr069adapter.acs.cpe;\r
-\r
-import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.ACS_SESSIONID;\r
-import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.CWMP_VERSION;\r
-import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.INFORM;\r
-import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.TRANSFER_COMPLETE;\r
-\r
-import java.io.ByteArrayOutputStream;\r
-import java.io.IOException;\r
-import java.io.InputStream;\r
-import java.io.InputStreamReader;\r
-import java.io.OutputStream;\r
-import java.io.OutputStreamWriter;\r
-import java.io.PipedInputStream;\r
-import java.io.PipedOutputStream;\r
-import java.io.Reader;\r
-import java.io.UnsupportedEncodingException;\r
-import java.io.Writer;\r
-import java.net.URLDecoder;\r
-import java.nio.charset.StandardCharsets;\r
-import java.util.Enumeration;\r
-\r
-import javax.servlet.http.Cookie;\r
-import javax.servlet.http.HttpServletRequest;\r
-import javax.servlet.http.HttpServletResponse;\r
-import javax.ws.rs.core.Context;\r
-import javax.xml.soap.MessageFactory;\r
-import javax.xml.soap.MimeHeaders;\r
-import javax.xml.soap.SOAPException;\r
-import javax.xml.soap.SOAPMessage;\r
-\r
-import org.commscope.tr069adapter.acs.common.exception.TR069EventProcessingException;\r
-import org.commscope.tr069adapter.acs.common.response.DeviceInformResponse;\r
-import org.commscope.tr069adapter.acs.common.utils.ErrorCode;\r
-import org.commscope.tr069adapter.acs.cpe.handler.DeviceEventHandler;\r
-import org.commscope.tr069adapter.acs.cpe.rpc.Fault;\r
-import org.commscope.tr069adapter.acs.cpe.rpc.Inform;\r
-import org.commscope.tr069adapter.acs.cpe.rpc.InformResponse;\r
-import org.commscope.tr069adapter.acs.cpe.rpc.TransferComplete;\r
-import org.commscope.tr069adapter.acs.cpe.rpc.TransferCompleteResponse;\r
-import org.slf4j.Logger;\r
-import org.slf4j.LoggerFactory;\r
-import org.springframework.beans.factory.annotation.Autowired;\r
-import org.springframework.web.bind.annotation.PostMapping;\r
-import org.springframework.web.bind.annotation.RequestMapping;\r
-import org.springframework.web.bind.annotation.RestController;\r
-\r
-@RestController\r
-@RequestMapping("/CPEMgmt")\r
-public class CPEManagementService {\r
-\r
-  private static final String UTF_8 = "UTF-8";\r
-\r
-  private static final Logger logger = LoggerFactory.getLogger(CPEManagementService.class);\r
-\r
-  private static final int MY_MAX_ENVELOPES = 1;\r
-\r
-  @Autowired\r
-  DeviceEventHandler deviceEventHandler;\r
-\r
-  /**\r
-   * @param request\r
-   * @param response\r
-   */\r
-  @SuppressWarnings("static-access")\r
-  @PostMapping("/acs")\r
-  public void processDeviceEvent(@Context HttpServletRequest request,\r
-      @Context HttpServletResponse response) {\r
-\r
-    logger.debug("A device event occurred");\r
-    logHeaderElements(request.getHeaderNames());\r
-\r
-    try {\r
-      Boolean isEmptyCPERequest = true;\r
-      SOAPMessage soapMsg = null;\r
-      ByteArrayOutputStream out = new ByteArrayOutputStream();\r
-\r
-      String ct = request.getContentType();\r
-      int csix = -1;\r
-      String csFrom = "ISO-8859-1";\r
-      if (ct != null) {\r
-        csix = ct.indexOf("charset=");\r
-        response.setContentType(ct);\r
-      } else {\r
-        response.setContentType("text/xml;charset=UTF-8");\r
-      }\r
-\r
-      if (csix != -1)\r
-        csFrom = ct.substring(csix + 8).replaceAll("\"", "");\r
-\r
-      Cookie[] cookies = request.getCookies();\r
-      String acsSessionID = getACSSessionCookieData(cookies);\r
-      String cwmpVersion = getCWMPVersionCookieData(cookies);\r
-\r
-      XmlFilterInputStream f =\r
-          new XmlFilterInputStream(request.getInputStream(), request.getContentLength());\r
-      MessageFactory mf = getSOAPMessageFactory();\r
-      while (f.next()) {\r
-        isEmptyCPERequest = false;\r
-        MimeHeaders hdrs = new MimeHeaders();\r
-        hdrs.setHeader("Content-Type", "text/xml; charset=UTF-8");\r
-        InputStream in = getInputStream(csFrom, f);\r
-        soapMsg = mf.createMessage(hdrs, in);\r
-\r
-        logSoapMsg(soapMsg);\r
-\r
-        TR069RPC msg = null;\r
-        msg = TR069RPC.parse(soapMsg);\r
-\r
-        String reqType = getRequestType(msg);\r
-        logger.info("Event notified by the device is of type: {}", reqType);\r
-\r
-        if (reqType != null) {\r
-          if (reqType.equals(INFORM)) {\r
-            processDeviceInform(msg, request, response, out);\r
-          } else if (reqType.equals(TRANSFER_COMPLETE)) {\r
-            processTransferComplete(msg, response, out);\r
-          } else {\r
-            processOperationResult(msg, response, reqType, acsSessionID, out);\r
-          }\r
-        }\r
-      }\r
-\r
-      if (isEmptyCPERequest.booleanValue()) {\r
-        processEmptyCPERequest(response, cwmpVersion, acsSessionID, out);\r
-      }\r
-\r
-      if (out.size() < 1) {// To delete dm_sessionId cookie\r
-        clearCookies(cookies, response);\r
-      }\r
-\r
-      response.setContentLength(out.size());\r
-      response.setHeader("SOAPAction", "");\r
-      String sout = out.toString().trim();\r
-      logger.info(sout);\r
-      response.getOutputStream().print(sout);\r
-      response.getOutputStream().flush();\r
-      logger.debug("End of processing");\r
-\r
-    } catch (Exception e) {\r
-      logger.error("An error occurred while processing device event, Exception: {}",\r
-          e.getMessage());\r
-      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);\r
-    }\r
-\r
-    logger.debug("End of processing the HTTP post request");\r
-  }\r
-\r
-  private InputStream getInputStream(String csFrom, XmlFilterInputStream f) throws IOException {\r
-    InputStream in = null;\r
-    if (csFrom.equalsIgnoreCase(UTF_8)) {\r
-      in = new XmlFilterNS(f);\r
-    } else {\r
-      in = new CharsetConverterInputStream(csFrom, UTF_8, new XmlFilterNS(f));\r
-    }\r
-    return in;\r
-  }\r
-\r
-  private void processDeviceInform(TR069RPC msg, HttpServletRequest request,\r
-      HttpServletResponse response, ByteArrayOutputStream out) throws IOException {\r
-    Inform inform = (Inform) msg;\r
-    DeviceInformResponse deviceInformResponse = null;\r
-    try {\r
-      deviceInformResponse =\r
-          deviceEventHandler.processDeviceInform(inform, request.getHeader("Authorization"));\r
-      Cookie cookie = new Cookie(ACS_SESSIONID, deviceInformResponse.getSessionId());\r
-      Cookie cwmpVerCookie = new Cookie(CWMP_VERSION, msg.getCWMPVersion());\r
-      response.addCookie(cookie);\r
-      response.addCookie(cwmpVerCookie);\r
-    } catch (TR069EventProcessingException tr069ex) {\r
-      ErrorCode errorCode = tr069ex.getErrorCode();\r
-      if (ErrorCode.OUI_OR_PC_MISMATCH.equals(errorCode)) {\r
-        sendFault(response, out, Fault.FCODE_ACS_REQUEST_DENIED, "OUIandProductClassNotValid",\r
-            inform.getId());\r
-      } else {\r
-        int httpStatusCode = deviceEventHandler.handleException(tr069ex);\r
-        response.setStatus(httpStatusCode);\r
-      }\r
-      int httpStatusCode = deviceEventHandler.handleException(tr069ex);\r
-      response.setStatus(httpStatusCode);\r
-    }\r
-\r
-    InformResponse resp = new InformResponse(inform.getId(), MY_MAX_ENVELOPES);\r
-    resp.setCWMPVersion(msg.getCWMPVersion());\r
-    resp.writeTo(out);\r
-  }\r
-\r
-  private void processTransferComplete(TR069RPC msg, HttpServletResponse response,\r
-      ByteArrayOutputStream out) {\r
-    TransferComplete tc = (TransferComplete) msg;\r
-    try {\r
-      DeviceInformResponse deviceInformResponse = deviceEventHandler.processTransferComplete(tc);\r
-      Cookie cookie = new Cookie(ACS_SESSIONID, deviceInformResponse.getSessionId());\r
-      Cookie cwmpVerCookie = new Cookie(CWMP_VERSION, msg.getCWMPVersion() + ";");\r
-      response.addCookie(cookie);\r
-      response.addCookie(cwmpVerCookie);\r
-    } catch (Exception e) {\r
-      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);\r
-      return;\r
-    }\r
-    TransferCompleteResponse tr = new TransferCompleteResponse(tc.getId());\r
-    tr.setCWMPVersion(msg.getCWMPVersion());\r
-    tr.writeTo(out);\r
-  }\r
-\r
-  private void processOperationResult(TR069RPC msg, HttpServletResponse response, String reqType,\r
-      String acsSessionID, ByteArrayOutputStream out) {\r
-    logger.debug("Received Operation Result response {}", msg);\r
-    if (null == acsSessionID) {\r
-      logger.error("Received response without session ID, response: {}", reqType);\r
-    } else {\r
-      try {\r
-        TR069RPC message = deviceEventHandler.processRPCResponse(msg, acsSessionID);\r
-        if (null != message) {\r
-          message.setCWMPVersion(msg.getCWMPVersion());\r
-          message.writeTo(out);\r
-        } else {\r
-          response.setStatus(HttpServletResponse.SC_NO_CONTENT);\r
-        }\r
-      } catch (TR069EventProcessingException tr069ex) {\r
-        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);\r
-      }\r
-    }\r
-  }\r
-\r
-  private void processEmptyCPERequest(HttpServletResponse response, String cwmpVersion,\r
-      String acsSessionID, ByteArrayOutputStream out) {\r
-    if (null == acsSessionID) {\r
-      logger.error("Received empty response without session ID");\r
-      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);\r
-      return;\r
-    }\r
-\r
-    try {\r
-      logger.info("Received Empty Device response");\r
-      TR069RPC message = deviceEventHandler.processEmptyRequest(acsSessionID);\r
-      if (null != message) {\r
-        message.setCWMPVersion(cwmpVersion);\r
-        message.writeTo(out);\r
-      } else {\r
-        response.setStatus(HttpServletResponse.SC_NO_CONTENT);\r
-      }\r
-    } catch (TR069EventProcessingException tr069ex) {\r
-      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);\r
-    }\r
-  }\r
-\r
-  private void clearCookies(Cookie[] cookies, HttpServletResponse response) {\r
-    Cookie cookieToDelete = null;\r
-    if (null != cookies) {\r
-      logger.debug("Clearing the cookies");\r
-      for (int i = 0; i < cookies.length; i++) {\r
-        if (cookies[i].getName().equals(ACS_SESSIONID)\r
-            || cookies[i].getName().equals(CWMP_VERSION)) {\r
-          cookieToDelete = cookies[i];\r
-          cookieToDelete.setMaxAge(0);\r
-          response.addCookie(cookieToDelete);\r
-        }\r
-      }\r
-    }\r
-  }\r
-\r
-  private static class XmlFilterInputStream extends InputStream {\r
-\r
-    private InputStream istream;\r
-    private int lvl;\r
-    private int lastchar;\r
-    @SuppressWarnings("unused")\r
-    private int len;\r
-    private int nextchar;\r
-    private boolean intag = false;\r
-    private StringBuilder buff = new StringBuilder(16);\r
-\r
-    /** Creates a new instance of xmlFilterInputStream */\r
-    public XmlFilterInputStream(InputStream is, int l) {\r
-      len = l;\r
-      istream = is;\r
-    }\r
-\r
-    @Override\r
-    public int read() throws IOException {\r
-      if (lastchar == '>' && lvl == 0) {\r
-        return -1;\r
-      }\r
-      int l = lastchar;\r
-      if (!readLastChar())\r
-        return lastchar;\r
-\r
-      if (!intag && lastchar == '&') {\r
-        int amppos = buff.length();\r
-        updateBuffer();\r
-        String s = buff.substring(amppos);\r
-        replaceSpecialChars(s, amppos);\r
-        return read();\r
-      }\r
-\r
-      if (l == '<') {\r
-        intag = true;\r
-        if (lastchar == '/') {\r
-          lvl--;\r
-        } else {\r
-          lvl++;\r
-        }\r
-      }\r
-\r
-      len--;\r
-      return lastchar;\r
-    }\r
-\r
-    public boolean next() throws IOException {\r
-      if ((nextchar = istream.read()) == -1) {\r
-        logger.debug("Next char is {}", nextchar);\r
-        lvl = 0;\r
-        lastchar = 0;\r
-      }\r
-      return (nextchar != -1);\r
-    }\r
-\r
-    private boolean readLastChar() throws IOException {\r
-      if (nextchar != -1) {\r
-        lastchar = nextchar;\r
-        nextchar = -1;\r
-      } else {\r
-        if (buff.length() > 0) {\r
-          lastchar = buff.charAt(0);\r
-          buff.deleteCharAt(0);\r
-          return false;\r
-        } else {\r
-          lastchar = istream.read();\r
-        }\r
-      }\r
-\r
-      if (lastchar == '<') {\r
-        intag = true;\r
-      } else if (lastchar == '>') {\r
-        intag = false;\r
-      }\r
-\r
-      return true;\r
-    }\r
-\r
-    private void updateBuffer() throws IOException {\r
-      // fix up broken xml not encoding &\r
-      buff.append((char) lastchar);\r
-      for (int c = 0; c < 10; c++) {\r
-        int ch = istream.read();\r
-        boolean breakLoop = false;\r
-        if (ch == -1) {\r
-          breakLoop = true;\r
-        }\r
-        if (ch == '&') {\r
-          nextchar = ch;\r
-          breakLoop = true;\r
-        }\r
-        if (breakLoop)\r
-          break;\r
-\r
-        buff.append((char) ch);\r
-      }\r
-    }\r
-\r
-    private void replaceSpecialChars(String s, int amppos) {\r
-      if (!s.startsWith("&amp;") && !s.startsWith("&lt;") && !s.startsWith("&gt;")\r
-          && !s.startsWith("&apos;") && !s.startsWith("&quot;") && !s.startsWith("&#")) {\r
-        buff.replace(amppos, amppos + 1, "&amp;");\r
-      }\r
-    }\r
-  }\r
-\r
-  private static class XmlFilterNS extends InputStream {\r
-    // Dumb class to filter out declaration of default xmlns\r
-\r
-    private String pat = "xmlns=\"urn:dslforum-org:cwmp-1-0\"";\r
-    private String pat2 = "xmlns=\"urn:dslforum-org:cwmp-1-1\"";\r
-    private int length = 0;\r
-    private int pos = 0;\r
-    private boolean f = false;\r
-    private byte[] buff = new byte[1024];\r
-    private InputStream is;\r
-\r
-    @Override\r
-    public int read() throws IOException {\r
-      if (!f) {\r
-        length = is.read(buff);\r
-        if (length < buff.length) {\r
-          byte[] b2 = new byte[length];\r
-          System.arraycopy(buff, 0, b2, 0, length);\r
-          buff = b2;\r
-        }\r
-\r
-        String b = new String(buff, StandardCharsets.UTF_8);\r
-        b = b.replace(pat, "");\r
-        b = b.replace(pat2, "");\r
-        buff = b.getBytes(StandardCharsets.UTF_8);\r
-        length = buff.length;\r
-        f = true;\r
-      }\r
-\r
-      if (pos < length) {\r
-        return buff[pos++] & 0xFF;\r
-      }\r
-      return is.read();\r
-    }\r
-\r
-    public XmlFilterNS(InputStream is) {\r
-      this.is = is;\r
-    }\r
-  }\r
-\r
-  private static class CharsetConverterInputStream extends InputStream {\r
-\r
-    @SuppressWarnings("unused")\r
-    private InputStream in;\r
-    private PipedInputStream pipein;\r
-    private OutputStream pipeout;\r
-    private Reader r;\r
-    private Writer w;\r
-\r
-    public CharsetConverterInputStream(String csFrom, String csTo, InputStream in)\r
-        throws IOException {\r
-      this.in = in;\r
-      r = new InputStreamReader(in, csFrom);\r
-      pipein = new PipedInputStream();\r
-      pipeout = new PipedOutputStream(pipein);\r
-      w = new OutputStreamWriter(pipeout, csTo);\r
-    }\r
-\r
-    @Override\r
-    public int read() throws IOException {\r
-      if (pipein.available() > 0) {\r
-        return pipein.read();\r
-      }\r
-      int c = r.read();\r
-      if (c == -1) {\r
-        return -1;\r
-      }\r
-      w.write(c);\r
-      w.flush();\r
-      return pipein.read();\r
-    }\r
-  }\r
-\r
-  /**\r
-   * @return\r
-   * @throws Exception\r
-   */\r
-  private MessageFactory getSOAPMessageFactory() throws SOAPException {\r
-    MessageFactory mf = null;\r
-    mf = MessageFactory.newInstance();\r
-    return mf;\r
-  }\r
-\r
-  /**\r
-   * @param cookies\r
-   * @return\r
-   */\r
-  private String getACSSessionCookieData(Cookie[] cookies) {\r
-    String acsSessionID = null;\r
-    if (null != cookies) {\r
-      for (Cookie cookie : cookies) {\r
-        if (cookie.getName().equals(ACS_SESSIONID)) {\r
-          acsSessionID = cookie.getValue();\r
-          logger.debug("The session id is {}", acsSessionID);\r
-        }\r
-      }\r
-    }\r
-    return acsSessionID;\r
-  }\r
-\r
-  /**\r
-   * @param cookies\r
-   * @return\r
-   * @throws TR069EventProcessingException\r
-   */\r
-  private String getCWMPVersionCookieData(Cookie[] cookies) throws TR069EventProcessingException {\r
-    String cwmpVersion = null;\r
-    try {\r
-      if (null != cookies) {\r
-        for (Cookie cookie : cookies) {\r
-          if (cookie.getName().equals(CWMP_VERSION)) {\r
-            cwmpVersion = cookie.getValue();\r
-            if (cwmpVersion != null) {\r
-              cwmpVersion = URLDecoder.decode(cwmpVersion, StandardCharsets.UTF_8.name());\r
-            }\r
-            logger.debug("The CWMP version supported by the device is: {}", cwmpVersion);\r
-          }\r
-        }\r
-      }\r
-    } catch (UnsupportedEncodingException e) {\r
-      logger.error(e.getMessage());\r
-      TR069EventProcessingException ex = new TR069EventProcessingException(\r
-          ErrorCode.UNSUPPORTED_CHARACTER_ENCODING, StandardCharsets.UTF_8.name());\r
-      logger.error(ex.getMessage());\r
-      throw ex;\r
-    }\r
-    return cwmpVersion;\r
-  }\r
-\r
-  /**\r
-   * @param soapMsg\r
-   */\r
-  private void logSoapMsg(SOAPMessage soapMsg) {\r
-    StringBuilder buffer = new StringBuilder();\r
-    buffer.append(soapMsg.toString());\r
-    ByteArrayOutputStream baos = new ByteArrayOutputStream();\r
-    try {\r
-      soapMsg.writeTo(baos);\r
-    } catch (SOAPException | IOException e) {\r
-      logger.error("Error while writting soap message");\r
-    }\r
-    buffer.append(baos);\r
-    String soapMessage = buffer.toString();\r
-    logger.debug(soapMessage);\r
-  }\r
-\r
-  /**\r
-   * @param response\r
-   * @param out\r
-   * @param fcodeAcsRequestDenied\r
-   * @param faultString\r
-   * @param id\r
-   * @throws IOException\r
-   */\r
-  private void sendFault(HttpServletResponse response, ByteArrayOutputStream out,\r
-      String fcodeAcsRequestDenied, String faultString, String id) throws IOException {\r
-    Fault fault = new Fault(fcodeAcsRequestDenied, faultString, id);\r
-    fault.writeTo(out);\r
-    response.setContentLength(out.size());\r
-    String sout = out.toString(UTF_8);\r
-    sout = sout.replace('\'', '"');\r
-    response.getOutputStream().print(sout);\r
-  }\r
-\r
-  private void logHeaderElements(Enumeration<String> headerName) {\r
-    while (headerName.hasMoreElements()) {\r
-      String requestHeader = headerName.nextElement();\r
-      logger.debug("Request Headers {}", requestHeader);\r
-    }\r
-  }\r
-\r
-  private String getRequestType(TR069RPC msg) {\r
-    String requestType = msg.getName();\r
-    if (requestType == null)\r
-      requestType = "Empty Request";\r
-\r
-    return requestType;\r
-  }\r
-\r
-  /******************************************************************************************************************/\r
-\r
-  public DeviceEventHandler getDeviceEventHandler() {\r
-    return deviceEventHandler;\r
-  }\r
-\r
-  public void setDeviceEventHandler(DeviceEventHandler deviceEventHandler) {\r
-    this.deviceEventHandler = deviceEventHandler;\r
-  }\r
-\r
-}\r
+/*
+ * ============LICENSE_START========================================================================
+ * ONAP : tr-069-adapter
+ * =================================================================================================
+ * Copyright (C) 2020 CommScope Inc Intellectual Property.
+ * =================================================================================================
+ * This tr-069-adapter software file is distributed by CommScope Inc 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
+ *
+ * This file 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.
+ * ===============LICENSE_END=======================================================================
+ */
+
+package org.commscope.tr069adapter.acs.cpe;
+
+import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.ACS_SESSIONID;
+import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.CWMP_VERSION;
+import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.INFORM;
+import static org.commscope.tr069adapter.acs.common.utils.AcsConstants.TRANSFER_COMPLETE;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.Context;
+import javax.xml.soap.MessageFactory;
+import javax.xml.soap.MimeHeaders;
+import javax.xml.soap.SOAPException;
+import javax.xml.soap.SOAPMessage;
+
+import org.commscope.tr069adapter.acs.common.exception.TR069EventProcessingException;
+import org.commscope.tr069adapter.acs.common.response.DeviceInformResponse;
+import org.commscope.tr069adapter.acs.common.utils.ErrorCode;
+import org.commscope.tr069adapter.acs.cpe.handler.DeviceEventHandler;
+import org.commscope.tr069adapter.acs.cpe.rpc.Fault;
+import org.commscope.tr069adapter.acs.cpe.rpc.Inform;
+import org.commscope.tr069adapter.acs.cpe.rpc.InformResponse;
+import org.commscope.tr069adapter.acs.cpe.rpc.TransferComplete;
+import org.commscope.tr069adapter.acs.cpe.rpc.TransferCompleteResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/CPEMgmt")
+public class CPEManagementService {
+
+  private static final String UTF_8 = "UTF-8";
+
+  private static final Logger logger = LoggerFactory.getLogger(CPEManagementService.class);
+
+  private static final int MY_MAX_ENVELOPES = 1;
+
+  @Autowired
+  DeviceEventHandler deviceEventHandler;
+
+  /**
+   * @param request
+   * @param response
+   */
+  @SuppressWarnings("static-access")
+  @PostMapping("/acs")
+  public void processDeviceEvent(@Context HttpServletRequest request,
+      @Context HttpServletResponse response) {
+
+    logger.debug("A device event occurred");
+    logHeaderElements(request.getHeaderNames());
+
+    try {
+      Boolean isEmptyCPERequest = true;
+      SOAPMessage soapMsg = null;
+      ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+      String ct = request.getContentType();
+      int csix = -1;
+      String csFrom = "ISO-8859-1";
+      if (ct != null) {
+        csix = ct.indexOf("charset=");
+        response.setContentType(ct);
+      } else {
+        response.setContentType("text/xml;charset=UTF-8");
+      }
+
+      if (csix != -1)
+        csFrom = ct.substring(csix + 8).replaceAll("\"", "");
+
+      Cookie[] cookies = request.getCookies();
+      String acsSessionID = getACSSessionCookieData(cookies);
+      String cwmpVersion = getCWMPVersionCookieData(cookies);
+
+      XmlFilterInputStream f =
+          new XmlFilterInputStream(request.getInputStream(), request.getContentLength());
+      MessageFactory mf = getSOAPMessageFactory();
+      while (f.next()) {
+        isEmptyCPERequest = false;
+        MimeHeaders hdrs = new MimeHeaders();
+        hdrs.setHeader("Content-Type", "text/xml; charset=UTF-8");
+        InputStream in = getInputStream(csFrom, f);
+        soapMsg = mf.createMessage(hdrs, in);
+
+        logSoapMsg(soapMsg);
+
+        TR069RPC msg = null;
+        msg = TR069RPC.parse(soapMsg);
+
+        String reqType = getRequestType(msg);
+        logger.info("Event notified by the device is of type: {}", reqType);
+
+        if (reqType != null) {
+          if (reqType.equals(INFORM)) {
+            processDeviceInform(msg, request, response, out);
+          } else if (reqType.equals(TRANSFER_COMPLETE)) {
+            processTransferComplete(msg, request, response, out);
+          } else {
+            processOperationResult(msg, response, reqType, acsSessionID, out);
+          }
+        }
+      }
+
+      if (isEmptyCPERequest.booleanValue()) {
+        processEmptyCPERequest(response, cwmpVersion, acsSessionID, out);
+      }
+
+      if (out.size() < 1) {// To delete dm_sessionId cookie
+        clearCookies(cookies, response);
+      }
+
+      response.setContentLength(out.size());
+      response.setHeader("SOAPAction", "");
+      String sout = out.toString().trim();
+      logger.info(sout);
+      response.getOutputStream().print(sout);
+      response.getOutputStream().flush();
+      logger.debug("End of processing");
+
+    } catch (Exception e) {
+      logger.error("An error occurred while processing device event, Exception: {}",
+          e.getMessage());
+      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+    }
+
+    logger.debug("End of processing the HTTP post request");
+  }
+
+  private InputStream getInputStream(String csFrom, XmlFilterInputStream f) throws IOException {
+    InputStream in = null;
+    if (csFrom.equalsIgnoreCase(UTF_8)) {
+      in = new XmlFilterNS(f);
+    } else {
+      in = new CharsetConverterInputStream(csFrom, UTF_8, new XmlFilterNS(f));
+    }
+    return in;
+  }
+
+  private void processDeviceInform(TR069RPC msg, HttpServletRequest request,
+      HttpServletResponse response, ByteArrayOutputStream out) throws IOException {
+    Inform inform = (Inform) msg;
+    DeviceInformResponse deviceInformResponse = null;
+    try {
+      deviceInformResponse =
+          deviceEventHandler.processDeviceInform(inform, request.getHeader("Authorization"));
+      Cookie cookie = new Cookie(ACS_SESSIONID, deviceInformResponse.getSessionId());
+      cookie.setSecure(request.isSecure());
+      cookie.setHttpOnly(true);
+      Cookie cwmpVerCookie = new Cookie(CWMP_VERSION, msg.getCWMPVersion());
+      cwmpVerCookie.setSecure(request.isSecure());
+      cwmpVerCookie.setHttpOnly(true);
+      response.addCookie(cookie);
+      response.addCookie(cwmpVerCookie);
+    } catch (TR069EventProcessingException tr069ex) {
+      ErrorCode errorCode = tr069ex.getErrorCode();
+      if (ErrorCode.OUI_OR_PC_MISMATCH.equals(errorCode)) {
+        sendFault(response, out, Fault.FCODE_ACS_REQUEST_DENIED, "OUIandProductClassNotValid",
+            inform.getId());
+      } else {
+        int httpStatusCode = deviceEventHandler.handleException(tr069ex);
+        response.setStatus(httpStatusCode);
+      }
+      int httpStatusCode = deviceEventHandler.handleException(tr069ex);
+      response.setStatus(httpStatusCode);
+    }
+
+    InformResponse resp = new InformResponse(inform.getId(), MY_MAX_ENVELOPES);
+    resp.setCWMPVersion(msg.getCWMPVersion());
+    resp.writeTo(out);
+  }
+
+  private void processTransferComplete(TR069RPC msg, HttpServletRequest request,
+      HttpServletResponse response, ByteArrayOutputStream out) {
+    TransferComplete tc = (TransferComplete) msg;
+    try {
+      DeviceInformResponse deviceInformResponse = deviceEventHandler.processTransferComplete(tc);
+      Cookie cookie = new Cookie(ACS_SESSIONID, deviceInformResponse.getSessionId());
+      cookie.setSecure(request.isSecure());
+      cookie.setHttpOnly(true);
+      Cookie cwmpVerCookie = new Cookie(CWMP_VERSION, msg.getCWMPVersion() + ";");
+      cwmpVerCookie.setSecure(request.isSecure());
+      cwmpVerCookie.setHttpOnly(true);
+      response.addCookie(cookie);
+      response.addCookie(cwmpVerCookie);
+    } catch (Exception e) {
+      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+      return;
+    }
+    TransferCompleteResponse tr = new TransferCompleteResponse(tc.getId());
+    tr.setCWMPVersion(msg.getCWMPVersion());
+    tr.writeTo(out);
+  }
+
+  private void processOperationResult(TR069RPC msg, HttpServletResponse response, String reqType,
+      String acsSessionID, ByteArrayOutputStream out) {
+    logger.debug("Received Operation Result response {}", msg);
+    if (null == acsSessionID) {
+      logger.error("Received response without session ID, response: {}", reqType);
+    } else {
+      try {
+        TR069RPC message = deviceEventHandler.processRPCResponse(msg, acsSessionID);
+        if (null != message) {
+          message.setCWMPVersion(msg.getCWMPVersion());
+          message.writeTo(out);
+        } else {
+          response.setStatus(HttpServletResponse.SC_NO_CONTENT);
+        }
+      } catch (TR069EventProcessingException tr069ex) {
+        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+      }
+    }
+  }
+
+  private void processEmptyCPERequest(HttpServletResponse response, String cwmpVersion,
+      String acsSessionID, ByteArrayOutputStream out) {
+    if (null == acsSessionID) {
+      logger.error("Received empty response without session ID");
+      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+      return;
+    }
+
+    try {
+      logger.info("Received Empty Device response");
+      TR069RPC message = deviceEventHandler.processEmptyRequest(acsSessionID);
+      if (null != message) {
+        message.setCWMPVersion(cwmpVersion);
+        message.writeTo(out);
+      } else {
+        response.setStatus(HttpServletResponse.SC_NO_CONTENT);
+      }
+    } catch (TR069EventProcessingException tr069ex) {
+      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+    }
+  }
+
+  private void clearCookies(Cookie[] cookies, HttpServletResponse response) {
+    Cookie cookieToDelete = null;
+    if (null != cookies) {
+      logger.debug("Clearing the cookies");
+      for (int i = 0; i < cookies.length; i++) {
+        if (cookies[i].getName().equals(ACS_SESSIONID)
+            || cookies[i].getName().equals(CWMP_VERSION)) {
+          cookieToDelete = cookies[i];
+          cookieToDelete.setMaxAge(0);
+          response.addCookie(cookieToDelete);
+        }
+      }
+    }
+  }
+
+  private static class XmlFilterInputStream extends InputStream {
+
+    private InputStream istream;
+    private int lvl;
+    private int lastchar;
+    @SuppressWarnings("unused")
+    private int len;
+    private int nextchar;
+    private boolean intag = false;
+    private StringBuilder buff = new StringBuilder(16);
+
+    /** Creates a new instance of xmlFilterInputStream */
+    public XmlFilterInputStream(InputStream is, int l) {
+      len = l;
+      istream = is;
+    }
+
+
+    @Override
+    public int read() throws IOException {
+      if (lastchar == '>' && lvl == 0) {
+        return -1;
+      }
+      int l = lastchar;
+      if (!readLastChar())
+        return lastchar;
+
+      if (!intag && lastchar == '&') {
+        int amppos = buff.length();
+        updateBuffer();
+        String s = buff.substring(amppos);
+        replaceSpecialChars(s, amppos);
+        return read();
+      }
+
+      if (l == '<') {
+        intag = true;
+        if (lastchar == '/') {
+          lvl--;
+        } else {
+          lvl++;
+        }
+      }
+
+      len--;
+      return lastchar;
+    }
+
+    public boolean next() throws IOException {
+      if ((nextchar = istream.read()) == -1) {
+        logger.debug("Next char is {}", nextchar);
+        lvl = 0;
+        lastchar = 0;
+      }
+      return (nextchar != -1);
+    }
+
+    private boolean readLastChar() throws IOException {
+      if (nextchar != -1) {
+        lastchar = nextchar;
+        nextchar = -1;
+      } else {
+        if (buff.length() > 0) {
+          lastchar = buff.charAt(0);
+          buff.deleteCharAt(0);
+          return false;
+        } else {
+          lastchar = istream.read();
+        }
+      }
+
+      if (lastchar == '<') {
+        intag = true;
+      } else if (lastchar == '>') {
+        intag = false;
+      }
+
+      return true;
+    }
+
+    private void updateBuffer() throws IOException {
+      // fix up broken xml not encoding &
+      buff.append((char) lastchar);
+      for (int c = 0; c < 10; c++) {
+        int ch = istream.read();
+        boolean breakLoop = false;
+        if (ch == -1) {
+          breakLoop = true;
+        }
+        if (ch == '&') {
+          nextchar = ch;
+          breakLoop = true;
+        }
+        if (breakLoop)
+          break;
+
+        buff.append((char) ch);
+      }
+    }
+
+    private void replaceSpecialChars(String s, int amppos) {
+      if (!s.startsWith("&amp;") && !s.startsWith("&lt;") && !s.startsWith("&gt;")
+          && !s.startsWith("&apos;") && !s.startsWith("&quot;") && !s.startsWith("&#")) {
+        buff.replace(amppos, amppos + 1, "&amp;");
+      }
+    }
+  }
+
+  private static class XmlFilterNS extends InputStream {
+    // Dumb class to filter out declaration of default xmlns
+
+    private String pat = "xmlns=\"urn:dslforum-org:cwmp-1-0\"";
+    private String pat2 = "xmlns=\"urn:dslforum-org:cwmp-1-1\"";
+    private int length = 0;
+    private int pos = 0;
+    private boolean f = false;
+    private byte[] buff = new byte[1024];
+    private InputStream is;
+
+    @Override
+    public int read() throws IOException {
+      if (!f) {
+        length = is.read(buff);
+        if (length < buff.length) {
+          byte[] b2 = new byte[length];
+          System.arraycopy(buff, 0, b2, 0, length);
+          buff = b2;
+        }
+
+        String b = new String(buff, StandardCharsets.UTF_8);
+        b = b.replace(pat, "");
+        b = b.replace(pat2, "");
+        buff = b.getBytes(StandardCharsets.UTF_8);
+        length = buff.length;
+        f = true;
+      }
+
+      if (pos < length) {
+        return buff[pos++] & 0xFF;
+      }
+      return is.read();
+    }
+
+    public XmlFilterNS(InputStream is) {
+      this.is = is;
+    }
+  }
+
+  private static class CharsetConverterInputStream extends InputStream {
+
+    @SuppressWarnings("unused")
+    private InputStream in;
+    private PipedInputStream pipein;
+    private OutputStream pipeout;
+    private Reader r;
+    private Writer w;
+
+    public CharsetConverterInputStream(String csFrom, String csTo, InputStream in)
+        throws IOException {
+      this.in = in;
+      r = new InputStreamReader(in, csFrom);
+      pipein = new PipedInputStream();
+      pipeout = new PipedOutputStream(pipein);
+      w = new OutputStreamWriter(pipeout, csTo);
+    }
+
+    @Override
+    public int read() throws IOException {
+      if (pipein.available() > 0) {
+        return pipein.read();
+      }
+      int c = r.read();
+      if (c == -1) {
+        return -1;
+      }
+      w.write(c);
+      w.flush();
+      return pipein.read();
+    }
+  }
+
+  /**
+   * @return
+   * @throws Exception
+   */
+  private MessageFactory getSOAPMessageFactory() throws SOAPException {
+    MessageFactory mf = null;
+    mf = MessageFactory.newInstance();
+    return mf;
+  }
+
+  /**
+   * @param cookies
+   * @return
+   */
+  private String getACSSessionCookieData(Cookie[] cookies) {
+    String acsSessionID = null;
+    if (null != cookies) {
+      for (Cookie cookie : cookies) {
+        if (cookie.getName().equals(ACS_SESSIONID)) {
+          acsSessionID = cookie.getValue();
+          logger.debug("The session id is {}", acsSessionID);
+        }
+      }
+    }
+    return acsSessionID;
+  }
+
+  /**
+   * @param cookies
+   * @return
+   * @throws TR069EventProcessingException
+   */
+  private String getCWMPVersionCookieData(Cookie[] cookies) throws TR069EventProcessingException {
+    String cwmpVersion = null;
+    try {
+      if (null != cookies) {
+        for (Cookie cookie : cookies) {
+          if (cookie.getName().equals(CWMP_VERSION)) {
+            cwmpVersion = cookie.getValue();
+            if (cwmpVersion != null) {
+              cwmpVersion = URLDecoder.decode(cwmpVersion, StandardCharsets.UTF_8.name());
+            }
+            logger.debug("The CWMP version supported by the device is: {}", cwmpVersion);
+          }
+        }
+      }
+    } catch (UnsupportedEncodingException e) {
+      logger.error(e.getMessage());
+      TR069EventProcessingException ex = new TR069EventProcessingException(
+          ErrorCode.UNSUPPORTED_CHARACTER_ENCODING, StandardCharsets.UTF_8.name());
+      logger.error(ex.getMessage());
+      throw ex;
+    }
+    return cwmpVersion;
+  }
+
+  /**
+   * @param soapMsg
+   */
+  private void logSoapMsg(SOAPMessage soapMsg) {
+    StringBuilder buffer = new StringBuilder();
+    buffer.append(soapMsg.toString());
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    try {
+      soapMsg.writeTo(baos);
+    } catch (SOAPException | IOException e) {
+      logger.error("Error while writting soap message");
+    }
+    buffer.append(baos);
+    String soapMessage = buffer.toString();
+    logger.debug(soapMessage);
+  }
+
+  /**
+   * @param response
+   * @param out
+   * @param fcodeAcsRequestDenied
+   * @param faultString
+   * @param id
+   * @throws IOException
+   */
+  private void sendFault(HttpServletResponse response, ByteArrayOutputStream out,
+      String fcodeAcsRequestDenied, String faultString, String id) throws IOException {
+    Fault fault = new Fault(fcodeAcsRequestDenied, faultString, id);
+    fault.writeTo(out);
+    response.setContentLength(out.size());
+    String sout = out.toString(UTF_8);
+    sout = sout.replace('\'', '"');
+    response.getOutputStream().print(sout);
+  }
+
+  private void logHeaderElements(Enumeration<String> headerName) {
+    while (headerName.hasMoreElements()) {
+      String requestHeader = headerName.nextElement();
+      logger.debug("Request Headers {}", requestHeader);
+    }
+  }
+
+  private String getRequestType(TR069RPC msg) {
+    String requestType = msg.getName();
+    if (requestType == null)
+      requestType = "Empty Request";
+
+    return requestType;
+  }
+
+  /******************************************************************************************************************/
+
+  public DeviceEventHandler getDeviceEventHandler() {
+    return deviceEventHandler;
+  }
+
+  public void setDeviceEventHandler(DeviceEventHandler deviceEventHandler) {
+    this.deviceEventHandler = deviceEventHandler;
+  }
+
+}