Get spring-data-mongodb to honor getter/setter without backing field? - spring-data-mongodb

I have a general-purpose POJO:
public class Thing {
private String name;
private String etc;
public String getName() {
return name;
}
// other getters and setters
}
I'm using Spring 4.3.9 and Spring-data-mongodb 1.10.4. I want to store instances of this POJO in Mongodb, but I have some constraints:
I can't add Spring annotations to the base class (but I can subclass Thing and annotate that).
I want to use the name field as the Mongodb unique ID (mainly to avoid creating a separate unique index for it).
I want to (redundantly) store the name field as an actual field named "name", so that other consumers of the collection don't have to know that "name" is stored in the _id.
I started out trying this:
public class SpringThing extends Thing {
#Id
#Override
public String getName() {
return super.getName();
}
#Override
public void setName(String name) {
super.setName(name);
}
}
This causes spring to use the value of name for _id, but of course it doesn't store a field named "name" in Mongodb. The documentation says that spring will use a "property or field" named "id" or annotated with #Id. So I tried defining a redundant getter/setter which accesses the name field:
public class SpringThing extends Thing {
#Id
public String getId() {
return super.getName();
}
public void setId(String id) {
super.setName(id);
}
}
Unfortunately, spring ignores getId and setId here, and stores the object with an autogenerated ID. I also tried creating redundant getters/setters annotated with #Field("name"), but spring seems to ignore any getter/setter pair without an actual field.
Adding an actual ID field and storing a copy of the name there does work:
public class SpringThing extends Thing {
#Id
private String id;
#Override
public void setName(String id) {
this.id = id;
super.setName(id);
}
}
But it requires defining a pointless field named "id".
Is there a more reasonable way to get what I want? Is what I'm trying to do reasonable to begin with?

Thanks to a hint by #mp911de, I ended up creating a subclass of Thing that looks like this:
#TypeAlias("thing")
#Document(collection = "things")
public class SpringThing extends Thing {
#Id
#AccessType(Type.PROPERTY)
#JsonIgnore
public String getId() {
return super.getName();
}
public void setId(String taskName) {
super.setName(taskName);
}
}
The #TypeAlias annotation overrides the name which spring would use for the type, to cover up the fact that I've created a subclass just to add annotations.
#Id says that this is the getter for _id.
#AccessType says to access this field through the getter and setter rather than by directly accessing the field. This is what I needed; without it, spring looks for a private member variable named something like id.
#JsonIgnore is the Jackson (JSON library that we're using) annotation to prevent including the id field when serializing these objects to JSON.

Related

Building an aspect with the same annotation can be on the class or the method and can be both

I'm creating a custom Metric Aspect so that I don't have to do METRIC.aboutTo("save", "order") in the code. My organization uses custom metric libraries.
So, I want to create a custom metric annotation and aspect to do this for me.
I'm looking to be able to add the annotation #PostgressMetric to both the class and the method. It would look something like this:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
public #interface PostgressMetric {
String value() default "";
String table() default "";
}
If the metric is on the class, then I want to use those values as "defaults". So you could have something like this:
#Repository
#PostgressMetric(table = "order")
public class OrderRepository {
#PostgressMetric("save")
public void save(Order order) {
}
}
This is also a valid setup:
#Repository
public class OrderRepository {
#PostgressMetric(table="order", value="save")
public void save(Order order) {
}
}
and of course this is also a valid setup:
#Repository
#PostgressMetric(table="order", value="save")
public class OrderRepository {
public void save(Order order) {
}
}
Now, in my aspect I want to get the annotation from the class, then get the annotation from the method. If the class has the annotation then get the table and value and use those as default values. If the method has the annotation then if the values aren't blank, use them to override the default values specified in the class annotation.
Is there a standard way to get the two annotations, or is it just a brute force get the class values and then get the method values and override them?

How to use dynamic schema in spring data with mongodb?

