I created a custom router with one endpoint. The custom router looks up the destination of the endpoint based on the URL parameters of the inbound URL. I have an example of this up and running, and I am testing it out in a browser. I am trying to solve one last thing with this. When I make the call in the browser using http://localhost:8787/my-site, the call makes a redirect and the URL in the browser changes to http://server2.xyz.com:8080/my-site. I don't want the user to ever see http://server2.xyz.com:8080/my-site. I want the user to always see http://localhost:8787/my-site. How can I achieve this? I am using Mule 2.2.1 community edition with Java 1.6.
Here is my Mule configuration file:
<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns="http://www.mulesource.org/schema/mule/core/2.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:spring="http://www.springframework.org/schema/beans"
xmlns:http="http://www.mulesource.org/schema/mule/http/2.2"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.mulesource.org/schema/mule/core/2.2 http://www.mulesource.org/schema/mule/core/2.2/mule.xsd
http://www.mulesource.org/schema/mule/http/2.2 http://www.mulesource.org/schema/mule/http/2.2/mule-http.xsd">
<model name="ProxyService">
<service name="HttpProxyService">
<inbound>
<http:inbound-endpoint address="http://localhost:8787" synchronous="true"/>
</inbound>
<outbound>
<custom-outbound-router class="com.abc.xyz.routing.LookupOutboundRouter">
<outbound-endpoint name="custom" address="http://nonexistant.server.com:8080" synchronous="true"/>
</custom-outbound-router>
</outbound>
</service>
</model>
</mule>
Here is my custom router:
public class LookupOutboundRouter extends AbstractOutboundRouter {
Logger logger = Logger.getLogger(LookupOutboundRouter.class);
#Override
public boolean isMatch(MuleMessage message) throws MessagingException {
return true;
}
#Override
public MuleMessage route(MuleMessage message, MuleSession session) throws MessagingException {
String[] urlValues = StringUtils.split(message.getProperty("http.request").toString(), "/");
String newUri = lookupServiceUri(urlValues[0]) + urlValues[1];
logger.info("newUri=" + newUri);
DynamicURIOutboundEndpoint ep;
try {
ep = new DynamicURIOutboundEndpoint((OutboundEndpoint) getEndpoints().get(0), new MuleEndpointURI(newUri));
MuleMessage message2 = send(session, message, ep);
return message2;
} catch (EndpointException e1) {
e1.printStackTrace();
} catch (MuleException e) {
e.printStackTrace();
}
return null;
}
/**
* This will call the service registry.
* #param id
* #return
*/
private String lookupServiceUri(String id) {
if(id.equalsIgnoreCase("12345")) {
return "http://server.xyz.com:8080/";
} else {
return "http://server2.xyz.com:8080/";
}
}
}
I was able to achieve this in the browser by setting followRedirects to true on the HTTP connector. The only issue with this now is that it does not work for POST redirects. I'm making a SOAP call from SoapUI now instead of using the browser.
Entity enclosing requests cannot be redirected without user intervention
Message : Failed to route event via endpoint: org.mule.endpoint.DynamicURIOutboundEndpoint#fd285ee0. Message payload is of type: PostMethod
Type : org.mule.api.transport.DispatchException
Code : MULE_ERROR-42999
Payload : org.apache.commons.httpclient.methods.PostMethod#9fa8f
JavaDoc : http://www.mulesource.org/docs/site/current2/apidocs/org/mule/api/transport/DispatchException.html
Related
I have a consumer (RabbitListner) in RPC mode and I would like to know if it is possible to throw exception that can be treated by the publisher.
To make more clear my explication the case is as follow :
The publisher send a message in RPC mode
The consumer receive the message, check the validity of the message and if the message can not be take in count, because of missing parameters, then I would like to throw Exception. The exception can be a specific business exception or a particular AmqpException but I want that the publisher can handle this exception if it is not go in timeout.
I try with the AmqpRejectAndDontRequeueException, but my publisher do not receive the exception, but just a response which is empty.
Is it possible to be done or may be it is not a good practice to implement like that ?
EDIT 1 :
After the #GaryRussel response here is the resolution of my question:
For the RabbitListner I create an error handler :
#Configuration
public class RabbitErrorHandler implements RabbitListenerErrorHandler {
#Override public Object handleError(Message message, org.springframework.messaging.Message<?> message1, ListenerExecutionFailedException e) {
throw e;
}
}
Define the bean into a configuration file :
#Configuration
public class RabbitConfig extends RabbitConfiguration {
#Bean
public RabbitTemplate getRabbitTemplate() {
Message.addWhiteListPatterns(RabbitConstants.CLASSES_TO_SEND_OVER_RABBITMQ);
return new RabbitTemplate(this.connectionFactory());
}
/**
* Define the RabbitErrorHandle
* #return Initialize RabbitErrorHandle bean
*/
#Bean
public RabbitErrorHandler rabbitErrorHandler() {
return new RabbitErrorHandler();
}
}
Create the #RabbitListner with parameters where rabbitErrorHandler is the bean that I defined previously :
#Override
#RabbitListener(queues = "${rabbit.queue}"
, errorHandler = "rabbitErrorHandler"
, returnExceptions = "true")
public ReturnObject receiveMessage(Message message) {
For the RabbitTemplate I set this attribute :
rabbitTemplate.setMessageConverter(new RemoteInvocationAwareMessageConverterAdapter());
When the messsage threated by the consumer, but it sent an error, I obtain a RemoteInvocationResult which contains the original exception into e.getCause().getCause().
See the returnExceptions property on #RabbitListener (since 2.0). Docs here.
The returnExceptions attribute, when true will cause exceptions to be returned to the sender. The exception is wrapped in a RemoteInvocationResult object.
On the sender side, there is an available RemoteInvocationAwareMessageConverterAdapter which, if configured into the RabbitTemplate, will re-throw the server-side exception, wrapped in an AmqpRemoteException. The stack trace of the server exception will be synthesized by merging the server and client stack traces.
Important
This mechanism will generally only work with the default SimpleMessageConverter, which uses Java serialization; exceptions are generally not "Jackson-friendly" so can’t be serialized to JSON. If you are using JSON, consider using an errorHandler to return some other Jackson-friendly Error object when an exception is thrown.
What worked for me was :
On "serving" side :
Service
#RabbitListener(id = "test1", containerFactory ="BEAN CONTAINER FACTORY",
queues = "TEST QUEUE", returnExceptions = "true")
DataList getData() {
// this exception will be transformed by rabbit error handler to a RemoteInvocationResult
throw new IllegalStateException("mon expecion");
//return dataHelper.loadAllData();
}
On "requesting" side :
Service
public void fetchData() throws AmqpRemoteException {
var response = (DataList) amqpTemplate.convertSendAndReceive("TEST EXCHANGE", "ROUTING NAME", new Object());
Optional.ofNullable(response)
.ifPresentOrElse(this::setDataContent, this::handleNoData);
}
Config
#Bean
AmqpTemplate amqpTemplate(ConnectionFactory connectionFactory, MessageConverter messageConverter) {
var rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(messageConverter);
return rabbitTemplate;
}
#Bean
MessageConverter jsonMessageConverter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.registerModule(new JavaTimeModule());
var jsonConverter = new Jackson2JsonMessageConverter(objectMapper);
DefaultClassMapper classMapper = new DefaultClassMapper();
Map<String, Class<?>> idClassMapping = Map.of(
DataList.class.getName(), DataList.class,
RemoteInvocationResult.class.getName(), RemoteInvocationResult.class
);
classMapper.setIdClassMapping(idClassMapping);
jsonConverter.setClassMapper(classMapper);
// json converter with returned exception awareness
// this will transform RemoteInvocationResult into a AmqpRemoteException
return new RemoteInvocationAwareMessageConverterAdapter(jsonConverter);
}
You have to return a message as an error, which the consuming application can choose to treat as an exception. However, I don't think normal exception handling flows apply with messaging. Your publishing application (the consumer of the RPC service) needs to know what can go wrong and be programmed to deal with those possibilities.
I get user input (calorie) and want to insert it in Google Fit but the insertion does not work.
private DataSet insertNutritionData(){
Calendar cal = Calendar.getInstance();
Date now = new Date();
cal.setTime(now);
long endTime = cal.getTimeInMillis();
DataSource nutritionSource = new DataSource.Builder()
.setAppPackageName(getApplicationContext().getPackageName())
.setDataType(DataType.TYPE_NUTRITION)
.setType(DataSource.TYPE_RAW)
.build();
DataSet dataSet = DataSet.create(nutritionSource);
DataPoint dataPoint = DataPoint.create(nutritionSource);
dataPoint.setTimestamp(endTime, TimeUnit.MILLISECONDS);
dataPoint.getValue(Field.FIELD_NUTRIENTS).setKeyValue(Field.NUTRIENT_CALORIES,calorie);
dataSet.add(dataPoint);
return dataSet;
}
The insertion is done in AsyncTask :
private class InsertAndVerifyNutritionTask extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... params) {
DataSet dataSet = insertNutritionData();
Log.i(TAG, "Inserting the dataset in the History API");
com.google.android.gms.common.api.Status insertStatus =
Fitness.HistoryApi.insertData(mClient, dataSet)
.await(1, TimeUnit.MINUTES);
if (!insertStatus.isSuccess()) {
Log.i(TAG, "There was a problem inserting the dataset.");
return null;
}
Log.i(TAG, "Data insert was successful!");
return null;
}
}
Unfortunately, the insertion is not done and I don't know why. There is no sample to explain how can we use TYPE_NUTRIENTS...
Thanks a lot !
[UPDATE]
I found this error :
Couldn't connect to Google API client: ConnectionResult{statusCode=API_UNAVAILABLE, resolution=null}
However, I build my client like this :
mClient = new GoogleApiClient.Builder(this)
.addApi(Fitness.RECORDING_API)
.addApi(Fitness.SENSORS_API)
.addApi(Fitness.HISTORY_API)
.addScope(new Scope(Scopes.FITNESS_ACTIVITY_READ_WRITE))
.addScope(new Scope(Scopes.FITNESS_NUTRITION_READ_WRITE))
.addConnectionCallbacks(
new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(Bundle bundle) {
Log.i(TAG, "Google Fit connected.");
mTryingToConnect = false;
Log.d(TAG, "Notifying the UI that we're connected.");
notifyUiFitConnected();
}
#Override
public void onConnectionSuspended(int i) {
// If your connection to the sensor gets lost at some point,
// you'll be able to determine the reason and react to it here.
mTryingToConnect = false;
if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_NETWORK_LOST) {
Log.i(TAG, "Connection lost. Cause: Network Lost.");
} else if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_SERVICE_DISCONNECTED) {
Log.i(TAG, "Connection lost. Reason: Service Disconnected");
}
}
}
)
.addOnConnectionFailedListener(
new GoogleApiClient.OnConnectionFailedListener() {
// Called whenever the API client fails to connect.
#Override
public void onConnectionFailed(ConnectionResult result) {
//Toast.makeText(getActivity(),"connection failed 1",Toast.LENGTH_SHORT).show();
mTryingToConnect = false;
notifyUiFailedConnection(result);
}
}
)
.build();
}
Moreover, I don't understand why I cannot connect to fit whereas it worked perfectly...
Updated with the manifest :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.webear.mysilhouette">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application
android:allowBackup="true"
android:label="mySilhouette"
android:icon="#drawable/ic_launcher"
>
<meta-data android:name="com.google.android.gms.version"
android:value="#integer/google_play_services_version" />
<service
android:enabled="true"
android:name="com.example.webear.mysilhouette.GoogleApiIntentService"/>
(...)
</application>
</manifest>
Kamel
The statusCode says API_UNAVAILABLE. It means:
One of the API components you attempted to connect to is not
available. The API will not work on this device, and updating Google
Play services will not likely solve the problem. Using the API on the
device should be avoided.
Maybe try another device? Or Play Services are misconfigured.
I am developing a REST service using SpringMVC, where I have #RequestMapping at class and method level.
This application is currently configured to return error-page jsp configured in web.xml.
<error-page>
<error-code>404</error-code>
<location>/resourceNotFound</location>
</error-page>
I however want to return custom JSON instead of this error page.
I am able to handle exception and return json for other exceptions, by writing this in controller, but not sure how and where to write the logic to return JSON when the url does not exist at all.
#ExceptionHandler(TypeMismatchException.class)
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ResponseBody
public ResponseEntity<String> handleTypeMismatchException(HttpServletRequest req, TypeMismatchException ex) {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json; charset=utf-8");
Locale locale = LocaleContextHolder.getLocale();
String errorMessage = messageSource.getMessage("error.patient.bad.request", null, locale);
errorMessage += ex.getValue();
String errorURL = req.getRequestURL().toString();
ErrorInfo errorInfo = new ErrorInfo(errorURL, errorMessage);
return new ResponseEntity<String>(errorInfo.toJson(), headers, HttpStatus.BAD_REQUEST);
}
I tried #ControllerAdvice, it works for other exception scenarios, but not when mapping is not avaialble,
#ControllerAdvice
public class RestExceptionProcessor {
#Autowired
private MessageSource messageSource;
#ExceptionHandler(HttpRequestMethodNotSupportedException.class)
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ResponseBody
public ResponseEntity<String> requestMethodNotSupported(HttpServletRequest req, HttpRequestMethodNotSupportedException ex) {
Locale locale = LocaleContextHolder.getLocale();
String errorMessage = messageSource.getMessage("error.patient.bad.id", null, locale);
String errorURL = req.getRequestURL().toString();
ErrorInfo errorInfo = new ErrorInfo(errorURL, errorMessage);
return new ResponseEntity<String>(errorInfo.toJson(), HttpStatus.BAD_REQUEST);
}
#ExceptionHandler(NoSuchRequestHandlingMethodException.class)
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ResponseBody
public ResponseEntity<String> requestHandlingMethodNotSupported(HttpServletRequest req, NoSuchRequestHandlingMethodException ex) {
Locale locale = LocaleContextHolder.getLocale();
String errorMessage = messageSource.getMessage("error.patient.bad.id", null, locale);
String errorURL = req.getRequestURL().toString();
ErrorInfo errorInfo = new ErrorInfo(errorURL, errorMessage);
return new ResponseEntity<String>(errorInfo.toJson(), HttpStatus.BAD_REQUEST);
}
}
After digging around DispatcherServlet and HttpServletBean.init() in SpringFramework I see that its possible in Spring 4.
org.springframework.web.servlet.DispatcherServlet
/** Throw a NoHandlerFoundException if no Handler was found to process this request? **/
private boolean throwExceptionIfNoHandlerFound = false;
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + requestUri +
"] in DispatcherServlet with name '" + getServletName() + "'");
}
if(throwExceptionIfNoHandlerFound) {
ServletServerHttpRequest req = new ServletServerHttpRequest(request);
throw new NoHandlerFoundException(req.getMethod().name(),
req.getServletRequest().getRequestURI(),req.getHeaders());
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
throwExceptionIfNoHandlerFound is false by default and we should enable that in web.xml
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
And then you can catch it in a class annotated with #ControllerAdvice using this method.
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ResponseBody
public ResponseEntity<String> requestHandlingNoHandlerFound(HttpServletRequest req, NoHandlerFoundException ex) {
Locale locale = LocaleContextHolder.getLocale();
String errorMessage = messageSource.getMessage("error.bad.url", null, locale);
String errorURL = req.getRequestURL().toString();
ErrorInfo errorInfo = new ErrorInfo(errorURL, errorMessage);
return new ResponseEntity<String>(errorInfo.toJson(), HttpStatus.BAD_REQUEST);
}
Which allows me to return JSON response for bad URLs for which no mapping exist, instead of redirecting to a JSP page :)
{"message":"URL does not exist","url":"http://localhost:8080/service/patientssd"}
If you are using Spring Boot, set BOTH of these two properties:
spring.resources.add-mappings=false
spring.mvc.throw-exception-if-no-handler-found=true
Now your #ControllerAdvice annotated class can handle the "NoHandlerFoundException", as below.
#ControllerAdvice
#RequestMapping(produces = "application/json")
#ResponseBody
public class RestControllerAdvice {
#ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<Map<String, Object>> unhandledPath(final NoHandlerFoundException e) {
Map<String, Object> errorInfo = new LinkedHashMap<>();
errorInfo.put("timestamp", new Date());
errorInfo.put("httpCode", HttpStatus.NOT_FOUND.value());
errorInfo.put("httpStatus", HttpStatus.NOT_FOUND.getReasonPhrase());
errorInfo.put("errorMessage", e.getMessage());
return new ResponseEntity<Map<String, Object>>(errorInfo, HttpStatus.NOT_FOUND);
}
}
note it is not sufficient to only specify this property:
spring.mvc.throw-exception-if-no-handler-found=true
, as by default Spring maps unknown urls to /**, so there really never is "no handler found".
To disable the unknown url mapping to /**, you need
spring.resources.add-mappings=false ,
which is why the two properties together produce the desired behavior.
If you're using spring 3.2 or later you can use a controller advice (#ControllerAdvice) to deal with, amongst other things, mapping errors (404's). You can find documentation here. Take a look at section 17.11. You can use this, for example, to provide more detailed logging on why your request bindings aren't being matched for specific urls, or to simply return a more specific response than a generic 404.
you can return json in the location below,that /handle/404.
<error-page>
<error-code>404</error-code>
<location>/handle/404</location>
</error-page>
after you config this in web.xml,a 404 error will redirect to /handle/404,and you can create a controller with this mapping and return a json result. for example.
#RestController
#RequestMapping(value = "handle")
public class HttpErrorController {
#RequestMapping(value = "404")
public String handle404() {
return "404 error";
}
}
I've run into a weird problem.
I use Jersey 2.2 to do my restful web services (with jersey-media-moxy).
If I produce my output as application/xml, it runs fine.
But if produce my output as application/json, I get "Internal Server Error 500".
My dependency settings in ivy.xml are:
<dependency org="org.glassfish.jersey.core" name="jersey-server" rev="2.2"/>
<dependency org="org.glassfish.jersey.containers" name="jersey-container-servlet-core" rev="2.2"/>
<dependency org="org.glassfish.jersey.media" name="jersey-media-moxy" rev="2.2"/>
My service class is:
#Path("/projects/{companykey: [0-9]*}")
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public class ProjectResource {
private static Logger logger = Logger.getLogger(ProjectResource.class);
private final Application app = Application.getInstance();
#GET
public List<ProjectBase> getProjectBases(
#PathParam("companykey") String companyKeyStr) {
...
}
#GET
#Path("/{projectkey: [0-9]*}")
public ProjectBase getProjectBase(
#PathParam("companykey") String companyKeyStr,
#PathParam("projectkey") String projectKeyStr) {
int companyKey = Integer.valueOf(companyKeyStr);
int projObjKey = Integer.valueOf(projectKeyStr);
logger.debug(MessageFormat.format("get project {1} of company {0}",
companyKey, projObjKey));
ProjectBase project = null;
try {
project = app.getProjectIF().getProjectBase(companyKey, projObjKey);
if (project == null) throw new WebApplicationException(404);
return project;
} catch (ServerException se) {
logger.warn("get project fails ! " + se);
throw new WebApplicationException(500);
}
}
...
}
//class end
If I ask for the xml output (visit http://biz.loc.net:8080/tm/rest/projects/100/104), I get:
<projectBase>
<_checkTopicAccess>false</_checkTopicAccess>
<_checkTaskAccess>false</_checkTaskAccess>
....
If I ask for the json output, I get:
HTTP Status 500 - Internal Server Error
type Status report
message Internal Server Error
description The server encountered an internal error (Internal Server Error) that prevented it from fulfilling this request.
I do not find any error messages in my app's log file or Tomcat's log file, so I have no
idea what is going on.
Does anyone know any possible reason for this problem? Really appreciate ...
Can you show the entity code? Are you missing an empty constructor?
Thanks for your help, the following code snippet is my entity clas:
#XmlRootElement
public class ProjectBase implements UdaEnabled, SdaEnabled, FormBean {
private int projObjKey;
private String projName;
//...
private Timestamp createdAt;
//...
//...
#XmlElement(name = "createdAt")
#XmlJavaTypeAdapter(TimestampAdapter.class)
public Timestamp getCreatedAt() {
return createdAt;
}
// non-args Constructor
public ProjectBase() {
init();
}
}
It does has an empty constructor, although these's a init() inside.
As I said, I think it is weird because producing xml is OK.
I am wanting to return a view from a Spring MVC Controller depending on logic. If an error occurs I want to return JSON, if not, an HTML view. This is like ASP.NET MVC ActionResult, where you can return any kind of view and it will render the result, and it won't depend on the content-type being sent in the request. I can't find any examples of this.
I achieved this with the following.
#RequestMapping(value="/users", method=RequestMethod.POST)
public Object index(#RequestBody SearchUsersViewModel model, HttpServletResponse response) {
model.setList(userService.getUsers(model));
if(true)
{
return new ModelAndView("controls/tables/users", "model", model);
}
else
{
return JsonView.Render(model, response);
}
}
JsonView.java
public class JsonView {
public static ModelAndView Render(Object model, HttpServletResponse response)
{
MappingJacksonHttpMessageConverter jsonConverter = new MappingJacksonHttpMessageConverter();
MediaType jsonMimeType = MediaType.APPLICATION_JSON;
try {
jsonConverter.write(model, jsonMimeType, new ServletServerHttpResponse(response));
} catch (HttpMessageNotWritableException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
JS Function that makes the call
config.request = $
.ajax({
url : url,
data : $.toJSON(def.data),
type : def.type,
dataType : def.dataType,
processData : true,
contentType : def.contentType,
success : function(data) {
try {
var json = data;
if (typeof data === "String" || typeof data == "string") {
json = (eval('(' + data + ')'));
}
if ('object' === typeof json) {
if (json.Validation && json.Validation.Errors.length > 0) {
$.each(json.Validation.Errors, function() {
// Error Code Check
});
// Error Callback
if (typeof (def.errorCallback) == 'function') {
def.errorCallback(json);
}
} else {
def.callback(data);
}
} else {
def.callback(data);
}
} catch (e) {
def.callback(data);
// Hide Loading
}
},
error : function(data) {
}
});
Just in case and you want to return Json on exception you can do the following:
#ExceptionHandler(Exception.class)
#ResponseBody
public void handleIOException(Exception exception,HttpServletRequest request, HttpServletResponse response) {
response.setContentType("application/json");
String json = "{\"Name\": 50}";
PrintWriter out= response.getWriter();
out.write(json);
}
I'm not sure that this is what you wanted to do, but just in case.... :)
Program your controller to return a different logical view name depending on a condition. For example:
#RequestMapping(value="/hello/{name}", method=RequestMethod.GET)
public ModelAndView hello(#PathVariable String name) {
String viewName = (name.length() > 1) ? "hello" : "error";
ModelAndView mav = new ModelAndView(viewName);
mav.addObject("name", name);
return mav;
}
Configure the view resolvers to resolve the view name "error" to the JSON view. Spring provides many ways to configure the view name to view mapping, including:
XmlViewResolver which reads bean definition XML files,
ResourceBundleViewResolver which reads properties files, and
BeanNameViewResolver which looks in the application context of the executing DispatcherServlet for a bean having the same name as the view name.
For example, to use BeanNameViewResolver:
<bean name="error" class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"/>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="1"/>
</bean>
There's nothing to prevent you from returning an actual View object directly from your controller method - it doesn't have to be a view name. So your controller can construct a View object using its own logic, and return that, with or without being wrapped in a ModelAndView object.
This is probably simpler than trying to persuade the ViewResolver framework from doing this for you, although that would work as well.
Perhaps you can look at ResolveBundleViewResolver, which allows you to mix views. The docs give some info on how to use this.
From the docs (example is to mix tiles and jstl, but should apply for others as well)...
context file
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename" value="views"/>
</bean>
views.properties
...
welcomeView.(class)=org.springframework.web.servlet.view.tiles2.TilesView
welcomeView.url=welcome (this is the name of a Tiles definition)
vetsView.(class)=org.springframework.web.servlet.view.tiles2.TilesView
vetsView.url=vetsView (again, this is the name of a Tiles definition)
findOwnersForm.(class)=org.springframework.web.servlet.view.JstlView
findOwnersForm.url=/WEB-INF/jsp/findOwners.jsp
...
To extend Chin Huang's answer, here is what works for me. No configuration required.
response.setStatus(500);
return new ModelAndView(new MappingJacksonJsonView(), model);
This will automatically convert the model into JSON and write to response.
Here model is of type Map<String,Object> and response is of type HttpServletResponse