In my applicationContext.xml I declare the following 2 custom converters,
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="util.BooleanToYesNoDataTypeConverter"/>
<bean class="util.YesNoDataTypeToBooleanConverter"/>
</set>
</property>
</bean>
The converters take care of converting between a Boolean and a custom type called 'YesNoDataType',
public class BooleanToYesNoDataTypeConverter implements Converter<Boolean,YesNoDataType> {
#Override
public YesNoDataType convert(Boolean source) {
if (Boolean.TRUE.equals(source))
return YesNoDataType.Y_YES;
else
return YesNoDataType.N_NO;
}
}
public class YesNoDataTypeToBooleanConverter implements Converter<YesNoDataType,Boolean> {
#Override
public Boolean convert(YesNoDataType source) {
if (YesNoDataType.Y_YES.equals(source))
return Boolean.TRUE;
else
return Boolean.FALSE;
}
}
In theory the Converters are now registered and can be used, right? I'm following the example in this post: Spring MVC type conversion : PropertyEditor or Converter?
However, in my Controller, when I come to the following initBinder override, I check my current active converters. Guess what, my 2 converters aren't available. Why is that? Take a look below.
#Override
#InitBinder
public void initBinder(WebDataBinder binder) {
// Check current ConversionService
ConversionService conversionService = binder.getConversionService();
// The below FAILS with a "Converter not found" error
Boolean b = conversionService.convert(YesNoDataType.Y_YES, Boolean.class);
// These 2 return FALSE, meaning we CAN'T convert YesNoDataType/Boolean
System.out.println("Can convert YesNo -> Boolean? " + conversionService.canConvert(Boolean.class, YesNoDataType.class));
System.out.println("Can convert Boolean -> YesNo? " + conversionService.canConvert(YesNoDataType.class, Boolean.class));
}
By the way, let me ask a broader question about Spring/SpringMVC. The Spring folks eliminated/closed a forum that existed until 2014, http://forum.spring.io/ . The only way to get SpringMVC support now is by posting here, on StackOverflow, but any SpringMVC threads here get 5-6 views and no answers. A few years ago the community was active. What's happened to SpringMVC, is it no longer well-supported? Where is the community for it? Just wondering.
Just as an FYI, the only thing that seems to work in my Controller is to cast the ConversionService to a GenericConversionService and then manually invoke addConverter(...) methods to add converters.
Now my conversion works. But this isn't the right way to do it -- all my XML registrations, incl. the #Component annotation on the Converter classes, should have worked. So the question is still open.
#Override
#InitBinder
public void initBinder(WebDataBinder binder) {
// Obtain a GenericConversionService via casting
GenericConversionService conversionService = (GenericConversionService)binder.getConversionService();
// Manually add converters on it
conversionService.addConverter(YesNoDataType.class, Boolean.class, new YesNoDataTypeToBooleanConverter());
conversionService.addConverter(Boolean.class, YesNoDataType.class, new BooleanToYesNoDataTypeConverter());
// Now it works -- these return TRUE
System.out.println("Can convert YesNo -> Boolean? " + conversionService.canConvert(Boolean.class, YesNoDataType.class));
System.out.println("Can convert Boolean -> YesNo? " + conversionService.canConvert(YesNoDataType.class, Boolean.class));
Related
I struggle to make hibernate 3.1 lazy loading working with JSF 1.2
Caused by: javax.el.ELException: Cannot convert foo.bar.Protocol#7ebc9002 of type class foo.bar.Protocol to class foo.bar.Protocol$$EnhancerByCGLIB$$22af7fa3
at org.apache.el.lang.ELSupport.coerceToType(ELSupport.java:438)
at org.apache.el.ExpressionFactoryImpl.coerceToType(ExpressionFactoryImpl.java:46)
at com.sun.faces.renderkit.html_basic.RadioRenderer.renderOption(RadioRenderer.java:87)
at com.sun.faces.renderkit.html_basic.SelectManyCheckboxListRenderer.encodeEnd(SelectManyCheckboxListRenderer.java:146)
I read that hibernate will replace the lazy loading proxy on demand, but it seems to do not work on JSF converter call.
Note that Protocols are bound to radio buttons in the view
Do you know how to workaround this ? I can't find someone who have the same issue as me.
applicationContext :
<bean id="protocol" class="foo.bar.Protocol" abstract="false"
lazy-init="default" autowire="byName" dependency-check="default" scope="session">
<aop:scoped-proxy />
</bean>
<bean id="protocolConverter" class="foo.bar.ProtocolConverter" abstract="false"
lazy-init="default" autowire="byName" dependency-check="default" scope="singleton">
<property name="protocolDAO" ref="protocolDAO" />
</bean>
view :
<h:selectOneRadio value="#{pingControler.ping.protocol}" converter="#{protocolConverter}">
<f:selectItems value="#{pingControler.allProtocolsSelectItems}" />
<a4j:support event="onchange" reRender="foo1,foo2" />
</h:selectOneRadio>
ping :
public class Ping {
// Fields
private Integer pingId;
private Protocol protocol;
...
}
pingControler :
private Ping ping;
public void init(ActionEvent event) {
ping = new Ping();
}
public void save(ActionEvent event) throws Exception {
if (ping.getPingId() == null) {
pingPersistent.addPing(ping);
} else {
pingPersistent.updatePing(ping);
}
}
I figured out (finally) how to workaround this. I'm not sure if this is the proper way, but it works.
I added this code :
private static <T> T initializeAndUnproxy(T entity) {
if (entity == null) {
throw new NullPointerException("Entity passed for initialization is null");
}
Hibernate.initialize(entity);
if (entity instanceof HibernateProxy) {
entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation();
}
return entity;
}
And i called it in my Converter like this :
#Override
public Object getAsObject(FacesContext arg0, UIComponent arg1, String value) {
Protocol protocol = protocolDAO.findById(new Integer(value));
return initializeAndUnproxy(protocol);
}
instead of this :
#Override
public Object getAsObject(FacesContext arg0, UIComponent arg1, String value) {
return protocolDAO.findById(new Integer(value));
}
By the way, i did a mistake when i though that $$EnhancedByCGLIB$$ necessarily means an hibernate proxy. I read somewhere else that it's a library and could be used by Spring dependency injection too for example. Just to let you know.
I hope this will help the few jsf 1.2 / hibernate 3.1 users remaining. Should i upvote myself ?
I have been evaluating to adopt spring-data-mongodb for a project. In summary, my aim is:
Using existing XML schema files to generate Java classes.
This is achieved using JAXB xjc
The root class is TSDProductDataType and is further modeled as below:
The thing to note here is that ExtensionType contains protected List<Object> any; allowing it to store Objects of any class. In my case, it is amongst the classes named TSDModule_Name_HereModuleType and can be browsed here
Use spring-data-mongodb as persistence store
This is achieved using a simple ProductDataRepository
#RepositoryRestResource(collectionResourceRel = "product", path = "product")
public interface ProductDataRepository extends MongoRepository<TSDProductDataType, String> {
TSDProductDataType queryByGtin(#Param("gtin") String gtin);
}
The unmarshalled TSDProductDataType, however, contains JAXBElement which spring-data-mongodb doesn't seem to handle by itself and throws a CodecConfigurationException org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class java.lang.Class.
Here is the faulty statement:
TSDProductDataType tsdProductDataType = jaxbElement.getValue();
repository.save(tsdProductDataType);
I tried playing around with Converters for spring-data-mongodb as explained here, however, it seems I am missing something since the exception is about "Codecs" and not "Converters".
Any help is appreciated.
EDIT:
Adding converters for JAXBElement
Note: Works with version 1.5.6.RELEASE of org.springframework.boot::spring-boot-starter-parent. With version 2.0.0.M3, hell breaks loose
It seems that I missed something while trying to add converter earlier. So, I added it like below for testing:
#Component
#ReadingConverter
public class JAXBElementReadConverter implements Converter<DBObject, JAXBElement> {
//#Autowired
//MongoConverter converter;
#Override
public JAXBElement convert(DBObject dbObject) {
Class declaredType, scope;
QName name = qNameFromString((String)dbObject.get("name"));
Object rawValue = dbObject.get("value");
try {
declaredType = Class.forName((String)dbObject.get("declaredType"));
} catch (ClassNotFoundException e) {
if (rawValue.getClass().isArray()) declaredType = List.class;
else declaredType = LinkedHashMap.class;
}
try {
scope = Class.forName((String) dbObject.get("scope"));
} catch (ClassNotFoundException e) {
scope = JAXBElement.GlobalScope.class;
}
//Object value = rawValue instanceof DBObject ? converter.read(declaredType, (DBObject) rawValue) : rawValue;
Object value = "TODO";
return new JAXBElement(name, declaredType, scope, value);
}
QName qNameFromString(String s) {
String[] parts = s.split("[{}]");
if (parts.length > 2) return new QName(parts[1], parts[2], parts[0]);
if (parts.length == 1) return new QName(parts[0]);
return new QName("undef");
}
}
#Component
#WritingConverter
public class JAXBElementWriteConverter implements Converter<JAXBElement, DBObject> {
//#Autowired
//MongoConverter converter;
#Override
public DBObject convert(JAXBElement jaxbElement) {
DBObject dbObject = new BasicDBObject();
dbObject.put("name", qNameToString(jaxbElement.getName()));
dbObject.put("declaredType", jaxbElement.getDeclaredType().getName());
dbObject.put("scope", jaxbElement.getScope().getCanonicalName());
//dbObject.put("value", converter.convertToMongoType(jaxbElement.getValue()));
dbObject.put("value", "TODO");
dbObject.put("_class", JAXBElement.class.getName());
return dbObject;
}
public String qNameToString(QName name) {
if (name.getNamespaceURI() == XMLConstants.NULL_NS_URI) return name.getLocalPart();
return name.getPrefix() + '{' + name.getNamespaceURI() + '}' + name.getLocalPart();
}
}
#SpringBootApplication
public class TsdApplication {
public static void main(String[] args) {
SpringApplication.run(TsdApplication.class, args);
}
#Bean
public CustomConversions customConversions() {
return new CustomConversions(Arrays.asList(
new JAXBElementReadConverter(),
new JAXBElementWriteConverter()
));
}
}
So far so good. However, how do I instantiate MongoConverter converter;?
MongoConverter is an interface so I guess I need an instantiable class adhering to this interface. Any suggestions?
I understand the desire for convenience in being able to just map an existing domain object to the database layer with no boilerplate, but even if you weren't having the JAXB class structure issue, I would still be recommending away from using it verbatim. Unless this is a simple one-off project, you almost definitely will hit a point where your domain models will need to change but your persisted data need to remain in an existing state. If you are just straight persisting the data, you have no mechanism to convert between a newer domain schema and an older persisted data scheme. Versioning of the persisted data scheme would be wise too.
The link you posted for writing the customer converters is one way to achieve this and fits in nicely with the Spring ecosystem. That method should also solve the issue you are experiencing (about the underlying messy JAXB data structure not converting cleanly).
Are you unable to get that method working? Ensure you are loading them into the Spring context with #Component plus auto-class scanning or manually via some Configuration class.
EDIT to address your EDIT:
Add the following to each of your converters:
private final MongoConverter converter;
public JAXBElement____Converter(MongoConverter converter) {
this.converter = converter;
}
Try changing your bean definition to:
#Bean
public CustomConversions customConversions(#Lazy MongoConverter converter) {
return new CustomConversions(Arrays.asList(
new JAXBElementReadConverter(converter),
new JAXBElementWriteConverter(converter)
));
}
Before the result is sent, we want to add common fields (timestamp, version etc) in JSON response. We don't want to do this in every Controller. Is there any elegant way to do this in spring mvc?
Another similar question is if the parameter validation is failed, how to return the same JSON response.
Yes there is. You can use Spring AOP. You can intercept every service you write and add parameters from one place. For example, using Spring Around Advice. Note you need to write yourself addParameters function that return JsonNode. Good luck!
public class DoAroundMethod implements MethodInterceptor {
private static final Logger LOG = LoggerFactory.getLogger(DoAroundMethod.class);
#Autowired
ObjectMapper mapper;
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
LOG.debug("****SPRING AOP**** DoAroundMethod: Method name : "
+ methodInvocation.getMethod().getName());
LOG.debug("****SPRING AOP**** DoAroundMethod: Method name : "
+ methodInvocation.getMethod().getName());
LOG.debug("****SPRING AOP**** DoAroundMethod: Method arguments : "
+ Arrays.toString(methodInvocation.getArguments()));
// same with MethodBeforeAdvice
LOG.debug("****SPRING AOP**** DoAroundMethod: Before method executing!");
try {
// proceed to original method call
Object result = methodInvocation.proceed();
// same with AfterReturningAdvice
if(result!=null){
//LOG.debug("Return value "+result.toString());
try{
JsonNode jN = mapper.readTree(result.toString());
result=addParameters(jN);
}catch(JsonParseException e){
LOG.debug("****SPRING AOP**** DoAroundMethod: When JsonParse throws Exception!");
return result;
}
}
LOG.debug("****SPRING AOP**** DoAroundMethod: After method executing!");
return result;
} catch (IllegalArgumentException e) {
// same with ThrowsAdvice
LOG.debug("****SPRING AOP**** DoAroundMethod: When method throws Exception!");
throw e;
}
}
Then to assign this Advice to all services you have, assuming they all end in *Service.java
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>*Service</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>regexAdvisor</value>
</list>
</property>
</bean>
Maybe a custom Jackson plugin, or a ResponseBodyAdvice? (see this blog post for more details).
I'm trying to create a multiaction web controller using Spring annotations. This controller will be responsible for adding and removing user profiles and preparing reference data for the jsp page.
#Controller
public class ManageProfilesController {
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(UserAccount.class,"account", new UserAccountPropertyEditor(userManager));
binder.registerCustomEditor(Profile.class, "profile", new ProfilePropertyEditor(profileManager));
logger.info("Editors registered");
}
#RequestMapping("remove")
public void up( #RequestParam("account") UserAccount account,
#RequestParam("profile") Profile profile) {
...
}
#RequestMapping("")
public ModelAndView defaultView(#RequestParam("account") UserAccount account) {
logger.info("Default view handling");
ModelAndView mav = new ModelAndView();
logger.info(account.getLogin());
mav.addObject("account", account);
mav.addObject("profiles", profileManager.getProfiles());
mav.setViewName(view);
return mav;
}
...
}
Here is the part of my webContext.xml file:
<context:component-scan base-package="ru.mirea.rea.webapp.controllers" />
<context:annotation-config/>
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<value>
...
/home/users/manageProfiles=users.manageProfilesController
</value>
</property>
</bean>
<bean id="users.manageProfilesController" class="ru.mirea.rea.webapp.controllers.users.ManageProfilesController">
<property name="view" value="home\users\manageProfiles"/>
</bean>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
However, when i open the mapped url, i get exception:
java.lang.IllegalArgumentException: Cannot convert value of type [java.lang.String] to required type [ru.mirea.rea.model.UserAccount]: no matching editors or conversion strategy found
I use spring 2.5.6 and plan to move to the Spring 3.0 in some not very distant future. However, according to this JIRA https://jira.springsource.org/browse/SPR-4182 it should be possible already in spring 2.5.1.
The debug shows that the InitBinder method is correctly called.
What am i doing wrong?
Update:
public class UserAccountPropertyEditor extends PropertyEditorSupport {
static Logger logger = Logger.getLogger(UserAccountPropertyEditor.class);
public UserAccountPropertyEditor(IUserDAO dbUserManager) {
this.dbUserManager = dbUserManager;
}
private IUserDAO dbUserManager;
public String getAsText() {
UserAccount obj = (UserAccount) getValue();
if (null==obj) {
return "";
} else {
return obj.getId().toString();
}
}
public void setAsText(final String value) {
try {
Long id = Long.parseLong(value);
UserAccount acct = dbUserManager.getUserAccountById(id);
if (null!=acct) {
super.setValue(acct);
} else {
logger.error("Binding error. Cannot find userAccount with id ["+value+"]");
throw new IllegalArgumentException("Binding error. Cannot find userAccount with id ["+value+"]");
}
} catch (NumberFormatException e) {
logger.error("Binding error. Invalid id: " + value);
throw new IllegalArgumentException("Binding error. Invalid id: " + value);
}
}
}
There are no errors logged from UserAccountPropertyEditor.
I don't think you want to be specifying the field argument to WebDataBinder.registerCustomEditor(). This intended to work alongside form-backing objects, and you're not using that.
Try the simpler 2-arg method instead, and it should work:
binder.registerCustomEditor(UserAccount.class, new UserAccountPropertyEditor(userManager));
binder.registerCustomEditor(Profile.class, new ProfilePropertyEditor(profileManager));
The intent here is to deal with obfuscated passwords for resources.
We have an Advisor that intercepts calls to setPassword and decrypts the argument.
We've set up a template that looks somewhat like this:
<bean id="pwAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice"><bean class="our.advice.bean.class"/></property>
<property name="mappedName" value="setPassword"/>
</bean>
<bean id="passwordHandlerTemplate" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true">
<property name="interceptorNames"><list><value>pwAdvisor</value></list></property>
</bean>
I'm unclear on the exact syntax to use it. The most obvious way is:
<bean id="myPasswordProtectedThing" parent="passwordHandlerTemplate">
<property name="target">
<bean class="the.target.class.name">
<property name="password" value="encrypted garbage"/>
</bean>
</property>
</bean>
But that doesn't work right, since the password property is applied to the inner bean, which means that the advisor won't wind up doing its work.
Well, what about this:
<bean id="myPasswordProtectedThing" parent="passwordHandlerTemplate">
<property name="target"><bean class="the.target.class.name"/></property>
<property name="password" value="encrypted garbage"/>
</bean>
Nope. Spring complains that the ProxyFactoryBean doesn't have a password property. And, of course, it doesn't. The thing that has the password property is the thing the factory bean creates.
Bueller?
My first effort was poor, but I was in hurry. I apologize. Now I think I know how it should work, because I believe I've implemented what you want myself.
I started with a Credential class (note: no interface):
package aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Credential
{
private static final String DEFAULT_USERNAME = "username";
private static final String DEFAULT_PASSWORD = "password";
private String username;
private String password;
public static void main(String[] args)
{
Credential cred1 = new Credential("foo", "bar");
System.out.println("created using new: " + cred1);
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:aop-context.xml");
Credential cred2 = (Credential) context.getBean("credential");
System.out.println("created using app context: " + cred2);
String password = ((args.length > 0) ? args[0] : "baz");
cred2.setPassword(password);
System.out.println("initialized using setter: " + cred2);
}
public Credential()
{
this(DEFAULT_USERNAME, DEFAULT_PASSWORD);
}
public Credential(String username, String password)
{
this.setUsername(username);
this.setPassword(password);
}
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
public String toString()
{
return new StringBuilder().append("Credential{").append("username='").append(username).append('\'').append(", password='").append(password).append('\'').append('}').toString();
}
}
I created a Decryptor interface:
package aop;
public interface Decryptor
{
String decrypt(String encrypted);
}
And a DecryptorImpl:
package aop;
public class DecryptorImpl implements Decryptor
{
public static final String DEFAULT_DECRYPTED_VALUE = " - not secret anymore";
public String decrypt(String encrypted)
{
// Any transform will do; this suffices to demonstrate
return encrypted + DEFAULT_DECRYPTED_VALUE;
}
}
I needed DecryptorAdvice to implement Spring's MethodBeforeAdvice:
package aop;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class DecryptionAdvice implements MethodBeforeAdvice
{
private Decryptor decryptor;
public DecryptionAdvice(Decryptor decryptor)
{
this.decryptor = decryptor;
}
public void before(Method method, Object[] args, Object target) throws Throwable
{
String encryptedPassword = (String) args[0];
args[0] = this.decryptor.decrypt(encryptedPassword);
}
}
And I wired it together in an aop-context.xml. (If you tell me how to get XML to display, I'll post it.) Note the passwordDecryptionAdvisor: it only matches the setPassword method.
The interesting part happens when I run it. Here's what I see in the console:
created using new: Credential{username='foo', password='bar'}
created using app context: Credential{username='stackoverflow', password='encrypted-password'}
initialized using setter: Credential{username='stackoverflow', password='baz - not secret anymore'}
What this tells me is:
If I create an object with new it's
not under Spring's control, advice
isn't applied.
If I call setPassword in the ctor
before the app context is
initialized, advice isn't applied.
If I call setPassword in my code
after the app context is
initialized, advice is applied.
I hope this can help you.
I thought you wanted the beforeMethod advice to use the encrypted password String that's passed into the setPassword method. You want to decrypt that and have the advised class get an decrypted version.
I also don't see a proxy interface set in your proxy factory. "Spring In Action" says "...Creating a proxy with interfaces is favored over proxying classes..." Proxying classes should be the exception, not the rule.
Post your advice class.