MongoDB Cannot use a complex object as a key value Java - java

I'm implementing an object that represents a graph to be stored in a MongoDb. Here's the class definition:
#Document(collection = "mygraph")
public class MyGraph {
#Id
#JsonSerialize(using = ToStringSerializer.class)
public String id;
public Map<Instance, Set<Instance>> graph;
public String timestamp;
/**
* Add the default constructor
*/
public MyGraph() {
this.id = new ObjectId().toHexString();
this.graph = new LinkedHashMap<>();
DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
this.timestamp = dateFormat.format(new Date());
}
/**
*
* #param instances
*/
public MyGraph(Set<Instance> instances) {
this.id = new ObjectId().toHexString();
this.graph = new LinkedHashMap<>();
DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
this.timestamp = dateFormat.format(new Date());
/* Initialize the graph */
for (Instance instance : instances) {
this.graph.put(instance, new HashSet<>());
}
}
// get and set method are below
}
However I'm getting the following error:
Time elapsed: 0.03 sec <<< FAILURE!
org.springframework.data.mapping.model.MappingException: Cannot use a complex object as a key value.
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writeMapInternal(MappingMongoConverter.java:669)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.createMap(MappingMongoConverter.java:585)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writePropertyInternal(MappingMongoConverter.java:471)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$3.doWithPersistentProperty(MappingMongoConverter.java:430)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$3.doWithPersistentProperty(MappingMongoConverter.java:418)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:312)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writeInternal(MappingMongoConverter.java:418)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writeInternal(MappingMongoConverter.java:392)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.write(MappingMongoConverter.java:356)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.write(MappingMongoConverter.java:79)
at org.springframework.data.mongodb.core.MongoTemplate.toDbObject(MongoTemplate.java:853)
at org.springframework.data.mongodb.core.MongoTemplate.doSave(MongoTemplate.java:1014)
at org.springframework.data.mongodb.core.MongoTemplate.save(MongoTemplate.java:963)
at org.springframework.data.mongodb.repository.support.SimpleMongoRepository.save(SimpleMongoRepository.java:80)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:503)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:488)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:460)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:61)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
at com.sun.proxy.$Proxy197.save(Unknown Source)
at models.CloudGraphTest.testSaveCloudGraph(CloudGraphTest.java:79)
How can I change my MappingMongoConverter to accomodate for that?

There's an open Improvement in Spring: Don't require Converters for Complex classes that are used as Ids.
Unfortunately, currently Spring team decided not to support it.
decided not to support it at all
EDIT
There's an open related issue Allow using complex types as Map keys that may include a fix later

Related

HttpClientErrorException$BadRequest: 400 BAD REQUEST when service return []

I'm running a service and it works perfectly when it returns some entidade. But when it doesn't find any, I get the following error:
org.springframework.web.client.HttpClientErrorException$BadRequest: 400 BAD REQUEST: [{
"entidades": [],
"saida": "" rua bandeirantes, 1578 - bairro: cent... (19970 bytes)]
at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:101)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:186)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:125)
at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:818)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:776)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:710)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:467)
at br.mp.mpf.cened01.cargaIaRJ.RJServico.executarServico(RJServico.java:953)
at br.mp.mpf.cened01.cargaIaRJ.RJServico.executarServicoByPagina(RJServico.java:857)
at br.mp.mpf.cened01.cargaIaRJ.RJServico.executarComPaginacao(RJServico.java:752)
at br.mp.mpf.cened01.cargaIaRJ.RJApplication.lambda$0(RJApplication.java:52)
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:804)
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:788)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:333)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1311)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1300)
at br.mp.mpf.cened01.cargaIaRJ.RJApplication.main(RJApplication.java:29)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
private RetornoServicoDto executarServico(String sentenca)
throws Exception {
RetornoServicoDto retornoServico = new RetornoServicoDto();
final String sentencaOri = sentenca;
RestTemplate restTemplate = new RestTemplate();
final Iterator<HttpMessageConverter<?>> iterator = restTemplate.getMessageConverters().iterator();
while (iterator.hasNext()) {
final HttpMessageConverter<?> converter = iterator.next();
if (converter instanceof StringHttpMessageConverter) {
iterator.remove();
}
}
restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
try {
ResponseEntity<RetornoServicoDto> responseEntity =
restTemplate.postForEntity(Constantes.SERVICO_URL,
sentencaOri, RetornoServicoDto.class);
retornoServico = responseEntity.getBody();
} catch (Exception e) {
e.printStackTrace();
}
return retornoServico;
}
#Data
public class RetornoServicoDto {
private List<EntidadeDto> entidades;
private String saida;
private String status;
}
#Data
public class EntidadeDto {
private String entidade;
private int id;
private PosicaoDto posicao;
private String texto;
}
What to do to work even when the entities list returns []?
Based on what I see from your log, your response object starts with an "[{..." so probably your server side is returning an list of RetornoServicoDto, not a single object that what you are asking the request for, since your problem is a bad request, would be great to see the service side that process the request to understand better.

