Mapper and SpelMapper
git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@1902 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
parent
aa86f061f7
commit
7e43aaec3e
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2009 the original author or authors.
|
||||||
|
*
|
||||||
|
* 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 org.springframework.mapping;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps between a source and target.
|
||||||
|
* @author Keith Donald
|
||||||
|
* @param <S> the source type mapped from
|
||||||
|
* @param <T> the target type mapped to
|
||||||
|
*/
|
||||||
|
public interface Mapper<S, T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map the source to the target.
|
||||||
|
* @param source the source to map from
|
||||||
|
* @param target the target to map to
|
||||||
|
* @throws MappingException if the mapping process failed
|
||||||
|
*/
|
||||||
|
void map(S source, T target);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2009 the original author or authors.
|
||||||
|
*
|
||||||
|
* 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 org.springframework.mapping;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base runtime exception for the mapping system.
|
||||||
|
* @author Keith Donald
|
||||||
|
*/
|
||||||
|
public class MappingException extends RuntimeException {
|
||||||
|
|
||||||
|
public MappingException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package org.springframework.mapping.support;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.springframework.expression.EvaluationContext;
|
||||||
|
|
||||||
|
interface MappableType {
|
||||||
|
|
||||||
|
Set<String> getMappableFields(Object instance);
|
||||||
|
|
||||||
|
EvaluationContext getMappingContext(Object instance);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.springframework.mapping.support;
|
||||||
|
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
|
||||||
|
public interface MappingConfiguration {
|
||||||
|
MappingConfiguration setConverter(Converter converter);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,222 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2009 the original author or authors.
|
||||||
|
*
|
||||||
|
* 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 org.springframework.mapping.support;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.springframework.context.expression.MapAccessor;
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.expression.EvaluationContext;
|
||||||
|
import org.springframework.expression.Expression;
|
||||||
|
import org.springframework.expression.ExpressionParser;
|
||||||
|
import org.springframework.expression.ParseException;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
|
import org.springframework.mapping.Mapper;
|
||||||
|
import org.springframework.mapping.MappingException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic object mapper implementation based on the Spring Expression Language (SpEL).
|
||||||
|
* @author Keith Donald
|
||||||
|
*/
|
||||||
|
public class SpelMapper implements Mapper<Object, Object> {
|
||||||
|
|
||||||
|
private static final MappableType MAP_MAPPABLE_TYPE = new MapMappableType();
|
||||||
|
|
||||||
|
private static final MappableType BEAN_MAPPABLE_TYPE = new BeanMappableType();
|
||||||
|
|
||||||
|
private static final ExpressionParser expressionParser = new SpelExpressionParser();
|
||||||
|
|
||||||
|
private Set<Mapping> mappings = new LinkedHashSet<Mapping>();
|
||||||
|
|
||||||
|
private boolean autoMappingEnabled = true;
|
||||||
|
|
||||||
|
public void setAutoMappingEnabled(boolean autoMappingEnabled) {
|
||||||
|
this.autoMappingEnabled = autoMappingEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingConfiguration addMapping(String source, String target) {
|
||||||
|
Expression sourceExp;
|
||||||
|
try {
|
||||||
|
sourceExp = expressionParser.parseExpression(source);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw new IllegalArgumentException("The mapping source '" + source
|
||||||
|
+ "' is not a parseable value expression", e);
|
||||||
|
}
|
||||||
|
Expression targetExp;
|
||||||
|
try {
|
||||||
|
targetExp = expressionParser.parseExpression(target);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw new IllegalArgumentException("The mapping target '" + source
|
||||||
|
+ "' is not a parseable property expression", e);
|
||||||
|
}
|
||||||
|
Mapping mapping = new Mapping(sourceExp, targetExp);
|
||||||
|
if (mappings == null) {
|
||||||
|
mappings = new LinkedHashSet<Mapping>();
|
||||||
|
}
|
||||||
|
mappings.add(mapping);
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void map(Object source, Object target) throws MappingException {
|
||||||
|
EvaluationContext sourceContext = getMappingContext(source);
|
||||||
|
EvaluationContext targetContext = getMappingContext(target);
|
||||||
|
for (Mapping mapping : mappings) {
|
||||||
|
mapping.map(sourceContext, targetContext);
|
||||||
|
}
|
||||||
|
Set<Mapping> autoMappings = getAutoMappings(source);
|
||||||
|
for (Mapping mapping : autoMappings) {
|
||||||
|
mapping.map(sourceContext, targetContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected EvaluationContext getMappingContext(Object object) {
|
||||||
|
if (object instanceof Map) {
|
||||||
|
return MAP_MAPPABLE_TYPE.getMappingContext(object);
|
||||||
|
} else {
|
||||||
|
return BEAN_MAPPABLE_TYPE.getMappingContext(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Set<String> getMappableFields(Object object) {
|
||||||
|
if (object instanceof Map) {
|
||||||
|
return MAP_MAPPABLE_TYPE.getMappableFields(object);
|
||||||
|
} else {
|
||||||
|
return BEAN_MAPPABLE_TYPE.getMappableFields(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<Mapping> getAutoMappings(Object source) {
|
||||||
|
if (autoMappingEnabled) {
|
||||||
|
Set<Mapping> autoMappings = new LinkedHashSet<Mapping>();
|
||||||
|
Set<String> fields = getMappableFields(source);
|
||||||
|
for (String field : fields) {
|
||||||
|
if (!explicitlyMapped(field)) {
|
||||||
|
Expression exp;
|
||||||
|
try {
|
||||||
|
exp = expressionParser.parseExpression(field);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw new IllegalArgumentException("The mapping source '" + source
|
||||||
|
+ "' is not a parseable value expression", e);
|
||||||
|
}
|
||||||
|
Mapping mapping = new Mapping(exp, exp);
|
||||||
|
autoMappings.add(mapping);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return autoMappings;
|
||||||
|
} else {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean explicitlyMapped(String field) {
|
||||||
|
for (Mapping mapping : mappings) {
|
||||||
|
if (mapping.source.getExpressionString().equals(field)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Mapping implements MappingConfiguration {
|
||||||
|
|
||||||
|
private Expression source;
|
||||||
|
|
||||||
|
private Expression target;
|
||||||
|
|
||||||
|
private Converter converter;
|
||||||
|
|
||||||
|
public Mapping(Expression source, Expression target) {
|
||||||
|
this.source = source;
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingConfiguration setConverter(Converter converter) {
|
||||||
|
this.converter = converter;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void map(EvaluationContext sourceContext, EvaluationContext targetContext) throws MappingException {
|
||||||
|
try {
|
||||||
|
Object value = source.getValue(sourceContext);
|
||||||
|
if (converter != null) {
|
||||||
|
value = converter.convert(value);
|
||||||
|
}
|
||||||
|
target.setValue(targetContext, value);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new MappingException("Could not perform mapping", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
return source.getExpressionString().hashCode() + target.getExpressionString().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof Mapping)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Mapping m = (Mapping) o;
|
||||||
|
return source.getExpressionString().equals(m.source.getExpressionString())
|
||||||
|
&& target.getExpressionString().equals(m.source.getExpressionString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return source.getExpressionString() + " -> " + target.getExpressionString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MapMappableType implements MappableType {
|
||||||
|
|
||||||
|
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
|
||||||
|
|
||||||
|
public Set<String> getMappableFields(Object instance) {
|
||||||
|
Map map = (Map) instance;
|
||||||
|
LinkedHashSet<String> fields = new LinkedHashSet<String>(map.size(), 1);
|
||||||
|
for (Object key : map.keySet()) {
|
||||||
|
fields.add(key.toString());
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EvaluationContext getMappingContext(Object instance) {
|
||||||
|
StandardEvaluationContext context = new StandardEvaluationContext(instance);
|
||||||
|
context.addPropertyAccessor(new MapAccessor());
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class BeanMappableType implements MappableType {
|
||||||
|
|
||||||
|
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
|
||||||
|
|
||||||
|
public Set<String> getMappableFields(Object instance) {
|
||||||
|
// TODO
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EvaluationContext getMappingContext(Object instance) {
|
||||||
|
return new StandardEvaluationContext(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
package org.springframework.mapping.support;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.mapping.MappingException;
|
||||||
|
import org.springframework.mapping.support.SpelMapper;
|
||||||
|
|
||||||
|
public class SpelMapperTests {
|
||||||
|
|
||||||
|
private SpelMapper mapper = new SpelMapper();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mapAutomatic() throws MappingException {
|
||||||
|
Map<String, Object> source = new HashMap<String, Object>();
|
||||||
|
source.put("name", "Keith");
|
||||||
|
source.put("age", 31);
|
||||||
|
|
||||||
|
Person target = new Person();
|
||||||
|
|
||||||
|
mapper.map(source, target);
|
||||||
|
|
||||||
|
assertEquals("Keith", target.name);
|
||||||
|
assertEquals(31, target.age);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mapExplicit() throws MappingException {
|
||||||
|
mapper.setAutoMappingEnabled(false);
|
||||||
|
mapper.addMapping("name", "name");
|
||||||
|
|
||||||
|
Map<String, Object> source = new HashMap<String, Object>();
|
||||||
|
source.put("name", "Keith");
|
||||||
|
source.put("age", 31);
|
||||||
|
|
||||||
|
Person target = new Person();
|
||||||
|
|
||||||
|
mapper.map(source, target);
|
||||||
|
|
||||||
|
assertEquals("Keith", target.name);
|
||||||
|
assertEquals(0, target.age);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mapAutomaticWithExplictOverrides() throws MappingException {
|
||||||
|
mapper.addMapping("test", "age");
|
||||||
|
|
||||||
|
Map<String, Object> source = new HashMap<String, Object>();
|
||||||
|
source.put("name", "Keith");
|
||||||
|
source.put("test", "3");
|
||||||
|
source.put("favoriteSport", "FOOTBALL");
|
||||||
|
|
||||||
|
Person target = new Person();
|
||||||
|
|
||||||
|
mapper.map(source, target);
|
||||||
|
|
||||||
|
assertEquals("Keith", target.name);
|
||||||
|
assertEquals(3, target.age);
|
||||||
|
assertEquals(Sport.FOOTBALL, target.favoriteSport);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mapSameSourceFieldToMultipleTargets() throws MappingException {
|
||||||
|
mapper.addMapping("test", "name");
|
||||||
|
mapper.addMapping("test", "favoriteSport");
|
||||||
|
|
||||||
|
Map<String, Object> source = new HashMap<String, Object>();
|
||||||
|
source.put("test", "FOOTBALL");
|
||||||
|
|
||||||
|
Person target = new Person();
|
||||||
|
|
||||||
|
mapper.map(source, target);
|
||||||
|
|
||||||
|
assertEquals("FOOTBALL", target.name);
|
||||||
|
assertEquals(0, target.age);
|
||||||
|
assertEquals(Sport.FOOTBALL, target.favoriteSport);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static class Person {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private int age;
|
||||||
|
|
||||||
|
private Sport favoriteSport;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAge() {
|
||||||
|
return age;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAge(int age) {
|
||||||
|
this.age = age;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Sport getFavoriteSport() {
|
||||||
|
return favoriteSport;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFavoriteSport(Sport favoriteSport) {
|
||||||
|
this.favoriteSport = favoriteSport;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Sport {
|
||||||
|
FOOTBALL, BASKETBALL
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue