Below is my mapper interface. I am using mapstruct 1.3.0.Final.
#Mapper(componentModel = "spring")
public interface ApiMapper {
#Mappings({
#Mapping(source = "in.entityName.fn", target="name.fn"),
#Mapping(source = "in.entityName.ln", target="name.ln"),
#Mapping(source = "in.salute.sln", target="name.salutation"),
})
public MyOutput map(InputData in);
}
It looks super simple, but the implementation class sets the name object in the target twice, so I get only the last mapped object. Can someone help me to understand what am I missing or doing wrong here?
#Component
public class ApiMapperImpl implements ApiMapper {
#Override
public MyOutput map(InputData in) {
if ( in == null ) {
return null;
}
MyOutput myOutput = new MyOutput();
myOutput.setName( entityNameToNameDetails( in.getEntityName() ) );
myOutput.setName( saluteServiceOutputToNameDetails( in.getSalute() ) );
return myOutput;
}
protected NameDetails entityNameToNameDetails(EntityName entityName) {
if ( entityName == null ) {
return null;
}
NameDetails nameDetails = new NameDetails();
nameDetails.setFn( entityName.getFn() );
nameDetails.setLn( entityName.getLn() );
return nameDetails;
}
protected NameDetails saluteServiceOutputToNameDetails(SaluteServiceOutput saluteServiceOutput) {
if ( saluteServiceOutput == null ) {
return null;
}
NameDetails nameDetails = new NameDetails();
nameDetails.setSalutation( saluteServiceOutput.getSln() );
return nameDetails;
}
}
I think, you should help to mapstruct in this context, adding a simple method, for example :
#Mappings({
#Mapping(source = "in.entityName.fn", target="fn"),
#Mapping(source = "in.entityName.ln", target="ln"),
#Mapping(source = "in.salute.sln", target="salutation"),
})
public NameDetails mapNameDetails(InputData in);`
I found the answers from here and here. I like the 2nd option to use #MappingTarget to update existing beans. The only new thing is I need to create object for MyOutput and use it when calling the map method.
I have modified my mapper code something like below:
#Mappings({
#Mapping(source = "in.entityName.fn", target="out.name.fn"),
#Mapping(source = "in.entityName.ln", target="out.name.ln"),
#Mapping(source = "in.salute.sln", target="out.name.salutation"),
})
public void mapNameDetails(InputData in, #MappingTarget MyOutput out);
The Junit for the above mapper code.
#Autowired
private ApiMapper apiMapper;
#Test
public void testApiMapper() {
MyOutput output = new MyOutput();
InputData input = createInputData();
apiMapper.mapNameDetails(input, output);
assertNotNull(output);
assertNotNull(output.getName());
assertEquals("Sridhar", output.getName().getFn());
assertNull(output.getName().getLn());
assertEquals("Mr.", output.getName().getSalutation());
}
private InputData createInputData() {
InputData data = new InputData();
data.setEntityName(new EntityName());
data.setSalute(new SaluteServiceOutput());
data.getEntityName().setFn("Sridhar");
data.getSalute().setSln("Mr.");
return data;
}
This bug was fixed in version 1.3.1.Final
Link to bug
Related
I wanted to fetch cached(#Cachable) value using redisson client but it return strange data if i use any codec in redisson client (getBucket("fruit::1",StringCodec.INSTANCE)) and it throws error unless i use codec.
i have used below code for caching
#Cacheable(value = "fruits", key = "#id")
public Fruit getFruitById(int id) {
// get fruit by id
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Fruit> query = builder.createQuery(Fruit.class);
Root<Fruit> root = query.from(Fruit.class);
query.select(root);
query.where(builder.equal(root.get("id"), id));
TypedQuery<Fruit> fruitQuery = em.createQuery(query);
return fruitQuery.getSingleResult();
}
When i use codec for getting that cached data
RBucket<String> bucket = client.getBucket("fruits::1",
StringCodec.INSTANCE);
String fruit = bucket.get();
It returns following strange data
��srcom.home.redis.Fruit��.ܵo*rIidIpriceLnametLjava/lang/String;xp,tpomegrantite
RedisConfiguration
#Bean
public RedisCacheConfiguration cacheConfiguration() {
RedisCacheConfiguration cacheConfig = RedisCacheConfiguration
.defaultCacheConfig().entryTtl(Duration.ofSeconds(600))
.disableCachingNullValues();
return cacheConfig;
}
#Bean
public RedisCacheManager cacheManager() {
RedisCacheManager rcm = RedisCacheManager
.builder(this.getRedissonStoreFactory())
.cacheDefaults(cacheConfiguration()).transactionAware().build();
return rcm;
}
#Bean
#Primary
public RedisProperties redisProperties() {
return new RedisProperties();
}
#Bean
public RedissonConnectionFactory getRedissonStoreFactory() {
return new RedissonConnectionFactory(getConfig());
}
#Bean
public RedissonNode getNode() {
RedissonNodeConfig nodeConfig = new RedissonNodeConfig(getConfig());
nodeConfig.setExecutorServiceWorkers(
Collections.singletonMap("ensimp", 1));
RedissonNode node = RedissonNode.create(nodeConfig);
node.start();
return node;
}
#Bean
public Config getConfig()
{
Config config = new Config();
RedisProperties properties = redisProperties();
config.useSingleServer().setAddress(
"redis://" + properties.getHost() + ":" + properties.getPort());
return config;
}
redisson.json
{
"singleServerConfig":{
"idleConnectionTimeout":500,
"connectTimeout":1000,
"timeout":3000,
"retryAttempts":3,
"retryInterval":1500,
"password":null,
"subscriptionsPerConnection":5,
"clientName":null,
"address": "redis://127.0.0.1:6379",
"subscriptionConnectionMinimumIdleSize":0,
"subscriptionConnectionPoolSize":1,
"connectionMinimumIdleSize":0,
"connectionPoolSize":20,
"database":0,
"dnsMonitoringInterval":5000
},
"threads":16,
"nettyThreads":32,
"codec":{
"class":"org.redisson.codec.FstCodec"
},
"transportMode":"NIO"
}
i've used fst codec too but got the same strange data. i want correctly decoded data it'd be great if anyone help me with a right code.
You need to use RMapCache data to obtain data and not RBucket.
client.getMapCache("fruits::1", StringCodec.INSTANCE);
try this:
RMapCache mycache;
mycache=client.getMapCache("fruits::1");
then to retrieve the data use readAllValues()
Collection<Fruit> map=mycache.readAllValues();
System.out.println(map);
I got following error after adding #Cacheable annotation to one of my rest method:
"status": 500,
"error": "Internal Server Error",
"message": "class java.util.ArrayList cannot be cast to class java.util.Map (java.util.ArrayList and java.util.Map are in module java.base of loader 'bootstrap')",
Method declaration is:
#Cacheable("loadDevicesFloors")
#GetMapping("/floors/all-devices")
public Map<String, DevicesFloorDTO> loadDevicesFloors() {...
and DevicesFloorDTO looks as follows:
public class DevicesFloorDTO implements Serializable {
private final List<DeviceDTO> deviceDTOs;
private final String floorName;
private final Integer floorIndex;
public DevicesFloorDTO(List<DeviceDTO> devicesDtos, String floorName, Integer floorIndex) {
this.deviceDTOs = devicesDtos;
this.floorName = floorName;
this.floorIndex = floorIndex;
}...
Additionally my #Bean redisTemplate method implementation:
#Bean
JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory jedisConFactory
= new JedisConnectionFactory();
jedisConFactory.setHostName(redisHost);
jedisConFactory.setPort(redisPort);
jedisConFactory.setPassword(redisPassword);
return jedisConFactory;
}
#Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<byte[], byte[]> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
return template;
}
Does anyone know what is wrong in this implementation? Without #Cacheable it works as expected, but after adding #Cacheable error occurs. I was searching a lot and still don't know what causes this error and how to fix this. Any comment may be helpful. Thaks a lot!
The Generics you have specified for the Map Map<String, DevicesFloorDTO> will not be available at runtime during serialization/deserialization. What format are you trying to save your objects to in Reids? Are they saving as JSON (string) or binary?
We have had success with the GenericJackson2JsonRedisSerializer because it will save the class info inside the JSON string so Redis know exactly how to recreate objects.
There are also some instances where a Wrapper Object is needed in order to correctly serialize/deserialize objects.
#Bean
public RedisCacheManager cacheManager( RedisConnectionFactory redisConnectionFactory,
ResourceLoader resourceLoader ) {
RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager
.builder( redisConnectionFactory )
.cacheDefaults( determineConfiguration() );
List<String> cacheNames = this.cacheProperties.getCacheNames();
if ( !cacheNames.isEmpty() ) {
builder.initialCacheNames( new LinkedHashSet<>( cacheNames ) );
}
return builder.build();
}
private RedisCacheConfiguration determineConfiguration() {
if ( this.redisCacheConfiguration != null ) {
return this.redisCacheConfiguration;
}
CacheProperties.Redis redisProperties = this.cacheProperties.getRedis();
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
ObjectMapper mapper = new Jackson2ObjectMapperBuilder()
.modulesToInstall( new SimpleModule().addSerializer( new NullValueSerializer( null ) ) )
.failOnEmptyBeans( false )
.build();
mapper.enableDefaultTyping( ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY );
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer( mapper );
//get the mapper b/c they registered some internal modules
config = config.serializeValuesWith( RedisSerializationContext.SerializationPair.fromSerializer( serializer ) );
if ( redisProperties.getTimeToLive() != null ) {
config = config.entryTtl( redisProperties.getTimeToLive() );
}
if ( redisProperties.getKeyPrefix() != null ) {
config = config.prefixKeysWith( redisProperties.getKeyPrefix() );
}
if ( !redisProperties.isCacheNullValues() ) {
config = config.disableCachingNullValues();
}
if ( !redisProperties.isUseKeyPrefix() ) {
config = config.disableKeyPrefix();
config = config.computePrefixWith( cacheName -> cacheName + "::" );
}
return config;
}
In my controller, I receive a string parameter on the basis of which I need to decide which service to call, how can I do the same in my Spring Boot application using Spring annotations?
For example: we have different types of cars. Now, on the basis of parameter in the request I should be able to decide which particular car service I should call.
How can I have a factory using annotations in Spring Boot, and objects should be returned from that factory on the basis of input.
I remember implementing support for this approach a few years ago, I believe inspired and using https://www.captechconsulting.com/blogs/combining-strategy-pattern-and-spring as the entry point to my utility library, use the following code snippets at your convenience:
Strategy.java
package ...
#Documented
#Target({ ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
public #interface Strategy {
Class<?> type();
String[] profiles() default {};
}
StrategyFactory.java
package ...
public class StrategyFactory {
private static final Logger LOG = Logger.getLogger( StrategyFactory.class );
private Map<Class<?>, Strategy> strategiesCache = new HashMap<Class<?>, Strategy>();
private String[] packages;
#PostConstruct
public void init() {
if (this.packages != null) {
Set<Class<?>> annotatedClasses = new HashSet<Class<?>>();
for (String pack : this.packages) {
Reflections reflections = new Reflections( pack );
annotatedClasses.addAll( reflections.getTypesAnnotatedWith( Strategy.class ) );
}
this.sanityCheck( annotatedClasses );
}
}
public <T> T getStrategy(Class<T> strategyClass) {
return this.getStrategy( strategyClass, null );
}
#SuppressWarnings("unchecked")
public <T> T getStrategy(Class<T> strategyClass, String currentProfile) {
Class<T> clazz = (Class<T>) this.findStrategyMatchingProfile( strategyClass, currentProfile );
if (clazz == null) {
throw new StrategyNotFoundException( String.format( "No strategies found of type '%s', are the strategies marked with #Strategy?", strategyClass.getName() ) );
}
try {
return (T) clazz.newInstance();
} catch (Exception e) {
throw ExceptionUtils.rethrowAs( e, StrategyException.class );
}
}
/**
* Checks to make sure there is only one strategy of each type(Interface) annotated for each profile Will throw an exception on startup if multiple strategies are mapped to the same profile.
* #param annotatedClasses a list of classes
*/
private void sanityCheck(Set<Class<?>> annotatedClasses) {
Set<String> usedStrategies = new HashSet<String>();
for (Class<?> annotatedClass : annotatedClasses) {
Strategy strategyAnnotation = AnnotationUtils.findAnnotation( annotatedClass, Strategy.class );
if (!strategyAnnotation.type().isAssignableFrom( annotatedClass )) {
throw new StrategyProfileViolationException( String.format( "'%s' should be assignable from '%s'", strategyAnnotation.type(), annotatedClass ) );
}
this.strategiesCache.put( annotatedClass, strategyAnnotation );
if (this.isDefault( strategyAnnotation )) {
this.ifNotExistAdd( strategyAnnotation.type(), "default", usedStrategies );
} else {
for (String profile : strategyAnnotation.profiles()) {
this.ifNotExistAdd( strategyAnnotation.type(), profile, usedStrategies );
}
}
}
}
private void ifNotExistAdd(Class<?> type, String profile, Set<String> usedStrategies) {
String key = this.createKey( type, profile );
if (usedStrategies.contains( key )) {
throw new StrategyProfileViolationException( String.format( "There can only be a single strategy for each type, found multiple for type '%s' and profile '%s'", type, profile ) );
}
usedStrategies.add( key );
}
private String createKey(Class<?> type, String profile) {
return String.format( "%s_%s", type, profile ).toLowerCase();
}
private boolean isDefault(Strategy strategyAnnotation) {
return (strategyAnnotation.profiles().length == 0);
}
private Class<?> findStrategyMatchingProfile(Class<?> strategyClass, String currentProfile) {
for (Map.Entry<Class<?>, Strategy> strategyCacheEntry : this.strategiesCache.entrySet()) {
Strategy strategyCacheEntryValue = strategyCacheEntry.getValue();
if (strategyCacheEntryValue.type().equals( strategyClass )) {
if (currentProfile != null) {
for (String profile : strategyCacheEntryValue.profiles()) {
if (currentProfile.equals( profile )) {
Class<?> result = strategyCacheEntry.getKey();
if (LOG.isDebugEnabled()) {
LOG.debug( String.format( "Found strategy [strategy=%s, profile=%s, strategyImpl=%s]", strategyClass, currentProfile, result ) );
}
return result;
}
}
} else if (this.isDefault( strategyCacheEntryValue )) {
Class<?> defaultClass = strategyCacheEntry.getKey();
if (LOG.isDebugEnabled()) {
LOG.debug( String.format( "Found default strategy [strategy=%s, profile=%s, strategyImpl=%s]", strategyClass, currentProfile, defaultClass ) );
}
return defaultClass;
}
}
}
return null;
}
public void setPackages(String[] packages) {
this.packages = packages;
}
}
StrategyException.java
package ...
public class StrategyException extends RuntimeException {
...
}
StrategyNotFoundException.java
package ...
public class StrategyNotFoundException extends StrategyException {
...
}
StrategyProfileViolationException.java
package ...
public class StrategyProfileViolationException extends StrategyException {
...
}
Usage without Spring:
NavigationStrategy.java
package com.asimio.core.test.strategy.strategies.navigation;
public interface NavigationStrategy {
public String naviateTo();
}
FreeNavigationStrategy.java
package com.asimio.core.test.strategy.strategies.navigation;
#Strategy(type = NavigationStrategy.class)
public class FreeNavigationStrategy implements NavigationStrategy {
public String naviateTo() {
return "free";
}
}
LimitedPremiumNavigationStrategy.java
package com.asimio.core.test.strategy.strategies.navigation;
#Strategy(type = NavigationStrategy.class, profiles = { "limited", "premium" })
public class LimitedPremiumNavigationStrategy implements NavigationStrategy {
public String naviateTo() {
return "limited+premium";
}
}
Then
...
StrategyFactory factory = new StrategyFactory();
factory.setPackages( new String[] { "com.asimio.core.test.strategy.strategies.navigation" } );
this.factory.init();
NavigationStrategy ns = this.factory.getStrategy( NavigationStrategy.class );
String result = ns.naviateTo();
Assert.assertThat( "free", Matchers.is( result ) );
...
Or
...
String result = factory.getStrategy( NavigationStrategy.class, "limited" ).naviateTo();
Assert.assertThat( "limited+premium", Matchers.is( result ) );
...
Usage with Spring:
Spring context file:
<bean id="strategyFactory" class="com.asimio.core.strategy.StrategyFactory">
<property name="packages">
<list>
<value>com.asimio.jobs.feed.impl</value>
</list>
</property>
</bean>
IFeedProcessor.java
package ...
public interface IFeedProcessor {
void runBatch(String file);
}
CsvRentalsFeedProcessor.java
package ...
#Configurable(dependencyCheck = true)
#Strategy(type = IFeedProcessor.class, profiles = { "csv" })
public class CsvRentalsFeedProcessor implements IFeedProcessor, Serializable {
#Autowired
private CsvRentalsBatchReporter batchReporter;
...
}
Then
...
IFeedProcessor feedProcessor = this.strategyFactory.getStrategy( IFeedProcessor.class, feedFileExt );
feedProcessor.runBatch( unzippedFeedDir.getAbsolutePath() + File.separatorChar + feedFileName );
...
Notice CsvRentalsBatchReporter is "injected" in CsvRentalsFeedProcessor bean (a Strategy implementation) but StrategyFactory instantiates the strategy implementation using return (T) clazz.newInstance();, so what's needed to make this object Spring-aware?
First CsvRentalsFeedProcessor to be annotated with #Configurable(dependencyCheck = true) and when running the Java application this argument is needed in the java command: -javaagent:<path to spring-agent-${spring.version}.jar>
I'm using DynamoDB with the Java SDK, but I'm having some issues with querying nested documents. I've included simplified code below. If I remove the filter expression, then everything gets returned. With the filter expression, nothing is returned. I've also tried using withQueryFilterEntry(which I'd prefer to use) and I get the same results. Any help is appreciated. Most of the documentation and forums online seem to use an older version of the java sdk than I'm using.
Here's the Json
{
conf:
{type:"some"},
desc: "else"
}
Here's the query
DynamoDBQueryExpression<JobDO> queryExpression = new DynamoDBQueryExpression<PJobDO>();
queryExpression.withFilterExpression("conf.Type = :type").addExpressionAttributeValuesEntry(":type", new AttributeValue(type));
return dbMapper.query(getItemType(), queryExpression);
Is it a naming issue? (your sample json has "type" but the query is using "Type")
e.g. the following is working for me using DynamoDB Local:
public static void main(String [] args) {
AmazonDynamoDBClient client = new AmazonDynamoDBClient(new BasicAWSCredentials("akey1", "skey1"));
client.setEndpoint("http://localhost:8000");
DynamoDBMapper mapper = new DynamoDBMapper(client);
client.createTable(new CreateTableRequest()
.withTableName("nested-data-test")
.withAttributeDefinitions(new AttributeDefinition().withAttributeName("desc").withAttributeType("S"))
.withKeySchema(new KeySchemaElement().withKeyType("HASH").withAttributeName("desc"))
.withProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(1L).withWriteCapacityUnits(1L)));
NestedData u = new NestedData();
u.setDesc("else");
Map<String, String> c = new HashMap<String, String>();
c.put("type", "some");
u.setConf(c);
mapper.save(u);
DynamoDBQueryExpression<NestedData> queryExpression = new DynamoDBQueryExpression<NestedData>();
queryExpression.withHashKeyValues(u);
queryExpression.withFilterExpression("conf.#t = :type")
.addExpressionAttributeNamesEntry("#t", "type") // returns nothing if use "Type"
.addExpressionAttributeValuesEntry(":type", new AttributeValue("some"));
for(NestedData u2 : mapper.query(NestedData.class, queryExpression)) {
System.out.println(u2.getDesc()); // "else"
}
}
NestedData.java:
#DynamoDBTable(tableName = "nested-data-test")
public class NestedData {
private String desc;
private Map<String, String> conf;
#DynamoDBHashKey
public String getDesc() { return desc; }
public void setDesc(String desc) { this.desc = desc; }
#DynamoDBAttribute
public Map<String, String> getConf() { return conf; }
public void setConf(Map<String, String> conf) { this.conf = conf; }
}
I am trying to deserialize/map the below JSON to List<Bill> java object using Jackson json library. (this json was generated by jackson, Iam omitting that piece for brevity)
{"bills":[{"amount":"13","billId":"billid3"}]}
Here is my conversion method:
private static void convert(){
String jsonBill = "{\"bills\":[{\"amount\":\"13\",\"billId\":\"billid3\"}]}";
ObjectMapper mapper = new ObjectMapper();
List<Bill> bills = null;
try {
bills = mapper.readValue(jsonBill, new TypeReference<List<Bill>>() { });
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("bills = " + bills.size());
}
The Bill entity is below:
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS)
public class Bill {
private String amount;
private String billId;
public String getBillId() {
return billId;
}
public void setBillId(String billId) {
this.billId = billId;
}
public String getAmount() {
return amount;
}
public void setAmount(String amount) {
this.amount = amount;
}
}
and I get this error:
**org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.util.List out of START_OBJECT token
at [Source: java.io.StringReader#7a84e4; line: 1, column: 1]**
at org.codehaus.jackson.map.JsonMappingException.from(JsonMappingException.java:160)
at org.codehaus.jackson.map.deser.StdDeserializationContext.mappingException(StdDeserializationContext.java:194)
at org.codehaus.jackson.map.deser.CollectionDeserializer.deserialize(CollectionDeserializer.java:103)
at org.codehaus.jackson.map.deser.CollectionDeserializer.deserialize(CollectionDeserializer.java:93)
at org.codehaus.jackson.map.deser.CollectionDeserializer.deserialize(CollectionDeserializer.java:25)
at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:1980)
at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1278)
Here is my simplified spring3 controller which returns the i/p json (with Jackson mapping configured as default view):
#ModelAttribute("bills")
#RequestMapping(value = "/", method = RequestMethod.GET)
public List<Bill> fetchBills() throws IOException {
Bill bill = new Bill();
bill.setAmount("13");
bill.setBillId("billid3");
List<Bill> bills = new ArrayList<Bill>();
bills.add(bill);
return bills;
}
I guess I am missing something obvious.. but not sure what it is.. Any ideas?
The problem lies not in your code, but your example input. What you're actually trying to deserialize is an object with a field named "bills", not a list! What you should be using as input is:
[{"billId":"billid3","amount":"13"}]
This is an array of objects, which is converted to a list.
Try using ObjectWriter instead of ObjectMapper
Writer writer=new StringWriter();
ObjectWriter oWriter=om.writerWithType(new TypeReference<List<Bill>>() {
});
oWriter.writeValue(writer, result);
I'm using jackson 1.9.2