Skip to content
Permalink
Browse files
first round to pass TCKs, still a few things to enhance/polish/impl b…
…ut this is a first milestone, yeah :)
  • Loading branch information
rmannibucau committed Jul 8, 2018
1 parent 1bbaf24 commit 32ecb9ef5c19df2bcaa141e59dabdba0de0fd828
Showing 21 changed files with 433 additions and 90 deletions.
@@ -218,7 +218,7 @@
<dependency>
<groupId>org.apache.geronimo.config</groupId>
<artifactId>geronimo-config-impl</artifactId>
<version>1.1</version>
<version>1.2</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -17,6 +17,7 @@
package org.apache.geronimo.microprofile.openapi.cdi;

import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toSet;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
@@ -29,10 +30,12 @@
import java.util.stream.Stream;

import javax.enterprise.event.Observes;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.spi.Annotated;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.BeforeBeanDiscovery;
import javax.enterprise.inject.spi.CDI;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.ProcessBean;
@@ -41,31 +44,63 @@
import javax.ws.rs.core.Application;

import org.apache.geronimo.microprofile.openapi.config.GeronimoOpenAPIConfig;
import org.apache.geronimo.microprofile.openapi.impl.filter.FilterImpl;
import org.apache.geronimo.microprofile.openapi.impl.loader.DefaultLoader;
import org.apache.geronimo.microprofile.openapi.impl.model.PathsImpl;
import org.apache.geronimo.microprofile.openapi.impl.processor.AnnotatedMethodElement;
import org.apache.geronimo.microprofile.openapi.impl.processor.AnnotatedTypeElement;
import org.apache.geronimo.microprofile.openapi.impl.processor.AnnotationProcessor;
import org.eclipse.microprofile.openapi.OASConfig;
import org.eclipse.microprofile.openapi.OASFilter;
import org.eclipse.microprofile.openapi.OASModelReader;
import org.eclipse.microprofile.openapi.models.OpenAPI;

