/* ===================================================================
* AbstractNodeController.java
*
* Created Aug 6, 2009 10:17:13 AM
*
* Copyright (c) 2009 Solarnetwork.net Dev Team.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
* ===================================================================
*/
package net.solarnetwork.central.web;
import java.util.Locale;
import java.util.TimeZone;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.solarnetwork.central.ValidationException;
import net.solarnetwork.central.dao.SolarNodeDao;
import net.solarnetwork.central.domain.SolarNode;
import net.solarnetwork.central.security.AuthorizationException;
import net.solarnetwork.util.CloningPropertyEditorRegistrar;
import net.solarnetwork.util.JodaDateFormatEditor;
import net.solarnetwork.web.domain.Response;
import net.solarnetwork.web.support.WebUtils;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
/**
* Abstract base class to support node-related controllers.
*
* @author matt
* @version 1.1
*/
public abstract class AbstractNodeController {
/** The default value for the {@code requestDateFormat} property. */
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
/** The default value for the {@code requestDateFormat} property. */
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
/** A class-level logger. */
protected final Logger log = LoggerFactory.getLogger(getClass());
@Autowired
private MessageSource messageSource;
private SolarNodeDao solarNodeDao;
private String viewName;
private String[] requestDateFormats = new String[] { DEFAULT_DATE_TIME_FORMAT, DEFAULT_DATE_FORMAT };
/**
* Resolve a ModelAndView with an empty model and a view name determined by
* the URL "suffix".
*
* <p>
* If the {@link #getViewName()} method returns a value, that view name is
* used for every request. Otherwise, this sets the view name to the value
* of the URL "suffix", that is, everything after the last period in the
* URL. This uses {@link StringUtils#getFilenameExtension(String)} on the
* request URI to accomplish this. For example a URL like
* {@code /myController.json} would resolve to a view named {@code json}.
* This can be handy when you want to return different data formats for the
* same business logic, such as XML or JSON.
* </p>
*
* @param request
* the HTTP request
* @return a ModelAndView (never <em>null</em>)
*/
protected ModelAndView resolveViewFromUrlExtension(HttpServletRequest request) {
String viewName = WebUtils.resolveViewFromUrlExtension(request, getViewName());
return new ModelAndView(viewName);
}
/**
* Set up a {@link CloningPropertyEditorRegistrar} as a request attribute.
*
* <p>
* This sets up a new {@link CloningPropertyEditorRegistrar} as a request
* attribute, which could be used by the view for serializing model
* properties in some way. A common use for this is to serialize
* {@link DateTime} objects into Strings, so this method accepts a
* {@code dateFormat} and {@code node} property which, if provided, will add
* a {@link JodaDateFormatEditor} to the registrar for all {@link DateTime}
* objects, configured with the node's time zone.
* </p>
*
* @param request
* the HTTP request
* @param dateFormat
* an optional date format
* @param node
* an optional node (required if {@code dateFormat} provided)
* @return the registrar
*/
protected CloningPropertyEditorRegistrar setupViewPropertyEditorRegistrar(
HttpServletRequest request, String dateFormat, SolarNode node) {
return setupViewPropertyEditorRegistrar(request, dateFormat,
(node == null ? null : node.getTimeZone()));
}
/**
* Set up a {@link CloningPropertyEditorRegistrar} as a request attribute.
*
* <p>
* This sets up a new {@link CloningPropertyEditorRegistrar} as a request
* attribute, which could be used by the view for serializing model
* properties in some way. A common use for this is to serialize
* {@link DateTime} objects into Strings, so this method accepts a
* {@code dateFormat} and {@code node} property which, if provided, will add
* a {@link JodaDateFormatEditor} to the registrar for all {@link DateTime}
* objects, configured with the node's time zone.
* </p>
*
* @param request
* the HTTP request
* @param dateFormat
* an optional date format
* @param timeZone
* an optional node (required if {@code dateFormat} provided)
* @return the registrar
*/
protected CloningPropertyEditorRegistrar setupViewPropertyEditorRegistrar(
HttpServletRequest request, String dateFormat, TimeZone timeZone) {
// set up a PropertyEditorRegistrar that can be used for serializing data into view-friendly values
CloningPropertyEditorRegistrar registrar = new CloningPropertyEditorRegistrar();
if ( dateFormat != null && timeZone != null ) {
// TODO implement caching of JodaDateFormatEditors based on dateFormat + time zone
registrar.setPropertyEditor(DateTime.class, new JodaDateFormatEditor(dateFormat, timeZone));
}
request.setAttribute("propertyEditorRegistrar", registrar);
return registrar;
}
/**
* Add a {@link DateTime} property editor, using the
* {@link #getRequestDateFormat()} pattern.
*
* <p>
* This is typically called from an "init binder" method.
* </p>
*
* @param binder
* the binder to add the editor to
*/
protected void initBinderDateFormatEditor(WebDataBinder binder) {
binder.registerCustomEditor(DateTime.class, new JodaDateFormatEditor(this.requestDateFormats,
null));
}
/**
* Handle an {@link AuthorizationException}.
*
* @param e
* the exception
* @param response
* the response
* @return an error response object
*/
@ExceptionHandler(AuthorizationException.class)
@ResponseBody
public Response<?> handleAuthorizationException(AuthorizationException e,
HttpServletResponse response) {
log.debug("AuthorizationException in {} controller: {}", getClass().getSimpleName(),
e.getMessage());
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return new Response<Object>(Boolean.FALSE, null, e.getReason().toString(), null);
}
/**
* Handle an {@link RuntimeException}.
*
* @param e
* the exception
* @param response
* the response
* @return an error response object
*/
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public Response<?> handleRuntimeException(RuntimeException e, HttpServletResponse response) {
log.error("RuntimeException in {} controller", getClass().getSimpleName(), e);
return new Response<Object>(Boolean.FALSE, null, "Internal error", null);
}
/**
* Handle an {@link BindException}.
*
* @param e
* the exception
* @param response
* the response
* @return an error response object
*/
@ExceptionHandler(BindException.class)
@ResponseBody
public Response<?> handleBindException(BindException e, HttpServletResponse response, Locale locale) {
log.debug("BindException in {} controller", getClass().getSimpleName(), e);
response.setStatus(422);
String msg = generateErrorsMessage(e, locale, messageSource);
return new Response<Object>(Boolean.FALSE, null, msg, null);
}
/**
* Handle an {@link ValidationException}.
*
* @param e
* the exception
* @param response
* the response
* @return an error response object
*/
@ExceptionHandler(ValidationException.class)
@ResponseBody
public Response<?> handleValidationException(ValidationException e, HttpServletResponse response,
Locale locale) {
log.debug("ValidationException in {} controller", getClass().getSimpleName(), e);
response.setStatus(422);
String msg = generateErrorsMessage(e.getErrors(), locale,
e.getMessageSource() != null ? e.getMessageSource() : messageSource);
return new Response<Object>(Boolean.FALSE, null, msg, null);
}
private String generateErrorsMessage(Errors e, Locale locale, MessageSource msgSrc) {
String msg = (msgSrc == null ? "Validation error" : msgSrc.getMessage("error.validation", null,
"Validation error", locale));
if ( msgSrc != null && e.hasErrors() ) {
StringBuilder buf = new StringBuilder();
for ( ObjectError error : e.getAllErrors() ) {
if ( buf.length() > 0 ) {
buf.append(" ");
}
buf.append(msgSrc.getMessage(error, locale));
}
msg = buf.toString();
}
return msg;
}
/**
* Get the first request date format.
*
* @return the requestDateFormat
*/
public String getRequestDateFormat() {
if ( requestDateFormats == null || requestDateFormats.length < 1 ) {
return null;
}
return requestDateFormats[0];
}
/**
* Set a single request date format.
*
* @param requestDateFormat
* the requestDateFormat to set
*/
public void setRequestDateFormat(String requestDateFormat) {
this.requestDateFormats = new String[] { requestDateFormat };
}
public SolarNodeDao getSolarNodeDao() {
return solarNodeDao;
}
public void setSolarNodeDao(SolarNodeDao solarNodeDao) {
this.solarNodeDao = solarNodeDao;
}
public String getViewName() {
return viewName;
}
public void setViewName(String viewName) {
this.viewName = viewName;
}
public String[] getRequestDateFormats() {
return requestDateFormats;
}
public void setRequestDateFormats(String[] requestDateFormats) {
this.requestDateFormats = requestDateFormats;
}
public MessageSource getMessageSource() {
return messageSource;
}
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
}