Entries Tagged 'spring' ↓
January 8th, 2009 — Java, cas, spring
JaValid is an annotation-based framework for validating fields or methods (i.e. NotNull, NotEmpty, etc.). We initially leveraged it in CAS to provide domain-validation to credential objects. This would allow the domain class to contain all the information about itself (similar to what Grails does).
CAS previously used some custom code in the Inspektr library to validate Spring beans via a BeanPostProcessor. Some of the code in Inspektr and JaValid was similar (i.e. the annotations) such that we figured it might be better to leverage more of JaValid (less code for us to maintain). To that end, we’ve managed to construct a BeanPostProcessor that leverages JaValid for validating the properties of Spring beans after they are created. I’ve pasted it below for anyone who’s interested in doing something similar:
/**
* Copyright 2008 JA-SIG, Inc.
*
* Licensed 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.jasig.cas.server.util;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.binding.message.MessageResolver;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.javalid.core.AnnotationValidator;
import org.javalid.core.AnnotationValidatorImpl;
import org.javalid.core.ValidationMessage;
import java.util.List;
import java.util.Locale;
/**
* Implementation of a {@link org.springframework.beans.factory.config.BeanPostProcessor} that uses Javalid to validate
* properties of POJOs managed by Spring.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 4.0.0
*/
public final class JavalidAnnotationValidatorBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {
private final AnnotationValidatorImpl annotationValidator;
private String pathPrefix = "";
private String group = null;
private int recursiveLevels = 2;
private final ResourceBundleMessageSource resourceBundleMessageSource;
public JavalidAnnotationValidatorBeanPostProcessor(final String configLocation) {
if (configLocation != null) {
this.annotationValidator = new AnnotationValidatorImpl(configLocation);
} else {
this.annotationValidator = new AnnotationValidatorImpl();
}
this.resourceBundleMessageSource = new ResourceBundleMessageSource();
this.resourceBundleMessageSource.setBasename("org/javalid/core/validator/jv_messages");
}
public JavalidAnnotationValidatorBeanPostProcessor() {
this(null);
}
public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
final List<ValidationMessage> messages = this.annotationValidator.validateObject(bean, this.group, this.pathPrefix, true, this.recursiveLevels);
final StringBuilder builder = new StringBuilder();
if (messages.isEmpty()) {
return bean;
}
builder.append("bean '");
builder.append(beanName);
builder.append("' encountered errors on properties: '");
for (final ValidationMessage message : messages) {
builder.append("[property=");
builder.append(message.getPath());
builder.append(";error='");
builder.append(this.resourceBundleMessageSource.getMessage(message.getMessage(), message.getValues(), Locale.getDefault()));
builder.append("'] ");
}
throw new FatalBeanException(builder.toString());
}
}
December 17th, 2008 — Java, spring
Its a tale of romance and tragedy. Okay, not really. But that’s certainly a better starting sentence than, “hey I got Spring Web Flow 2 to work with JUEL instead of JBoss EL.” Anyway, because CAS 4 is utilizing JaValid for its annotation-based validation logic, we’re required to have JUEL on our classpath (JaValid has some compile-time dependencies on JUEL). Unfortunately, the EL specification states that only one provider can be on the path at a time (at the very least, it complains when I have two). Damn them! Can’t we all just get along? Unfortunately, out of the box Spring Web Flow can either work with JBoss EL, or OGNL. OGNL was causing its own set of problems. Since I couldn’t get JaValid to compile against JBoss EL, that meant getting Spring Web Flow 2 to work with JUEL. You think that’d be easy. Not so much.
Its not exceedingly difficult. It just requires writing some custom code. First, we need a ELResolver for JUEL that can process methods (JUEL doesn’t include one). I “borrowed” the one from Camel and modified it a little bit:
/**
* 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.jasig.cas.server.web.util;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
import java.beans.FeatureDescriptor;
import javax.el.BeanELResolver;
import javax.el.ELContext;
import javax.el.ELResolver;
/**
* Allows JUEL to resolve methods.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 4.0.0
*/
public class MethodELResolver extends ELResolver {
@Override
public Object getValue(final ELContext elContext, final Object base, final Object property) {
Method method = findMethod(elContext, base, property);
if (method != null) {
elContext.setPropertyResolved(true);
return method;
}
return null;
}
public boolean isReadOnly(final ELContext elContext, final Object o,final Object o1) {
return false;
}
public Class<?> getType(ELContext elContext, Object o, Object o1) {
return null;
}
public void setValue(ELContext elContext, Object o, Object o1, Object o2) {
// nothing to do
}
public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext elContext, Object o) {
return null;
}
public Class<?> getCommonPropertyType(ELContext elContext, Object o) {
return null;
}
protected Method findMethod(final ELContext elContext, final Object base, final Object property) {
if (base != null &amp;&amp; property instanceof String) {
final Method[] methods = base.getClass().getMethods();
final List<Method> matching = new ArrayList<Method>();
for (final Method method : methods) {
if (method.getName().equals(property) &amp;&amp; Modifier.isPublic(method.getModifiers())) {
matching.add(method);
}
}
int size = matching.size();
if (!matching.isEmpty()) {
if (size > 1) {
// TODO there's currently no way for JUEL to tell us how many parameters there are
// so lets just pick the first one that has a single param by default
for (final Method method : matching) {
final Class<?>[] paramTypes = method.getParameterTypes();
if (paramTypes.length == 1) {
return method;
}
}
}
// lets default to the first one
return matching.get(0);
}
}
return null;
}
}
Since JUEL doesn’t have a ELResolver that handles methods you can’t just give the ExpressionFactory to the existing WebFlowELParser as a construction parameter (methods still won’t work then). You need to construct your own parser based off the existing parser:
/**
* Copyright 2008 JA-SIG, Inc.
*
* Licensed 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.jasig.cas.server.web.util;
import java.util.ArrayList;
import java.util.List;
import javax.el.ELContext;
import javax.el.ELResolver;
import javax.el.FunctionMapper;
import javax.el.VariableMapper;
import org.springframework.binding.expression.el.DefaultELResolver;
import org.springframework.binding.expression.el.ELContextFactory;
import org.springframework.binding.expression.el.ELExpressionParser;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.expression.el.*;
import de.odysseus.el.ExpressionFactoryImpl;
import de.odysseus.el.tree.impl.Builder;
import de.odysseus.el.tree.impl.Cache;
import de.odysseus.el.tree.TreeStore;
public final class JuelWebFlowELExpressionParser extends ELExpressionParser {
public JuelWebFlowELExpressionParser() {
super(new ExpressionFactoryImpl(new TreeStore(new Builder(Builder.Feature.METHOD_INVOCATIONS), new Cache(1000))));
putContextFactory(RequestContext.class, new RequestContextELContextFactory());
}
private static class RequestContextELContextFactory implements ELContextFactory {
public ELContext getELContext(final Object target) {
final RequestContext context = (RequestContext) target;
final List<ELResolver> customResolvers = new ArrayList<ELResolver>();
customResolvers.add(new RequestContextELResolver(context));
customResolvers.add(new FlowResourceELResolver(context));
customResolvers.add(new ImplicitFlowVariableELResolver(context));
customResolvers.add(new ScopeSearchingELResolver(context));
customResolvers.add(new SpringBeanWebFlowELResolver(context));
customResolvers.add(new ActionMethodELResolver());
customResolvers.add(new MethodELResolver());
ELResolver resolver = new DefaultELResolver(customResolvers);
return new WebFlowELContext(resolver);
}
}
private static class WebFlowELContext extends ELContext {
private final ELResolver resolver;
public WebFlowELContext(ELResolver resolver) {
this.resolver = resolver;
}
public ELResolver getELResolver() {
return resolver;
}
public FunctionMapper getFunctionMapper() {
return null;
}
public VariableMapper getVariableMapper() {
return null;
}
}
}
Finally, all of this needs to be wired up in your Spring XML configuration file. I’ve included our example here:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:webflow="http://www.springframework.org/schema/webflow-config"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/webflow-config http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd">
<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
<webflow:flow-location path="/WEB-INF/login/login.xml" id="login"/>
</webflow:flow-registry>
<webflow:flow-builder-services expression-parser="expressionParser" id="flowBuilderServices" />
<bean id="expressionParser" class="org.jasig.cas.server.web.util.JuelWebFlowELExpressionParser" />
<webflow:flow-executor id="flowExecutor"/>
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter"
p:flowExecutor-ref="flowExecutor"/>
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping"
p:flowRegistry-ref="flowRegistry"
p:order="0"/>
</beans>
Be sure to include JUEL in your Web application and that should be all you need.
December 4th, 2008 — Java, Software Architecture, spring
For better or worse, CAS3 has always used Spring Web Flow 1 even after Spring Web Flow 2 came out. As part of CAS4, we’re looking at upgrading to Spring Web Flow 2. Overall, Spring Web Flow has simplified a lot of stuff. Calling our service methods directly (for the most part) instead of creating an action to do the same thing is a huge time and codesaver.
I’ve encountered a few weird things along the way that I’m still working the kinks out on:
1. Configuring an External Validator - Spring Web Flow 2 makes it easy to use validators, as long as you follow their conventions (either a method on the model object or a validator that follows particular naming conventions). I’m looking at leveraging the Javalid framework, which uses annotations on the model objects and one validator. This should make it easier for deployers to use their own custom credentials objects and keep their validation logic within their class (reducing maintenance). One problem, its not clear how to register a custom validator that doesn’t conform to the Spring Web Flow 2 conventions. I’ve asked for information on the forum for anyone interested: http://forum.springframework.org/showthread.php?t=64418
2. Using Simple Domain/Model Objects - In Spring Web Flow 1 you called setupForm on a FormAction and magical stuff happened to instantiate the appropriate objects, etc. In Spring Web Flow 2 you can merely call evaluate on a method and put the resulting object in the flow or request scope and use that as the model object. Powerful! However, its not clear to me how to merely use a simple object (i.e. an instantiation of UsernamePasswordCredentialsImpl).
For those interested in learning more, I’ve found the following resources useful: