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