public class GeronimoOpenAPIExtension implements Extension {

private final AnnotationProcessor processor = new AnnotationProcessor();

private final Collection<Bean<?>> endpoints = new ArrayList<>();

private final Map<Application, OpenAPI> openapis = new HashMap<>();

public <T> void findEndpointsAndApplication(@Observes final ProcessBean<T> event) {
if (event.getAnnotated().isAnnotationPresent(Path.class)) {
private GeronimoOpenAPIConfig config;
private AnnotationProcessor processor;
private boolean skipScan;
private Collection<String> classes;
private Collection<String> packages;
private Collection<String> excludePackages;
private Collection<String> excludeClasses;

void init(@Observes final BeforeBeanDiscovery beforeBeanDiscovery) {
config = GeronimoOpenAPIConfig.create();
processor = new AnnotationProcessor(config);
skipScan = Boolean.parseBoolean(config.read(OASConfig.SCAN_DISABLE, "false"));
classes = getConfigCollection(OASConfig.SCAN_CLASSES);
packages = getConfigCollection(OASConfig.SCAN_PACKAGES);
excludePackages = getConfigCollection(OASConfig.SCAN_EXCLUDE_PACKAGES);
excludeClasses = getConfigCollection(OASConfig.SCAN_EXCLUDE_CLASSES);
}

<T> void findEndpointsAndApplication(@Observes final ProcessBean<T> event) {
final String typeName = event.getAnnotated().getBaseType().getTypeName();
if (classes == null && !skipScan && event.getAnnotated().isAnnotationPresent(Path.class) &&
!typeName.startsWith("org.apache.geronimo.microprofile.openapi.") &&
(packages == null || packages.stream().anyMatch(typeName::startsWith))) {
endpoints.add(event.getBean());
}
}

public OpenAPI getOrCreateOpenAPI(final Application application) {
// todo: there are configs for that, todo: handle it
if (!application.getSingletons().isEmpty() || !application.getClasses().isEmpty()) {
if (classes != null) {
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
return openapis.computeIfAbsent(application,
app -> createOpenApi(application.getClass(), classes.stream().map(c -> {
try {
return loader.loadClass(c);
} catch (final ClassNotFoundException e) {
throw new IllegalArgumentException(e);
}
})));
}
if (packages == null && (!application.getSingletons().isEmpty() || !application.getClasses().isEmpty())) {
return openapis.computeIfAbsent(application,
app -> createOpenApi(application.getClass(), Stream.concat(endpoints.stream().map(Bean::getBeanClass),
Stream.concat(app.getClasses().stream(), app.getSingletons().stream().map(Object::getClass)))));
@@ -74,47 +109,63 @@ public OpenAPI getOrCreateOpenAPI(final Application application) {
app -> createOpenApi(application.getClass(), endpoints.stream().map(Bean::getBeanClass)));
}

private Collection<String> getConfigCollection(final String key) {
return ofNullable(config.read(key, null))
.map(vals -> Stream.of(vals.split(",")).map(String::trim).filter(v -> !v.isEmpty()).collect(toSet()))
.orElse(null);
}

private OpenAPI createOpenApi(final Class<?> application, final Stream<Class<?>> beans) {
final CDI<Object> current = CDI.current();
final GeronimoOpenAPIConfig config = GeronimoOpenAPIConfig.create();
final OpenAPI api = ofNullable(config.read("mp.openapi.model.reader", null)).map(value -> newInstance(current, value))
.map(it -> OASModelReader.class.cast(it).buildModel()).orElseGet(() -> {
final OpenAPI api = ofNullable(config.read(OASConfig.MODEL_READER, null))
.map(value -> newInstance(current, value))
.map(it -> OASModelReader.class.cast(it).buildModel())
.orElseGet(() -> {
final BeanManager beanManager = current.getBeanManager();
final OpenAPI impl = current.select(DefaultLoader.class).get().loadDefaultApi();
processor.processApplication(impl, new ElementImpl(beanManager.createAnnotatedType(application)));

// todo: use servlet to get the servlet mapping which is a valid deployment too
final String base = ofNullable(application.getAnnotation(ApplicationPath.class)).map(ApplicationPath::value)
.filter(it -> !"/".equals(it))
.map(it -> it.endsWith("*") ? it.substring(0, it.length() - 1) : it)
.orElse("");
beans.map(beanManager::createAnnotatedType).forEach(at -> processor.processClass(base, impl, new ElementImpl(at),
at.getMethods().stream().map(MethodElementImpl::new)));
return impl;
});

return ofNullable(config.read("mp.openapi.filter", null))
final BeanManager beanManager = current.getBeanManager();
processor.processApplication(api, new ElementImpl(beanManager.createAnnotatedType(application)));
if (skipScan) {
return api.paths(new PathsImpl());
}

final String base = getApplicationBinding(application);
beans.filter(c -> (excludeClasses == null || !excludeClasses.contains(c.getName())))
.filter(c -> (excludePackages == null || excludePackages.stream().noneMatch(it -> c.getName().startsWith(it))))
.map(beanManager::createAnnotatedType)
.forEach(at -> processor.processClass(
base, api, new ElementImpl(at), at.getMethods().stream().map(MethodElementImpl::new)));

return ofNullable(config.read(OASConfig.FILTER, null))
.map(it -> newInstance(current, it))
.map(i -> {
OASFilter.class.cast(i).filterOpenAPI(api);
return api;
})
.map(i -> new FilterImpl(OASFilter.class.cast(i)).filter(api))
.orElse(api);
}

private String getApplicationBinding(final Class<?> application) {
// todo: use servlet to get the servlet mapping which is a valid deployment too
return ofNullable(application.getAnnotation(ApplicationPath.class)).map(ApplicationPath::value)
.filter(it -> !"/".equals(it))
.map(it -> it.endsWith("*") ? it.substring(0, it.length() - 1) : it)
.orElse("");
}

private Object newInstance(final CDI<Object> current, final String value) {
try {
final Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(value.trim());
try {
return current.select(clazz);
} catch (final RuntimeException e) {
try {
return clazz.getConstructor().newInstance();
} catch (final Exception e1) {
throw e;
final Instance<?> instance = current.select(clazz);
if (instance.isResolvable()) {
return instance.get();
}
} catch (final RuntimeException e) {
// let do " new"
}
} catch (final ClassNotFoundException e) {
return clazz.getConstructor().newInstance();
} catch (final Exception e) {
throw new IllegalArgumentException("Can't load " + value, e);
}
}
@@ -128,6 +179,11 @@ private MethodElementImpl(final AnnotatedMethod<?> delegate) {
this.delegate = delegate;
}

@Override
public String getName() {
return delegate.getJavaMember().getName();
}

@Override
public Type getReturnType() {
return delegate.getJavaMember().getGenericReturnType();
@@ -0,0 +1,189 @@
/*
* 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.geronimo.microprofile.openapi.impl.filter;

import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;

import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.eclipse.microprofile.openapi.OASFilter;
import org.eclipse.microprofile.openapi.models.Components;
import org.eclipse.microprofile.openapi.models.OpenAPI;
import org.eclipse.microprofile.openapi.models.Operation;
import org.eclipse.microprofile.openapi.models.callbacks.Callback;
import org.eclipse.microprofile.openapi.models.links.Link;
import org.eclipse.microprofile.openapi.models.parameters.Parameter;
import org.eclipse.microprofile.openapi.models.servers.Server;
import org.eclipse.microprofile.openapi.models.tags.Tag;

public class FilterImpl {
private final OASFilter delegate;

public FilterImpl(final OASFilter delegate) {
this.delegate = delegate;
}

// todo: likely complete it since the visitor pattern is not complete ATM
public OpenAPI filter(final OpenAPI api) {
ofNullable(api.getComponents()).ifPresent(this::filterComponents);
ofNullable(api.getPaths())
.ifPresent(paths -> {
paths.forEach((k, v) -> {
ofNullable(v.readOperationsMap())
.ifPresent(operations -> {
operations.entrySet().stream()
.filter(it -> it.getValue() != null)
.filter(it -> delegate.filterOperation(it.getValue()) == null)
.map(Map.Entry::getKey)
.collect(toList())
.forEach(operations::remove);
operations.values().stream().filter(Objects::nonNull).forEach(this::filterOperation);
});

paths.entrySet().stream()
.filter(it -> delegate.filterPathItem(it.getValue()) == null)
.map(Map.Entry::getKey)
.collect(toList())
.forEach(paths::remove);

ofNullable(v.getParameters()).ifPresent(this::filterParameters);
ofNullable(v.getServers()).ifPresent(this::filterServers);
});
});
ofNullable(api.getServers()).ifPresent(this::filterServers);
ofNullable(api.getTags()).ifPresent(this::filterTags);
delegate.filterOpenAPI(api);
return api;
}

private void filterOperation(final Operation op) {
ofNullable(op.getServers()).ifPresent(this::filterServers);
ofNullable(op.getRequestBody()).ifPresent(it -> {
if (delegate.filterRequestBody(it) == null) {
op.requestBody(null);
}
});
ofNullable(op.getParameters()).ifPresent(this::filterParameters);
ofNullable(op.getCallbacks()).ifPresent(this::filterCallbacks);
ofNullable(op.getResponses())
.ifPresent(responses -> {
responses.entrySet().stream()
.filter(it -> delegate.filterAPIResponse(it.getValue()) == null)
.map(Map.Entry::getKey)
.collect(toList())
.forEach(responses::remove);
responses.forEach((rk, response) -> {
ofNullable(response.getLinks()).ifPresent(this::filterLinks);
ofNullable(response.getContent())
.ifPresent(contents -> contents.values().forEach(content -> {
ofNullable(content.getSchema()).ifPresent(schema -> {
if (delegate.filterSchema(schema) == null) {
content.setSchema(null);
}
});
}));
});
});
}

private void filterComponents(final Components components) {
ofNullable(components.getCallbacks()).ifPresent(this::filterCallbacks);
ofNullable(components.getHeaders())
.ifPresent(headers ->
headers.entrySet().stream()
.filter(it -> delegate.filterHeader(it.getValue()) == null)
.map(Map.Entry::getKey)
.collect(toList())
.forEach(headers::remove));
ofNullable(components.getLinks()).ifPresent(this::filterLinks);
ofNullable(components.getParameters())
.ifPresent(parameters ->
parameters.entrySet().stream()
.filter(it -> delegate.filterParameter(it.getValue()) == null)
.map(Map.Entry::getKey)
.collect(toList())
.forEach(parameters::remove));
ofNullable(components.getRequestBodies())
.ifPresent(requestBodies ->
requestBodies.entrySet().stream()
.filter(it -> delegate.filterRequestBody(it.getValue()) == null)
.map(Map.Entry::getKey)
.collect(toList())
.forEach(requestBodies::remove));
ofNullable(components.getResponses())
.ifPresent(responses ->
responses.entrySet().stream()
.filter(it -> delegate.filterAPIResponse(it.getValue()) == null)
.map(Map.Entry::getKey)
.collect(toList())
.forEach(responses::remove));
ofNullable(components.getSchemas())
.ifPresent(schemas ->
schemas.entrySet().stream()
.filter(it -> delegate.filterSchema(it.getValue()) == null)
.map(Map.Entry::getKey)
.collect(toList())
.forEach(schemas::remove));
ofNullable(components.getSecuritySchemes())
.ifPresent(schemes ->
schemes.entrySet().stream()
.filter(it -> delegate.filterSecurityScheme(it.getValue()) == null)
.map(Map.Entry::getKey)
.collect(toList())
.forEach(schemes::remove));
}

private void filterLinks(final Map<String, Link> links) {
links.entrySet().stream()
.filter(it -> delegate.filterLink(it.getValue()) == null)
.map(Map.Entry::getKey)
.collect(toList())
.forEach(links::remove);
}

private void filterCallbacks(final Map<String, Callback> callbacks) {
callbacks.entrySet().stream()
.filter(it -> delegate.filterCallback(it.getValue()) == null)
.map(Map.Entry::getKey)
.collect(toList())
.forEach(callbacks::remove);
}

private void filterTags(final List<Tag> tags) {
tags.stream()
.filter(it -> delegate.filterTag(it) == null)
.collect(toList())
.forEach(tags::remove);
}

private void filterParameters(final List<Parameter> parameters) {
parameters.stream()
.filter(it -> delegate.filterParameter(it) == null)
.collect(toList())
.forEach(parameters::remove);
}

private void filterServers(final List<Server> servers) {
servers.stream()
.filter(it -> delegate.filterServer(it) == null)
.collect(toList())
.forEach(servers::remove);
}
}
@@ -144,7 +144,7 @@ public String getRef() {

@Override
public void setRef(final String _ref) {
this._ref = _ref;
this._ref = _ref.startsWith("#") ? _ref : ("#/components/responses/" + _ref);
}

@Override

0 comments on commit 32ecb9e

Please sign in to comment.