blob: f1477d5d880cefa4c9dbc109b276da121aac6a73 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.meecrowave.cxf;
import org.apache.cxf.cdi.CXFCdiServlet;
import org.apache.cxf.common.util.ReflectionUtil;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.jaxrs.JAXRSServiceFactoryBean;
import org.apache.cxf.jaxrs.model.ApplicationInfo;
import org.apache.cxf.jaxrs.model.ClassResourceInfo;
import org.apache.cxf.jaxrs.model.MethodDispatcher;
import org.apache.cxf.jaxrs.model.OperationResourceInfo;
import org.apache.cxf.jaxrs.model.ProviderInfo;
import org.apache.cxf.jaxrs.provider.ProviderFactory;
import org.apache.cxf.jaxrs.provider.ServerProviderFactory;
import org.apache.cxf.service.model.EndpointInfo;
import org.apache.cxf.transport.ChainInitiationObserver;
import org.apache.cxf.transport.http.DestinationRegistry;
import org.apache.cxf.transport.servlet.ServletDestination;
import org.apache.meecrowave.Meecrowave;
import org.apache.meecrowave.logging.tomcat.LogFacade;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.ws.rs.core.Application;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
import static java.util.Optional.ofNullable;
// this look a bit complicated but it just:
// - wraps cxf in a filter to support plain resources when not conflicting with application path
// - logs resources
public class CxfCdiAutoSetup implements ServletContainerInitializer {
private static final String NAME = "cxf-cdi";
@Override
public void onStartup(final Set<Class<?>> c, final ServletContext ctx) throws ServletException {
final Meecrowave.Builder builder = Meecrowave.Builder.class.cast(ctx.getAttribute("meecrowave.configuration"));
final MeecrowaveCXFCdiServlet delegate = new MeecrowaveCXFCdiServlet();
final FilterRegistration.Dynamic jaxrs = ctx.addFilter(NAME, new Filter() {
private final String servletPath = builder.getJaxrsMapping().endsWith("/*") ?
builder.getJaxrsMapping().substring(0, builder.getJaxrsMapping().length() - 2) : builder.getJaxrsMapping();
@Override
public void init(final FilterConfig filterConfig) throws ServletException {
delegate.init(new ServletConfig() {
@Override
public String getServletName() {
return NAME;
}
@Override
public ServletContext getServletContext() {
return filterConfig.getServletContext();
}
@Override
public String getInitParameter(final String name) {
return filterConfig.getInitParameter(name);
}
@Override
public Enumeration<String> getInitParameterNames() {
return filterConfig.getInitParameterNames();
}
});
}
@Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
if (!HttpServletRequest.class.isInstance(request)) {
chain.doFilter(request, response);
return;
}
final HttpServletRequest http = HttpServletRequest.class.cast(request);
final String path = http.getRequestURI().substring(http.getContextPath().length());
final Optional<String> app = Stream.of(delegate.prefixes).filter(path::startsWith).findAny();
if (app.isPresent()) {
delegate.service(new HttpServletRequestWrapper(http) { // fake servlet pathInfo and path
@Override
public String getPathInfo() {
return path;
}
@Override
public String getServletPath() {
return servletPath;
}
}, response);
} else {
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
delegate.destroy();
}
});
jaxrs.setAsyncSupported(true);
jaxrs.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC), true, builder.getJaxrsMapping());
ofNullable(builder.getCxfServletParams()).ifPresent(m -> m.forEach(jaxrs::setInitParameter));
}
private static class Logs {
private Logs() {
// no-op
}
private static String forceLength(final String httpMethod, final int l, final boolean right) {
final String http;
if (httpMethod == null) { // subresourcelocator implies null http method
http = "";
} else {
http = httpMethod;
}
final StringBuilder builder = new StringBuilder();
if (!right) {
for (int i = 0; i < l - http.length(); i++) {
builder.append(" ");
}
}
builder.append(http);
if (right) {
for (int i = 0; i < l - http.length(); i++) {
builder.append(" ");
}
}
return builder.toString();
}
private static String toSimpleString(final Method mtd) {
try {
final StringBuilder sb = new StringBuilder();
final Type[] typeparms = mtd.getTypeParameters();
if (typeparms.length > 0) {
boolean first = true;
sb.append("<");
for (Type typeparm : typeparms) {
if (!first) {
sb.append(",");
}
sb.append(name(typeparm));
first = false;
}
sb.append("> ");
}
final Type genRetType = mtd.getGenericReturnType();
sb.append(name(genRetType)).append(" ");
sb.append(mtd.getName()).append("(");
final Type[] params = mtd.getGenericParameterTypes();
for (int j = 0; j < params.length; j++) {
sb.append(name(params[j]));
if (j < (params.length - 1)) {
sb.append(", ");
}
}
sb.append(")");
final Type[] exceptions = mtd.getGenericExceptionTypes();
if (exceptions.length > 0) {
sb.append(" throws ");
for (int k = 0; k < exceptions.length; k++) {
sb.append(name(exceptions[k]));
if (k < (exceptions.length - 1)) {
sb.append(",");
}
}
}
return sb.toString();
} catch (final Exception e) {
return "<" + e + ">";
}
}
private static String name(final Type type) {
if (type instanceof Class<?>) {
return ((Class) type).getSimpleName().replace("java.lang.", "").replace("java.util", "");
} else if (type instanceof ParameterizedType) {
final ParameterizedType pt = (ParameterizedType) type;
final StringBuilder builder = new StringBuilder();
builder.append(name(pt.getRawType()));
final Type[] args = pt.getActualTypeArguments();
if (args != null) {
builder.append("<");
for (int i = 0; i < args.length; i++) {
builder.append(name(args[i]));
if (i < args.length - 1) {
builder.append(", ");
}
}
builder.append(">");
}
return builder.toString();
}
return type.toString();
}
private static String singleSlash(final String address, final String value) {
if (address.endsWith("/") && value.startsWith("/")) {
return address + value.substring(1);
}
if (!address.endsWith("/") && !value.startsWith("/")) {
return address + '/' + value;
}
if ("/".equals(value)) {
return address;
}
return address + value;
}
private static class LogOperationEndpointInfo implements Comparable<LogOperationEndpointInfo> {
private final String http;
private final String address;
private final String method;
private LogOperationEndpointInfo(final String http, final String address, final String method) {
this.address = address;
this.method = method;
if (http != null) {
this.http = http;
} else { // can happen with subresource locators
this.http = "";
}
}
@Override
public int compareTo(final LogOperationEndpointInfo o) {
int compare = http.compareTo(o.http);
if (compare != 0) {
return compare;
}
compare = address.compareTo(o.address);
if (compare != 0) {
return compare;
}
return method.compareTo(o.method);
}
}
private static class LogResourceEndpointInfo implements Comparable<LogResourceEndpointInfo> {
private final String address;
private final String classname;
private final List<LogOperationEndpointInfo> operations;
private final int methodSize;
private final int methodStrSize;
private LogResourceEndpointInfo(final String address, final String classname,
final List<LogOperationEndpointInfo> operations,
final int methodSize, final int methodStrSize) {
this.address = address;
this.classname = classname;
this.operations = operations;
this.methodSize = methodSize;
this.methodStrSize = methodStrSize;
}
@Override
public int compareTo(final LogResourceEndpointInfo o) {
final int compare = address.compareTo(o.address);
if (compare != 0) {
return compare;
}
return classname.compareTo(o.classname);
}
}
}
private static class MeecrowaveCXFCdiServlet extends CXFCdiServlet {
private String[] prefixes;
@Override
public void init(final ServletConfig sc) throws ServletException {
super.init(sc);
// just logging the endpoints
final LogFacade log = new LogFacade(CxfCdiAutoSetup.class.getName());
final String transportId = sc.getInitParameter(TRANSPORT_ID);
final DestinationRegistry registry = getDestinationRegistryFromBusOrDefault(transportId);
prefixes = registry.getDestinations().stream()
.filter(ServletDestination.class::isInstance)
.map(ServletDestination.class::cast)
.map(getServletDestinationPath(sc, log))
.filter(Objects::nonNull)
.toArray(String[]::new);
}
private Function<ServletDestination, String> getServletDestinationPath(ServletConfig sc, LogFacade log)
{
return sd -> {
final Endpoint endpoint = ChainInitiationObserver.class.cast(sd.getMessageObserver()).getEndpoint();
final ApplicationInfo app = ApplicationInfo.class.cast(endpoint.get(Application.class.getName()));
final JAXRSServiceFactoryBean sfb = JAXRSServiceFactoryBean.class.cast(endpoint.get(JAXRSServiceFactoryBean.class.getName()));
final String base = sd.getEndpointInfo().getAddress();
if (sfb != null) {
final List<Logs.LogResourceEndpointInfo> resourcesToLog = new ArrayList<>();
int classSize = 0;
int addressSize = 0;
final List<ClassResourceInfo> resources = sfb.getClassResourceInfo();
for (final ClassResourceInfo info : resources) {
if (info.getResourceClass() == null) { // possible?
continue;
}
final String address = Logs.singleSlash(base, info.getURITemplate().getValue());
final String clazz = uproxyName(info.getResourceClass().getName());
classSize = Math.max(classSize, clazz.length());
addressSize = Math.max(addressSize, address.length());
int methodSize = 7;
int methodStrSize = 0;
final List<Logs.LogOperationEndpointInfo> toLog = new ArrayList<>();
final MethodDispatcher md = info.getMethodDispatcher();
for (final OperationResourceInfo ori : md.getOperationResourceInfos()) {
final String httpMethod = ori.getHttpMethod();
final String currentAddress = Logs.singleSlash(address, ori.getURITemplate().getValue());
final String methodToStr = Logs.toSimpleString(ori.getMethodToInvoke());
toLog.add(new Logs.LogOperationEndpointInfo(httpMethod, currentAddress, methodToStr));
if (httpMethod != null) {
methodSize = Math.max(methodSize, httpMethod.length());
}
addressSize = Math.max(addressSize, currentAddress.length());
methodStrSize = Math.max(methodStrSize, methodToStr.length());
}
Collections.sort(toLog);
resourcesToLog.add(new Logs.LogResourceEndpointInfo(address, clazz, toLog, methodSize, methodStrSize));
}
// effective logging
log.info("REST Application: " + endpoint.getEndpointInfo().getAddress() + " -> "
+ ofNullable(app).map(ApplicationInfo::getResourceClass).map(Class::getName).map(CxfCdiAutoSetup::uproxyName).orElse(""));
Collections.sort(resourcesToLog);
final int fClassSize = classSize;
final int fAddressSize = addressSize;
resourcesToLog.forEach(resource -> {
log.info(" Service URI: "
+ Logs.forceLength(resource.address, fAddressSize, true) + " -> "
+ Logs.forceLength(resource.classname, fClassSize, true));
resource.operations.forEach(info -> {
log.info(" "
+ Logs.forceLength(info.http, resource.methodSize, false) + " "
+ Logs.forceLength(info.address, fAddressSize, true) + " -> "
+ Logs.forceLength(info.method, resource.methodStrSize, true));
});
resource.operations.clear();
});
resourcesToLog.clear();
// log @Providers
if (Meecrowave.Builder.class.cast(sc.getServletContext().getAttribute("meecrowave.configuration")).isJaxrsLogProviders()) {
final ServerProviderFactory spf = ServerProviderFactory.class.cast(endpoint.get(ServerProviderFactory.class.getName()));
dump(log, spf, "MessageBodyReaders", "messageReaders");
dump(log, spf, "MessageBodyWriters", "messageWriters");
}
} else {
final EndpointInfo endpointInfo = endpoint.getEndpointInfo();
if (endpointInfo.getName() != null) {
log.info("@WebService > " + endpointInfo.getName().toString() + " -> " + base);
}
}
return base;
};
}
private void dump(final LogFacade log, final ServerProviderFactory spf, final String description, final String fieldName) {
final Field field = ReflectionUtil.getDeclaredField(ProviderFactory.class, fieldName);
if (!field.isAccessible()) {
field.setAccessible(true);
}
try {
final Collection<ProviderInfo<?>> providers = Collection.class.cast(field.get(spf));
log.info(" " + description);
providers.stream().map(ProviderInfo::getProvider).forEach(o -> {
try {
log.info(" - " + o);
} catch (final RuntimeException re) {
// no-op: maybe cdi context is not active
}
});
} catch (IllegalAccessException e) {
// ignore, not that a big deal
}
}
}
private static String uproxyName(final String clazz) {
if (clazz.contains("$$")) {
return clazz.substring(0, clazz.indexOf("$$"));
}
return clazz;
}
}