I'm trying to create a Spring MVC 4 web project (Maven project) in order to handle REST requests.
The system should answer with a web page in case the HTTP accept is text/html
and in json in case it is application/json (and possibly other formats like XML in case it is application/xml).
I've set up a controller and the JSP (I've also used the #RestController). The problem is that I cannot make it working togheter. If the system is correctly answer with a JSP, then the json service is not working and vice versa.
Do I have to set an handler for each representation, and how?
Thank you in advance.
To determining what format the user has requested relies on a ContentNegotationStrategy,there are default implementations available out of the box,but you can also implement your own if you wish
To configure and use content negotiation with Spring using HTTP message converters :
You have to enable Content Negotiation in Spring MVC :
<bean id="cnManager"
class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="true"/>
<property name="ignoreAcceptHeader" value="true" />
<property name="defaultContentType" value="application/json" />
<property name="useJaf" value="false"/>
<property name="mediaTypes">
<map>
<entry key="html" value="text/html" />
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
</bean>
Or using java configuration :
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(
ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(true).
ignoreAcceptHeader(true).
useJaf(false).
defaultContentType(MediaType.TEXT_HTML).
mediaType("html", MediaType.TEXT_HTML).
mediaType("xml", MediaType.APPLICATION_XML).
mediaType("json", MediaType.APPLICATION_JSON);
}
}
This is an exemple :
the first method will return client.jsp page for each get for url end .../client and the second will return a client json object for each get end .../clients/id
#RequestMapping(value = "/client", method = RequestMethod.GET)
public ModelAndView getClientPage(final HttpServletRequest request,
final Model model,final HttpSession session) {
// traitement
return new ModelAndView("client");
}
#RequestMapping(value = "/clients/{id}", method = RequestMethod.GET)
public #ResponseBody Client getClient(#pathVariable("id") long id) {
Client client = YourService.getClient(id);
return client;
}
Contentnegotiatingviewresolver java config :
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.yourbasepackage")
public class AppConfig extends WebMvcConfigurerAdapter {
#Bean
public ViewResolver jspViewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
Related
I'm using this as a reference to create a REST only configuration on Struts2:
https://cwiki.apache.org/confluence/display/WW/REST+Plugin
I have one model, Receipt with a few test fields: title, body.
Currently to create a receipt, I send a request in this way:
POST /receipt/?body=new_body&title=new_title
and it creates me a receipt with the new body and title passed in.
This doesn't work:
POST /receipt/
{
"body": "new_body",
"title": "new title"
}
Here's some code:
struts.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<bean type="org.apache.struts2.rest.handler.ContentTypeHandler" name="jackson" class="org.apache.struts2.rest.handler.JacksonLibHandler"/>
<constant name="struts.rest.handlerOverride.json" value="jackson"/>
<constant name="struts.enable.DynamicMethodInvocation" value="false"/>
<constant name="struts.devMode" value="true"/>
<constant name="struts.rest.content.restrictToGET" value="false"/>
<constant name="struts.rest.defaultExtension" value="json"/>
<constant name="struts.rest.handlerOverride.EXTENSION" value="json"/>
<constant name="struts.i18n.encoding" value="UTF-8"/>
<constant name="struts.action.extension" value="xhtml,,xml,json,action"/>
<constant name="struts.mapper.class" value="org.apache.struts2.dispatcher.mapper.PrefixBasedActionMapper" />
<constant name="struts.mapper.prefixMapping" value="/receipt:rest,:struts"/>
<constant name="struts.convention.action.suffix" value="Controller"/>
<constant name="struts.convention.action.mapAllMatches" value="true"/>
<constant name="struts.convention.default.parent.package" value="receipto"/>
<constant name="struts.convention.package.locators" value="controllers,actions"/>
</struts>
ReceiptController.java:
public class ReceiptController implements ModelDriven<Object> {
private ReceiptManager receiptManager = new ReceiptManager();
private String id;
private Receipt model = new Receipt();
private Collection list;
public Object getModel()
{
return (list==null ? model : list);
}
public HttpHeaders create()
{
receiptManager.save(model);
return new DefaultHttpHeaders("create");
}
public HttpHeaders show()
{
model = receiptManager.find(id);
return new DefaultHttpHeaders("show");
}
public HttpHeaders update()
{
receiptManager.save(model);
return new DefaultHttpHeaders("update");
}
public HttpHeaders destroy()
{
model = receiptManager.destroy(id);
return new DefaultHttpHeaders("destroy");
}
public HttpHeaders index()
{
list = receiptManager.list();
return new DefaultHttpHeaders("index").disableCaching();
}
public String getId()
{
return id;
}
public void setId(String id)
{
this.id = id;
}
}
Is it supposed to work as I want it to, or is it just how the plugin works?
I guess that postman is sending JSON in the body of the request and sets the content type application/json. Struts can parse the request if you add json interceptor to the stack.
<interceptor-stack name="myStack">
<interceptor-ref name="json"/>
<interceptor-ref name="myInterceptor"/>
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
The description for "json" interceptor in the JSON Plugin:
If the interceptor is used, the action will be populated from the JSON content in the request, these are the rules of the interceptor:
The "content-type" must be "application/json"
The JSON content must be well formed, see json.org for grammar.
Action must have a public "setter" method for fields that must be populated.
Supported types for population are: Primitives (int,long...String), Date, List, Map, Primitive Arrays, Other class (more on this later), and Array of Other class.
Any object in JSON, that is to be populated inside a list, or a map, will be of type Map (mapping from properties to values), any whole number will be of type Long, any decimal number will be of type Double, and any array of type List.
Resources:
Kickstart FAQ
Getting Started
FAQs
Other Resources
I have the aspect(see below) which should log actions(create, update, delete) in db. Depends on action logging happens in a preProcess or postProcess method. I shouldn't log anything if some fail happens through these actions. I.e. if create didn't happened, then there is no need to logging it.
I tried to tested it. I throw RunTimeException in the join point and expect that there is no new log in db. Unfortunately, new log is saved in spite of exception in the join point.
Aspect:
#Component
#Aspect
public class LoggingAspect {
#Autowired
private ApplicationContext appContext;
#Autowired
private LoggingService loggingService;
#Around("#annotation(Loggable)")
#Transactional
public void saveActionMessage(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature ms = (MethodSignature) joinPoint.getSignature();
Loggable m = ms.getMethod().getAnnotation(Loggable.class);
LoggingStrategy strategy = appContext.getBean(m.strategy());
Object argument = joinPoint.getArgs()[0];
strategy.preProcess(argument);
joinPoint.proceed();
strategy.postProcess(argument);
}
}
TestApplicationConfig:
<context:spring-configured/>
<import resource="applicationConfig-common.xml"/>
<import resource="applicationConfig-security.xml"/>
<aop:aspectj-autoproxy/>
<util:map id="testValues">
<entry key="com.exadel.mbox.test.testSvnFile" value="${svnFolder.configPath}${svnRoot.file[0].fileName}"/>
<entry key="com.exadel.mbox.test.testCommonRepositoryPath" value="${svnRoot.commonRepositoryPath}"/>
<entry key="com.exadel.mbox.test.testMailFile" value="${mailingList.configPath}"/>
</util:map>
<context:component-scan base-package="com.exadel.report.common" />
<!-- Jpa Repositories -->
<jpa:repositories base-package="com.exadel.report.common.dao" />
<tx:annotation-driven proxy-target-class="true"
transaction-manager="txManager" mode="aspectj"/>
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- Data Source -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver" />
<property name="url" value="jdbc:hsqldb:mem:testdb" />
<property name="username" value="sa" />
<property name="password" value="" />
</bean>
<!-- Entity Manager -->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true"/>
<property name="generateDdl" value="true"/>
<property name="databasePlatform" value="org.hibernate.dialect.HSQLDialect"/>
</bean>
</property>
<property name="persistenceUnitName" value="exviewer-test"/>
</bean>
<!-- Transaction Manager -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
[Update]
LoggingStrategy:
public interface LoggingStrategy {
public void preProcess(Object obj);
public void postProcess(Object obj);
}
BaseLoggingStrategy:
public class BaseLoggingStrategy implements LoggingStrategy {
#Override
public void preProcess(Object obj) {}
#Override
public void postProcess(Object obj) {}
}
UpdateProcessStrategy:
#Service
public class UpdateProcessStrategy extends BaseLoggingStrategy {
#Autowired
private LoggingService loggingService;
#Autowired
private UserService userService;
#Autowired
DeviceService deviceService;
private Device currentDevice;
#Override
#Transactional
public void preProcess(Object obj) {
currentDevice = (Device) obj;
Device previousDevice = deviceService.getById(currentDevice.getId());
String deviceDataBeforeUpdate = deviceService.getDeviceDetailsInJSON(previousDevice);
String deviceDataAfterUpdate = deviceService.getDeviceDetailsInJSON(currentDevice);
String login = userService.getCurrentUser().getLogin();
String actionMessage = LoggingMessages.DEVICE_UPDATE.name();
loggingService.save(
new Logging(
login,
actionMessage,
deviceDataBeforeUpdate,
deviceDataAfterUpdate,
new Date())
);
}
#Override
public void postProcess(Object obj) {}
}
Class intercepted by aspcet:
#Service
public class DeviceService {
#Loggable(value = LoggingMessages.DEVICE_CREATE, strategy = CreateProcessStrategy.class)
#Transactional
public void create(Device device) {
createOrUpdate(device);
}
#Loggable(value = LoggingMessages.DEVICE_UPDATE, strategy = UpdateProcessStrategy.class)
#Transactional
public void update(Device device) {
createOrUpdate(device);
}
private void createOrUpdate(Device device) {
deviceRepository.save(device);
}
#Loggable(value = LoggingMessages.DEVICE_REMOVE, strategy = RemoveProcessStrategy.class)
public void remove(Long deviceId) {
deviceRepository.delete(deviceId);
}
}
Loggable annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Loggable {
LoggingMessages value();
Class<? extends LoggingStrategy> strategy();
}
Log for update action contains:
id, created_dtm, action(DEVICE_UPDATE), device_data_before_action_on_the_device(in json format), device_data_after_action_on_the_device(in json format), created_by.
Disclaimer: Actually I am not a Spring expert, maybe someone else can help you out here. My field of expertise it AspectJ, which is how I found your question.
Anyway, you have two issues here:
#Transactional annotation on your aspect's advice LoggingAspect.saveActionMessage(..). Actually I have no idea if this works at all (I found no example using #Transactional on an aspect method/advice on the web, but maybe I searched in the wrong way) because declarative transaction handling in Spring is implemented via proxy-based technology, just like Spring AOP. Read the chapter 12 about transaction management in the Spring manual for further details, especially chapter 12.5.1. I am pretty sure you will find a way to do what you want there.
Nested transactions, because e.g. UpdateProcessStrategy.preProcess(..) is called by the very advice which is meant to be transactional, but is declared #Transactional too. So you have a transaction within a transaction. How Spring handles this, I have no idea, but maybe this tutorial about Spring transaction propagation contains enlightening details.
The Spring manual lists several means to implement transactional behaviour: programmatically, declaratively via annotations, XML-based <tx:advice> stuff and so forth. I don't know which way is the best for you, I merely wanted to provide some general hints.
I am using routing data sources, and my create operations are annotated with #Transactional annotations. But i noticed that transaction does not begin or commit. Following is my routing data source configuration.
<bean id="routingDataSource" class="com.test.dataaccess.base.dao.CustomerRoutingDataSource">
<property name="defaultTargetDataSource" ref="testDataSource" />
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="0" value-ref="testDataSource" />
</map>
</property>
</bean>
<bean class="org.springframework.orm.jpa.JpaTransactionManager"
id="customerTransactionManager">
<property name="entityManagerFactory" ref="customerEntityManagerFactory" />
</bean>
Same data source i am using with my org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.
I am adding another datasources to routing data sources at deployment time as follows.
Spring transaction management does not works.
#Component
public class CustomerDataSourcePostProcessor implements ApplicationListener {
#Autowired
DatasourcesDAO datasourcesDAO;
#Autowired
#Qualifier("customerEntityManagerFactory")
private LocalContainerEntityManagerFactoryBean testContentEntityManagerFactory;
#Autowired
#Qualifier("routingDataSource")
private CustomerRoutingDataSource routingDataSource;
#Autowired
#Qualifier("customerTransactionManager")
private JpaTransactionManager customerTransactionManager;
private static final Logger LOGGER = LoggerFactory.getLogger(CustomerDataSourcePostProcessor.class);
public void onApplicationEvent(ApplicationEvent e) {
if (e instanceof ContextRefreshedEvent) {
loadCustomerDBConfigForServer();
}
}
private void loadCustomerDBConfigForServer() {
Map<Object, Object> databaseConfig = loadCustomerDatabaseConfig();
routingDataSource.setTargetDataSources(databaseConfig);
routingDataSource.afterPropertiesSet();
testContentEntityManagerFactory.setDataSource(routingDataSource);
testContentEntityManagerFactory.afterPropertiesSet();
EntityManagerFactory emf =testContentEntityManagerFactory.getObject(); // transaction not begin possible root cause one
customerTransactionManager.setEntityManagerFactory(emf);
customerTransactionManager.afterPropertiesSet();
}
}
I am learning spring AOP, but I am having issues running the #After annotation.
For some reason, #After gets executed BEFORE the method call.
What am I doing wrong? Is it my Eclipse environment issue?
#Aspect
public class LoggingAspect {
#After("execution(* aop.*.* (..))")
public void AfterLoggingAdvice() {
System.out.println("AfterLoggingAdvice() is running");
}
}
This is my main class:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"aop.xml");
ShapeService service = context.getBean("shapeService",
ShapeService.class);
System.out.println(service.getCircle().getName());
}
XML file:
<aop:aspectj-autoproxy/>
<bean id="circle" class="aop.Circle" >
<property name="name" value="Circle Name" />
<property name="id" value="Circle ID" />
</bean>
<bean id="shapeService" class="aop.ShapeService" autowire="byName"/>
<bean id="loggingAspect" class="aop.LoggingAspect"/>
</beans>
This is the output regardless of using #After or #Before:
AfterLoggingAdvice() is running
AfterLoggingAdvice() is running
Circle Name
Ok here iam back to find a solution.
Iam trying Spring RestTemplate postForEntity method to send a Collection of instances. When attempting spring gives an error org.springframework.http.converter.HttpMessageNotWritableException: Could not write request: no suitable HttpMessageConverter found for request type [com.abc.base.domai
n.dto.gift.GiftItemList Appriciate, if someone can tells me how to send a an ArrayList with spring resttemplate POST method.
RestTemplate bean:
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<constructor-arg ref="httpClientFactory"/>
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.FormHttpMessageConverter" />
<bean class="org.springframework.http.converter.StringHttpMessageConverter" />
<bean id="jsonViewResolver" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" >
<property name="objectMapper">
<ref bean="JacksonObjectMapper" />
</property>
<property name="supportedMediaTypes">
<list>
<bean class="org.springframework.http.MediaType">
<constructor-arg value="application" />
<constructor-arg value="json" />
<constructor-arg value="#{T(java.nio.charset.Charset).forName('UTF-8')}"/>
</bean>
</list>
</property>
</bean>
</list>
</property>
</bean>
<bean id="JacksonObjectMapper" class="org.codehaus.jackson.map.ObjectMapper" />
<bean id="httpClient" class="org.apache.commons.httpclient.HttpClient">
<constructor-arg ref="httpClientParams"/>
</bean>
<bean id="httpClientParams" class="org.apache.commons.httpclient.params.HttpClientParams">
<property name="connectionManagerClass" value="org.apache.commons.httpclient.MultiThreadedHttpConnectionManager"/>
</bean>
<bean id="httpClientFactory" class="org.springframework.http.client.CommonsClientHttpRequestFactory">
<constructor-arg ref="httpClient"/>
</bean>
Instance that iam trying to POST,
public class GiftItem implements Entity, Serializable {
private static final long serialVersionUID = 1L;
private String redeemLocation;
private String itemName;
private String itemDescription;
private String merchantName;
private Integer quantity;
private Integer imageId;
public GiftItem() {
super();
}
//with getters and setters
}
GiftItem instance wraaper class
public class GiftItemList implements Serializable {
private static final long serialVersionUID = -8202204714984099030L;
public GiftItemList() {
}
private List<GiftItem> giftItemList;
public List<GiftItem> getGiftItemList() {
return giftItemList;
}
public void setGiftItemList(List<GiftItem> giftItemList) {
this.giftItemList = giftItemList;
}
}
this is how i use it,
public BaseResponse sendGiftEmail(final String token, final User sender,final String message, final GiftItemList giftItemList) {
MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();
map.add("token", token);
map.add("sender", sender);
map.add("message", message);
map.add("giftItemList", giftItemList);
return getRestTemplate().postForEntity(
"http://localhost:8080/notification/api/notification/send_gift_email",
map, BaseResponse.class).getBody();
}
and the error i am getting,
org.springframework.http.converter.HttpMessageNotWritableException: Could not write request: no suitable HttpMessageConverter found for request type [com.abc.base.domain.dto.gift.GiftItemList]
at org.springframework.http.converter.FormHttpMessageConverter.write Part(FormHttpMessageConverter.java:310)
at org.springframework.http.converter.FormHttpMessageConverter.write Parts(FormHttpMessageConverter.java:270)
at org.springframework.http.converter.FormHttpMessageConverter.write Multipart(FormHttpMessageConverter.java:260)
at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:200)
at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:1)
at org.springframework.web.client.RestTemplate$HttpEntityRequestCallback.doWithRequest(RestTemplate.java:588)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:436)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:401)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:302)
at com.tapgift.gift.client.impl.GiftClientImpl.sendGiftNotifications(GiftClientImpl.java:101)
pom:
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.2</version>
</dependency>
almost forgot receiver controller,
#RequestMapping(value = "/notification/send_gift_email", method = RequestMethod.POST)
public #ResponseBody BaseResponse sendGiftEmail(#RequestParam("token") String token, #RequestParam("sender")final User sender, #RequestParam("message")final String message,#RequestParam("giftItemList") GiftItemList giftItemList) {
}
You have to add MappingJacksonHttpMessageConveter to your messageCoverter :
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(new FormHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new MappingJacksonHttpMessageConverter());
restTemplate = new RestTemplate();
restTemplate.setMessageConverters(messageConverters);
You also need to add the dependency for :
jackson-core-asl-x.x.x.jar & jaackson.mapper.asl-x.x.x.jar
Another thing, you have to make sure that your class have the same attribute as your JSON properties. For example :
{"data":{"ticket":"TICKET_870299cf98e227abdbd5f9b7064390c5723a0c6a"}}
To fill your class properties, they have to be like this :
person.java
public class person {
private Data data;
public Data getData() {
return data;
}
public void setData(Data data) {
this.data = data;
}
}
data.java
public class Data {
private String ticket;
public String getTicket() {
return ticket;
}
public void setTicket(String ticket) {
this.ticket = ticket;
}
}
Finaly in your application your add :
person entity = restTemplate.postForObject(url, requestEntity,
person.class);
requestEntity is a String that contains your request body (JSON in my case).
Hope that helped !