/*
* Copyright 2002-2006,2009 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.opensymphony.xwork2.ognl.accessor;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer;
import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.ognl.OgnlUtil;
import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
import ognl.ObjectPropertyAccessor;
import ognl.OgnlException;
import ognl.OgnlRuntime;
import ognl.SetPropertyAccessor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* @author Gabe
*/
public class XWorkCollectionPropertyAccessor extends SetPropertyAccessor {
private static final Logger LOG = LogManager.getLogger(XWorkCollectionPropertyAccessor.class);
public static final String KEY_PROPERTY_FOR_CREATION = "makeNew";
//use a basic object Ognl property accessor here
//to access properties of the objects in the Set
//so that nothing is put in the context to screw things up
private ObjectPropertyAccessor _accessor = new ObjectPropertyAccessor();
private XWorkConverter xworkConverter;
private ObjectFactory objectFactory;
private ObjectTypeDeterminer objectTypeDeterminer;
private OgnlUtil ognlUtil;
@Inject
public void setXWorkConverter(XWorkConverter conv) {
this.xworkConverter = conv;
}
@Inject
public void setObjectFactory(ObjectFactory fac) {
this.objectFactory = fac;
}
@Inject
public void setObjectTypeDeterminer(ObjectTypeDeterminer ot) {
this.objectTypeDeterminer = ot;
}
@Inject
public void setOgnlUtil(OgnlUtil util) {
this.ognlUtil = util;
}
/**
* Gets the property of a Collection by indexing the collection
* based on a key property. For example, if the key property were
* 'id', this method would convert the key Object to whatever
* type the id property was, and then access the Set like it was
* a Map returning a JavaBean with the value of id property matching
* the input.
*
* @see ognl.PropertyAccessor#getProperty(java.util.Map, Object, Object)
*/
@Override
public Object getProperty(Map context, Object target, Object key) throws OgnlException {
LOG.trace("Entering getProperty()");
//check if it is a generic type property.
//if so, return the value from the
//superclass which will determine this.
if (!ReflectionContextState.isGettingByKeyProperty(context) && !key.equals(KEY_PROPERTY_FOR_CREATION)) {
return super.getProperty(context, target, key);
} else {
//reset context property
ReflectionContextState.setGettingByKeyProperty(context,false);
}
Collection c = (Collection) target;
//get the bean that this collection is a property of
Class lastBeanClass = ReflectionContextState.getLastBeanClassAccessed(context);
//get the property name that this collection uses
String lastPropertyClass = ReflectionContextState.getLastBeanPropertyAccessed(context);
//if one or the other is null, assume that it isn't
//set up correctly so just return whatever the
//superclass would
if (lastBeanClass == null || lastPropertyClass == null) {
ReflectionContextState.updateCurrentPropertyPath(context, key);
return super.getProperty(context, target, key);
}
//get the key property to index the
//collection with from the ObjectTypeDeterminer
String keyProperty = objectTypeDeterminer.getKeyProperty(lastBeanClass, lastPropertyClass);
//get the collection class of the
Class collClass = objectTypeDeterminer.getElementClass(lastBeanClass, lastPropertyClass, key);
Class keyType;
Class toGetTypeFrom = (collClass != null) ? collClass : c.iterator().next().getClass();
try {
keyType = OgnlRuntime.getPropertyDescriptor(toGetTypeFrom, keyProperty).getPropertyType();
} catch (Exception exc) {
throw new OgnlException("Error getting property descriptor: " + exc.getMessage());
}
if (ReflectionContextState.isCreatingNullObjects(context)) {
Map collMap = getSetMap(context, c, keyProperty);
if (key.toString().equals(KEY_PROPERTY_FOR_CREATION)) {
//this should return the XWorkList
//for this set that contains new entries
//then the ListPropertyAccessor will be called
//to access it in the next sequence
return collMap.get(null);
}
Object realKey = xworkConverter.convertValue(context, key, keyType);
Object value = collMap.get(realKey);
if (value == null
&& ReflectionContextState.isCreatingNullObjects(context)
&& objectTypeDeterminer
.shouldCreateIfNew(lastBeanClass,lastPropertyClass,c,keyProperty,false)) {
//create a new element and
//set the value of keyProperty
//to be the given value
try {
value=objectFactory.buildBean(collClass, context);
//set the value of the keyProperty
_accessor.setProperty(context,value,keyProperty,realKey);
//add the new object to the collection
c.add(value);
//add to the Map if accessed later
collMap.put(realKey, value);
} catch (Exception exc) {
throw new OgnlException("Error adding new element to collection", exc);
}
}
return value;
} else {
if (key.toString().equals(KEY_PROPERTY_FOR_CREATION)) {
return null;
}
//with getting do iteration
//don't assume for now it is
//optimized to create the Map
//and unlike setting, there is
//no easy key for the Set
Object realKey = xworkConverter.convertValue(context, key, keyType);
return getPropertyThroughIteration(context, c, keyProperty, realKey);
}
}
/*
* Gets an indexed Map by a given key property with the key being
* the value of the property and the value being the
*/
private Map getSetMap(Map context, Collection collection, String property) throws OgnlException {
LOG.trace("getting set Map");
String path = ReflectionContextState.getCurrentPropertyPath(context);
Map map = ReflectionContextState.getSetMap(context, path);
if (map == null) {
LOG.trace("creating set Map");
map = new HashMap();
map.put(null, new SurrugateList(collection));
for (Object currTest : collection) {
Object currKey = _accessor.getProperty(context, currTest, property);
if (currKey != null) {
map.put(currKey, currTest);
}
}
ReflectionContextState.setSetMap(context, map, path);
}
return map;
}
/*
* gets a bean with the given
*/
public Object getPropertyThroughIteration(Map context, Collection collection, String property, Object key)
throws OgnlException {
//TODO
for (Object currTest : collection) {
if (_accessor.getProperty(context, currTest, property).equals(key)) {
return currTest;
}
}
//none found
return null;
}
@Override
public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException {
Class lastClass = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED);
String lastProperty = (String) context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED);
Class convertToClass = objectTypeDeterminer.getElementClass(lastClass, lastProperty, name);
if (name instanceof String && value.getClass().isArray()) {
// looks like the input game in the form of "someCollection.foo" and
// we are expected to define the index values ourselves.
// So let's do it:
Collection c = (Collection) target;
Object[] values = (Object[]) value;
for (Object v : values) {
try {
Object o = objectFactory.buildBean(convertToClass, context);
ognlUtil.setValue((String) name, context, o, v);
c.add(o);
} catch (Exception e) {
throw new OgnlException("Error converting given String values for Collection.", e);
}
}
// we don't want to do the normal collection property setting now, since we've already done the work
// just return instead
return;
}
Object realValue = getRealValue(context, value, convertToClass);
super.setProperty(context, target, name, realValue);
}
private Object getRealValue(Map context, Object value, Class convertToClass) {
if (value == null || convertToClass == null) {
return value;
}
return xworkConverter.convertValue(context, value, convertToClass);
}
}
/**
* @author Gabe
*/
class SurrugateList extends ArrayList {
private Collection surrugate;
public SurrugateList(Collection surrugate) {
this.surrugate = surrugate;
}
@Override
public void add(int arg0, Object arg1) {
if (arg1 != null) {
surrugate.add(arg1);
}
super.add(arg0, arg1);
}
@Override
public boolean add(Object arg0) {
if (arg0 != null) {
surrugate.add(arg0);
}
return super.add(arg0);
}
@Override
public boolean addAll(Collection arg0) {
surrugate.addAll(arg0);
return super.addAll(arg0);
}
@Override
public boolean addAll(int arg0, Collection arg1) {
surrugate.addAll(arg1);
return super.addAll(arg0, arg1);
}
@Override
public Object set(int arg0, Object arg1) {
if (arg1 != null) {
surrugate.add(arg1);
}
return super.set(arg0, arg1);
}
}