You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2020/03/07 18:15:35 UTC
[juneau] branch master updated: JUNEAU-188
This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new 3d5a340 JUNEAU-188
3d5a340 is described below
commit 3d5a3403ad79f96def9ed6571b3c39e607b3cdec
Author: JamesBognar <ja...@apache.org>
AuthorDate: Sat Mar 7 13:15:19 2020 -0500
JUNEAU-188
@Bean annotation should override class visibility rules.
---
.../java/org/apache/juneau/VisibilityTest.java | 8 +-
.../src/test/java/org/apache/juneau/a/A1.java | 5 -
.../juneau/annotation/BeanAnnotationTest.java | 156 +++++++++++++++++++++
.../src/main/java/org/apache/juneau/BeanMeta.java | 55 +++++---
.../java/org/apache/juneau/reflect/ClassInfo.java | 26 ++++
juneau-doc/docs/ReleaseNotes/8.1.4.html | 10 +-
6 files changed, 230 insertions(+), 30 deletions(-)
diff --git a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/VisibilityTest.java b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/VisibilityTest.java
index e2d67dc..d00101e 100755
--- a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/VisibilityTest.java
+++ b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/VisibilityTest.java
@@ -26,10 +26,10 @@ public class VisibilityTest {
//====================================================================================================
@Test
public void testClassDefault() throws Exception {
- JsonSerializerBuilder s1 = JsonSerializer.create().ssq().beansRequireSomeProperties(false);
- JsonSerializerBuilder s2 = JsonSerializer.create().ssq().beansRequireSomeProperties(false).beanClassVisibility(PROTECTED);
- JsonSerializerBuilder s3 = JsonSerializer.create().ssq().beansRequireSomeProperties(false).beanClassVisibility(Visibility.DEFAULT);
- JsonSerializerBuilder s4 = JsonSerializer.create().ssq().beansRequireSomeProperties(false).beanClassVisibility(PRIVATE);
+ JsonSerializerBuilder s1 = JsonSerializer.create().ssq().sortProperties().beansRequireSomeProperties(false);
+ JsonSerializerBuilder s2 = JsonSerializer.create().ssq().sortProperties().beansRequireSomeProperties(false).beanClassVisibility(PROTECTED);
+ JsonSerializerBuilder s3 = JsonSerializer.create().ssq().sortProperties().beansRequireSomeProperties(false).beanClassVisibility(Visibility.DEFAULT);
+ JsonSerializerBuilder s4 = JsonSerializer.create().ssq().sortProperties().beansRequireSomeProperties(false).beanClassVisibility(PRIVATE);
A1 a1 = A1.create();
String r;
diff --git a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/a/A1.java b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/a/A1.java
index 8700f13..911b2ef 100755
--- a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/a/A1.java
+++ b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/a/A1.java
@@ -16,7 +16,6 @@ import org.apache.juneau.annotation.*;
// Default class
@SuppressWarnings({"unused"})
-@Bean(sort=true)
public class A1 {
public int f1;
protected int f2;
@@ -89,7 +88,6 @@ public class A1 {
return x;
}
- @Bean(sort=true)
public static class A2 {
public int f1;
protected int f2;
@@ -114,7 +112,6 @@ public class A1 {
}
}
- @Bean(sort=true)
protected static class A3 {
public int f1;
protected int f2;
@@ -139,7 +136,6 @@ public class A1 {
}
}
- @Bean(sort=true)
static class A4 {
public int f1;
protected int f2;
@@ -164,7 +160,6 @@ public class A1 {
}
}
- @Bean(sort=true)
private static class A5 {
public int f1;
protected int f2;
diff --git a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/annotation/BeanAnnotationTest.java b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/annotation/BeanAnnotationTest.java
new file mode 100644
index 0000000..197d1e1
--- /dev/null
+++ b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/annotation/BeanAnnotationTest.java
@@ -0,0 +1,156 @@
+// ***************************************************************************************************************************
+// * 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.juneau.annotation;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.juneau.json.*;
+import org.apache.juneau.marshall.*;
+import org.apache.juneau.reflect.*;
+import org.junit.*;
+
+public class BeanAnnotationTest {
+
+ //------------------------------------------------------------------------------------------------------------------
+ // @Bean annotation overrides visibility rules on class and constructor.
+ //------------------------------------------------------------------------------------------------------------------
+
+ @Bean
+ @SuppressWarnings("unused")
+ private static class A1 {
+ public int f1;
+
+ public static A1 create() {
+ A1 a = new A1();
+ a.f1 = 1;
+ return a;
+ }
+ }
+
+ @Test
+ public void testBeanAnnotationOverridesPrivate() throws Exception {
+ String json = SimpleJson.DEFAULT.toString(A1.create());
+ assertEquals("{f1:1}", json);
+ A1 a = SimpleJson.DEFAULT.read(json, A1.class);
+ json = SimpleJson.DEFAULT.toString(a);
+ assertEquals("{f1:1}", json);
+ }
+
+ @BeanConfig(applyBean=@Bean(on="A2"))
+ @SuppressWarnings("unused")
+ private static class A2 {
+ public int f1;
+
+ public static A2 create() {
+ A2 a = new A2();
+ a.f1 = 1;
+ return a;
+ }
+ }
+ static ClassInfo a2ci = ClassInfo.of(A2.class);
+
+ @Test
+ public void testBeanAnnotationOverridesPrivate_usingConfig() throws Exception {
+ AnnotationList al = a2ci.getAnnotationList(null);
+ JsonSerializer js = JsonSerializer.create().simple().applyAnnotations(al, null).build();
+ JsonParser jp = JsonParser.create().applyAnnotations(al, null).build();
+
+ String json = js.serialize(A2.create());
+ assertEquals("{f1:1}", json);
+ A2 a = jp.parse(json, A2.class);
+ json = js.serialize(a);
+ assertEquals("{f1:1}", json);
+ }
+
+ //------------------------------------------------------------------------------------------------------------------
+ // @Beanc and @Beanp annotations overrides visibility rules on constructors/properties.
+ //------------------------------------------------------------------------------------------------------------------
+
+ public static class B1 {
+
+ @Beanp
+ private int f1;
+
+ private int f2;
+
+ @Beanp
+ private void setF2(int f2) {
+ this.f2 = f2;
+ }
+
+ @Beanp
+ private int getF2() {
+ return f2;
+ }
+
+ @Beanc
+ private B1() {}
+
+ public static B1 create() {
+ B1 b = new B1();
+ b.f1 = 1;
+ b.f2 = 2;
+ return b;
+ }
+ }
+
+ @Test
+ public void testBeanxAnnotationOverridesPrivate() throws Exception {
+ String json = SimpleJson.DEFAULT.toString(B1.create());
+ assertEquals("{f1:1,f2:2}", json);
+ B1 b = SimpleJson.DEFAULT.read(json, B1.class);
+ json = SimpleJson.DEFAULT.toString(b);
+ assertEquals("{f1:1,f2:2}", json);
+ }
+
+ @BeanConfig(applyBeanc=@Beanc(on="B2()"),applyBeanp={@Beanp(on="B2.f1"),@Beanp(on="B2.setF2"),@Beanp(on="B2.getF2")})
+ @SuppressWarnings("unused")
+ public static class B2 {
+
+ private int f1;
+
+ private int f2;
+
+ private void setF2(int f2) {
+ this.f2 = f2;
+ }
+
+ private int getF2() {
+ return f2;
+ }
+
+ private B2() {}
+
+ public static B2 create() {
+ B2 b = new B2();
+ b.f1 = 1;
+ b.f2 = 2;
+ return b;
+ }
+ }
+ static ClassInfo b2ci = ClassInfo.of(B2.class);
+
+ @Test
+ public void testBeanxAnnotationOverridesPrivate_usingConfig() throws Exception {
+ AnnotationList al = b2ci.getAnnotationList(null);
+ JsonSerializer js = JsonSerializer.create().simple().applyAnnotations(al, null).build();
+ JsonParser jp = JsonParser.create().applyAnnotations(al, null).build();
+
+ String json = js.serialize(B2.create());
+ assertEquals("{f1:1,f2:2}", json);
+ B2 b = jp.parse(json, B2.class);
+ json = js.serialize(b);
+ assertEquals("{f1:1,f2:2}", json);
+ }
+}
+
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
index 808c056..eaf4769 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
@@ -21,6 +21,7 @@ import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.*;
+import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
@@ -198,21 +199,25 @@ public class BeanMeta<T> {
Map<String,BeanPropertyMeta.Builder> normalProps = new LinkedHashMap<>();
+ Annotation ba = ci.findFirstAnnotation(ctx, Bean.class, BeanIgnore.class);
+ boolean hasBean = ba != null && ba.annotationType() == Bean.class;
+ boolean hasBeanIgnore = ba != null && ba.annotationType() == BeanIgnore.class;
+
/// See if this class matches one the patterns in the exclude-class list.
if (ctx.isNotABean(c))
return "Class matches exclude-class list";
- if (! (cVis.isVisible(c.getModifiers()) || c.isAnonymousClass()))
+ if (! hasBean && ! (cVis.isVisible(c.getModifiers()) || c.isAnonymousClass()))
return "Class is not public";
- if (isIgnored(c))
+ if (hasBeanIgnore)
return "Class is annotated with @BeanIgnore";
// Make sure it's serializable.
if (beanFilter == null && ctx.isBeansRequireSerializable() && ! ci.isChildOf(Serializable.class))
return "Class is not serializable";
- // Look for @BeanConstructor constructor.
+ // Look for @Beanc constructor on public constructors.
for (ConstructorInfo x : ci.getPublicConstructors()) {
if (x.hasAnnotation(BeanConstructor.class)) {
if (constructor != null)
@@ -254,12 +259,38 @@ public class BeanMeta<T> {
}
}
+ // Look for @Beanc on all other constructors.
+ if (constructor == null) {
+ for (ConstructorInfo x : ci.getDeclaredConstructors()) {
+ if (ctx.hasAnnotation(Beanc.class, x)) {
+ if (constructor != null)
+ throw new BeanRuntimeException(c, "Multiple instances of '@Beanc' found.");
+ constructor = x;
+ constructorArgs = split(ctx.getAnnotation(Beanc.class, x).properties());
+ if (constructorArgs.length != x.getParamCount()) {
+ if (constructorArgs.length != 0)
+ throw new BeanRuntimeException(c, "Number of properties defined in '@Beanc' annotation does not match number of parameters in constructor.");
+ constructorArgs = new String[x.getParamCount()];
+ int i = 0;
+ for (ParamInfo pi : x.getParams()) {
+ String pn = pi.getName();
+ if (pn == null)
+ throw new BeanRuntimeException(c, "Could not find name for parameter #{0} of constructor ''{1}''", i, x.getFullName());
+ constructorArgs[i++] = pn;
+ }
+ }
+ constructor.setAccessible();
+ }
+ }
+ }
+
+
// If this is an interface, look for impl classes defined in the context.
if (constructor == null)
constructor = ctx.getImplClassConstructor(c, conVis);
if (constructor == null)
- constructor = ci.getNoArgConstructor(conVis);
+ constructor = ci.getNoArgConstructor(hasBean ? Visibility.PRIVATE : conVis);
if (constructor == null && beanFilter == null && ctx.isBeansRequireDefaultConstructor())
return "Class does not have the required no-arg constructor";
@@ -482,22 +513,6 @@ public class BeanMeta<T> {
return null;
}
- private boolean isIgnored(Class<?> c) {
- if (c == null)
- return false;
- if (ctx.hasDeclaredAnnotation(BeanIgnore.class, c))
- return true;
- if (ctx.hasDeclaredAnnotation(Bean.class, c))
- return false;
- for (Class<?> ci : c.getInterfaces()) {
- if (ctx.hasDeclaredAnnotation(BeanIgnore.class, ci))
- return true;
- if (ctx.hasDeclaredAnnotation(Bean.class, ci))
- return false;
- }
- return isIgnored(c.getSuperclass());
- }
-
private String findDictionaryName(ClassMeta<?> cm) {
BeanRegistry br = cm.getBeanRegistry();
if (br != null) {
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ClassInfo.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ClassInfo.java
index 4f591a2..0aced5d 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ClassInfo.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ClassInfo.java
@@ -1285,6 +1285,32 @@ public final class ClassInfo {
return l;
}
+ /**
+ * Searches up the parent hierarchy of this class for the first annotation in the list it finds.
+ *
+ * @param mp Metadata provider.
+ * @param annotations The annotations to search for.
+ * @return The first annotation found, or <jk>null</jk> if not found.
+ */
+ @SafeVarargs
+ public final Annotation findFirstAnnotation(MetaProvider mp, Class<? extends Annotation>...annotations) {
+ for (Class<? extends Annotation> ca : annotations) {
+ Annotation x = getAnnotation(ca, mp);
+ if (x != null)
+ return x;
+ }
+ for (ClassInfo ci : getInterfaces()) {
+ for (Class<? extends Annotation> ca : annotations) {
+ Annotation x = ci.getAnnotation(ca, mp);
+ if (x != null)
+ return x;
+ }
+ }
+ ClassInfo ci = getParent();
+ return ci == null ? null : ci.findFirstAnnotation(mp, annotations);
+ }
+
+
AnnotationList appendAnnotationList(AnnotationList m) {
for (ClassInfo ci : getParents())
for (Annotation a : ci.c.getDeclaredAnnotations())
diff --git a/juneau-doc/docs/ReleaseNotes/8.1.4.html b/juneau-doc/docs/ReleaseNotes/8.1.4.html
index 9008dc2..479d397 100644
--- a/juneau-doc/docs/ReleaseNotes/8.1.4.html
+++ b/juneau-doc/docs/ReleaseNotes/8.1.4.html
@@ -21,9 +21,17 @@
<h5 class='topic w800'>juneau-marshall</h5>
<ul class='spaced-list'>
<li>
- <ja>@BeanIgnore</ja> and <ja>@Bean</ja> annotations can alternately occur in parent class hierarchy.
+ {@link oaj.annotation.Bean @Bean} and {@link oaj.annotation.BeanIgnore @BeanIgnore} annotations can alternately occur in parent class hierarchy.
The first one found dictates whether a class is ignored as a bean or not.
<li>
+ Applying the {@link oaj.annotation.Bean @Bean} annotation on a class will now force non-public classes to be interpreted as beans.
+ For example, applying {@link oaj.annotation.Bean @Bean} to a <jk>private</jk> class will force it to be treated as a bean.
+ <br>
+ Also, if a public bean constructor cannot be found, the default constructor will be used
+ regardless of it's visibility if the {@link oaj.annotation.Bean @Bean} annotation is on the class.
+ <li>
+ The <ja>@Beanc</ja> annotation can now be recognized and used on non-public constructors.
+ <li>
Several bug fixes in the {@link HtmlSerializer} and {@link HtmlParser} classes around the handling of
collections and arrays of beans with <c><ja>@Bean</ja>(typeName)</c> annotations.
<li>