Mongodb is a no-schema document database, but in spring data, it's necessary to define entity class and repository class, like following:
Entity class:
#Document(collection = "users")
public class User implements UserDetails {
#Id private String userId;
#NotNull #Indexed(unique = true) private String username;
#NotNull private String password;
#NotNull private String name;
#NotNull private String email;
}
Repository class:
public interface UserRepository extends MongoRepository<User, String> {
User findByUsername(String username);
}
Is there anyway to use map not class in spring data mongodb so that the server can accept any dynamic JSON data then store it in BSON without any pre-class define?
First, a few insightful links about schemaless data:
what does “schemaless” even mean anyway?
“schemaless” doesn't mean “schemafree”
Second... one may wonder if Spring, or Java, is the right solution for your problem - why not a more dynamic tool, such a Ruby, Python or the Mongoshell?
That being said, let's focus on the technical issue.
If your goal is only to store random data, you could basically just define your own controller and use the MongoDB Java Driver directly.
If you really insist on having no predefined schema for your domain object class, use this:
#Document(collection = "users")
public class User implements UserDetails {
#Id
private String id;
private Map<String, Object> schemalessData;
// getters/setters omitted
}
Basically it gives you a container in which you can put whatever you want, but watch out for serialization/deserialization issues (this may become tricky if you had ObjectIds and DBRefs in your nested document). Also, updating data may become nasty if your data hierarchy becomes too complex.
Still, at some point, you'll realize your data indeed has a schema that can be pinpointed and put into well-defined POJOs.
Update
A late update since people still happen to read this post in 2020: the Jackson annotations JsonAnyGetter and JsonAnySetter let you hide the root of the schemaless-data container so your unknown fields can be sent as top-level fields in your payload. They will still be stored nested in your MongoDB document, but will appear as top-level fields when the ressource is requested through Spring.
#Document(collection = "users")
public class User implements UserDetails {
#Id
private String id;
// add all other expected fields (getters/setters omitted)
private String foo;
private String bar;
// a container for all unexpected fields
private Map<String, Object> schemalessData;
#JsonAnySetter
public void add(String key, Object value) {
if (null == schemalessData) {
schemalessData = new HashMap<>();
}
schemalessData.put(key, value);
}
#JsonAnyGetter
public Map<String, Object> get() {
return schemalessData;
}
// getters/setters omitted
}

Create my own API

I use in my force.com application Apex Classes, and many of them have the same structure, I want to make an API to reuse it after.
For exaple, these are two classes :
//first class
public class insererActAct{
public List<Activites_actuelles__c> accts {get; set;}
public insererActAct(ApexPages.StandardController controller){
accts = new List<Activites_actuelles__c>();
accts.add(new Activites_actuelles__c());
}
public void addrow(){
accts.add(new Activites_actuelles__c());
}
public PageReference deleteRow(){
if (accts.size()>1)
{
accts.remove(accts.size()-1);
}
return null;
}
public PageReference save()
{
insert accts;
Assure__c theParent = new Assure__c(id=accts[0].Activites_actuelles__c);
PageReference acctPage = new ApexPages.StandardController(theParent).view();
acctPage.setRedirect(true);
return acctPage;
}
}
//second class
public class insererEnfants{
public List<Enfants__c> accts {get; set;}
public insererEnfants(ApexPages.StandardController controller){
accts = new List<Enfants__c>();
accts.add(new Enfants__c());
}
public void addrow(){
accts.add(new Enfants__c());
}
public PageReference deleteRow(){
if (accts.size()>1)
{
accts.remove(accts.size()-1);
}
return null;
}
public PageReference save()
{
insert accts;
Assure__c theParent = new Assure__c(id=accts[0].Parent__c);
PageReference acctPage = new ApexPages.StandardController(theParent).view();
acctPage.setRedirect(true);
return acctPage;
}
}
Can any one tell me it is possible or not, if yes, how can I do this, please ?
Do you mean you want to write code that works for different kinds of objects, rather than stating the type of object in the code?
It is possible to do this, by declaring your sObject variables with the sObject type, e.g, instead of
public List<Activites_actuelles__c> accts {get; set;}
you would put
public List<sObject> sObjects{get; set;}
and by referencing fields using get and put, e.g. instead of
Account theAccount = new Account();
theAccount.name = 'Fred';
you would put
sObject theSObject = new SObject();
theSObject.put('name', 'Fred');
I've not tried using code like this in a controller extension, but in theory since you can pass in any standard controller you ought to be OK.
You will need to think about how to create the parent object inside the save method, because you will need to pass in what type you want to create. It's quite complicated but it is possible to take in a string representing the object type (e.g. 'Account') and create a new object of that type:
Schema.getGlobalDescribe().get('Account').newSObject()
will get you a new Account. So you could replace 'Account' with any type passed in as a parameter.
For more information look up Dynamic Apex, Dynamic SOQL and Dynamic DML in the documentation.
A word of warning: Passing around object types and field names as strings means that the compiler will not know those types are mentioned in the code. So if you try and delete a custom object or rename a field, salesforce will not warn you that the object or field is in use and will allow you to do the deletion.
Another word of warning: Although this allows code reuse and that is a benefit, you may find it is not worth it. I avoid writing dynamic apex unless I absolutely have to. It is far more complex than ordinary apex and it is therefore difficult to debug and maitain, especially if someone other than yourself has to maintain it.

RIA Services SP2 Function Complex type not visible in Object Context

