AEM: How common component injected in multiple components can have dynamic value - adobe

I have a common component which is injected in multiple components via data-sly-resource.
componentA
<div
data-sly-resource="${ 'abc' # resourceType = 'btplayer-cms/components/content/some-common-component' }">
</div>
componentB
<div
data-sly-resource="${ 'xyz' # resourceType = 'btplayer-cms/components/content/some-common-component' }">
</div>
In some-common-component.html a "class" need to be added to the div which will be dynamic and specific to the component it is injected from. For example, when this component is added in componentA
the html wuld be:
<div class="componenta-button"></div>
and when added in componentB it would be <div class="componentb-button"></div>
How can I achieve this? How would I know who is injecting this component or it possible to send extra parameters from the parent component which I can access from some-common-component.html

For this use case it’s probably best to use HTL templates:
<sly data-sly-use.common="common.html" data-sly-call="${common.myTemplate # buttonClass='myClassA'}"></sly>

You can make use of requestAttributes (refer here)
Component A (passing the value):
Sightly :
<sly data-sly-use.compA = "com.mysite.core.models.CompA"/>
<div
data-sly-resource="${ 'abc' # resourceType = 'btplayer-cms/components/content/some-common-component', requestAttributes = compA.attribute }">
</div>
Sling Model :
package com.realogy.sir.core.models;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Model;
#Model(adaptables = SlingHttpServletRequest.class)
public class CompA {
public Map<String, Object> attribute = new HashMap<>();
#PostConstruct
protected void init() {
attribute.put("attributeVal", "componenta");
}
}
Component B (passing the value):
Sightly :
<sly data-sly-use.compB = "com.mysite.core.models.CompB"/>
<div
data-sly-resource="${ 'xyz' # resourceType = 'btplayer-cms/components/content/some-common-component', requestAttributes = compB.attribute }">
</div>
Sling Model :
package com.realogy.sir.core.models;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Model;
#Model(adaptables = SlingHttpServletRequest.class)
public class CompB {
public Map<String, Object> attribute = new HashMap<>();
#PostConstruct
protected void init() {
attribute.put("attributeVal", "componentb");
}
}
Common Component (consuming the value):
Sightly :
<sly data-sly-use.commonComp= "com.mysite.core.models.CommonComp"/>
<div class="${[commonComp.attributeVal, 'button'] # join='-'}"></div>
Sling Model:
package com.mysite.core.models;
import javax.inject.Inject;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Model;
#Model(adaptables = SlingHttpServletRequest.class)
public class CommonComp {
#Inject #Optional #Default(values="component")
private String attributeVal;
public String getAttributeVal() {
return attributeVal;
}
}

Related

BatchMessagingMessageConverter support headerMapper trusted types