How to fix 'NotSerializableException: java.time.format.DateTimeFormatter' Error

I am trying to use ObjectOutputStream to save all objects in an Arraylist to file. An attribute of the object is a LocalDate and whenever I try to write to file an error NotSerializableException: java.time.format.DateTimeFormatter returns despite having no DateTimeFormatter for any of the LocalDates
Full Error:
java.io.NotSerializableException: java.time.format.DateTimeFormatter
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1185)
at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1553)
at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1510)
at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1433)
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1179)
at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:349)
at java.base/java.util.ArrayList.writeObject(ArrayList.java:791)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at java.base/java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1130)
at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1497)
at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1433)
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1179)
at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:349)
at BikeNow.saveRent(BikeNow.java:330)
at BikeNow.main(BikeNow.java:114)
The method using Object output Stream
public void saveRent() {
//Create file and object output stream
try {
FileOutputStream fos = new FileOutputStream("tmp.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
//Write array to file
oos.writeObject(rents);
oos.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
An Example of object trying to be saved to file
rents.add(new Rent(0001, "John Smith", true, "Roubaix Sport", LocalDate.of(2019, 03, 06), LocalDate.of(2019, 04, 05), 30, true));
The Object Class
import java.io.Serializable;
import java.time.LocalDate;
public class Rent extends Customer implements Serializable {
private LocalDate startDate;
private LocalDate endDate;
private int duration;
private boolean overdue;
public Rent(int customerID, String customerName, boolean renting, String bikeRented, LocalDate startDate, LocalDate endDate, int duration, boolean overdue) {
super(customerID, customerName, renting, bikeRented);
this.startDate = startDate;
this.endDate = endDate;
this.duration = duration;
this.overdue = overdue;
}
There's not much to go on here, but clearly you're trying to write a DateTimeFormatter when you write your object. This leads me to believe there is one defined in Customer, but since DTF doesn't implement Serializable, it explodes.
The best solution is to edit your Customer class to properly serialize. The other option is to stuff it into your Rent class, but that might not be possible if the fields are private.

java.util.List.iterator() is no accessor method

When I am trying to access, List method iterator() i am getting below exception:
java.lang.IllegalArgumentException: Invoked method public abstract java.util.Iterator java.util.List.iterator() is no accessor method!
at org.springframework.util.Assert.notNull(Assert.java:115) ~[spring-core-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.data.projection.MapAccessingMethodInterceptor$Accessor.<init>(MapAccessingMethodInterceptor.java:97) ~[spring-data-commons-1.12.6.RELEASE.jar:na]
at org.springframework.data.projection.MapAccessingMethodInterceptor.invoke(MapAccessingMethodInterceptor.java:62) ~[spring-data-commons-1.12.6.RELEASE.jar:na]
at org.springframework.data.projection.ProjectingMethodInterceptor.invoke(ProjectingMethodInterceptor.java:75) ~[spring-data-commons-1.12.6.RELEASE.jar:na]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.data.projection.ProxyProjectionFactory$TargetAwareMethodInterceptor.invoke(ProxyProjectionFactory.java:218) ~[spring-data-commons-1.12.6.RELEASE.jar:na]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE]
Code snippet below:
#RequestMapping (value = "/{field}", method = RequestMethod.PATCH, produces = "application/json")
#ResponseBody
public void applyPatchRequest(#PathVariable String field ,JsonArray operations) {
LookupPatchDto lookupPatchDto = new LookupPatchDto();
lookupPatchDto.setField(field);
lookupPatchDto.setVals(lookupValsService.getAllByField(field));
final JsonPatch patch = Json.createPatch(operations);
final JsonObject result = patch.apply(lookupPatchConverter.toJson(lookupPatchDto));
lookupPatchDto = lookupPatchConverter.fromJson(result);
lookupValsService.save(lookupPatchDto.getVals());
}
I am getting error on below line while applying apply function.
final JsonObject result = patch.apply(lookupPatchConverter.toJson(lookupPatchDto));

NonUniqueObjectException in Confluence with scheduled job

Got a huge problem with quartz Job in Confluence, regarding the page creation.
QUARTZ Job class:
#ComponentImport
private final SpaceManager spaceManager;
#Autowired
private final GeneralConfig config;
#Autowired
private final PageCreator pageCreator;
#ComponentImport
private final PageManager pageManager;
#Autowired
public ReportingPluginJob(GeneralConfig config, SpaceManager spaceManager, PageCreator pageCreator,
PageManager pageManager) {
this.config = config;
this.spaceManager = spaceManager;
this.pageCreator = pageCreator;
this.pageManager = pageManager;
}
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(ReportingPluginJob.class);
#Override
public void execute(JobExecutionContext jec) throws JobExecutionException {
Collection<String> keys = spaceManager.getAllSpaceKeys(SpaceStatus.CURRENT);
String parentPageName = config.getSchedulerWeeklyParentPageName();
for (String key : keys) {
Page parentPage
= pageManager.getPage(key, parentPageName);
if (parentPage != null) {
LOG.debug("Creating weekly report for space " + key);
long pageId = parentPage.getId();
try {
pageCreator.createEazyBiReport(key, pageId);
} catch (ApplicationException e) {
LOG.error("FAILED TO CREATE A REPORT FOR SPACE " + key + " with error: " + System.lineSeparator()
+ e.getMessage());
}
}
}
}
PAGE CREATION class:
Space space = spaceManager.getSpace(spaceKey);
if (page != null) {
page.setTitle(pageTitle);
page.setSpace(space);
page.setVersion(1);
page.addLabelling(new Labelling(label, page.getEntity(), AuthenticatedUserThreadLocal.get()));
page.setCreator(AuthenticatedUserThreadLocal.get());
page.setCreationDate(new Date());
Page parent = pageManager.getPage(parentId);
if (parent != null) {
parent.addChild(page);
}
}
pageManager.saveContentEntity(page, DefaultSaveContext.builder().suppressNotifications(true).build());
attachmentProvider.attachExcelFileToPage(page);
AND Finally the exception which is driving me mad:
org.springframework.dao.DuplicateKeyException: A different object with the same identifier value was already associated with the session : [com.atlassian.confluence.spaces.Space#31653891]; nested exception is org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session : [com.atlassian.confluence.spaces.Space#31653891]
at org.springframework.orm.hibernate5.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:259)
at org.springframework.orm.hibernate5.HibernateTemplate.doExecute(HibernateTemplate.java:362)
at org.springframework.orm.hibernate5.HibernateTemplate.executeWithNativeSession(HibernateTemplate.java:326)
at org.springframework.orm.hibernate5.HibernateTemplate.saveOrUpdate(HibernateTemplate.java:704)
at com.atlassian.confluence.core.persistence.hibernate.HibernateObjectDao.saveRaw(HibernateObjectDao.java:207)
at com.atlassian.confluence.pages.persistence.dao.hibernate.CachingPageDao.saveRaw(CachingPageDao.java:157)
at com.atlassian.confluence.core.DefaultContentEntityManager.saveContentEntity(DefaultContentEntityManager.java:150)
at com.atlassian.confluence.pages.DefaultPageManager.saveContentEntity(DefaultPageManager.java:1388)
at sun.reflect.GeneratedMethodAccessor2132.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:302)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at com.atlassian.spring.interceptors.SpringProfilingInterceptor.invoke(SpringProfilingInterceptor.java:16)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at com.atlassian.confluence.util.profiling.ConfluenceMonitoringMethodInterceptor.invoke(ConfluenceMonitoringMethodInterceptor.java:34)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)
at com.sun.proxy.$Proxy105.saveContentEntity(Unknown Source)
at sun.reflect.GeneratedMethodAccessor2132.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.atlassian.plugin.util.ContextClassLoaderSettingInvocationHandler.invoke(ContextClassLoaderSettingInvocationHandler.java:26)
at com.sun.proxy.$Proxy253.saveContentEntity(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:302)
at org.eclipse.gemini.blueprint.service.importer.support.internal.aop.ServiceInvoker.doInvoke(ServiceInvoker.java:56)
at org.eclipse.gemini.blueprint.service.importer.support.internal.aop.ServiceInvoker.invoke(ServiceInvoker.java:60)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:133)
at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:121)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.eclipse.gemini.blueprint.service.util.internal.aop.ServiceTCCLInterceptor.invokeUnprivileged(ServiceTCCLInterceptor.java:70)
at org.eclipse.gemini.blueprint.service.util.internal.aop.ServiceTCCLInterceptor.invoke(ServiceTCCLInterceptor.java:53)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.eclipse.gemini.blueprint.service.importer.support.LocalBundleContextAdvice.invoke(LocalBundleContextAdvice.java:57)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:133)
at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:121)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)
at com.sun.proxy.$Proxy2863.saveContentEntity(Unknown Source)
at com.censored.atlassian.plugins.service.PageCreator.createEazyBiReport(PageCreator.java:128)
at com.censored.atlassian.plugins.service.ReportingPluginJob.execute(ReportingPluginJob.java:64)
at com.atlassian.confluence.plugin.descriptor.JobModuleDescriptor$DelegatingPluginJob.lambda$execute$0(JobModuleDescriptor.java:113)
at com.atlassian.confluence.impl.vcache.VCacheRequestContextManager.doInRequestContextInternal(VCacheRequestContextManager.java:87)
at com.atlassian.confluence.impl.vcache.VCacheRequestContextManager.doInRequestContext(VCacheRequestContextManager.java:71)
at com.atlassian.confluence.plugin.descriptor.JobModuleDescriptor$DelegatingPluginJob.execute(JobModuleDescriptor.java:112)
at org.quartz.core.JobRunShell.run(JobRunShell.java:223)
at com.atlassian.confluence.schedule.quartz.ConfluenceQuartzThreadPool.lambda$runInThread$0(ConfluenceQuartzThreadPool.java:16)
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:549)
According the stack trace, I've got a problem during the page save (saveContentEntity). It seems, the page save needs to update a space associated with the page too.
And here comes the problem. According to the stack trace, this space exists in the Hibernate session already. evict and clear of the actual session does not work.
Any suggestions, how can I handle this exception, or how can I actually remove the space from Hibernate session?
Apparently, this is what's happening in your code:
Within your SpaceManager, your session finds a Session object with ID 1 (assume that) and returns it.
Your method uses this reference to associate with another object, a Page one.
And finally, your PageManager persist this data in the database.
Probably, your problem is problem that:
From step 1 to 2, when the SpaceManager returns the data, your transaction is ended and your returned object becomes detached.
After that, when you call save, you are trying to persist a detached Session, but it already exists in the database. Then you get an exception.
You could try to use the merge method or remove the cascade from this reference in your entity.
(Please provide your entity code so I can improve my answer if there's anything wrong or which don't work for you.)
We ran into the same problem and what solved it for us was to put the code that creates the page (and loads other objects, like the space or the parent page) all into a transaction:
private void createNewPage(String title, String pageInput, Long parentPageId, String spaceKey) {
transactionTemplate.execute(() -> {
Page page = new Page();
page.setTitle(title);
page.setBodyAsString(pageInput);
Space space = spaceManager.getSpace(spaceKey);
page.setSpace(space);
Page parentPage = pageManager.getPage(parentPageId);
if (parentPage != null) {
page.setParentPage(parentPage);
parentPage.addChild(page);
}
pageManager.saveContentEntity(page, DefaultSaveContext.DEFAULT);
return null;
});
}
com.atlassian.sal.api.transaction.TransactionTemplate can be injected with #ComponentImport