I am struggling with returning a complex type from my services layer. It doesnt seem to be accessible from my object context.
This is the query in the service layer. All compiling fine.
public IQueryable<USP_GetPostsByThreadID_Result> uspGetPostsByThreadID(int ThreadID)
{
return this.ObjectContext.USP_GetPostsByThreadID(ThreadID).AsQueryable();
}
When I try and call it from my client, the ForumContext is not seeing it. I checked the client generated file and nothing similar is being generated. Help!!!
The name of your method may not meet the expected convention for queries. Try one or both of the following:
Add the [Query] attribute
Rename the method to GetUspPostsByThreadID
Result:
[System.ServiceModel.DomainServices.Server.Query]
public IQueryable<USP_GetPostsByThreadID_Result> GetUspPostsByThreadID(int ThreadID)
{
return this.ObjectContext.USP_GetPostsByThreadID(ThreadID).AsQueryable();
}
Its very common to have a stored procedure returning data from multiple tables. The return type doesn't fit well under any of the Entity Types(Tables). Therefore if we define Complex Type as the return collection of objects from Stored Procedure invocation, it becomes quite a powerful tool for the developer.
Following these steps I have achieved successfully the configuration of complex type on a sample AdventureWorks database.
1. Refer the picture and ensure the Stored procedure and function import is done.
2. Add the Domain Service name it as AdventureDomainService.
3. Now its time to define the tell the RIA services framework to identify my Complex Type as Entity Type. To be able to do this, we need to identify a [Key] DataAnnotation. Entity types provide data structure to the application's data model and by design, each entity type is required to define a unique entity key. We can define key on one property or a set of properties in metadata class file AdventureDomainService.metadata.cs
First define the class then add MetadatatypeAttribute like :
[MetadataTypeAttribute(typeof(CTEmployeeManagers.CTEmployeeManagersMetadata))]
public partial class CTEmployeeManagers
{
internal sealed class CTEmployeeManagersMetadata
{
private CTEmployeeManagersMetadata() { }
[Key]
public int EmployeeID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int ManagerID { get; set; }
public string ManagerFirstName { get; set; }
public string ManagerLastName { get; set; }
}
}
Define the Domain service method to return the collection of objects/entities for populating the Silverlight Grid or any other data consuming controls.
public IQueryable<CTEmployeeManagers> GetEmployeeManagers(int empId)
{
return this.ObjectContext.GetEmployeeManagers(empId).AsQueryable();
}
We define IQueryable if we are to fetch the records from datasources like SQL, whereas we define IEnumerable if we are to fetch the records from in memory collections,dictionaty,arrays.lists, etc.
Compile the server side to generate the client proxy.
In the Silverlight side open the MainPage.xaml or wherever the datagrid is put, then add following namespaces :
using System.ServiceModel.DomainServices.Client;
using SLBusinessApplication.Web;
using SLBusinessApplication.Web.Services;
..
Load the data and display:
public partial class MyPage : Page
{
AdventureDomainContext ctx = new AdventureDomainContext();
public MyPage()
{
InitializeComponent();
LoadOperation loadOp = this.ctx.Load(this.ctx.GetEmployeeManagersQuery(29));
myGrid.ItemsSource = loadOp.Entities;
}
// Executes when the user navigates to this page.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}
}
That is all that is needed to do.
It has to be part of an entity. Complex types cannot be returned by themselves

Inheritance concept in jpa

I created one table using Inheritance concept to sore data into google app engine datastore. It contained following coding but it shows error.How to user Inheritance concept.What error in my program
Program 1:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class calender {
#Id
private String EmailId;
#Basic
private String CalName;
#Basic
public void setEmailId(String emailId) {
EmailId = emailId;
}
public String getEmailId() {
return EmailId;
}
public void setCalName(String calName) {
CalName = calName;
}
public String getCalName() {
return CalName;
}
public calender(String EmailId, String CalName) {
this.EmailId = EmailId;
this.CalName = CalName;
}
}
Program 2:
#Entity
public class method extends calender {
#Id
private String method;
public void setMethod(String method) {
this.method = method;
}
public String getMethod() {
return method;
}
public method(String method) {
this.method = method;
}
}
My constraint is I want output like this
Calendartable contain
Emailid
calendarname
and method table contain
Emailid
method
How to achieve this?
It shows the following error in this line public method(String method)
java.lang.Error: Unresolved compilation problem:
Implicit super constructor calender() is undefined. Must explicitly invoke another constructor
According to Using JPA with App Engine, the JOINED inheritance strategy is not supported.
Your code doesn't compile, add a default constructor in Calendar.
I don't think you should annotate the method field with #Id.
The datastore of GAE/J is not an RDBMS so consequently the only "inheritance strategy" that makes any sense is TABLE_PER_CLASS. I would expect GAE/J to throw an exception if you specify that strategy, and if it doesn't then you ought to raise an issue against them
Your error "constructor calender() is undefined" is rather straightforward. You should create constructor without parameters in calendar class (you can make it private if you don't want to use it). That's because compiler can create default constructor by himself only if there aren't another constructors in the class.

Resources