I'm using spring-boot 2.7.4 and spring-cloud-dependencies 2021.0.4.
I haven't found any solution in spring documentation for add trustedTypes in BatchMessagingMessageConverter. I'm using kafka for read messages in batch-mode. If I insert a custom header (my own class) when the consumer read the header return a DefaultKafkaHeaderMapper$NonTrustedHeaderType and not my class.
I have in my configuration this key to activate batch mode:
spring.cloud.stream.bindings.nameBind-in-0.consumer.batch-mode=true
I tried in debug to add to headerMapper in BatchMessagingMessageConverter the package of my class and all works fine. There is a way to specify my package in configuration?
I followed the documentation https://docs.spring.io/spring-cloud-stream/docs/3.2.5/reference/html/spring-cloud-stream-binder-kafka.html#kafka-binder-properties, I created a bean like this:
#Bean("kafkaHeaderMapperCustom")
KafkaHeaderMapper getKafkaHeaderMapperCustom() {
var defKHM = new DefaultKafkaHeaderMapper();
defKHM.addTrustedPackages("*");
return defKHM;
}
Specified to key spring.cloud.stream.kafka.binder.headerMapperBeanName in configuration but doesn't work, I suppose that configuration is valid for not batch context?
I tried also these properties:
spring.kafka.consumer.properties.spring.json.trusted.packages
spring.json.trusted.packages
EDIT - Add example:
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.context.annotation.Bean;
import org.springframework.kafka.support.DefaultKafkaHeaderMapper;
import org.springframework.kafka.support.KafkaHeaderMapper;
import org.springframework.kafka.support.KafkaHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
#SpringBootApplication
public class Application {
public static final String HEADER_KEY = "CUSTOM_HEADER_KEY";
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ApplicationRunner runner(StreamBridge streamBridge) {
return args -> {
var headers = new MessageHeaders(Map.of(HEADER_KEY, new CustomHeaderClass("field1Value", LocalDate.now())));
headers.get(KafkaHeaders.BATCH_CONVERTED_HEADERS);
var message = MessageBuilder.createMessage(new ExampleBrokenHeaderEntity("randomValue", "randomName"), headers);
streamBridge.send("stackoverflow-example", message);
};
}
#Bean
public Consumer<Message<List<ExampleBrokenHeaderEntity>>> readFromKafkaBatchMode() {
return messages -> {
var brokenHeader = ((ArrayList<Map<String, Object>>) messages.getHeaders().get(KafkaHeaders.BATCH_CONVERTED_HEADERS)).get(0).get(HEADER_KEY);
System.out.println("BATCH - Class header: " + (brokenHeader != null ? brokenHeader.getClass() : null));
};
}
#Bean
public Consumer<Message<ExampleBrokenHeaderEntity>> readFromKafkaNoBatchMode() {
return messages -> {
var brokenHeader = messages.getHeaders().get(HEADER_KEY);
System.out.println("NO_BATCH - Class header: " + (brokenHeader != null ? brokenHeader.getClass() : null));
};
}
#Bean("kafkaHeaderMapperCustom")
public KafkaHeaderMapper getKafkaHeaderMapperBatchMode() {
var kafkaHeaderMapperCustom = new DefaultKafkaHeaderMapper();
kafkaHeaderMapperCustom.addTrustedPackages("*");
return kafkaHeaderMapperCustom;
}
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
#Data
#NoArgsConstructor
#AllArgsConstructor
public class CustomHeaderClass {
private String field1;
private LocalDate field2;
}
import lombok.AllArgsConstructor;
import lombok.Data;
#Data
#AllArgsConstructor
public final class ExampleBrokenHeaderEntity {
private String type;
private String name;
}
spring.cloud.stream.kafka.binder.brokers=x.x.x.x:xxxx
spring.cloud.function.definition=readFromKafkaNoBatchMode;readFromKafkaBatchMode
spring.cloud.stream.bindings.readFromKafkaBatchMode-in-0.destination=stackoverflow-example
spring.cloud.stream.bindings.readFromKafkaBatchMode-in-0.group=readFromKafkaBatchMode
spring.cloud.stream.bindings.readFromKafkaBatchMode-in-0.consumer.batch-mode=true
spring.cloud.stream.bindings.readFromKafkaNoBatchMode-in-0.destination=stackoverflow-example
spring.cloud.stream.bindings.readFromKafkaNoBatchMode-in-0.group=readFromKafkaNoBatchMode
spring.cloud.stream.kafka.binder.headerMapperBeanName=kafkaHeaderMapperCustom
The output of example is:
NO_BATCH - Class header: class com.example.kafka.header.types.CustomHeaderClass
BATCH - Class header: class org.springframework.kafka.support.DefaultKafkaHeaderMapper$NonTrustedHeaderType
It's a bug; the binder only sets the custom header mapper on a record converter:
private MessageConverter getMessageConverter(
final ExtendedConsumerProperties<KafkaConsumerProperties> extendedConsumerProperties) {
MessageConverter messageConverter = BindingUtils.getConsumerMessageConverter(getApplicationContext(),
extendedConsumerProperties, this.configurationProperties);
if (messageConverter instanceof MessagingMessageConverter) {
((MessagingMessageConverter) messageConverter).setHeaderMapper(getHeaderMapper(extendedConsumerProperties));
}
return messageConverter;
}
There should be similar code for when the converter is a BatchMessagingMessageConverter.
The work around is to define a custom message converter for the batch consumer:
#Bean("batchConverter")
BatchMessageConverter batchConverter(KafkaHeaderMapper kafkaHeaderMapperCustom) {
BatchMessagingMessageConverter batchConv = new BatchMessagingMessageConverter();
batchConv.setHeaderMapper(kafkaHeaderMapperCustom);
return batchConv;
}
spring.cloud.stream.kafka.bindings.readFromKafkaBatchMode-in-0.consumer.converter-bean-name=batchConverter
NO_BATCH - Class header: class com.example.demo.So74294156Application$CustomHeaderClass
BATCH - Class header: class com.example.demo.So74294156Application$CustomHeaderClass
Please open an issue against Spring Cloud Stream, referencing this question/answer.

mock bean unable to inject dependecy in unit test

I want to test my spring boot application using TestRestemplate, however I am unable to test get url, what I am trying to do here is I am injecting some predefine objects setting into list and mocking find all method.
below is my test code looks like.
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.junit.Before;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles("test")
public class PersonControllerTests {
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private PersonRepository mockRepository;
#Before
public void init() {
List<Person> list = new ArrayList<>();
Person p1 = new Person("dumm1", "lastName1",22);
Person p2 = new Person("dumm2", "lastName2",32);
p1.setId(1l);
list.add(p2);
list.add(p1);
when(mockRepository.findAll()).thenReturn(list);
}
#Test
public void getPersonsGoodReq() throws Exception {
ResponseEntity<Person[]> response = restTemplate
.withBasicAuth("admin", "password")
.getForEntity("/persons/all", Person[].class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(response.getBody().length, 2);
}
}
I am expecting answer to be 2, but when I am seeing body response it is empty array.
what might have gone wrong I am unable to get
As you're using JUnit Jupiter, you have to use #BeforeEach. #Before is from JUnit 4 and hence wasn't invoked as part of the test's lifecycle:
#BeforeEach
public void init() {
List<Person> list = new ArrayList<>();
Person p1 = new Person("dumm1", "lastName1",22);
Person p2 = new Person("dumm2", "lastName2",32);
p1.setId(1l);
list.add(p2);
list.add(p1);
when(mockRepository.findAll()).thenReturn(list);
}

How to autowire MockMvc bean correctly

I have a bit of trouble with this.
I'm trying to test the web layer of my Spring boot app (with JUnit5).
I'm using the #WebMvcTest(NoteController::class) to allow me to autowire MockMvc in order to mock requests.
But I get the below error :
kotlin.UninitializedPropertyAccessException: lateinit property mvc has not been initialized
NoteControllerTest
import org.hamcrest.Matchers.`is`
import org.junit.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.http.MediaType
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
#ExtendWith(SpringExtension::class)
#WebMvcTest(NoteController::class)
class NoteControllerTest {
#Autowired
private lateinit var mvc: MockMvc
#Test
fun should_create_a_note() {
mvc.perform(
post("/notes"))
.andExpect(status().isCreated)
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$.content", `is`("my content")))
}
}
NoteController
import fr.$$.$$.api.CreateNote
import fr.$$.$$.api.FetchNote
import fr.$$.$$.resources.Note
import fr.$$.$$.resources.toResource
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RestController
import java.net.URI
#RestController("/notes")
class NoteController(val createNote: CreateNote,
val fetchNote: FetchNote) {
#GetMapping
fun getAllNotes(): ResponseEntity<List<Note>> {
return ResponseEntity(fetchNote.all().toResource(), HttpStatus.OK)
}
#PostMapping
fun createNote(): ResponseEntity<Note> {
val note = createNote.with("my content").toResource()
return ResponseEntity.created(URI("")).body(note)
}
}
SmartNotesApplicationTest
import org.junit.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT
import org.springframework.test.context.junit.jupiter.SpringExtension
#ExtendWith(SpringExtension::class)
#SpringBootTest(webEnvironment = RANDOM_PORT)
class SmartNotesApplicationTest {
#Test
fun contextLoad() {
}
}
Thanks in advance.
I inject the WebApplicationContext and then build a new MockMvc for each test from that.
#SpringBootTest
class SomeTest {
#Autowired
lateinit var webApplicationContext: WebApplicationContext
lateinit var mockMvc: MockMvc
#BeforeEach
fun beforeEach() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build()
}
// Tests go here!
}
thanks for the answers, I put the working answer in Java with Spring Boot 2.2.6:
AuthorController.class
import org.springframework.web.bind.annotation.*;
#RestController
#RequestMapping(path = "/authors")
public class AuthorController {
#GetMapping("/health")
public boolean healthcheck() {
return true;
}
}
AuthorControllerIT.class
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
#SpringBootTest
#AutoConfigureMockMvc
class AuthorControllerIT {
#Autowired
private MockMvc mockMvc;
#Test
public void test_web_layer() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/authors/health"))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("true"));
}
}
Note :
You can use #WebMvcTest(AuthorController.class) instead of #SpringBootTest + AutoConfigureMockMvc
With this annotation, it will only load the web layer.
If you have dependencies in your controller (services, repositories...) you must use :
#MockBean
private MyService service;

