/* ==================================================================
* AbstractView.java - Feb 11, 2012 3:07:31 PM
*
* Copyright 2007-2012 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.web.support;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.solarnetwork.util.PropertySerializerRegistrar;
/**
* Extension of {@link org.springframework.web.servlet.view.AbstractView} that
* preserves model attribute ordering and supports serializing model properties
* with a {@link PropertySerializerRegistrar}.
*
* <p>
* The configurable properties of this class are:
* </p>
*
* <dl>
* <dt>propertySerializerRegistrar</dt>
* <dd>An optional registrar of PropertySerializer instances that can be used to
* serialize specific objects into String values. This can be useful for
* formatting Date objects into strings, for example.</dd>
*
* <dt>modelObjectIgnoreTypes</dt>
* <dd>A set of class types to ignore from the model, and never output in the
* response. Defaults to an empty array.</dd>
*
* <dt>javaBeanIgnoreProperties</dt>
* <dd>A set of JavaBean properties to ignore for all JavaBeans. This is useful
* for omitting things like <code>class</code> which is not usually desired.
* Defaults to {@link #DEFAULT_JAVA_BEAN_IGNORE_PROPERTIES}.</dd>
*
* <dt>javaBeanTreatAsStringValues</dt>
* <dd>A set of JavaBean property object types to treat as Strings for all
* JavaBeans. This is useful for omitting object values such as Class objects,
* which is not usually desired. Defaults to
* {@link #DEFAULT_JAVA_BEAN_STRING_VALUES}.</dd>
*
* </dl>
*
* @author matt
* @version 1.0
*/
public abstract class AbstractView extends org.springframework.web.servlet.view.AbstractView {
/**
* The default value for the <code>javaBeanIgnoreProperties</code> property.
*/
public static final String[] DEFAULT_JAVA_BEAN_IGNORE_PROPERTIES = new String[] { "class", };
/**
* The default value for the <code>javaBeanTreatAsStringValues</code>
* property.
*/
public static final Class<?>[] DEFAULT_JAVA_BEAN_STRING_VALUES = new Class<?>[] { Class.class, };
private static final Class<?>[] DEFAULT_MODEL_OBJECT_IGNORE_TYPES = new Class<?>[] {};
private static final Pattern CHARSET_PATTERN = Pattern.compile("charset\\s*=\\s*([^\\s]+)",
Pattern.CASE_INSENSITIVE);
private PropertySerializerRegistrar propertySerializerRegistrar = null;
private Set<Class<?>> modelObjectIgnoreTypes = new LinkedHashSet<Class<?>>(
Arrays.asList(DEFAULT_MODEL_OBJECT_IGNORE_TYPES));
private Set<String> javaBeanIgnoreProperties = new LinkedHashSet<String>(
Arrays.asList(DEFAULT_JAVA_BEAN_IGNORE_PROPERTIES));
private Set<Class<?>> javaBeanTreatAsStringValues = new LinkedHashSet<Class<?>>(
Arrays.asList(DEFAULT_JAVA_BEAN_STRING_VALUES));
/**
* This method performs the same functions as
* {@link AbstractView#render(Map, HttpServletRequest, HttpServletResponse)}
* except that it uses a LinkedHashMap to preserve model order rather than a
* HashMap.
*/
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
if ( logger.isDebugEnabled() ) {
logger.debug("Rendering view with name '" + getBeanName() + "' with model " + model
+ " and static attributes " + getStaticAttributes());
}
// Consolidate static and dynamic model attributes.
Map<String, Object> mergedModel = new LinkedHashMap<String, Object>(getStaticAttributes().size()
+ (model != null ? model.size() : 0));
mergedModel.putAll(getStaticAttributes());
if ( model != null ) {
mergedModel.putAll(model);
}
// Expose RequestContext?
if ( getRequestContextAttribute() != null ) {
mergedModel.put(getRequestContextAttribute(),
createRequestContext(request, response, mergedModel));
}
// remove objects that should be ignored
if ( modelObjectIgnoreTypes != null && modelObjectIgnoreTypes.size() > 0 ) {
Iterator<Object> objects = mergedModel.values().iterator();
while ( objects.hasNext() ) {
Object o = objects.next();
if ( o == null ) {
objects.remove();
continue;
}
for ( Class<?> clazz : modelObjectIgnoreTypes ) {
if ( clazz.isAssignableFrom(o.getClass()) ) {
if ( logger.isTraceEnabled() ) {
logger.trace("Ignoring model type [" + o.getClass() + ']');
}
objects.remove();
break;
}
}
}
}
// remove objects that serialize to null
if ( propertySerializerRegistrar != null ) {
for ( Iterator<Map.Entry<String, Object>> itr = mergedModel.entrySet().iterator(); itr
.hasNext(); ) {
Map.Entry<String, Object> me = itr.next();
Object o = (me.getValue() == null ? null : propertySerializerRegistrar
.serializeProperty(me.getKey(), me.getValue().getClass(), me.getValue(),
me.getValue()));
if ( o == null ) {
if ( logger.isDebugEnabled() ) {
logger.debug("Removing model entry [" + me.getKey()
+ "] because PropertySerializerRegistrar serialized to null");
}
itr.remove();
}
}
}
prepareResponse(request, response);
renderMergedOutputModel(mergedModel, request, response);
}
/**
* Get the configured character encoding to use for the response.
*
* <p>
* This method will extract the character encoding specified in
* {@link #getContentType()}, or default to {@code UTF-8} if none available.
* </p>
*
* @return character encoding name
*/
protected String getResponseCharacterEncoding() {
String charset = "UTF-8";
Matcher m = CHARSET_PATTERN.matcher(getContentType());
if ( m.find() ) {
charset = m.group(1);
}
return charset;
}
public PropertySerializerRegistrar getPropertySerializerRegistrar() {
return propertySerializerRegistrar;
}
public void setPropertySerializerRegistrar(PropertySerializerRegistrar propertySerializerRegistrar) {
this.propertySerializerRegistrar = propertySerializerRegistrar;
}
public Set<Class<?>> getModelObjectIgnoreTypes() {
return modelObjectIgnoreTypes;
}
public void setModelObjectIgnoreTypes(Set<Class<?>> modelObjectIgnoreTypes) {
this.modelObjectIgnoreTypes = modelObjectIgnoreTypes;
}
public Set<String> getJavaBeanIgnoreProperties() {
return javaBeanIgnoreProperties;
}
public void setJavaBeanIgnoreProperties(Set<String> javaBeanIgnoreProperties) {
this.javaBeanIgnoreProperties = javaBeanIgnoreProperties;
}
public Set<Class<?>> getJavaBeanTreatAsStringValues() {
return javaBeanTreatAsStringValues;
}
public void setJavaBeanTreatAsStringValues(Set<Class<?>> javaBeanTreatAsStringValues) {
this.javaBeanTreatAsStringValues = javaBeanTreatAsStringValues;
}
}