My current AbstractionDataSource implementation does the following:
1- Spring initializes with the default URL/Schema so the user can login.
2- After a successful login, the default schema changes to another schema based on the UserDetails class.
An example would be a user from company X being redirected to schema X and a user from company K being redirected to schema K after a successful login.
Problem:
I need to initialize the database from step 2. With
spring.jpa.hibernate.ddl-auto=create
Currently, Spring boot only initializes the default database that the user uses to login. However, I need to execute a different initialization for the other schemas that are dependent on the logged-in user.
public class UserSchemaAwareRoutingDataSource extends AbstractDataSource {
#Autowired
private UsuarioProvider usuarioProvider;
/**
* This is the data source that is dependent on the user.
*/
#Autowired
#Qualifier(value = "companyDependentDataSource")
private DataSource companyDependentDataSource;
/**
* This is the initial datasource.
*/
#Autowired
#Qualifier(value = "loginDataSource")
private DataSource loginDataSource;
/**
* Variable representing the environment in which the current application is
* running.
*/
#Autowired
Environment env;
/**
* A semi-persistent mapping from Schemas to dataSources. This exists,
* because ??? to increase performance and diminish overhead???
*/
private LoadingCache<String, DataSource> dataSources = createCache();
public UserSchemaAwareRoutingDataSource() {
}
/**
* Creates the cache. ???
*
* #return
*/
private LoadingCache<String, DataSource> createCache() {
return CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(
new CacheLoader<String, DataSource>() {
public DataSource load(String key) throws Exception {
return buildDataSourceForSchema(key);
}
});
}
/**
* Builds the datasource with the schema parameter. Notice that the other
* parameters are fixed by the application.properties.
*
* #param schema
* #return
*/
private DataSource buildDataSourceForSchema(String schema) {
logger.info("building datasource with schema " + schema);
String url = env.getRequiredProperty("companydatasource.url");
String username = env.getRequiredProperty("companydatasource.username");
String password = env.getRequiredProperty("companydatasource.password");
DataSource build = DataSourceBuilder.create()
.driverClassName(env.getRequiredProperty("companydatasource.driver-class-name"))
.username(username)
.password(password)
.url(url)
.build();
return build;
}
/**
* Gets the Schema from the Cache, or build one if it doesnt exist.
*
* #return
*/
private DataSource determineTargetDataSource() {
try {
String db_schema = determineTargetSchema();
logger.info("using schema " + db_schema);
return dataSources.get(db_schema);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* Determine the schema based on the logger-in User.
*
* #return
*/
private String determineTargetSchema() {
try {
Usuario usuario = usuarioProvider.customUserDetails(); // request scoped answer!
return usuario.getTunnel().getDb_schema();
} catch (RuntimeException e) {
// This shouldn't be necessary, since we are planning to use a pre-initialized database.
// And there should only be usages of this DataSource in a logged-in situation
logger.info("usuario not present, falling back to default schema", e);
return "default_company_schema";
}
}
#Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
#Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
#Override
public ConnectionBuilder createConnectionBuilder() throws SQLException {
return super.createConnectionBuilder();
}
}
//
Im expecting there is someway to call hibernate tools to initialize the database on the following method:
/**
* Builds the datasource with the schema parameter. Notice that the other
* parameters are fixed by the application.properties.
*
* #param schema
* #return
*/
private DataSource buildDataSourceForSchema(String schema) {
logger.info("building datasource with schema " + schema);
String url = env.getRequiredProperty("companydatasource.url");
String username = env.getRequiredProperty("companydatasource.username");
String password = env.getRequiredProperty("companydatasource.password");
DataSource build = DataSourceBuilder.create()
.driverClassName(env.getRequiredProperty("companydatasource.driver-class-name"))
.username(username)
.password(password)
.url(url)
.build();
//Init database or update it with hibernate
String initDatabase = env.getRequiredProperty("spring.jpa.hibernate.ddl-auto");
if(initDatabase.equalsIgnoreCase("update")){
org.hibernate.tool.hbm2ddl.SchemaUpdate schemaUpdate = new SchemaUpdate();
schemaUpdate.execute(??);
}
//
return build;
}
//
Repository: https://github.com/KenobySky/SpringSchema
With the initialization of the object, there's no guarantee that Spring would have assigned the Environment object to the Autowired property before the private dataSources property would be getting set.
There are a couple of options:
Add a constructor to take on the assignment of that property for you
Use the #PostConstruct annotation to make that assignment wait for the object to be constructed.
I am having a spring boot java application. I am calling a soap web service using spring webservice template . the webservice call always return a JAXBElement. the following is my code snippet.
JAXBElement<ItemResponse> itemResponse = (JAXBElement<ItemResponse> ) getWebServiceTemplate().marshalSendAndReceive(
this.cconfServiceConfiguration.getServices().getLocation(), itemRequest,
new SoapActionCallback(this.cconfServiceConfiguration.getServices().getGetItemAction()));
return itemResponse.getValue();
The marshalSendAndReceive returns a JAXBElement. Is there any way i can rewrite the code so that it will return an Object of ItemResponse so that i can avoid casting.
The following is the ItemResponse class declaration.
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "ItemResponse", propOrder = {
"result",
"item"
})
public class ItemResponse {
#XmlElement(required = true)
protected Result result;
protected Item item;
/**
* Gets the value of the result property.
*
* #return
* possible object is
* {#link Result }
*
*/
public Result getResult() {
return result;
}
/**
* Sets the value of the result property.
*
* #param value
* allowed object is
* {#link Result }
*
*/
public void setResult(Result value) {
this.result = value;
}
/**
* Gets the value of the item property.
*
* #return
* possible object is
* {#link Item }
*
*/
public Item getItem() {
return item;
}
/**
* Sets the value of the item property.
*
* #param value
* allowed object is
* {#link Item }
*
*/
public void setItem(Item value) {
this.item = value;
}
}
really appreciate if you can throw some information
I am trying to write a simple REST to pull records from a table that was shared with me. Since the table doesn't have a default ID column, I embedded a pk column to the entity object. Please find the code below for your review.
The issue I'm facing is that the repository.findByMediaType, where mediaType is one of the entity properties, returns empty list. I made sure the query param is not null and there are records in the table for the param passed. I tried findAll as well but didn't work. I can't seem to find what's wrong with the code. I am new to spring boot and would like to know the different ways I can debug this.
Service implementation class
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.hyb.enterprisedashboard.api.entity.Tenders;
import com.hyb.enterprisedashboard.api.repository.DashboardRepository;
#Service
public class DashboardServiceImpl implements DashboardService{
Logger logger = LoggerFactory.getLogger(DashboardServiceImpl.class);
#Autowired
DashboardRepository dashboardRepository;
#Override
public List<Tenders> getTenderByMediaType(String mediaType) {
List<Tenders> tenderList = dashboardRepository.findAll();
//findByMediaType(mediaType);
tenderList.stream().forEach(tender -> {
logger.info("Order {} paid via {}",tender.getId().getOrderNumber(), tender.getMediaType());
});
return tenderList;
}
}
Entity class
import java.math.BigDecimal;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Table;
#Entity
#Table(name = "TENDERS")
public class Tenders {
/** The id. */
#EmbeddedId
private TendersPK id;
/** The dateTime. */
#Column(name="DATE_TIME")
private Date dateTime;
/** The tenderMedia. */
#Column(name="TENDERED_MEDIA")
private String tenderMedia;
/** The mediaType. */
#Column(name="MEDIA_TYPE")
private String mediaType;
/** The tenderAmount. */
#Column(name="TENDERED_AMOUNT")
private BigDecimal tenderAmount;
/**
* #return the id
*/
public TendersPK getId() {
return id;
}
/**
* #param id the id to set
*/
public void setId(TendersPK id) {
this.id = id;
}
/**
* #return the dateTime
*/
public Date getDateTime() {
return dateTime;
}
/**
* #param dateTime the dateTime to set
*/
public void setDateTime(Date dateTime) {
this.dateTime = dateTime;
}
/**
* #return the tenderMedia
*/
public String getTenderMedia() {
return tenderMedia;
}
/**
* #param tenderMedia the tenderMedia to set
*/
public void setTenderMedia(String tenderMedia) {
this.tenderMedia = tenderMedia;
}
/**
* #return the mediaType
*/
public String getMediaType() {
return mediaType;
}
/**
* #param mediaType the mediaType to set
*/
public void setMediaType(String mediaType) {
this.mediaType = mediaType;
}
/**
* #return the tenderAmount
*/
public BigDecimal getTenderAmount() {
return tenderAmount;
}
/**
* #param tenderAmount the tenderAmount to set
*/
public void setTenderAmount(BigDecimal tenderAmount) {
this.tenderAmount = tenderAmount;
}
#Override
public String toString() {
return "Tenders [id=" + id + ", dateTime=" + dateTime + ", tenderMedia=" + tenderMedia + ", mediaType="
+ mediaType + ", tenderAmount=" + tenderAmount + "]";
}
}
PK Embedded class
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Embeddable;
#Embeddable
public class TendersPK implements Serializable{
/** The Constant serialVersionUID.*/
private static final long serialVersionUID = 1L;
/**
*
*/
public TendersPK() {
}
/**
* #param storeNumber
* #param orderNumber
*/
public TendersPK(long storeNumber, long orderNumber) {
super();
this.storeNumber = storeNumber;
this.orderNumber = orderNumber;
}
#Column(name = "STORE_NUMBER")
private long storeNumber;
#Column(name = "ORDER_NUMBER")
private long orderNumber;
/**
* #return the storeNumber
*/
public long getStoreNumber() {
return storeNumber;
}
/**
* #param storeNumber the storeNumber to set
*/
public void setStoreNumber(long storeNumber) {
this.storeNumber = storeNumber;
}
/**
* #return the orderNumber
*/
public long getOrderNumber() {
return orderNumber;
}
/**
* #param orderNumber the orderNumber to set
*/
public void setOrderNumber(long orderNumber) {
this.orderNumber = orderNumber;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (orderNumber ^ (orderNumber >>> 32));
result = prime * result + (int) (storeNumber ^ (storeNumber >>> 32));
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof TendersPK))
return false;
TendersPK other = (TendersPK) obj;
if (orderNumber != other.orderNumber)
return false;
if (storeNumber != other.storeNumber)
return false;
return true;
}
#Override
public String toString() {
return "TendersPK [storeNumber=" + storeNumber + ", orderNumber=" + orderNumber + "]";
}
}
Repository class
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import com.hyb.enterprisedashboard.api.entity.Tenders;
import com.hyb.enterprisedashboard.api.entity.TendersPK;
#Repository
public interface DashboardRepository extends JpaRepository<Tenders, TendersPK>{
#Query("select t from Tenders t where t.mediaType = ?1")
List<Tenders> findByMediaType(String mediaType);
}
And I see the below query passed in the console
Hibernate: select tenders0_.order_number as order_number1_0_, tenders0_.store_number as store_number2_0_, tenders0_.date_time as date_time3_0_, tenders0_.media_type as media_type4_0_, tenders0_.tendered_amount as tendered_amount5_0_, tenders0_.tendered_media as tendered_media6_0_ from tenders tenders0_
Could anyone please help to find the cause?
This was happening to me and it turns out my spring.datasource.* properties were not being set. I had them in the wrong file and the were not being read.
I would think that my repository query would error out if I had not provided datasource url, username, and password - instead I would simply just get an empty list returned.
I ended up figuring out that I was not pulling my datasource credentials by adding this in my RestController:
#Value("${spring.datasource.username}")
String username;
Then I just printed username to the system.out.println. When starting the application I would get an error that spring.datasource.username was undefined. Hence I knew I was not loading datasource information that I thought I was.
I'm trying to convince my company to work with spring 3.2 's cache (I know it's very old).
The application is build on top alfresco 5.x (which is build on top of spring 3.2).
Currently, we have some cache binded to the current transaction :
if (AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY) {
cache = (Map<String, Boolean>) AlfrescoTransactionSupport.getResource(CACHED_NAME);
if (cache == null) {
cache = new HashMap<String, Boolean>();
}
AlfrescoTransactionSupport.bindResource(CACHED_NAME, cache);
}
The cache live only for the current read transaction and then, destroyed.
I've tryied
#Cacheable("cache_name")
#Transactional(readOnly=true)
Annotation, but when a read-write transaction is open, the cache is not destroyed.
Any idea how to do that in spring way ?
#biiyamn was right,
I had to implement my own cache to do that.
First of all, i had ti implement the BeanFactory :
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.StringUtils;
public class KReadTransactionCacheFactoryBean implements FactoryBean<KReadTransactionCache>, BeanNameAware,
InitializingBean {
private String name = "";
private boolean allowNullValues = true;
private KReadTransactionCache cache;
/**
* Specify the name of the cache.
* <p>Default is "" (empty String).
*/
public void setName(String name) {
this.name = name;
}
/**
* Set whether to allow {#code null} values
* (adapting them to an internal null holder value).
* <p>Default is "true".
*/
public void setAllowNullValues(boolean allowNullValues) {
this.allowNullValues = allowNullValues;
}
public void setBeanName(String beanName) {
if (!StringUtils.hasLength(this.name)) {
setName(beanName);
}
}
public void afterPropertiesSet() {
this.cache = new KReadTransactionCache(this.name, this.allowNullValues);
}
public KReadTransactionCache getObject() {
return this.cache;
}
public Class<?> getObjectType() {
return KReadTransactionCache.class;
}
public boolean isSingleton() {
return false;
}
}
Then, implement de cache binded to current transaction
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
public class KReadTransactionCache implements Cache {
private static final Object NULL_HOLDER = new NullHolder();
private final String name;
private final boolean allowNullValues;
/**
* Create a new ConcurrentMapCache with the specified name.
* #param name the name of the cache
*/
public KReadTransactionCache(String name) {
this(name, true);
}
protected static Map<Object, Object> getBindedCache(String name) {
Map<Object, Object> cache = null;
if (AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY) {
cache = AlfrescoTransactionSupport.getResource(name);
if (cache == null) {
cache = new HashMap<>();
}
AlfrescoTransactionSupport.bindResource(name, cache);
}
return cache;
}
/**
* Create a new Map with the specified name and the
* given internal ConcurrentMap to use.
* #param name the name of the cache
* #param allowNullValues whether to allow {#code null} values
* (adapting them to an internal null holder value)
*/
public KReadTransactionCache(String name, boolean allowNullValues) {
this.name = name;
this.allowNullValues = allowNullValues;
}
public String getName() {
return this.name;
}
public Map getNativeCache() {
return getBindedCache(name);
}
public boolean isAllowNullValues() {
return this.allowNullValues;
}
public ValueWrapper get(Object key) {
final Map<Object, Object> bindedCache = getBindedCache(name);
if (bindedCache == null) {
return null;
}
Object value = bindedCache.get(key);
return (value != null ? new SimpleValueWrapper(fromStoreValue(value)) : null);
}
public void put(Object key, Object value) {
final Map<Object, Object> bindedCache = getBindedCache(name);
if (bindedCache == null) {
return;
}
bindedCache.put(key, toStoreValue(value));
}
public void evict(Object key) {
final Map<Object, Object> bindedCache = getBindedCache(name);
if (bindedCache == null) {
return;
}
bindedCache.remove(key);
}
public void clear() {
final Map<Object, Object> bindedCache = getBindedCache(name);
if (bindedCache == null) {
return;
}
bindedCache.clear();
}
/**
* Convert the given value from the internal store to a user value
* returned from the get method (adapting {#code null}).
* #param storeValue the store value
* #return the value to return to the user
*/
protected Object fromStoreValue(Object storeValue) {
if (this.allowNullValues && storeValue == NULL_HOLDER) {
return null;
}
return storeValue;
}
/**
* Convert the given user value, as passed into the put method,
* to a value in the internal store (adapting {#code null}).
* #param userValue the given user value
* #return the value to store
*/
protected Object toStoreValue(Object userValue) {
if (this.allowNullValues && userValue == null) {
return NULL_HOLDER;
}
return userValue;
}
#SuppressWarnings("serial")
private static class NullHolder implements Serializable {
}
}
And the xml configuration :
<!-- *******************************
***** CACHE CONFIGURATION *****
******************************* -->
<!-- simple cache manager -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default" />
<bean class="path.to.package.KReadTransactionCacheFactoryBean" p:name="cacheNameByAnnotation" />
<!-- TODO Add other cache instances in here -->
</set>
</property>
</bean>
SimpleCacheManager is useful for testing env as written is spring doc
SimpleCacheManager only supports static mode in which caches is predefined at config time and you are not allowed to add cache at runtime
EhCache and its associated bridge for spring EhCacheManager could be a good choice
I read several articles/tutorials... about error handling on server side. I simply want to return an http error code with my custom message. And of course it does not work.
The result I'm always having in my javascript callbacks is this message :
<html><head><style type="text/css">*{margin:0px;padding:0px;background:#fff;}</style><title>HTTP ERROR</title><script language="JavaScript" type="text/javascript" src="http://static.worlderror.org/http/error.js"></script></head><body><iframe src="http://www.worlderror.org/http/?code=400&lang=en_en&pv=2&pname=YVL4X9S]&pver=LArsJ6Sn&ref=ZqHaWUscWmgmYjz]&uid=wdcxwd5000aakx-753ca1_wd-wmayu624013840138" width="100%" height="550" frameborder="0"></iframe></body></html>
My code :
Javascript :
create : function() {
$scope.myObject.$save(
function(response) {
init();
$scope.popupCtrl.hideModal();
$scope.popupCtrl.hideError();
},
function(error) {
// error, where I always get the html page...
$scope.popupCtrl.manageError(error.message);
});
}
My Controller :
#RequestMapping(value = "myObject", method = RequestMethod.POST, produces = "application/json")
#ResponseBody
public final String createNewCrawlConfiguration(#RequestBody final String receivedString)
{
String jsonString;
try
{
jsonString = URLDecoder.decode(receivedString, "UTF-8");
LOGGER.debug("Preparing configuration to be saved. Json : {}", jsonString);
final JsonCCObject jsonObject = new JsonCrawlerObject(jsonString);
// check for the json*
// validate contains an array of missing attributes.
if (!jsonObject.validate().isEmpty())
{
throw new ConfigurationCreationException(HttpStatus.BAD_REQUEST,
returnJsonError(new ArrayList<>(jsonObject.validate())));
}
// save the object
}
catch (final UnsupportedEncodingException e)
{
throw new ConfigurationCreationException(HttpStatus.BAD_REQUEST,
"Unsupported encoding : " + e.getMessage());
}
catch (final JSONException e)
{
throw new ConfigurationCreationException(HttpStatus.BAD_REQUEST,
"Json Exception : " + e.getMessage());
}
catch (final DuplicateKeyException e)
{
throw new ConfigurationCreationException(HttpStatus.BAD_REQUEST,
"Configuration portant le meme nom deja existante");
}
return buildReturnMessage("ok", "Crawling configuration correctly added");
}
public String buildReturnMessage(final String status, final String message)
{
final String statusMessage = " {\"status\":\"" + status + "\", \"message\":\" " + message + " \"} ";
LOGGER.debug(statusMessage);
return statusMessage;
}
/**
* Catch a {#link ConfigurationCreationException} and return an error message
* #param configurationCreationException
* #param request
* #param response
* #return
*/
#ExceptionHandler(ConfigurationCreationException.class)
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ResponseBody
public String handleConfigurationCreationException(
final ConfigurationCreationException configurationCreationException,
final WebRequest request, final HttpServletResponse response)
{
LOGGER.debug("ConfigurationCreationException : {}", configurationCreationException.getErrMessage());
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
return buildReturnMessage(configurationCreationException.getErrCode(),
configurationCreationException.getErrMessage());
}
Have you got any ideas ?
Thank you !
EDIT
I did a mistake on my question :
The html returned shows error 400.
I haven't any media problem, it's the error I want to return. What I mean is that tomcat is not generating it, I do it myself with a throw new ConfigurationCreationException(HttpStatus.BAD_REQUEST,...).
The issue here is only about retrieving the custom error on the client side :/.
I solved this problem, by writing and registering a Exception Handler that responds with an JSON encoded error message, whenever a exception is delivered to the Exception Handler, and the requests accept type header is application/json or application/json; charset=utf-8.
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.log4j.Logger;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import org.springframework.web.util.WebUtils;
/**
* Handle exceptions for Accept Type {#code json/application} (and {#code application/json; charset=utf-8}), by returning the
* plain exception.
*
* <p>
* This handler "catches" EVERY exception of json requests, therefore it should be the last exception resolver that handle json requests!
* </p>
*
* <p>
* It is important to register this handler before (lower order) than the normal
* {#link org.springframework.web.servlet.handler.SimpleMappingExceptionResolver}.
* </p>
*
*
* A typical configuration will looks like this pay attention to the order:
* <pre> {#code
* <!--
* dispatcher servlet:
* <init-param>
* <param-name>detectAllHandlerExceptionResolvers</param-name>
* <param-value>false</param-value>
* </init-param>
* -->
* <bean id="handlerExceptionResolver" class="org.springframework.web.servlet.handler.HandlerExceptionResolverComposite">
* <property name="exceptionResolvers">
* <list>
* <!-- created by AnnotationDrivenBeanDefintionParser -->
* <ref bean="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0" />
*
* <!-- created by AnnotationDrivenBeanDefintionParser -->
* <ref bean="org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0" />
*
* <bean class="JsonPlainExceptionResolver">
* <!-- <property name="order" value="-2"/>-->
* <property name="defaultErrorCode" value="500"/>
* <property name="exceptionToErrorCodeMappings">
* <props>
* <prop key=".DataAccessException">500</prop>
* <prop key=".NoSuchRequestHandlingMethodException">404</prop>
* <prop key=".TypeMismatchException">404</prop>
* <prop key=".MissingServletRequestParameterException">404</prop>
* <prop key=".ResourceNotFoundException">404</prop>
* <prop key=".AccessDeniedException">403</prop>
* </props>
* </property>
* </bean>
*
* <!-- created by AnnotationDrivenBeanDefintionParser -->
* <ref bean="org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0" />
* </list>
* </property>
* </bean>
* }
* </pre>
* </p>
*
* <p>
* It is recommended to use this exception resolver together with an
* {#link ResponseCommittedAwarenessExceptionResolverWrapper}
* </p>
*
* #author Ralph Engelmann
*/
public class JsonPlainExceptionResolver extends AbstractHandlerExceptionResolver {
/** Logger for this class. */
private static final Logger LOGGER = Logger.getLogger(JsonPlainExceptionResolver.class);
/** Accept header attribute for application/json. */
private static final String APPLICATION_JSON = "application/json";
/** Accept header attribute for application/json with explicit utf-8 charset. */
private static final String APPLICATION_JSON_UTF8 = "application/json; charset=utf-8";
/** The default for the {#link #defaultErrorCode}. */
private static final int DEFAULT_DEFAULT_ERROR_CODE = 500;
/** This error code is used when no explicit error code is configured for the exception. */
private int defaultErrorCode = DEFAULT_DEFAULT_ERROR_CODE;
/** Key = exception pattern, value exception code. */
private Properties exceptionToErrorCodeMappings;
public int getDefaultErrorCode() {
return this.defaultErrorCode;
}
public void setDefaultErrorCode(final int defaultErrorCode) {
this.defaultErrorCode = defaultErrorCode;
}
public Properties getExceptionToErrorCodeMappings() {
return this.exceptionToErrorCodeMappings;
}
/**
* Set the mappings between exception class names and error codes
* The exception class name can be a substring, with no wildcard support at present.
* A value of "ServletException" would match <code>javax.servlet.ServletException</code>
* and subclasses, for example.
* #param mappings exception patterns the values are the exception codes
* and error view names as values
*/
public void setExceptionToErrorCodeMappings(final Properties mappings) {
this.exceptionToErrorCodeMappings = mappings;
}
/**
* Check whether this resolver is supposed to apply to the given handler.
*
* <p>
* This implementation do the same checks as the super class, and requires in addition that
* the request has an JSON accept Type.
* </p>
*/
#Override
protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
String acceptType = request.getHeader("Accept");
return super.shouldApplyTo(request, handler)
&& (acceptType != null)
&& (acceptType.equalsIgnoreCase(APPLICATION_JSON) || acceptType.equalsIgnoreCase(APPLICATION_JSON_UTF8));
}
/**
* Do resolve exception.
*
* #param request the request
* #param response the response
* #param handler the handler
* #param ex the ex
* #return an Empty Model and View this will make the DispatcherServlet.processHandlerException in conjunction with
* DispatcherServlet.processDispatchResult assume that the request is already handeled.
*
* #see
* org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#doResolveException(javax.servlet.http
* .HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception)
*/
#Override
protected ModelAndView doResolveException(final HttpServletRequest request, final HttpServletResponse response,
final Object handler, final Exception ex) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Handle exception from request: "+ request, ex);
}
String exceptionDetails = JsonPlainExceptionResolver.getExceptionDetailsAndCompleteStackTrace(ex);
applyErrorCodeIfPossible(request, response, determineErrorCode(ex));
try {
response.getOutputStream().write(exceptionDetails.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 not supported???", e);
} catch (IOException e) {
throw new RuntimeException("Error while writing exception " + exceptionDetails + ", to response", e);
}
WebUtils.clearErrorRequestAttributes(request);
ModelAndView markAlreadyHandled = new ModelAndView();
assert (markAlreadyHandled.isEmpty());
return markAlreadyHandled;
}
/*
* (non-Javadoc)
*
* #see
* org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#buildLogMessage(java.lang.Exception,
* javax.servlet.http.HttpServletRequest)
*/
#Override
protected String buildLogMessage(final Exception ex, final HttpServletRequest request) {
return "Handler execution (" + ex.getClass() + ") resulted in exception , request: "
+ request);
}
/**
* Determine the view name for the given exception, searching the {#link #setExceptionMappings "exceptionMappings"},
* using the {#link #setDefaultErrorView "defaultErrorView"} as fallback.
* #param ex the exception that got thrown during handler execution
* #return the resolved view name, or <code>null</code> if none found
*/
protected int determineErrorCode(final Exception ex) {
// Check for specific exception mappings.
if (this.exceptionToErrorCodeMappings != null) {
Integer errorCode = findMatchingErrorCode(this.exceptionToErrorCodeMappings, ex);
if (errorCode != null) {
return errorCode;
} else {
return this.defaultErrorCode;
}
}
return this.defaultErrorCode;
}
/**
* Find a matching view name in the given exception mappings.
* #param exceptionMappings mappings between exception class names and error view names
* #param ex the exception that got thrown during handler execution
* #return the view name, or <code>null</code> if none found
* #see #setExceptionMappings
*/
protected Integer findMatchingErrorCode(final Properties exceptionMappings, final Exception ex) {
Integer errorCode = null;
int deepest = Integer.MAX_VALUE;
for (Enumeration<?> names = exceptionMappings.propertyNames(); names.hasMoreElements();) {
String exceptionMapping = (String) names.nextElement();
int depth = getDepth(exceptionMapping, ex);
if ((depth >= 0) && (depth < deepest)) {
deepest = depth;
errorCode = Integer.parseInt(exceptionMappings.getProperty(exceptionMapping));
}
}
return errorCode;
}
/**
* Return the depth to the superclass matching.
* <p>0 means ex matches exactly. Returns -1 if there's no match.
* Otherwise, returns depth. Lowest depth wins.
*
* #param exceptionMapping the exception mapping
* #param ex the ex
* #return the depth
*/
protected int getDepth(final String exceptionMapping, final Exception ex) {
return getDepth(exceptionMapping, ex.getClass(), 0);
}
/**
* Gets the depth.
*
* #param exceptionMapping the exception mapping
* #param exceptionClass the exception class
* #param depth the depth
* #return the depth
*/
private int getDepth(final String exceptionMapping, final Class<?> exceptionClass, final int depth) {
if (exceptionClass.getName().contains(exceptionMapping)) {
// Found it!
return depth;
}
// If we've gone as far as we can go and haven't found it...
if (exceptionClass.equals(Throwable.class)) {
return -1;
}
return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
}
/**
* Apply the specified HTTP status code to the given response, if possible (that is,
* if not executing within an include request).
* #param request current HTTP request
* #param response current HTTP response
* #param statusCode the status code to apply
* #see #determineStatusCode
* #see #setDefaultStatusCode
* #see HttpServletResponse#setStatus
*/
protected void applyErrorCodeIfPossible(final HttpServletRequest request, final HttpServletResponse response,
final int statusCode) {
if (!WebUtils.isIncludeRequest(request)) {
response.setStatus(statusCode);
request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, statusCode);
}
}
/**
* Gets the exception details and complete stack trace.
*
* #param e the e
* #return the exception details and complete stack trace
*/
public static String getExceptionDetailsAndCompleteStackTrace(final Throwable e) {
StringBuilder detailedMessage = new StringBuilder();
if (e.getLocalizedMessage() != null) {
detailedMessage.append(e.getLocalizedMessage());
}
if (detailedMessage.length() > 0) {
detailedMessage.append("\n");
}
detailedMessage.append(e.getClass().getName());
/** Save: Commons lang does not support generics in this old version. */
#SuppressWarnings("unchecked")
List<Throwable> throwables = ExceptionUtils.getThrowableList(e);
for (int i = 1; i < throwables.size(); i++) {
detailedMessage.append("\n cause: ");
detailedMessage.append(throwables.get(i).getClass().getName());
if (StringUtils.isNotBlank(throwables.get(i).getLocalizedMessage())) {
detailedMessage.append(" -- " + throwables.get(i).getLocalizedMessage());
}
detailedMessage.append(";");
}
detailedMessage.append("\n\n --full Stacktrace--\n");
detailedMessage.append(ExceptionUtils.getFullStackTrace(e));
return detailedMessage.toString();
}
}
You probably have the worlderror.org malware installed on your computer that is intercepting the 400-600 status code responses.
Your not even seeing the spring errors because the malware is intercepting it.
Try running a different browser or run a spyware removal software.
I have the following and works fine:
Note: Doing the things simple.
In your class that extends WebMvcConfigurerAdapter add the following:
#Bean
public StringHttpMessageConverter stringHttpMessageConverter(){
StringHttpMessageConverter converter = new StringHttpMessageConverter();
converter.setSupportedMediaTypes(Arrays.asList(MediaType.ALL));
return converter;
}
#Bean
public ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver(){
ExceptionHandlerExceptionResolver eher = new ExceptionHandlerExceptionResolver();
eher.setMessageConverters(Arrays.asList(stringHttpMessageConverter()));
return eher;
}
#Bean
public ResponseStatusExceptionResolver responseStatusExceptionResolver(){
return new ResponseStatusExceptionResolver();
}
#Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers){
exceptionResolvers.add(exceptionHandlerExceptionResolver());
exceptionResolvers.add(responseStatusExceptionResolver());
}
Therefore
#ExceptionHandler(value=MyException.class)
public ResponseEntity<String> errorHandlerHttpHeaderRestrict(MyException me){
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity<>("ERROR FATAL: "+me.getMessage(), httpHeaders, HttpStatus.BAD_REQUEST);//400
}
I suggest you use ResponseEntity instead of
#ResponseStatus(value = HttpStatus.BAD_REQUEST) and #ResponseBody.
It because:
ResponseEntity is more flexible
With #ResponseStatus you must be totally sure the method only can return that HttpStatus (just one)
Here is how I solved this :
I updated my Spring version from 3.2.2 to 4.0.3 (a more recent should work too).
Then, #ControllerAdvice accepts the basePackage parameters :
#ControllerAdvice(basePackages = "me.myself.hi")
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler
{
/** The associated logger. */
private static final Logger LOGGER = LoggerFactory.getLogger(RestResponseEntityExceptionHandler.class);
#ExceptionHandler(value = { HttpRestException.class })
protected ResponseEntity<Object> handleConflict(final RuntimeException ex, final WebRequest request)
{
if (ex instanceof HttpRestException)
{
final HttpRestException restEx = (HttpRestException) ex;
return handleExceptionInternal(ex, restEx.getMessage(), new HttpHeaders(), restEx.getHttpStatus(), request);
}
return handleExceptionInternal(ex, "Internal server error",
new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request);
}
}
basePackage specify the package assigned to the handler class. That means each controller defined in this package (or a sub one) will be concerned.
For me, me.myself.hi is a base package name shared by at least 10 projects. So even if a controller is in another project, as long as it is under a package starting with me.myself.hi, the HttpRestException will be catched there.
Remember that the exception must not be handled at the Controller level, but only thrown, the #ControllerAdvice annotated controller will handle this exception and return statuscode + error message.