+/* Copyright (c) 2019 AT&T Intellectual Property. #\r
+# #\r
+# Licensed under the Apache License, Version 2.0 (the "License"); #\r
+# you may not use this file except in compliance with the License. #\r
+# You may obtain a copy of the License at #\r
+# #\r
+# http://www.apache.org/licenses/LICENSE-2.0 #\r
+# #\r
+# Unless required by applicable law or agreed to in writing, software #\r
+# distributed under the License is distributed on an "AS IS" BASIS, #\r
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #\r
+# See the License for the specific language governing permissions and #\r
+# limitations under the License. #\r
+##############################################################################*/\r
+\r
+\r
+package org.oran.otf.api.config;\r
+\r
+import org.glassfish.jersey.logging.LoggingFeature;\r
+import org.glassfish.jersey.message.MessageUtils;\r
+\r
+import javax.ws.rs.WebApplicationException;\r
+import javax.ws.rs.client.ClientRequestContext;\r
+import javax.ws.rs.client.ClientRequestFilter;\r
+import javax.ws.rs.client.ClientResponseContext;\r
+import javax.ws.rs.client.ClientResponseFilter;\r
+import javax.ws.rs.container.ContainerRequestContext;\r
+import javax.ws.rs.container.ContainerRequestFilter;\r
+import javax.ws.rs.container.ContainerResponseContext;\r
+import javax.ws.rs.container.ContainerResponseFilter;\r
+import javax.ws.rs.core.FeatureContext;\r
+import javax.ws.rs.core.MultivaluedMap;\r
+import javax.ws.rs.ext.WriterInterceptor;\r
+import javax.ws.rs.ext.WriterInterceptorContext;\r
+import java.io.*;\r
+import java.net.URI;\r
+import java.nio.charset.Charset;\r
+import java.util.ArrayList;\r
+import java.util.Base64;\r
+import java.util.List;\r
+import java.util.Objects;\r
+import java.util.logging.Level;\r
+import java.util.logging.Logger;\r
+\r
+public class OTFLoggingFeature extends LoggingFeature implements ContainerRequestFilter, ContainerResponseFilter,\r
+ ClientRequestFilter, ClientResponseFilter, WriterInterceptor {\r
+\r
+ private static final boolean printEntity = true;\r
+ private static final int maxEntitySize = 8 * 1024;\r
+ private final Logger logger = Logger.getLogger("OTFLoggingFeature");\r
+ private static final String ENTITY_LOGGER_PROPERTY = OTFLoggingFeature.class.getName();\r
+ private static final String NOTIFICATION_PREFIX = "* ";\r
+ private static final String REQUEST_PREFIX = "> ";\r
+ private static final String RESPONSE_PREFIX = "< ";\r
+ private static final String AUTHORIZATION = "Authorization";\r
+ private static final String EQUAL = " = ";\r
+ private static final String HEADERS_SEPARATOR = ", ";\r
+ private static List<String> requestHeaders;\r
+\r
+ static {\r
+ requestHeaders = new ArrayList<>();\r
+ requestHeaders.add(AUTHORIZATION);\r
+ }\r
+\r
+ public OTFLoggingFeature(Logger logger, Level level, Verbosity verbosity, Integer maxEntitySize) {\r
+ super(logger, level, verbosity, maxEntitySize);\r
+ }\r
+\r
+ @Override\r
+ public boolean configure(FeatureContext context) {\r
+ context.register(this);\r
+ return true;\r
+ }\r
+\r
+ private Object getEmail(Object authorization){\r
+ try{\r
+ String encoded = ((String) authorization).split(" ")[1];\r
+ String decoded = new String(Base64.getDecoder().decode(encoded));\r
+ return decoded.split(":")[0];\r
+ }\r
+ catch (Exception e){\r
+ return authorization;\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void filter(final ClientRequestContext context) {\r
+ final StringBuilder b = new StringBuilder();\r
+ printHeaders(b, context.getStringHeaders());\r
+ printRequestLine(b, "Sending client request", context.getMethod(), context.getUri());\r
+\r
+ if (printEntity && context.hasEntity()) {\r
+ final OutputStream stream = new LoggingStream(b, context.getEntityStream());\r
+ context.setEntityStream(stream);\r
+ context.setProperty(ENTITY_LOGGER_PROPERTY, stream);\r
+ // not calling log(b) here - it will be called by the interceptor\r
+ } else {\r
+ log(b);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void filter(final ClientRequestContext requestContext, final ClientResponseContext responseContext) throws IOException {\r
+ final StringBuilder b = new StringBuilder();\r
+ printResponseLine(b, "Client response received", responseContext.getStatus());\r
+\r
+ if (printEntity && responseContext.hasEntity()) {\r
+ responseContext.setEntityStream(logInboundEntity(b, responseContext.getEntityStream(),\r
+ MessageUtils.getCharset(responseContext.getMediaType())));\r
+ }\r
+ log(b);\r
+ }\r
+\r
+ @Override\r
+ public void filter(final ContainerRequestContext context) throws IOException {\r
+ final StringBuilder b = new StringBuilder();\r
+ printHeaders(b, context.getHeaders());\r
+ printRequestLine(b, "Server has received a request", context.getMethod(), context.getUriInfo().getRequestUri());\r
+\r
+ if (printEntity && context.hasEntity()) {\r
+ context.setEntityStream(logInboundEntity(b, context.getEntityStream(), MessageUtils.getCharset(context.getMediaType())));\r
+ }\r
+ log(b);\r
+ }\r
+\r
+ @Override\r
+ public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext) {\r
+ final StringBuilder b = new StringBuilder();\r
+ printResponseLine(b, "Server responded with a response", responseContext.getStatus());\r
+\r
+ if (printEntity && responseContext.hasEntity()) {\r
+ final OutputStream stream = new LoggingStream(b, responseContext.getEntityStream());\r
+ responseContext.setEntityStream(stream);\r
+ requestContext.setProperty(ENTITY_LOGGER_PROPERTY, stream);\r
+ // not calling log(b) here - it will be called by the interceptor\r
+ } else {\r
+ log(b);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void aroundWriteTo(final WriterInterceptorContext writerInterceptorContext) throws IOException, WebApplicationException {\r
+ final LoggingStream stream = (LoggingStream) writerInterceptorContext.getProperty(ENTITY_LOGGER_PROPERTY);\r
+ writerInterceptorContext.proceed();\r
+ if (stream != null) {\r
+ log(stream.getStringBuilder(MessageUtils.getCharset(writerInterceptorContext.getMediaType())));\r
+ }\r
+ }\r
+\r
+ private static class LoggingStream extends FilterOutputStream {\r
+ private final StringBuilder b;\r
+ private final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\r
+\r
+ LoggingStream(final StringBuilder b, final OutputStream inner) {\r
+ super(inner);\r
+\r
+ this.b = b;\r
+ }\r
+\r
+ StringBuilder getStringBuilder(Charset charset) {\r
+ // write entity to the builder\r
+ final byte[] entity = byteArrayOutputStream.toByteArray();\r
+\r
+ b.append(new String(entity, 0, Math.min(entity.length, maxEntitySize), charset));\r
+ if (entity.length > maxEntitySize) {\r
+ b.append("...more...");\r
+ }\r
+ b.append('\n');\r
+\r
+ return b;\r
+ }\r
+\r
+ public void write(final int i) throws IOException {\r
+ if (byteArrayOutputStream.size() <= maxEntitySize) {\r
+ byteArrayOutputStream.write(i);\r
+ }\r
+ out.write(i);\r
+ }\r
+ }\r
+\r
+ private void printHeaders(StringBuilder b, MultivaluedMap<String, String> headers) {\r
+ for (String header : requestHeaders) {\r
+ if (Objects.nonNull(headers.get(header))) {\r
+ if(header.equalsIgnoreCase("Authorization")){\r
+ b.append(header).append(EQUAL).append(getEmail(headers.get(header).get(0))).append(HEADERS_SEPARATOR);\r
+ }\r
+ else{\r
+ b.append(header).append(EQUAL).append(headers.get(header)).append(HEADERS_SEPARATOR);\r
+ }\r
+ }\r
+ }\r
+ int lastIndex = b.lastIndexOf(HEADERS_SEPARATOR);\r
+ if (lastIndex != -1) {\r
+ b.delete(lastIndex, lastIndex + HEADERS_SEPARATOR.length());\r
+ b.append("\n");\r
+ }\r
+ }\r
+\r
+ private void log(final StringBuilder b) {\r
+ String message = b.toString();\r
+ if (logger != null) {\r
+ logger.info(message);\r
+ }\r
+ }\r
+\r
+ private void printRequestLine(final StringBuilder b, final String note, final String method, final URI uri) {\r
+ b.append(NOTIFICATION_PREFIX)\r
+ .append(note)\r
+ .append(" on thread ").append(Thread.currentThread().getId())\r
+ .append(REQUEST_PREFIX).append(method).append(" ")\r
+ .append(uri.toASCIIString()).append("\n");\r
+ }\r
+\r
+ private void printResponseLine(final StringBuilder b, final String note, final int status) {\r
+ b.append(NOTIFICATION_PREFIX)\r
+ .append(note)\r
+ .append(" on thread ").append(Thread.currentThread().getId())\r
+ .append(RESPONSE_PREFIX)\r
+ .append(Integer.toString(status))\r
+ .append("\n");\r
+ }\r
+\r
+ private InputStream logInboundEntity(final StringBuilder b, InputStream stream, final Charset charset) throws IOException {\r
+ if (!stream.markSupported()) {\r
+ stream = new BufferedInputStream(stream);\r
+ }\r
+ stream.mark(maxEntitySize + 1);\r
+ final byte[] entity = new byte[maxEntitySize + 1];\r
+ final int entitySize = stream.read(entity);\r
+ b.append(new String(entity, 0, Math.min(entitySize, maxEntitySize), charset));\r
+ if (entitySize > maxEntitySize) {\r
+ b.append("...more...");\r
+ }\r
+ b.append('\n');\r
+ stream.reset();\r
+ return stream;\r
+ }\r
+}
\ No newline at end of file