I'm trying to implement the MongoDB client side auto encryption/decryption.
Below a part of the code.
I started manually the mongocryptd and everything looks fine (the mongocryptd is correctly detected on the app startup).
When I call saveCustomer(customer) of the service, the saved data is NOT encrypted and no data is created in the __keyVault collection.
Configuation:
#RequiredArgsConstructor
#EnableTransactionManagement
#Configuration
public class MongodbConfig extends AbstractReactiveMongoConfiguration {
private final DatabaseConfig databaseConfig;
#Override
public MongoClient reactiveMongoClient() {
byte[] masterKey = new byte[96];
try (FileInputStream fis = new FileInputStream(databaseConfig.getEncryptionLocalMasterKey())) {
fis.read(masterKey, 0, 96);
Map<String, Map<String, Object>> kmsProviders = Map.of("local", Map.of("key", masterKey));
String keyVaultNamespace = databaseConfig.getDatabase()+".__keyVault";
// MongoCLient
AutoEncryptionSettings encryptionSettings = AutoEncryptionSettings.builder()
.keyVaultNamespace(keyVaultNamespace)
.kmsProviders(kmsProviders)
.bypassAutoEncryption(false)
.build();
return super.createReactiveMongoClient(MongoClientSettings.builder()
.autoEncryptionSettings(encryptionSettings)
.applyConnectionString(new ConnectionString(databaseConfig.getUri()))
.build());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Entity:
#Data
#Document
#Encrypted(keyId = "xKVup8B1Q+CkHaVRx+qa+g==")
public class Customer {
#Id private String id;
#Indexed(unique = true)
#Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
private String email;
#Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
private String firstName;
#Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
private String lastName;
}
Service:
#Service
#RequiredArgsConstructor
public class MyService {
private final MongoOperations operations;
public Mono<Customer> saveCustomer(Customer customer) {
return operations.save(customer);
}
}
Related
I am using spring data Mongodb in my project and refer the below classes for my query on grouping the results:
Student class:
#Document(collection = "student")
public class Student {
#Id
private String id;
private String firstName;
private String lastName;
//other fields
//getters & setters
}
StudentResults (dto):
public class StudentResults {
private String firstName;
private List<String> studentIds; //I need List<Student> here
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public List<String> getStudentIds() {
return studentIds;
}
public void setStudentIds(List<String> studentIds) {
this.studentIds = studentIds;
}
}
StudentServiceImpl class:
public class StudentServiceImpl implements StudentService {
#Autowired
private MongoTemplate mongoTemplate;
public List<StudentResults> findStudentsGroupByFirstName() {
TypedAggregation<Student> studentAggregation =
Aggregation.newAggregation(Student.class,
Aggregation.group("firstName").
addToSet("id").as("studentIds"),
Aggregation.project("studentIds").
and("firstName").previousOperation());
AggregationResults<StudentResults> results = mongoTemplate.
aggregate(studentAggregation, StudentResults.class);
List<StudentResults> studentResultsList = results.getMappedResults();
return studentResultsList;
}
}
Using the above code, I am able to retrieve the List<String> studentIds successfully, but I need to retrieve List<Student> students using Aggregation.group()? Can you help?
Change your TypedAggregation part to below and add students field to StudentResults
TypedAggregation<Student> studentAggregation = Aggregation.newAggregation(Student.class,
Aggregation.group("firstName").
push("$$ROOT").as("students"));
$$ROOT will push the whole document.
Update:
TypedAggregation<Student> studentAggregation = Aggregation.newAggregation(Student.class,
Aggregation.group("firstName").
push(new BasicDBObject
("_id", "$_id").append
("firstName", "$firstName").append
("lastName", "$lastName")).as("students"));
i have this DTO:
#NoArgsConstructor
public class DataDTO implements DTO {
private static final long serialVersionUID = -5105904799152965475L;
private Long deviceId;
private OffsetDateTime generatedOn;
public Long getDeviceId() {
return deviceId;
}
public void setDeviceId(Long deviceId) {
this.deviceId = deviceId;
}
public OffsetDateTime getGeneratedOn() {
return generatedOn;
}
public void setGeneratedOn(OffsetDateTime generatedOn) {
this.generatedOn = generatedOn;
}
}
i have this MongoDB document:
#Document(collection = "data")
#EqualsAndHashCode
public class DataDocument {
private static final long serialVersionUID = 1772572723546311500L;
#Id
private IdByDeviceIdAndGeneratedOn id;
public DataDocument() {
}
public IdByDeviceIdAndGeneratedOn getId() {
return id;
}
public void setId(IdByDeviceIdAndGeneratedOn id) {
this.id = id;
}
}
and this is the #Id class for MongoDB Document:
#EqualsAndHashCode
#ToString
public class IdByDeviceIdAndGeneratedOn {
#Id
private final Long deviceId;
#Id
#Field("generated_on")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private final OffsetDateTime generatedOn;
public IdByDeviceIdAndGeneratedOn(final Long deviceId, final OffsetDateTime generatedOn) {
this.deviceId = Objects.requireNonNull(deviceId);
this.generatedOn = Objects.requireNonNull(generatedOn);
}
public Long getDeviceId() {
return deviceId;
}
public OffsetDateTime getGeneratedOn() {
return generatedOn;
}
}
this is the mapper for this Key class:
#Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR, componentModel = "spring")
public interface IdByDeviceIdAndGeneratedOnMapper {
default IdByDeviceIdAndGeneratedOn toId(final Long deviceId, final OffsetDateTime generatedOn) {
return new IdByDeviceIdAndGeneratedOn(deviceId, generatedOn);
}
default Long getDeviceId(final IdByDeviceIdAndGeneratedOn id) {
return id.getDeviceId();
}
default OffsetDateTime getGeneratedOn(final IdByDeviceIdAndGeneratedOn id) {
return id.getGeneratedOn();
}
and this is the #Mapper for DataDTO and DataDocument:
#Mapper( unmappedTargetPolicy = ReportingPolicy.ERROR,
uses = {IdByDeviceIdAndGeneratedOnMapper.class,
AccelerometerDocumentMapper.class,
GpsDocumentMapper.class,
GsmDocumentMapper.class
})
public interface DataDocumentMapper extends DocumentMapper<DataDTO, DataDocument> {
}
this is the generic mapper:
/**
* Contract for a generic dto to entity mapper.
*
* #param <DTO> - DTO source type parameter.
* #param <DOCUMENT> - MongoDB Document destination type parameter.
*/
public interface DocumentMapper<DTO, DOCUMENT> {
DOCUMENT toDocument(DTO dto);
DTO toDto(DOCUMENT document);
}
Currently i'm receiving this errors:
for MongoDB Data docment:
Unmapped target property: "id".
for DTO:
Unmapped target properties: "deviceId, generatedOn".
How to solve this errors without loosing immutability of Id class?
What you are trying to do is to use (using constructors to construct objects) is not yet supported. There is an open issue for it #73.
However, you can achieve what you are looking for by using Object factories, this is for the toDocument mapping, for the toDto mapping you can use nested source mappings.
Your mapper would look like:
#Mapper(uses = {AccelerometerDocumentMapper.class,
GpsDocumentMapper.class,
GsmDocumentMapper.class},
componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface DataDocumentMapper extends DocumentMapper<DataDTO, DataDocument> {
#Mapping(target = "id", source = "dto")
#Override
DataDocument toDocument(DataDTO dto);
#ObjectFactory
default IdByDeviceIdAndGeneratedOn createId(DataDTO dto) {
return dto == null ? null : new IdByDeviceIdAndGeneratedOn(dto.getDeviceId(), dto.getGeneratedOn());
}
#Mapping(target = "deviceId", source = "id.deviceId")
#Mapping(target = "generatedOn", source = "id.generatedOn")
#Override
DataDTO toDto(DataDocument document);
}
NB: You can also make DataDocumentMapper abstract class and make the createId method protected, in case you don't want to expose it in the interface
this is solved my problem, but this doesnt look elegant.
Maybe there is more elegant way?
#Mapper(uses = {AccelerometerDocumentMapper.class,
GpsDocumentMapper.class,
GsmDocumentMapper.class},
imports = {IdByDeviceIdAndGeneratedOn.class},
componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface DataDocumentMapper extends DocumentMapper<DataDTO, DataDocument> {
#Override
#Mapping(target = "id", expression = "java( new IdByDeviceIdAndGeneratedOn(dto.getDeviceId(), dto.getGeneratedOn()) )")
DataDocument toDocument(DataDTO dto);
#Override
#Mapping(target = "deviceId", expression = "java( document.getId().getDeviceId() )")
#Mapping(target = "generatedOn", expression = "java( document.getId().getGeneratedOn() )")
DataDTO toDto(DataDocument document);
}
I can't understand where I'm going wrong when saving a List within and JPA Entity.
I have a super class Person. Client class extends Person. Client class has a list of Phone entities as #OneToMany (Bidirection) as code shown below. Whenever a Client entity is persisted with that phone list, all phones in list are saved as well. However, in Phone Table there are no client id recorded.
#Entity#Inheritance(strategy=InheritanceType.SINGLE_TABLE)#DiscriminatorColum(name="type")
public abstract class Person implements Serializable {
private static final long serialVersionUID = 1L;
#Id #GeneratedValue(strategy=GenerationType.SEQUENCE,generator="PERSON_SEQ")
#SequenceGenerator(name="PERSON_SEQ",sequenceName="PERSON_SEQ", allocationSize=1,initialValue=1000)
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}}
Client.class
public class Client extends Person implements Serializable {
private static final long serialVersionUID = 1L;
private String foo;
#OneToMany(cascade=CascadeType.ALL,mappedBy="owner")
private List<Phone> phones;
public List<Phone> getPhones() {
return phones;
}
public void setPhones(List<Phone> phones) {
this.phones = phones;
}
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
}
Phone class
public class Phone implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long idPhone;
private String number;
#ManyToOne(fetch=FetchType.EAGER) #JoinColumn(name="id")
private Person owner;
public Long getIdPhone() {
return idPhone;
}
public void setIdPhone(Long idPhone) {
this.idPhone = idPhone;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public Person getOwner() {
return owner;
}
public void setOwner(Person owner) {
this.owner = owner;
}
}
ClientController class
#Named(value = "clientController")
#ViewScoped
public class ClientController extends BaseController implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Inject
private ClientService service;
#Inject
private Client client;
#Inject
private Employee employee;
#Inject
private Phone phone;
public void save(ActionEvent event) {
System.out.println(" Saving in Controller");
try {
client = new Client();
employee = new Employee();
Phone p1 = new Phone();
p1.setNumber("99998888");
Phone p2 = new Phone();
p2.setNumber("88887777");
List<Phone> phones = new ArrayList<Phone>();
phones.add(p1);
phones.add(p2);
client.setName("Novembro" );
client.setPhones(phones);
employee.setPhones(phones);
client.setFoo("foo value" );
employee.setBar("bar value");
service.saveOrUpdate(client);
//client = new Client();
addMessage(FacesMessage.SEVERITY_INFO, "Cliente registrado com sucesso");
} catch (Exception e) {
addMessage(FacesMessage.SEVERITY_ERROR, "Tente mais tarde");
e.printStackTrace();
}
}
}
Client Service class
public class ClientService implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Inject
private ClientDAO dao;
public Client saveOrUpdate(Client client) {
System.out.println(" Saving in Service");
return dao.save(client);
}
}
DAO save method
public T save(T entity) {
beginTransaction();
em.persist(entity);
em.flush();
commitAndCloseTransaction();
return entity;
}
I can't understand why it is not working as expected. I mean, save phones entities with ID from Client who owns the phones.
Database Postgresql 9.6
EclipseLink 2.6.0
JPA 2.1
All classes have #Entity and #Discrimator annotations
#Entity
#DiscriminatorValue(value="C")
#Entity
#Table(name="PHONE")
Here are the entity snippet.......Ignore DaoObject..it just adds autogenerated Id to each entity it subclasses.
I've highlighted which is necessary to persist Content. Eventullally it is not able to add value to contentid column in streams table. It though adds a row to both content and streams table with below code..
Please check and help me troubleshoot the problem
Content newContent = TestHelper.contentFactory(null, "streamForCREATE_IT", xxxx, 100);
Stream stream = TestHelper.streamFactory(null, "name", "appname",);
**stream.setContent(newContent);
List<Stream> streams = new ArrayList<Stream>();
streams.add(stream);
newContent.setStreams(streams);**
#Entity
public class Content extends DaoObject {
public Content() {
};
private String name;
private ContentType type;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "content", cascade = {CascadeType.PERSIST,CascadeType.MERGE}, orphanRemoval = true)
#JsonManagedReference
private List<Stream> streams = new ArrayList<Stream>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Stream> getStreams() {
return streams;
}
public void setStreams(List<Stream> streams) {
this.streams = streams;
}
public void addStream(Stream newStream) {
if (streams == null) {
streams = new ArrayList<Stream>();
}
newStream.setContent(this);
streams.add(newStream);
}
}
#Entity
public class Stream extends DaoObject {
public Stream() {
}
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "contentid")
#JsonBackReference
private Content content;**
private String name;
private String appName;
public Content getContent() {
return content;
}
public void setContent(Content content) {
this.content = content;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAppName() {
return appName;
}
public void setAppName(String appName) {
this.appName = appName;
}
}
Here is the image from mysql workbench.
Damn it was stupid. Am testing REST API where this is happening and I did set streams for content and each stream's content correctly but that was all happening before JSON transformation. When this association is recreated in dao it worked. Thanks #AndreiI for asking correct questions which helped me resolve this.
How can I implement a bidirectional one-to-one mapping using Google Application Engine (GAE) using Java Data Objects (JDO)?
I have a User class which holds contactInfo object and a ContactInfo class that holds a user object
#PersistenceCapable(identityType ="APPLICATION", detachable = "true")
public class User{
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
private String name;
#Persistent(dependent = "true")
private ContactInfo child;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ContactInfo getChild() {
return child;
}
public void setChild(ContactInfo child) {
this.child = child;
}
}
#PersistenceCapable(identityType ="APPLICATION", detachable = "true")
public class ContactInfo {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key id;
#Persistent(mappedBy = "child")
private User parent;
private String contactDetail;
public Key getId() {
return id;
}
public void setId(Key id) {
this.id = id;
}
public String getContactDetail() {
return contactDetail;
}
public void setContactDetail(String contactDetail) {
this.contactDetail = contactDetail;
}
}
Following error i am getting while testing API from API explorer
com.google.appengine.repackaged.org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: com.demo.jdo.ContactInfo[\"user\"]->com.demo.jdo.User[\"contactInfo\"]->com.demo.jdo.ContactInfo[\"user\"]-
Standard JDO 1-1 bidir is simply found from http://www.datanucleus.org/products/accessplatform_3_1/jdo/orm/one_to_one.html#bi
GAE ought to be no different in this respect; last time I used it (maybe 3 yrs ago) they had some tests, think those under here http://code.google.com/p/datanucleus-appengine/source/browse/#svn%2Ftrunk%2Ftests%2Fcom%2Fgoogle%2Fappengine%2Fdatanucleus
Your question provides no definition of what you have tried in terms of annotations
Got the Solution, problem was with wrong use of mappedBy & presence of getter and setter of parent object in child.
#Persistent(mappedBy = "") annotation should only be at non-owner side
In on-owner/ child side there should not be any getter/setter present for owner/parent object.
Working code:
User.java
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
#PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "true")
public class User {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
private String name;
#Persistent(dependent = "true")
private ContactInfo contactInfo;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ContactInfo getContactInfo() {
return contactInfo;
}
public void setContactInfo(ContactInfo contactInfo) {
this.contactInfo = contactInfo;
}
}
ContactInfo.java
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import com.google.appengine.api.datastore.Key;
#PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "true")
public class ContactInfo {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key id;
#Persistent(mappedBy = "contactInfo")
/*
* Important: Do not create getter and setters for this object else
* bidirectional mapping gives error
*/
private User user;
private String contactDetail;
public Key getId() {
return id;
}
public void setId(Key id) {
this.id = id;
}
public String getContactDetail() {
return contactDetail;
}
public void setContactDetail(String contactDetail) {
this.contactDetail = contactDetail;
}
}