How to store Date field as ISODate() using jackson in MongoDb

I am trying to persist a java object having java.util.Date field in mongo collection using fasterxml jackson.
The problem is the default nature of objectMapper is to store Date as NumberLong type.
For e.g , a createdTime field of java.util.Date type gets stored as below:
"createdTime" : NumberLong("1427728445176")
I want to store it in ISODate format which is available in mongo Shell.
Now, i know there is way to format object mapper to store Date in a String dateformat.
But I am ONLY looking for ISODate() format.
For e.g
"createdTime" : ISODate("2015-01-20T16:39:42.132Z")
Is there a way to do that ?
Please advise gurus .
Thanks in advance for help.
What you need is the Jackson Joda Module. If you import that into your classpath, you can do the following on your mapper to write it as your desired Timestamp:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JodaModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
mapper.writeValueAsString(date);
You can replace date in the code sample above with your POJO as necessary.
Edit:
It looks like what you really want is a custom serializer. That would look something like this:
public class IsoDateSerializer extends JsonSerializer<DateTime> {
#Override
public void serialize(DateTime value, JsonGenerator jgen, SerializerProvider provider) {
String isoDate = ISODateTimeFormat.dateTime().print(value);
jgen.writeRaw("ISODATE(\"" + isoDate + "\")");
}
Then you'll either register it on the mapper for all DateTime types
mapper.addSerializer(DateTime.class, new IsoDateSerializer());
or specify it on the function using annotations
#JsonSerializer(using = IsoDateSerializer.class)
public DateTime createdTime;
I was able to serialize the date string to ISODate format. I wrote a customer date serializer like below.
public void serialize(Date date, JsonGenerator jgen, SerializerProvider provider) throws IOException {
String dateValue = getISODateString(date);
String text = "{ \"$date\" : \""+ dateValue +"\"}";
jgen.writeRawValue(text);
}
Based on request from user #mmx73, I am adding code for customer Date DeSeriaizer.
public class IsoDateDeSerializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException, JsonProcessingException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
String dateValue = node.get("$date").asText();
//DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
Date date = null;
try {
date = df.parse(dateValue);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
None of these answers accomplished what I wanted. I was having trouble because when I serialized the JSON string to MongoDB, it was stored as a String. A nicely formatted string, but a string none the less.
I use the com.fasterxml.jackson.databind.ObjectMapper to convert my objects to/from JSON and I wanted to continue using this class. I have the following method:
public enum JsonIntent {NONE, MONGODB};
public static ObjectMapper getMapper(final JsonIntent intent) {
ObjectMapper mapper = new ObjectMapper();
// Setting to true saves the date as NumberLong("1463597707000")
// Setting to false saves the data as "2016-05-18T19:30:52.000+0000"
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.registerModule(new JodaModule());
if (intent == JsonIntent.MONGODB) {
// If you want a date stored in MONGO as a date, then you must store it in a way that MONGO
// is able to deal with it.
SimpleModule testModule = new SimpleModule("MyModule", new Version(1, 0, 0, null, null, null));
testModule.addSerializer(Date.class, new StdSerializer<Date>(Date.class) {
private static final long serialVersionUID = 1L;
#Override
public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
try {
if (value == null) {
jgen.writeNull();
} else {
jgen.writeStartObject();
jgen.writeFieldName("$date");
String isoDate = ISODateTimeFormat.dateTime().print(new DateTime(value));
jgen.writeString(isoDate);
jgen.writeEndObject();
}
} catch (Exception ex) {
Logger.getLogger(JsonUtil.class.getName()).log(Level.SEVERE, "Couldn't format timestamp " + value + ", writing 'null'", ex);
jgen.writeNull();
}
}
});
testModule.addDeserializer(Date.class, new StdDeserializer<Date>(Date.class) {
private static final long serialVersionUID = 1L;
#Override
public Date deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonNode tree = jp.readValueAsTree();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
try {
return ISODateTimeFormat.dateTime().parseDateTime(tree.get("$date").textValue()).toDate();
} catch (Throwable t) {
throw new IOException(t.getMessage(), t);
}
}
});
mapper.registerModule(testModule);
}
return mapper;
}
Now, I can run the following test code:
BObjectMapper mapper = getMapper(JsonUtil.JsonIntent.NONE);
Date d1 = new Date();
String v = mapper.writeValueAsString(d1);
System.out.println("Joda Mapping: " + v);
Date d2 = mapper.readValue(v, Date.class);
System.out.println("Decoded Joda: " + d2);
mapper = getMapper(JsonUtil.JsonIntent.MONGODB);
v = mapper.writeValueAsString(d1);
System.out.println("Mongo Mapping: " + v);
d2 = mapper.readValue(v, Date.class);
System.out.println("Decoded Mongo: " + d2);
The output is as follows:
Joda Mapping: "2016-06-13T14:58:11.937+0000"
Decoded Joda: Mon Jun 13 10:58:11 EDT 2016
Mongo Mapping: {"$date":"2016-06-13T10:58:11.937-04:00"}
Decoded Mongo: Mon Jun 13 10:58:11 EDT 2016
Note that the JSON that will be sent to MONGODB defines the value containing a field named "$date". This tells MongoDB that this is a date object it seems.
When I look at Mongo, I see the following:
"importDate" : ISODate("2016-05-18T18:55:07Z")
Now, I can access the field as a date rather than as a string.
To add an encoded JSON string to Mongo, my code is as follows:
MongoDatabase db = getDatabase();
Document d = Document.parse(json);
db.getCollection(bucket).insertOne(d);
In this case, "json" is the encoded JSON string. Because it is coming from a JSON string, it has no way of knowing the types unless it infers this, which is why we needed the "$date" portion. The "bucket" is just a string indicating which table to use.
As a side note, I found out that if I pull a BSON object from Mongo and convert it to a JSON string by calling doc.toJson() (where doc is of type org.bison.Document as returned from a query), the date object is stored with a long value rather than a formatted text string. I did not check to see if I could push data into mongo after formatting in this way, but, you can modify the deserializer shown above to support this as follows:
testModule.addDeserializer(Date.class, new StdDeserializer<Date>(Date.class) {
private static final long serialVersionUID = 1L;
#Override
public Date deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonNode tree = jp.readValueAsTree();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
try {
// Mongo will return something that looks more like:
// {$date:<long integer for milliseconds>}
// so handle that as well.
JsonNode dateNode = tree.get("$date");
if (dateNode != null) {
String textValue = dateNode.textValue();
if (!Util.IsNullOrEmpty(textValue)) {
return ISODateTimeFormat.dateTime().parseDateTime(textValue).toDate();
}
return Util.MillisToDate(dateNode.asLong());
}
return null;
} catch (Throwable t) {
Util.LogIt("Exception: " + t.getMessage());
throw new IOException(t.getMessage(), t);
}
}
});
You can convert milliseconds to a Date or DateTime as follows:
/**
* Convert milliseconds to a date time. If zero or negative, just return
* null.
*
* #param milliseconds
* #return
*/
public static Date MillisToDate(final long milliseconds) {
if (milliseconds < 1) {
return null;
}
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(milliseconds);
return calendar.getTime();
}
public static DateTime MillisToDateTime(final long milliseconds) {
if (milliseconds < 1) {
return null;
}
return new DateTime(milliseconds);
}
In case you get some message like
com.fasterxml.jackson.core.JsonGenerationException: Can not write a field name, expecting a value.
Make sure you use writeRawValue instead within the accepted answer. That ends the field correctly, otherwise the next field to be serialize may throw this error.
You can solve this issue by reading/writing bson instead of json. Here is a test class:
package com.nagra.jongo.mapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import de.undercouch.bson4jackson.BsonFactory;
import de.undercouch.bson4jackson.deserializers.BsonDateDeserializer;
import de.undercouch.bson4jackson.serializers.BsonDateSerializer;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.Date;
/**
* Uses Bson4Jackson 2.9.0
*
* <!-- https://mvnrepository.com/artifact/de.undercouch/bson4jackson -->
* <dependency>
* <groupId>de.undercouch</groupId>
* <artifactId>bson4jackson</artifactId>
* <version>2.9.2</version>
* </dependency>
*/
public class ObjectMapperTest {
private ObjectMapper mapper = new ObjectMapper(new BsonFactory());
private static class WrappedDate {
private Date Date = new Date(0);
public WrappedDate() {
}
public Date getDate() {
return Date;
}
public void setDate(Date Date) {
this.Date = Date;
}
}
#Before
public void setUp() {
SimpleModule module = new SimpleModule();
module.addSerializer(Date.class, new BsonDateSerializer());
module.addDeserializer(Date.class, new BsonDateDeserializer());
mapper.registerModule(module);
}
#Test
public void testDate() throws IOException {
WrappedDate date = new WrappedDate();
byte[] b = mapper.writeValueAsBytes(date);
WrappedDate i = mapper.readValue(b, WrappedDate.class);
Assert.assertEquals(date.getDate(), i.getDate());
System.out.println(i.getDate());
}}

Categories