Skip to content

Commit

Permalink
UP-4792: Refector to create new service 'ModelAttributeService'; add …
Browse files Browse the repository at this point in the history
…unit tests for ModelAttributeService
  • Loading branch information
drewwills committed Feb 14, 2017
1 parent 6db4c77 commit f69f2e0
Show file tree
Hide file tree
Showing 7 changed files with 406 additions and 124 deletions.
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ buildScan {
}

subprojects {
apply plugin: 'checkstyle'
apply plugin: 'idea'
apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'checkstyle'

repositories {
mavenLocal()
Expand Down
2 changes: 2 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,7 @@ hibernateVersion=4.2.19.Final
httpclientVersion=4.5.2
jasyptVersion=1.9.2
jjwtVersion=0.6.0
junitVersion=4.12
mockitoVersion=2.7.6
slf4jVersion=1.7.21
springVersion=3.2.9.RELEASE
3 changes: 3 additions & 0 deletions uPortal-soffit-renderer/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@ dependencies {

compile "org.springframework:spring-webmvc:${springVersion}"

testCompile "junit:junit:${junitVersion}"
testCompile "org.mockito:mockito-core:${mockitoVersion}"

provided "${servletApiDependency}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/**
* Licensed to Apereo under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Apereo 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 the following location:
*
* 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.apereo.portal.soffit.renderer;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apereo.portal.soffit.model.v1_0.Bearer;
import org.apereo.portal.soffit.model.v1_0.Definition;
import org.apereo.portal.soffit.model.v1_0.PortalRequest;
import org.apereo.portal.soffit.model.v1_0.Preferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

/**
* Responsible for marshalling data required for rendering based on the
* {@link SoffitModelAttribute} annotation.
*
* @since 5.0
* @author drewwills
*/
public class ModelAttributeService {

@Autowired
private ApplicationContext applicationContext;

private Map<AnnotatedElement,Object> modelAttributes;

private final Logger logger = LoggerFactory.getLogger(getClass());

@PostConstruct
public void init() {
/*
* Gather classes & methods that reference @SoffitMoldelAttribute
*/
final Map<AnnotatedElement,Object> map = new HashMap<>();
final String[] beanNames = applicationContext.getBeanDefinitionNames();
for (String name : beanNames) {
final Object bean = applicationContext.getBean(name);
final Class clazz = AopUtils.isAopProxy(bean)
? AopUtils.getTargetClass(bean)
: bean.getClass();
if (clazz.isAnnotationPresent(SoffitModelAttribute.class)) {
// The bean itself is the model attribute
map.put(clazz, bean);
} else {
// Check the bean for annotated methods...
for (Method m : clazz.getMethods()) {
if (m.isAnnotationPresent(SoffitModelAttribute.class)) {
map.put(m, bean);
}
}
}
}
logger.debug("Found {} beans and/or methods referencing @SoffitModelAttribute", map.size());
modelAttributes = Collections.unmodifiableMap(map);
}

/* package-private */ Map<String,Object> gatherModelAttributes(String viewName, HttpServletRequest req,
HttpServletResponse res, PortalRequest portalRequest, Bearer bearer, Preferences preferences,
Definition definition) {

final Map<String,Object> rslt = new HashMap<>();

logger.debug("Processing model attributes for viewName='{}'", viewName);

for (Map.Entry<AnnotatedElement,Object> y : modelAttributes.entrySet()) {
final AnnotatedElement annotatedElement = y.getKey();
final Object bean = y.getValue();
final SoffitModelAttribute sma = annotatedElement.getAnnotation(SoffitModelAttribute.class);
if (attributeAppliesToView(sma, viewName)) {
logger.debug("The following SoffitModelAttribute applies to viewName='{}': {}", viewName, sma);
final String modelAttributeName = sma.value();
// Are we looking at a class or a method?
if (annotatedElement instanceof Class) {
// The bean itself is the model attribute
rslt.put(modelAttributeName, bean);
} else if (annotatedElement instanceof Method) {
final Method m = (Method) annotatedElement;
final Object modelAttribute = getModelAttributeFromMethod(bean, m, req, res,
portalRequest, bearer, preferences, definition);
rslt.put(modelAttributeName, modelAttribute);
} else {
final String msg = "Unsupported AnnotatedElement type: " + AnnotatedElement.class.getName();
throw new UnsupportedOperationException(msg);
}
}
}

logger.debug("Calculated the following model attributes for viewName='{}': {}", viewName, rslt);

return rslt;

}

protected Object getModelAttributeFromMethod(Object bean, Method method, HttpServletRequest req, HttpServletResponse res,
PortalRequest portalRequest, Bearer bearer, Preferences preferences, Definition definition) {

// This Method must NOT have a void return type...
if (method.getReturnType().equals(Void.TYPE)) {
final String msg = "Methods annotated with SoffitModelAttribute must not specify a void return type; " + method.getName();
throw new IllegalStateException(msg);
}
final Object[] parameters = prepareMethodParameters(method, req, res,
portalRequest, bearer, preferences, definition);
try {
final Object rslt = method.invoke(bean, parameters);
return rslt;
} catch (IllegalAccessException | InvocationTargetException e) {
final String msg = "Failed to generate a model attribute by invoking '" + method.getName()
+ "' on the following bean: " + bean.toString();
throw new RuntimeException(msg);
}

}

protected Object[] prepareMethodParameters(Method method, HttpServletRequest req, HttpServletResponse res,
PortalRequest portalRequest, Bearer bearer, Preferences preferences, Definition definition) {

// Examine the parameters this Method declares and try to match them.
final Class<?>[] parameterTypes = method.getParameterTypes();
final Object[] rslt = new Object[parameterTypes.length];
for (int i=0; i < rslt.length; i++) {
final Class<?> pType = parameterTypes[i];
// At present, these are the parameter types we support...
if (HttpServletRequest.class.equals(pType)) {
rslt[i] = req;
} else if (HttpServletResponse.class.equals(pType)) {
rslt[i] = res;
} else if (PortalRequest.class.equals(pType)) {
rslt[i] = portalRequest;
} else if (Bearer.class.equals(pType)) {
rslt[i] = bearer;
} else if (Preferences.class.equals(pType)) {
rslt[i] = preferences;
} else if (Definition.class.equals(pType)) {
rslt[i] = definition;
} else {
final String msg = "Unsupported parameter type for SoffitModelAttribute method: " + pType;
throw new UnsupportedOperationException(msg);
}
}

return rslt;

}

protected boolean attributeAppliesToView(SoffitModelAttribute attributeAnnotation, String viewName) {
final Pattern pattern = Pattern.compile(attributeAnnotation.viewRegex());
final Matcher matcher = pattern.matcher(viewName);
return matcher.matches();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@
@Configuration
public class SoffitRendererConfiguration {

@Bean
public SoffitRendererController soffitRendererController() {
return new SoffitRendererController();
}

@Bean
public BearerService bearerService() {
return new BearerService();
Expand All @@ -62,4 +57,14 @@ public DefinitionService definitionService() {
return new DefinitionService();
}

@Bean
public ModelAttributeService modelAttributeService() {
return new ModelAttributeService();
}

@Bean
public SoffitRendererController soffitRendererController() {
return new SoffitRendererController();
}

}
Loading

0 comments on commit f69f2e0

Please sign in to comment.