You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by db...@apache.org on 2022/02/21 03:08:30 UTC

[tomee] 02/09: TOMEE-3844 Improve logging for JAX-RS application deployment

This is an automated email from the ASF dual-hosted git repository.

dblevins pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tomee.git

commit 61372ce4f5d1bcd25b4f42ec78b2024f301558e5
Author: David Blevins <db...@tomitribe.com>
AuthorDate: Sun Feb 20 03:28:03 2022 -0500

    TOMEE-3844 Improve logging for JAX-RS application deployment
---
 itests/jaxrs/pom.xml                               |  80 +++++++++
 .../itests/jaxrs/applogging/AnnotatedWriter.java   |  31 ++++
 .../jaxrs/applogging/ApplicationLoggingTest.java   | 196 +++++++++++++++++++++
 .../itests/jaxrs/applogging/CircleResource.java    |  29 +++
 .../jaxrs/applogging/DiscoveredResources.java      |  24 +++
 .../tomee/itests/jaxrs/applogging/GetClasses.java  |  35 ++++
 .../jaxrs/applogging/GetClassesNoProviders.java    |  34 ++++
 .../applogging/GetClassesNonAnnotatedProvider.java |  35 ++++
 .../itests/jaxrs/applogging/GetSingletons.java     |  35 ++++
 .../jaxrs/applogging/NotAnnotatedWriter.java       |  29 +++
 .../itests/jaxrs/applogging/SquareResource.java    |  29 +++
 .../itests/jaxrs/applogging/TriangleResource.java  |  28 +++
 itests/pom.xml                                     |   1 +
 .../openejb/server/cxf/rs/ApplicationData.java     | 173 ++++++++++++++++++
 .../openejb/server/cxf/rs/CxfRsHttpListener.java   |  96 +++++++++-
 .../server/cxf/rs/NoResourcesFoundException.java   |  23 +++
 16 files changed, 877 insertions(+), 1 deletion(-)

