/** * 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: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>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 */ 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(); } }