You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ss...@apache.org on 2016/08/25 11:03:44 UTC
svn commit: r1757657 - in
/sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/impl:
ConfigurationBuilderImpl.java ConfigurationProxy.java
Author: sseifert
Date: Thu Aug 25 11:03:44 2016
New Revision: 1757657
URL: http://svn.apache.org/viewvc?rev=1757657&view=rev
Log:
SLING-5982 refactor dynamic proxy handling to separate class and add a caching invocation handler
Added:
sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/impl/ConfigurationProxy.java (with props)
Modified:
sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/impl/ConfigurationBuilderImpl.java
Modified: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/impl/ConfigurationBuilderImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/impl/ConfigurationBuilderImpl.java?rev=1757657&r1=1757656&r2=1757657&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/impl/ConfigurationBuilderImpl.java (original)
+++ sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/impl/ConfigurationBuilderImpl.java Thu Aug 25 11:03:44 2016
@@ -18,21 +18,14 @@
*/
package org.apache.sling.contextaware.config.impl;
-import java.lang.reflect.Array;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.List;
import java.util.stream.Collectors;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.contextaware.config.ConfigurationBuilder;
-import org.apache.sling.contextaware.config.ConfigurationResolveException;
import org.apache.sling.contextaware.config.resource.ConfigurationResourceResolver;
class ConfigurationBuilderImpl implements ConfigurationBuilder {
@@ -94,7 +87,7 @@ class ConfigurationBuilderImpl implement
if (clazz == ValueMap.class) {
return (T)ResourceUtil.getValueMap(configResource);
}
- return convertPropsToClass(configResource, clazz);
+ return ConfigurationProxy.get(configResource, clazz);
}
@SuppressWarnings("unchecked")
@@ -109,77 +102,10 @@ class ConfigurationBuilderImpl implement
.collect(Collectors.toList());
}
return configResources.stream()
- .map(resource -> convertPropsToClass(resource, clazz))
+ .map(resource -> ConfigurationProxy.get(resource, clazz))
.collect(Collectors.toList());
}
return Collections.emptyList();
}
- @SuppressWarnings("unchecked")
- private <T> T convertPropsToClass(Resource resource, Class<T> clazz) {
-
- // only annotation interface classes are supported
- if (!(clazz.isInterface() && clazz.isAnnotation())) {
- throw new ConfigurationResolveException("Annotation interface class expected: " + clazz.getName());
- }
-
- // create dynamic proxy for annotation class accessing underlying resource properties
- return (T)Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz },
- new InvocationHandler() {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- String propName = getInterfacePropertyName(method);
- if (propName == null) {
- return null;
- }
-
- // check for nested configuration classes
- Class<?> targetType = method.getReturnType();
- Class<?> componentType = method.getReturnType();
- boolean isArray = targetType.isArray();
- if (isArray) {
- componentType = targetType.getComponentType();
- }
- if (componentType.isInterface() && componentType.isAnnotation()) {
- Resource childResource = resource != null ? resource.getChild(propName) : null;
- if (isArray) {
- Iterable<Resource> listItemResources = childResource != null ? childResource.getChildren() : new ArrayList<>();
- List<Object> listItems = new ArrayList<Object>();
- for (Resource listItemResource : listItemResources) {
- listItems.add(convertPropsToClass(listItemResource, componentType));
- }
- return listItems.toArray((Object[])Array.newInstance(componentType, listItems.size()));
- }
- else {
- return convertPropsToClass(childResource, componentType);
- }
- }
-
- // detect default value
- Object defaultValue = method.getDefaultValue();
- if (defaultValue == null && targetType.isPrimitive() && !targetType.isArray()) {
- // get default value for primitive data type (use hack via array)
- defaultValue = Array.get(Array.newInstance(targetType, 1), 0);
- }
-
- // get value from valuemap with given type/default value
- ValueMap props = ResourceUtil.getValueMap(resource);
- Object value;
- if (defaultValue != null) {
- value = props.get(propName, defaultValue);
- }
- else {
- value = props.get(propName, targetType);
- }
- return value;
-
- }
- });
- }
-
- private static String getInterfacePropertyName(Method md) {
- // TODO: support all the escaping mechanisms.
- return md.getName().replace('_', '.');
- }
-
}
Added: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/impl/ConfigurationProxy.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/impl/ConfigurationProxy.java?rev=1757657&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/impl/ConfigurationProxy.java (added)
+++ sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/impl/ConfigurationProxy.java Thu Aug 25 11:03:44 2016
@@ -0,0 +1,170 @@
+/*
+ * 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.sling.contextaware.config.impl;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.contextaware.config.ConfigurationResolveException;
+
+
+/**
+ * Maps the property of a resource to a dynamic proxy object implementing
+ * the annotation class defining the configuration parameters.
+ * Nested configurations with annotation classes referencing other annotation classes are also supported.
+ */
+final class ConfigurationProxy {
+
+ private ConfigurationProxy() {
+ // static methods only
+ }
+
+ /**
+ * Get dynamic proxy for given resources's properties mappend to given annotation class.
+ * @param resource Resource
+ * @param clazz Annotation class
+ * @return Dynamic proxy object
+ */
+ @SuppressWarnings("unchecked")
+ public @Nonnull static <T> T get(@Nullable Resource resource, @Nonnull Class<T> clazz) {
+
+ // only annotation interface classes are supported
+ if (!(clazz.isInterface() && clazz.isAnnotation())) {
+ throw new ConfigurationResolveException("Annotation interface class expected: " + clazz.getName());
+ }
+
+ // create dynamic proxy for annotation class accessing underlying resource properties
+ // wrap in caching invocation handler so client code can call all methods multiple times
+ // without having to worry about performance
+ return (T)Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz },
+ new CachingInvocationHandler(new DynamicProxyInvocationHandler(resource)));
+ }
+
+ /**
+ * Maps resource properties to annotation class proxy, and support nested configurations.
+ */
+ private static class DynamicProxyInvocationHandler implements InvocationHandler {
+
+ private final Resource resource;
+
+ private DynamicProxyInvocationHandler(Resource resource) {
+ this.resource = resource;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) {
+ String propName = getInterfacePropertyName(method);
+
+ // check for nested configuration classes
+ Class<?> targetType = method.getReturnType();
+ Class<?> componentType = method.getReturnType();
+ boolean isArray = targetType.isArray();
+ if (isArray) {
+ componentType = targetType.getComponentType();
+ }
+ if (componentType.isInterface() && componentType.isAnnotation()) {
+ Resource childResource = resource != null ? resource.getChild(propName) : null;
+ if (isArray) {
+ Iterable<Resource> listItemResources = childResource != null ? childResource.getChildren() : Collections.emptyList();
+ List<Object> listItems = new ArrayList<Object>();
+ for (Resource listItemResource : listItemResources) {
+ listItems.add(get(listItemResource, componentType));
+ }
+ return listItems.toArray((Object[])Array.newInstance(componentType, listItems.size()));
+ }
+ else {
+ return get(childResource, componentType);
+ }
+ }
+
+ // detect default value
+ Object defaultValue = method.getDefaultValue();
+ if (defaultValue == null && targetType.isPrimitive() && !targetType.isArray()) {
+ // get default value for primitive data type (use hack via array)
+ defaultValue = Array.get(Array.newInstance(targetType, 1), 0);
+ }
+
+ // get value from valuemap with given type/default value
+ ValueMap props = ResourceUtil.getValueMap(resource);
+ Object value;
+ if (defaultValue != null) {
+ value = props.get(propName, defaultValue);
+ }
+ else {
+ value = props.get(propName, targetType);
+ }
+ return value;
+
+ }
+
+ }
+
+ private static String getInterfacePropertyName(Method md) {
+ // TODO: support all the escaping mechanisms.
+ return md.getName().replace('_', '.');
+ }
+
+ /**
+ * Invocation handler that caches all results for each method name, and returns
+ * the result from cache on next invocation.
+ */
+ private static class CachingInvocationHandler implements InvocationHandler {
+
+ private final InvocationHandler delegate;
+ private final Map<String, Object> results = new HashMap<>();
+ private static final Object NULL_OBJECT = new Object();
+
+ public CachingInvocationHandler(InvocationHandler delegate) {
+ this.delegate = delegate;
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ String key = method.getName();
+ Object result = results.get(key);
+ if (result == null) {
+ result = delegate.invoke(proxy, method, args);
+ if (result == null) {
+ result = NULL_OBJECT;
+ }
+ results.put(key, result);
+ }
+ if (result == NULL_OBJECT) {
+ return null;
+ }
+ else {
+ return result;
+ }
+ }
+
+ }
+
+}
Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/impl/ConfigurationProxy.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/impl/ConfigurationProxy.java
------------------------------------------------------------------------------
--- svn:keywords (added)
+++ svn:keywords Thu Aug 25 11:03:44 2016
@@ -0,0 +1 @@
+LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author
Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/impl/ConfigurationProxy.java
------------------------------------------------------------------------------
svn:mime-type = text/plain