diff --git a/itests/jaxrs/pom.xml b/itests/jaxrs/pom.xml
new file mode 100644
index 0000000..880a6c3
--- /dev/null
+++ b/itests/jaxrs/pom.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+  <version>8.0.11-SNAPSHOT</version>
+
+  <parent>
+    <artifactId>itests</artifactId>
+    <groupId>org.apache.tomee</groupId>
+    <version>8.0.11-SNAPSHOT</version>
+  </parent>
+
+  <groupId>org.apache.tomee.itests</groupId>
+  <artifactId>jaxrs</artifactId>
+  <packaging>jar</packaging>
+  <name>TomEE :: iTests :: JAX-RS</name>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <systemPropertyVariables>
+            <version>${project.version}</version>
+          </systemPropertyVariables>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.tomee</groupId>
+      <artifactId>tomee-server-composer</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomee</groupId>
+      <artifactId>apache-tomee</artifactId>
+      <version>8.0.11-SNAPSHOT</version>
+      <type>tar.gz</type>
+      <classifier>microprofile</classifier>
+      <exclusions>
+        <exclusion>
+          <groupId>*</groupId>
+          <artifactId>*</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomee.bom</groupId>
+      <artifactId>tomee-webprofile-api</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+    </dependency>
+  </dependencies>
+</project>
+
diff --git a/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/AnnotatedWriter.java b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/AnnotatedWriter.java
new file mode 100644
index 0000000..291f00a
--- /dev/null
+++ b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/AnnotatedWriter.java
@@ -0,0 +1,31 @@
+/*
+ * 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.tomee.itests.jaxrs.applogging;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.ext.Provider;
+import javax.ws.rs.ext.WriterInterceptor;
+import javax.ws.rs.ext.WriterInterceptorContext;
+import java.io.IOException;
+
+@Provider
+public class AnnotatedWriter implements WriterInterceptor {
+    @Override
+    public void aroundWriteTo(final WriterInterceptorContext writerInterceptorContext) throws IOException, WebApplicationException {
+
+    }
+}
diff --git a/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/ApplicationLoggingTest.java b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/ApplicationLoggingTest.java
new file mode 100644
index 0000000..4d02e89
--- /dev/null
+++ b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/ApplicationLoggingTest.java
@@ -0,0 +1,196 @@
+/*
+ * 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.tomee.itests.jaxrs.applogging;
+
+import org.apache.tomee.server.composer.Archive;
+import org.apache.tomee.server.composer.TomEE;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.tomitribe.util.Join;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class ApplicationLoggingTest {
+
+    @Test
+    public void discovered() throws Exception {
+
+        final ArrayList<String> output = new ArrayList<>();
+        final TomEE tomee = TomEE.webprofile()
+                .add("webapps/test/WEB-INF/lib/app.jar", Archive.archive()
+                        .add(DiscoveredResources.class)
+                        .add(SquareResource.class)
+                        .add(TriangleResource.class)
+                        .add(AnnotatedWriter.class)
+                        .asJar())
+                .watch("org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logApplication ", "\n", output::add)
+                .build();
+
+        Collections.sort(output);
+
+        final String join = Join.join("\n", output);
+        assertEquals("Application{path='http://localhost:0/test/blue', class=org.apache.tomee.itests.jaxrs.applogging.DiscoveredResources, resources=2, providers=1, invalids=0}\n" +
+                "Provider{clazz=org.apache.tomee.itests.jaxrs.applogging.AnnotatedWriter, discovered=true, singleton=false}\n" +
+                "Resource{clazz=org.apache.tomee.itests.jaxrs.applogging.SquareResource, discovered=true, singleton=false}\n" +
+                "Resource{clazz=org.apache.tomee.itests.jaxrs.applogging.TriangleResource, discovered=true, singleton=false}", normalize(join));
+    }
+
+    @Test
+    public void getClasses() throws Exception {
+
+        final ArrayList<String> output = new ArrayList<>();
+        final TomEE tomee = TomEE.webprofile()
+                .add("webapps/test/WEB-INF/lib/app.jar", Archive.archive()
+                        .add(GetClasses.class)
+                        .add(SquareResource.class)
+                        .add(TriangleResource.class)
+                        .add(AnnotatedWriter.class)
+                        .asJar())
+                .watch("org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logApplication ", "\n", output::add)
+                .build();
+
+        Collections.sort(output);
+
+        final String join = Join.join("\n", output);
+        assertEquals("Application{path='http://localhost:0/test/red', class=org.apache.tomee.itests.jaxrs.applogging.GetClasses, resources=2, providers=1, invalids=0}\n" +
+                "Provider{clazz=org.apache.tomee.itests.jaxrs.applogging.AnnotatedWriter, discovered=false, singleton=false}\n" +
+                "Resource{clazz=org.apache.tomee.itests.jaxrs.applogging.SquareResource, discovered=false, singleton=false}\n" +
+                "Resource{clazz=org.apache.tomee.itests.jaxrs.applogging.TriangleResource, discovered=false, singleton=false}", normalize(join));
+    }
+
+    /**
+     * This test shows two bugs:
+     * - singletons show up as discovered when they were explicitly configured
+     *
+     * @throws Exception
+     */
+    @Ignore
+    @Test
+    public void getSingletons() throws Exception {
+
+        final ArrayList<String> output = new ArrayList<>();
+        final TomEE tomee = TomEE.webprofile()
+                .add("webapps/test/WEB-INF/lib/app.jar", Archive.archive()
+                        .add(GetSingletons.class)
+                        .add(SquareResource.class)
+                        .add(TriangleResource.class)
+                        .add(AnnotatedWriter.class)
+                        .asJar())
+                .watch("org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logApplication ", "\n", output::add)
+//                .debug(5005)
+                .build();
+
+        Collections.sort(output);
+
+        final String join = Join.join("\n", output);
+        assertEquals("Application{path='http://localhost:0/test/red', class=org.apache.tomee.itests.jaxrs.applogging.GetSingletons, resources=2, providers=2, invalids=0}\n" +
+                "Provider{clazz=org.apache.tomee.itests.jaxrs.applogging.AnnotatedWriter, discovered=false, singleton=true}\n" +
+                "Resource{clazz=org.apache.tomee.itests.jaxrs.applogging.SquareResource, discovered=false, singleton=true}\n" +
+                "Resource{clazz=org.apache.tomee.itests.jaxrs.applogging.TriangleResource, discovered=false, singleton=true}", normalize(join));
+    }
+
+
+    /**
+     * We appear to be breaking the second sentence of this requirement of the JAX-RS specification:
+     *
+     * ----
+     * When an Application subclass is present in the archive, if both Application.getClasses
+     * and Application.getSingletons return an empty collection then all root resource classes and
+     * providers packaged in the web application MUST be included and the JAX-RS implementation is
+     * REQUIRED to discover them automatically by scanning a .war file as described above. If either
+     * getClasses or getSingletons returns a non-empty collection then only those classes or singletons
+     * returned MUST be included in the published JAX-RS application.
+     * ----
+     *
+     * Despite there being a getClasses() method, we still scan for @Provider implementations in the classpath
+     * add them to the application.
+     */
+    @Ignore()
+    @Test
+    public void getClassesNoProviders() throws Exception {
+
+        final ArrayList<String> output = new ArrayList<>();
+        final TomEE tomee = TomEE.webprofile()
+                .add("webapps/test/WEB-INF/lib/app.jar", Archive.archive()
+                        .add(GetClassesNoProviders.class)
+                        .add(SquareResource.class)
+                        .add(TriangleResource.class)
+                        .add(CircleResource.class)
+                        .add(AnnotatedWriter.class)
+                        .add(NotAnnotatedWriter.class)
+                        .asJar())
+                .watch("org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logApplication ", "\n", output::add)
+                .build();
+
+        Collections.sort(output);
+        final String join = Join.join("\n", output);
+        assertEquals("Application{path='http://localhost:0/test/red', class=org.apache.tomee.itests.jaxrs.applogging.GetClassesNoProviders, resources=2, providers=1, invalids=0}\n" +
+                "Resource{clazz=org.apache.tomee.itests.jaxrs.applogging.SquareResource, discovered=false, singleton=false}\n" +
+                "Resource{clazz=org.apache.tomee.itests.jaxrs.applogging.TriangleResource, discovered=false, singleton=false}", normalize(join));
+    }
+
+    @Test
+    public void getClassesNonAnnotatedProvider() throws Exception {
+
+        final ArrayList<String> output = new ArrayList<>();
+        final TomEE tomee = TomEE.webprofile()
+                .add("webapps/test/WEB-INF/lib/app.jar", Archive.archive()
+                        .add(GetClassesNonAnnotatedProvider.class)
+                        .add(SquareResource.class)
+                        .add(TriangleResource.class)
+                        .add(CircleResource.class)
+                        .add(NotAnnotatedWriter.class)
+                        .asJar())
+                .watch("org.apache.openejb.server.cxf.rs.CxfRsHttpListener.logApplication ", "\n", output::add)
+                .build();
+
+        Collections.sort(output);
+        final String join = Join.join("\n", output);
+        assertEquals("Application{path='http://localhost:0/test/red', class=org.apache.tomee.itests.jaxrs.applogging.GetClassesNonAnnotatedProvider, resources=2, providers=1, invalids=0}\n" +
+                "Provider{clazz=org.apache.tomee.itests.jaxrs.applogging.NotAnnotatedWriter, discovered=false, singleton=false}\n" +
+                "Resource{clazz=org.apache.tomee.itests.jaxrs.applogging.SquareResource, discovered=false, singleton=false}\n" +
+                "Resource{clazz=org.apache.tomee.itests.jaxrs.applogging.TriangleResource, discovered=false, singleton=false}", normalize(join));
+    }
+
+    private String normalize(final String join) {
+        return join.replaceAll("localhost:[0-9]+", "localhost:0");
+    }
+
+    public void assertPresent(final ArrayList<String> output, final String s) {
+        final Optional<String> actual = output.stream()
+                .filter(line -> line.contains(s))
+                .findFirst();
+
+        assertTrue(actual.isPresent());
+    }
+
+    public void assertNotPresent(final ArrayList<String> output, final String s) {
+        final Optional<String> actual = output.stream()
+                .filter(line -> line.contains(s))
+                .findFirst();
+
+        assertTrue(!actual.isPresent());
+    }
+
+}
\ No newline at end of file
diff --git a/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/CircleResource.java b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/CircleResource.java
new file mode 100644
index 0000000..4136db5
--- /dev/null
+++ b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/CircleResource.java
@@ -0,0 +1,29 @@
+/*
+ * 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.tomee.itests.jaxrs.applogging;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+@Path("/circle")
+public class CircleResource {
+
+    @GET
+    public String get() {
+        return "";
+    }
+}
diff --git a/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/DiscoveredResources.java b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/DiscoveredResources.java
new file mode 100644
index 0000000..7b2ece7
--- /dev/null
+++ b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/DiscoveredResources.java
@@ -0,0 +1,24 @@
+/*
+ * 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.tomee.itests.jaxrs.applogging;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+
+@ApplicationPath("/blue")
+public class DiscoveredResources extends Application {
+}
diff --git a/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/GetClasses.java b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/GetClasses.java
new file mode 100644
index 0000000..0aa72d8
--- /dev/null
+++ b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/GetClasses.java
@@ -0,0 +1,35 @@
+/*
+ * 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.tomee.itests.jaxrs.applogging;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+import java.util.HashSet;
+import java.util.Set;
+
+@ApplicationPath("/red")
+public class GetClasses extends Application {
+
+    @Override
+    public Set<Class<?>> getClasses() {
+        final Set<Class<?>> classes = new HashSet<>();
+        classes.add(SquareResource.class);
+        classes.add(TriangleResource.class);
+        classes.add(AnnotatedWriter.class);
+        return classes;
+    }
+}
diff --git a/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/GetClassesNoProviders.java b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/GetClassesNoProviders.java
new file mode 100644
index 0000000..5678d14
--- /dev/null
+++ b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/GetClassesNoProviders.java
@@ -0,0 +1,34 @@
+/*
+ * 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.tomee.itests.jaxrs.applogging;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+import java.util.HashSet;
+import java.util.Set;
+
+@ApplicationPath("/red")
+public class GetClassesNoProviders extends Application {
+
+    @Override
+    public Set<Class<?>> getClasses() {
+        final Set<Class<?>> classes = new HashSet<>();
+        classes.add(SquareResource.class);
+        classes.add(TriangleResource.class);
+        return classes;
+    }
+}
diff --git a/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/GetClassesNonAnnotatedProvider.java b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/GetClassesNonAnnotatedProvider.java
new file mode 100644
index 0000000..ba65d16
--- /dev/null
+++ b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/GetClassesNonAnnotatedProvider.java
@@ -0,0 +1,35 @@
+/*
+ * 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.tomee.itests.jaxrs.applogging;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+import java.util.HashSet;
+import java.util.Set;
+
+@ApplicationPath("/red")
+public class GetClassesNonAnnotatedProvider extends Application {
+
+    @Override
+    public Set<Class<?>> getClasses() {
+        final Set<Class<?>> classes = new HashSet<>();
+        classes.add(SquareResource.class);
+        classes.add(TriangleResource.class);
+        classes.add(NotAnnotatedWriter.class);
+        return classes;
+    }
+}
diff --git a/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/GetSingletons.java b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/GetSingletons.java
new file mode 100644
index 0000000..d38f75a
--- /dev/null
+++ b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/GetSingletons.java
@@ -0,0 +1,35 @@
+/*
+ * 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.tomee.itests.jaxrs.applogging;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+import java.util.HashSet;
+import java.util.Set;
+
+@ApplicationPath("/red")
+public class GetSingletons extends Application {
+
+    @Override
+    public Set<Object> getSingletons() {
+        final Set<Object> singletons = new HashSet<>();
+        singletons.add(new SquareResource());
+        singletons.add(new TriangleResource());
+        singletons.add(new AnnotatedWriter());
+        return singletons;
+    }
+}
diff --git a/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/NotAnnotatedWriter.java b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/NotAnnotatedWriter.java
new file mode 100644
index 0000000..4d5a840
--- /dev/null
+++ b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/NotAnnotatedWriter.java
@@ -0,0 +1,29 @@
+/*
+ * 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.tomee.itests.jaxrs.applogging;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.ext.WriterInterceptor;
+import javax.ws.rs.ext.WriterInterceptorContext;
+import java.io.IOException;
+
+public class NotAnnotatedWriter implements WriterInterceptor {
+    @Override
+    public void aroundWriteTo(final WriterInterceptorContext writerInterceptorContext) throws IOException, WebApplicationException {
+
+    }
+}
diff --git a/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/SquareResource.java b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/SquareResource.java
new file mode 100644
index 0000000..fe4d572
--- /dev/null
+++ b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/SquareResource.java
@@ -0,0 +1,29 @@
+/*
+ * 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.tomee.itests.jaxrs.applogging;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+@Path("/square")
+public class SquareResource {
+
+    @GET
+    public String get() {
+        return "";
+    }
+}
diff --git a/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/TriangleResource.java b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/TriangleResource.java
new file mode 100644
index 0000000..cbce53f
--- /dev/null
+++ b/itests/jaxrs/src/test/java/org/apache/tomee/itests/jaxrs/applogging/TriangleResource.java
@@ -0,0 +1,28 @@
+/*
+ * 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.tomee.itests.jaxrs.applogging;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+@Path("/triangle")
+public class TriangleResource {
+    @GET
+    public String get() {
+        return "";
+    }
+}
diff --git a/itests/pom.xml b/itests/pom.xml
index a186f95..9db4403 100644
--- a/itests/pom.xml
+++ b/itests/pom.xml
@@ -46,6 +46,7 @@
     <module>openejb-itests-interceptor-beans</module>
     <module>openejb-itests-servlets</module>
     <module>openejb-itests-web</module>
+    <module>jaxrs</module>
   </modules>
 
 </project>
diff --git a/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/ApplicationData.java b/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/ApplicationData.java
new file mode 100644
index 0000000..488cce7
--- /dev/null
+++ b/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/ApplicationData.java
@@ -0,0 +1,173 @@
+/*
+ * 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.openejb.server.cxf.rs;
+
+import org.apache.openejb.server.rest.InternalApplication;
+
+import javax.ws.rs.core.Application;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ApplicationData {
+
+    private final String path;
+    private final Application application;
+    private final Class<?> applicationClass;
+    final List<Resource> resources = new ArrayList<>();
+    final List<Provider> providers = new ArrayList<>();
+    final List<Invalid> invalids = new ArrayList<>();
+
+    public ApplicationData(final String path, final Application application) {
+        this.path = path;
+        this.application = application;
+
+        if (application instanceof InternalApplication) {
+            final InternalApplication internalApplication = (InternalApplication) application;
+            this.applicationClass = internalApplication.getOriginal().getClass();
+        } else {
+            this.applicationClass = application.getClass();
+        }
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public Application getApplication() {
+        return application;
+    }
+
+    public Class<?> getApplicationClass() {
+        return applicationClass;
+    }
+
+    public List<Resource> getResources() {
+        return resources;
+    }
+
+    public List<Class<?>> getResourceClasses() {
+        return resources.stream()
+                .map(Resource::getClazz)
+                .collect(Collectors.toList());
+    }
+
+    public List<Provider> getProviders() {
+        return providers;
+    }
+
+    public List<Invalid> getInvalids() {
+        return invalids;
+    }
+
+    public void addProvider(final boolean discovered, final Class<?> clazz, final Object singleton) {
+        providers.add(new Provider(discovered, clazz, singleton));
+    }
+
+    public void addResource(final boolean discovered, final Class<?> clazz, final Object singleton){
+        resources.add(new Resource(discovered, clazz, singleton));
+    }
+
+    public void addInvalid(final Class<?> clazz, final String reason) {
+        invalids.add(new Invalid(clazz, reason));
+    }
+    
+    @Override
+    public String toString() {
+        return "Application{" +
+                "path='" + path + '\'' +
+                ", class=" + applicationClass.getName() +
+                ", resources=" + resources.size() +
+                ", providers=" + providers.size() +
+                ", invalids=" + invalids.size() +
+                '}';
+    }
+
+    public static class Resource {
+        private final Class<?> clazz;
+        private final boolean discovered;
+        private final Object singleton;
+
+        public Resource(final boolean discovered, final Class<?> clazz, final Object singleton) {
+            this.discovered = discovered;
+            this.clazz = clazz;
+            this.singleton = singleton;
+        }
+
+        public boolean isDiscovered() {
+            return discovered;
+        }
+
+        public Class<?> getClazz() {
+            return clazz;
+        }
+
+        public Object getSingleton() {
+            return singleton;
+        }
+
+        @Override
+        public String toString() {
+            return "Resource{" +
+                    "clazz=" + clazz.getName() +
+                    ", discovered=" + discovered +
+                    ", singleton=" + (singleton != null) +
+                    '}';
+        }
+    }
+
+    public static class Provider {
+        private final Class<?> clazz;
+        private final boolean discovered;
+        private final Object singleton;
+
+        public Provider(final boolean discovered, final Class<?> clazz, final Object singleton) {
+            this.discovered = discovered;
+            this.clazz = clazz;
+            this.singleton = singleton;
+        }
+
+        @Override
+        public String toString() {
+            return "Provider{" +
+                    "clazz=" + clazz.getName() +
+                    ", discovered=" + discovered +
+                    ", singleton=" + (singleton != null) +
+                    '}';
+        }
+    }
+
+    public static class Invalid {
+        private final Class<?> clazz;
+        private final String reason;
+
+        public Invalid(final Class<?> clazz, final String reason) {
+            this.clazz = clazz;
+            this.reason = reason;
+        }
+
+        @Override
+        public String toString() {
+            return "Invalid{" +
+                    "clazz=" + clazz.getName() +
+                    ", reason='" + reason + '\'' +
+                    '}';
+        }
+    }
+
+
+}
diff --git a/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/CxfRsHttpListener.java b/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/CxfRsHttpListener.java
index c612b4a..dd9580c 100644
--- a/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/CxfRsHttpListener.java
+++ b/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/CxfRsHttpListener.java
@@ -653,6 +653,14 @@ public class CxfRsHttpListener implements RsHttpListener {
         final ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
         Thread.currentThread().setContextClassLoader(CxfUtil.initBusLoader());
         try {
+            final ApplicationData applicationData = getApplicationData(application, prefix, additionalProviders);
+
+            logApplication(applicationData);
+
+            if (applicationData.getResources().size() == 0) {
+                throw new NoResourcesFoundException(applicationData);
+            }
+
             final JAXRSServerFactoryBean factory = newFactory(prefix, createServiceJmxName(classLoader), createEndpointName(application));
             configureFactory(additionalProviders, serviceConfiguration, factory, owbCtx, application);
             factory.setApplication(application);
@@ -808,6 +816,92 @@ public class CxfRsHttpListener implements RsHttpListener {
         }
     }
 
+    private void logApplication(final ApplicationData applicationData) {
+        LOGGER.info(applicationData.toString());
+        for (ApplicationData.Resource resource : applicationData.getResources()) {
+            String toString = resource.toString();
+            LOGGER.info(toString);
+        }
+        for (ApplicationData.Provider provider1 : applicationData.getProviders()) {
+            String toString = provider1.toString();
+            LOGGER.info(toString);
+        }
+        for (ApplicationData.Invalid invalid : applicationData.getInvalids()) {
+            String toString = invalid.toString();
+            LOGGER.warning(toString);
+        }
+    }
+
+    private ApplicationData getApplicationData(final Application application, final String prefix, final Collection<Object> additionalProviders) {
+
+        final ApplicationData applicationData = new ApplicationData(prefix, application);
+
+        final Set<Class<?>> declaredClasses = new HashSet<>();
+        final Set<Object> declaredSingletons = new HashSet<>();
+
+        if (application instanceof InternalApplication) {
+            final InternalApplication internalApplication = (InternalApplication) application;
+            declaredClasses.addAll(internalApplication.getOriginal().getClasses());
+            declaredSingletons.addAll(internalApplication.getOriginal().getSingletons());
+        } else {
+            declaredClasses.addAll(application.getClasses());
+            declaredSingletons.addAll(application.getSingletons());
+        }
+
+        for (final Object additionalProvider : additionalProviders) {
+            if (additionalProvider instanceof Class) {
+
+                final boolean discovered = !declaredSingletons.contains(additionalProvider);
+
+                applicationData.addProvider(discovered, (Class<?>) additionalProvider, null);
+
+            } else {
+                final boolean discovered = !declaredSingletons.contains(additionalProvider);
+
+                applicationData.addProvider(discovered, additionalProvider.getClass(), null);
+
+            }
+        }
+
+        for (final Class<?> clazz : application.getClasses()) {
+            // We've already added the provider above.  This is a duplicate
+            if (additionalProviders.contains(clazz)) continue;
+
+            if (clazz.isInterface()) {
+
+                applicationData.addInvalid(clazz, "is interface");
+
+            } else if (clazz.isEnum()) {
+
+                applicationData.addInvalid(clazz, "is enum");
+
+            } else if (clazz.isPrimitive()) {
+
+                applicationData.addInvalid(clazz, "is primitive");
+
+            } else {
+
+                final boolean discovered = !declaredClasses.contains(clazz);
+
+                applicationData.addResource(discovered, clazz, null);
+
+            }
+        }
+
+        for (final Object singleton : application.getSingletons()) {
+            // We've already added the provider above.  This is a duplicate
+            if (additionalProviders.contains(singleton)) continue;
+
+            final Class<?> clazz = realClass(singleton.getClass());
+
+            final boolean configured = declaredClasses.contains(clazz) || declaredClasses.contains(singleton.getClass());
+
+            applicationData.addResource(!configured, clazz, singleton);
+        }
+
+        return applicationData;
+    }
+
     /**
      * JAX-RS allows for the Application subclass to have @Context injectable fields, as is
      * the case for Resources and Providers.  CXF will do the injection on the Application
@@ -836,7 +930,7 @@ public class CxfRsHttpListener implements RsHttpListener {
             injectApplication(original, factory);
             return;
         }
-        
+
         final Bus bus = factory.getBus();
         new ApplicationInfo(application, bus);
     }
diff --git a/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/NoResourcesFoundException.java b/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/NoResourcesFoundException.java
new file mode 100644
index 0000000..310e735
--- /dev/null
+++ b/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/NoResourcesFoundException.java
@@ -0,0 +1,23 @@
+/*
+ * 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.openejb.server.cxf.rs;
+
+public class NoResourcesFoundException extends IllegalArgumentException {
+    public NoResourcesFoundException(final ApplicationData applicationData) {
+        super("Application must contain at least one JAX-RS resource class: " + applicationData);
+    }
+}