Vaadin-Grid not displaying the data from the database it is supposed to show

I am writing a Vaadin Spring application and have the problem that the data from the database are not showing in the Grid (= a Vaadin 8 UI-Component for displaying tables).
I have a suspicion as to where the error may be located (see my remarks at the end of this post).
Everything else (apart from the data not showing in the Grid) is working fine. The database table was automatically created with the help of Spring JPA annotations, the Grid is displayed nicely in the UI, etc. ... only the data from the database are not showing up in the Grid.
For this question I created a new mini-project. The code for this mini-project you can find below. The ui simply consists of a Grid, which is supposed to display the content of a database. (Since the Grid doesn't display anything, the UI is basically an empty frame, see the last screenshot).
The problem in this mini-project is the same as in the original (and much larger) project: the data from the database is not showing up in the Grid.
The mini-project is called "demo" and was created with the help of SpringInitializr. The selected dependencies are shown in the following screenshot:
My demo-app consists of 3 little packages: model, database_access and ui:
The model consists of a single class called "Person". Here's the code of this file:
package com.example.demo.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name="Persons")
public class Person {
#Id
#Column(name="id")
private int id;
#Column(name="name")
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
The database-access package consists of 3 files:
PersonRepository,
PersonService and
PersonServiceImpl
... whereby PersonServiceImpl is the implementation of the PersonService interface.
Here's PersonRepository:
package com.example.demo.database_acess;
import com.example.demo.model.Person;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.stream.Stream;
#Repository
public interface PersonRepository extends JpaRepository<Person, Integer> {
#Query("SELECT p FROM Person p")
Stream<Person> findAllByCustomQueryAndStream();
}
Here's PersonService:
package com.example.demo.database_acess;
import com.example.demo.model.Person;
import java.util.stream.Stream;
public interface PersonService {
Stream<Person> streamAllPersons();
long countPersons();
}
and here is PersonServiceImpl:
package com.example.demo.database_acess;
import com.example.demo.model.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.stream.Stream;
#Service
public class PersonServiceImpl implements PersonService {
#Autowired
private PersonRepository personRepository;
#Transactional
public Stream<Person> streamAllPersons() {
return personRepository.findAllByCustomQueryAndStream();
}
public long countPersons() {
return personRepository.count();
}
}
The ui package consists of the following four files:
GUI, PersonsView, PersonsGrid and PersonDataProvider
GUI:
package com.example.demo.ui;
import com.vaadin.annotations.Theme;
import com.vaadin.annotations.Title;
import com.vaadin.navigator.Navigator;
import com.vaadin.server.VaadinRequest;
import com.vaadin.spring.annotation.SpringUI;
import com.vaadin.spring.navigator.SpringViewProvider;
import com.vaadin.ui.Panel;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import org.springframework.beans.factory.annotation.Autowired;
#SpringUI
#Title("Demo App")
#Theme("valo")
public class GUI extends UI {
#Autowired
private SpringViewProvider viewProvider;
#Override
protected void init(VaadinRequest request) {
VerticalLayout rootLayout = new VerticalLayout();
rootLayout.setSizeFull();
setContent(rootLayout);
Panel viewContainer = new Panel();
viewContainer.setSizeFull();
rootLayout.addComponent(viewContainer);
rootLayout.setExpandRatio(viewContainer, 1.0f);
Navigator navigator = new Navigator(this, viewContainer);
navigator.addProvider(viewProvider);
}
}
PersonsView:
package com.example.demo.ui;
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.spring.annotation.SpringView;
import com.vaadin.spring.annotation.UIScope;
import com.vaadin.ui.VerticalLayout;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
#UIScope
#SpringView(name= PersonsView.NAME)
public class PersonsView extends VerticalLayout implements View {
public static final String NAME = "";
#Autowired
private PersonsGrid personsGrid;
#PostConstruct
void init() {
setMargin(false);
addComponent(personsGrid);
personsGrid.setSizeFull();
}
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
}
}
PersonsGrid:
package com.example.demo.ui;
import com.example.demo.model.Person;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.ui.Grid;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
#SpringComponent
public class PersonsGrid extends Grid<Person> {
#Autowired
private PersonDataProvider personDataProvider;
#PostConstruct
void init() {
setDataProvider(personDataProvider);
}
}
PersonDataProvider:
package com.example.demo.ui;
import com.example.demo.database_acess.PersonServiceImpl;
import com.example.demo.model.Person;
import com.vaadin.data.provider.AbstractBackEndDataProvider;
import com.vaadin.data.provider.Query;
import com.vaadin.spring.annotation.SpringComponent;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.stream.Stream;
#SpringComponent
public class PersonDataProvider extends AbstractBackEndDataProvider<Person, Void> {
#Autowired
PersonServiceImpl personService;
public Stream<Person> fetchFromBackEnd(Query<Person, Void> query) {
return personService.streamAllPersons();
}
public int sizeInBackEnd(Query<Person, Void> query) {
return (int) personService.countPersons();
}
}
The application.properties file looks as follows:
spring.datasource.url=jdbc:mysql://localhost:3306/demoDB
spring.datasource.username=root
spring.datasource.password=mypassword
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=create
I inserted two persons into the database (Bill and George):
However, these persons are not displayed in the grid, as you can see in the following screenshot:
When writing the code for the Grid (PersonGrid) and the DataProvider (PersonDataProvider), I followed examples from the internet (e.g. http://vaadinhelp.co.in/vaadin-dataprovider-example/)
... My suspicion is that in one of these two files (i.e. PersonGrid and PersonDataProvider) something is missing or wrong.
However, I don't know what.
************************************UPDATE****************************************
I added the following two lines to the init-function of the PersonsView-Class:
personsGrid.addColumn(Person::getName).setCaption("Name");
personsGrid.addColumn(Person::getId).setCaption("ID");
After restarting the application, there are now 3 rows in in the grid (one for the header, one for "George" and one for "Bill").
But: the rows are not correctly displayed, as you can see in the following screenshot:
In the upper left corner you can see the beginning of the three rows but there aren't any columns displayed.
What do I have to do for the data to be displayed correctly?
*******************************UPDATE2******************************************
Apparently, some SQL-operation is not allowed after the ResultSet is closed (see screenshot below). I don't know what this means though.

Annotated Spring-MVC controller not recognized when controller extends interface

I'm using spring 2.5, and am using annotations to configure my controllers. My controller works fine if I do not implement any additional interfaces, but the spring container doesn't recognize the controller/request mapping when I add interface implementations.
I can't figure out why adding an interface implementation messes up the configuration of the controller and the request mappings. Any ideas?
So, this works:
package com.shaneleopard.web.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.providers.encoding.Md5PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.shaneleopard.model.User;
import com.shaneleopard.service.UserService;
import com.shaneleopard.validator.RegistrationValidator;
import com.shaneleopard.web.command.RegisterCommand;
#Controller
public class RegistrationController {
#Autowired
private UserService userService;
#Autowired
private Md5PasswordEncoder passwordEncoder;
#Autowired
private RegistrationValidator registrationValidator;
#RequestMapping( method = RequestMethod.GET, value = "/register.html" )
public void registerForm(#ModelAttribute RegisterCommand registerCommand) {
// no op
}
#RequestMapping( method = RequestMethod.POST, value = "/register.html" )
public String registerNewUser( #ModelAttribute RegisterCommand command,
Errors errors ) {
String returnView = "redirect:index.html";
if ( errors.hasErrors() ) {
returnView = "register";
} else {
User newUser = new User();
newUser.setUsername( command.getUsername() );
newUser.setPassword( passwordEncoder.encodePassword( command
.getPassword(), null ) );
newUser.setEmailAddress( command.getEmailAddress() );
newUser.setFirstName( command.getFirstName() );
newUser.setLastName( command.getLastName() );
userService.registerNewUser( newUser );
}
return returnView;
}
public Validator getValidator() {
return registrationValidator;
}
}
but this doesn't:
package com.shaneleopard.web.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.providers.encoding.Md5PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.shaneleopard.model.User;
import com.shaneleopard.service.UserService;
import com.shaneleopard.validator.RegistrationValidator;
import com.shaneleopard.web.command.RegisterCommand;
#Controller
public class RegistrationController extends ValidatingController {
#Autowired
private UserService userService;
#Autowired
private Md5PasswordEncoder passwordEncoder;
#Autowired
private RegistrationValidator registrationValidator;
#RequestMapping( method = RequestMethod.GET, value = "/register.html" )
public void registerForm(#ModelAttribute RegisterCommand registerCommand) {
// no op
}
#RequestMapping( method = RequestMethod.POST, value = "/register.html" )
public String registerNewUser( #ModelAttribute RegisterCommand command,
Errors errors ) {
String returnView = "redirect:index.html";
if ( errors.hasErrors() ) {
returnView = "register";
} else {
User newUser = new User();
newUser.setUsername( command.getUsername() );
newUser.setPassword( passwordEncoder.encodePassword( command
.getPassword(), null ) );
newUser.setEmailAddress( command.getEmailAddress() );
newUser.setFirstName( command.getFirstName() );
newUser.setLastName( command.getLastName() );
userService.registerNewUser( newUser );
}
return returnView;
}
public Validator getValidator() {
return registrationValidator;
}
}
layne, you described the problem as happening when your controller class implements an interface, but in the code sample you provided, the problem occurs when your controller class extends another class of yours, ValidatingController.
Perhaps the parent class also defines some Spring annotations, and the Spring container noticed them first and classified the controller class as that type of managed object and did not bother to check for the #Controller annotation you also defined in the subclass. Just a guess, but if that pans out, I'd suggest reporting it to the Spring team, as it sounds like a bug.
By default JDK proxy are created using interface and if controller implements an interface the RequestMapping annotation gets ignored as the targetClass is not being used
Add this in your servlet context config:
<aop:aspectj-autoproxy proxy-target-class="true"/>
I think you'll find that the problem is to do with inheritance and using annotations, they do not mix well.
Have you tried to implement the above using inheritance and SimpleFormController with all other details configured in your application context? This will at least narrow down the problem to an annotations and inheritance issue.

Resources