Custom property editors do not work for request parameters in Spring MVC? - spring

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));

Related

MockMvc tests always returns status code 200 for get requests

I I am very new to spring boot and only have been working with it for a couple of days, so I am also very confused about this project (which is not my own). I am supposed to write tests with MockMvc for the rest controller, however each test with MockMvc only returns the status code 200 although it should be a 404.
Here is one of the tests:
#WebMvcTest(ObjectController.class)
#SpringJUnitConfig(TestConfig.class)
public class MvcTest {
#Autowired
MockMvc mockMvc;
#Test
public void shouldReturn404() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/obj/123"))
.andExpect(MockMvcResultMatchers.status().is(HttpStatus.NOT_FOUND.value()));
}
}
This is my rest controller I would like to test.
#RestController
#RequestMapping("obj")
public class ObjectController {
#Autowired
MyClass myClass;
#GetMapping()
public List<MyObject> findAll() {
List<MyObject> objList;
objList = myClass.getObjects();
return objList;
}
#GetMapping("/{id}")
public MyObject findById(#PathVariable String id) {
for (MyObject obj : myClass.getObjects()) {
if (obj.getId().equals(id))
return obj;
}
return null;
}
}
Then there is this class:
public class MyClass {
private List<MyObject> objList = new ArrayList<MyObject>();
public MyObject addObject(MyObject obj) {
objList.add(obj);
return obj;
}
public void setObjects(List<MyObject> objList) {
this.objList = objList;
}
public synchronized List<MyObject> getObjects() {
return objList;
}
}
There is an xml file that belongs to the class looking like this which can be found in the resource folder:
<?xml version='1.0' encoding='ISO-8859-1'?>
<beans xmlns='http://www.springframework.org/schema/beans'
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns:context='http://www.springframework.org/schema/context'
xsi:schemaLocation='http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd'>
<bean id='MyClass' class='it.is.some.path.MyClass'>
<property name='objList'>
<list>
<ref bean='[R01] Object1' />
</list>
</property>
</bean>
</beans>
The referenced beans can be found in separate files under resources too, in a sub folder called 'objList' and look like this:
<?xml version='1.0' encoding='ISO-8859-1'?>
<beans xmlns='http://www.springframework.org/schema/beans'
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns:context='http://www.springframework.org/schema/context'
xsi:schemaLocation='http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd'>
<bean id='[R01] Object1' class='this.is.some.path.MyObject'>
<property name='id' value='123' />
</bean>
</beans>
The myclass.xml as well as the folder with all xmls of the objects are imported via #ImportResource in the Application class.
And then there is
public class MyObject {
public String id = RandomStringUtils.randomAlphanumeric(5);
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
am sorry for the lengthy explanation but I have been spending two days now trying to figure out how to get MockMvc running and I feel like I am completely stuck. I would be very, very grateful if anybody could help out.
I assume 200 is the correct response as you have a mapping for /obj/{id} where {id} is a placeholder for any path variable like 123 in /obj/123.
Even though your controller returns null from a code perspective, Spring won't map this automatically to a 404 not found if this is what you would expect.
So you can literally try it with every path variable, you'll always get HTTP 200 because there wasn't any exception and the DisptacherServlet could properly route the HTTP request to a controller mapping. HTTP 200 is the default response code if you don't specify anything.
If you want your controller endpoint to return 404 when your MyObject is null, you have to be more explicit about this, e.g.:
#GetMapping("/{id}")
public ResponseEntity<MyObject> findById(#PathVariable String id) {
for (MyObject obj : myClass.getObjects()) {
if (obj.getId().equals(id))
return ResponseEntity.ok(obj);
}
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}

javax.el.ELException: Cannot convert Protocol#7ebc9002 of type class Protocol to class Protocol$$EnhancerByCGLIB$$22af7fa3 (lazy loadding)

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 ?

HttpInvokerServiceExporter + HttpInvokerProxyFactoryBean - Could not access HTTP invoker remote service

I'm trying to use HttpInvokerServiceExporter + HttpInvokerProxyFactoryBean, but whatever I do I get an exception:
org.springframework.remoting.RemoteAccessException: Could not access HTTP invoker remote service at [http://localhost:9999/testcaseapp/testcaseservice]; nested exception is java.io.IOException: Did not receive successful HTTP response: status code = 404, status message = [Not Found]
For the simplicity, I've created a test case.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class RemoteTest {
private static final Logger logger = LoggerFactory.getLogger("TestsLogger");
static interface TestCaseService {
public Integer add(Integer arg1, Integer arg2);
}
static class TestCaseServiceImpl implements TestCaseService {
public Integer add(Integer arg1, Integer arg2) {
return (arg1 != null ? arg1.intValue() : 0) + (arg2 != null ? arg2.intValue() : 0);
}
}
#Configuration
static class Config {
#Bean
public HttpInvokerServiceExporter httpInvokerServiceExporter() {
HttpInvokerServiceExporter httpInvokerServiceExporter = new HttpInvokerServiceExporter();
httpInvokerServiceExporter.setService(new TestCaseServiceImpl());
httpInvokerServiceExporter.setServiceInterface(TestCaseService.class);
return httpInvokerServiceExporter;
}
#Bean
public HttpInvokerProxyFactoryBean httpInvokerProxyFactoryBean() {
HttpInvokerProxyFactoryBean httpInvokerProxyFactoryBean = new HttpInvokerProxyFactoryBean();
httpInvokerProxyFactoryBean.setServiceInterface(TestCaseService.class);
httpInvokerProxyFactoryBean.setServiceUrl("http://localhost:9999/testcaseapp/testcaseservice");
httpInvokerProxyFactoryBean.afterPropertiesSet();
return httpInvokerProxyFactoryBean;
}
}
#Autowired
private TestCaseService[] testCaseServices;
private static Server server;
#BeforeClass
public static void setUp() {
try {
server = new Server();
SelectChannelConnector connector = new SelectChannelConnector();
connector.setPort(9999);
server.addConnector(connector);
//
WebAppContext webAppContext = new WebAppContext();
webAppContext.setContextPath("/testcaseapp");
webAppContext.setWar("src/test/java/" + RemotingTest.class.getPackage().getName().replace('.', '/'));
server.setHandler(webAppContext);
//
server.start();
} catch (Exception ex) {
logger.info("Could not permorm the set up: {}", ex.toString());
}
}
#AfterClass
public static void destroy() {
try {
server.stop();
} catch (Exception e) {
}
}
#Test
public void addTest() {
for (TestCaseService testCaseService : testCaseServices) {
Integer sum = testCaseService.add(10, 5);
Assert.assertNotNull(sum);
Assert.assertEquals(15, sum.intValue());
}
}
}
I've also tried to create a TestCaseService bean
#Bean public TestCaseService testCaseService() ...
and provide it as a httpInvokerServiceExporter argument
#Bean public HttpInvokerServiceExporter httpInvokerServiceExporter(TestCaseService testCaseService)
...
httpInvokerServiceExporter.setService(testCaseService);
but the result is still the same.
What am I doing wrong? Thanks!
I think the problem is that the Servlet is not accesible.
SERVER SIDE
Make sure you have in your WEB-INF/web.xml (on the app that is exposing the methods -SERVER-) this code:
<web-app>
...
<servlet>
<servlet-name>remoting</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>remoting</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
...
</web-app>
Here, the remote methods are served under "services", that is, for calling the method, the URL should be:
http://localhost:8080/sample/services/list
And you have to define this Servlet as accesible, by creating a bean (in my case under WEB-INF/remoting-servlet.xml):
<bean name="/list" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="myObjectQueryService" />
<property name="serviceInterface" value="com.kategor.myapp.sample.service.ObjectQueryService" />
</bean>
CLIENT SIDE
If your using Spring under the client (not as in your example), you must define a bean for accessing the remote resources, defining some beans (one for each public resource):
In this case, it would be:
<bean id="listService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl" value="http://localhost:8080/sample/services/list" />
<property name="serviceInterface" value="com.kategor.myapp.sample.service.ObjectQueryService" />
</bean>
In your example is right.
This way, calling the Service "listService", you would have all the methods available in the class com.kategor.myapp.sample.service.ObjectQueryService
#Controller
public class HomeController {
// This is the remote service definition
#Autowired
private ObjectQueryService<MyObject, Long> objectQueryService;
/* .... */
/**
* List all Objects retrieved through Web Service from a remote Server
*/
#RequestMapping(value = "listRemoteWS", method = RequestMethod.GET)
public String listRemoteWS(Locale locale, Model model) {
StringBuilder result = new StringBuilder();
try {
// The Remote Service is called
List objs = objectQueryService.findAll(0, 10);
result.append(objs.size() + " objs found");
for (MyObject o : objs) {
result.append("<br>* ").append(o.getId()).append(" = ").append(o.getName());
}
} catch (Exception e) {
result.append("No objs have been found");
e.printStackTrace();
}
model.addAttribute("result", result);
return "index";
}
}
So I think the problem comes from the URL: maybe the service is not visible or this is not the correct path to it.
For more information, check this links (the first is really useful):
https://github.com/JamesEarlDouglas/barebones-spring-mvc/tree/master/reference/spring-remoting
http://www.ibm.com/developerworks/web/library/wa-spring3webserv/index.html
For me the problem was tomcat picked up two versions of the same applications. This raised the above error on running the client from STS in debug mode.
So solution is to clean up all the expanded webapp folders in tomcat for the application. Then redeploy the application.

Unable to move from controller to view in Spring MVC

I am using Spring MVC framework for my project.
I am unable to get my code running from controller to view.
Sharing the important chunk of code here.....
Inside AdminController.java controller
System.out.println("controller returning");
return new ModelAndView("dataFrame_","frameData",dataString);
Inside dispatcher-servlet.xml
<bean name="/dataFrame.htm"
class="com.organization.dept.spec.proj.module.controller.DataFrameController" >
</bean>
<bean id="dataFrameViewResolver"
class="com.organization.dept.spec.proj.module.view.DataFrameViewResolver">
<property name="dataFrameView">
<bean class="com.organization.dept.spec.proj.module.view.DataFrameView" />
</property>
<property name="dataFramePrefix" value="dataFrame_"></property>
</bean>
inside DataFrameViewResolver.java
public class DataFrameViewResolver extends AbstractCachingViewResolver {
private String dataFramePrefix;
private View dataFrameView;
#Override
protected View loadView (String viewName, Locale locale) throws Exception {
View view = null;
if(viewName.startsWith(this.dataFramePrefix)){
view = dataFrameView;
}
return view;
}
and
public String getDataFramePrefix() {
return dataFramePrefix;
}
public void setDataFramePrefix(String dataFramePrefix) {
this.dataFramePrefix = dataFramePrefix;
}
public View getDataFrameView() {
return dataFrameView;
}
public void setDataFrameView(View dataFrameView) {
this.dataFrameView = dataFrameView;
}
}
inside DataFrameView.java ...
public class DataFrameView extends AbstractView {
#Override
protected void renderMergedOutputModel(Map map, HttpServletRequest request,HttpServletResponse response) throws Exception {
System.out.println("RenderMergeoutputModel"); //line 99
I was unable to get the above system.out.println i.e. was unable to execute my code till that line 99.
The localhost log files of tomcat revealed some exception java.lang.ClassNotFoundException: javax.servlet.jsp.jstl.core.Config
I put the jstl-1.2.jar in lib and this could just get me rid of exception however still was unable get sysout of DataFrameView of line 99.

Spring AOP: applying properties through the aspect

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.

Resources