GAE + JPA + JsonMappingException + You have just attempted to access field - google-app-engine

I'm new with JPA and GAE. I'm having some problems trying to retrive some entities to the front end. I copy mi entity code:
#Entity
public class User implements Serializable{
private static final long serialVersionUID = -7949567692618870100L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
private String userName;
private String email;
#OneToMany(fetch=FetchType.LAZY, cascade = CascadeType.ALL)
private List<User> friends;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Credential Credential;
//getters & setters
}
The Credential entity only contains userName and pass:
#Entity
public class Credential implements Serializable{
private static final long serialVersionUID = -3277842132830057420L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
private String userName;
private String pass;
//getters and setters
}
I generate UserEndpoint with GAE Eclipse Plugin and then I added a method to simulate a Login, this is the method:
#ApiMethod(name = "CredentialLogin")
public User CredentialLogin(#Named("pass") String pass,#Named("userName") String userName){
EntityManager mgr = getEntityManager();
Query query = mgr.createQuery("SELECT FROM User u WHERE u.email = :email");
query.setParameter("email", userName);
User user = null;
try {
user = (User) query.getSingleResult();
if (!user.getCredential().getPass().equals(pass))
//INVALID LOGIN
}
finally {
mgr.close();
}
return user;
}
Debugging the code I get the correct User from the datastore. Doing "user.getCredential()" the Credential fetchs to the User entity. As the "friends" field has LAZY fetching when I return the User entity I will appear NULL (This is what I want).
After the return I'm getting this error:
"com.google.appengine.repackaged.org.codehaus.jackson.map.JsonMappingException: You have just attempted to access field "friends" yet this field was not detached when you detached the object. Either dont access this field, or detach it when detaching the object."
I don't want to put EAGER fetching to my "friends" attribute because the list could be really big.
I don't want to remove my "friends" getter and setter method, because without them I couldn't access to the field.
Somebody knows what should I do.
Thank you very much

You can change the enhancement process to call a listener instead of throwing an exception when an undetached field is accessed. See http://www.datanucleus.org/products/accessplatform_3_1/enhancer.html and "detachListener" option

Related

Dynamic NamedDatabase in Play Framework

I'm running a java play framework setup where I would like to have several databases depending on what customer is making the call. I have a jwt setup where there is a tenant id. However I can't get my head around what's best practise in Play regarding this. As for now I have this code:
public class JavaNamedDatabase {
private Database db;
private DatabaseExecutionContext executionContext;
private static final Logger.ALogger LOGGER = Logger.of(JavaNamedDatabase.class);
#Inject
public JavaNamedDatabase(
#NamedDatabase("xxx") Database db, DatabaseExecutionContext executionContext) {
this.db = db;
this.executionContext = executionContext;
}
where I would like to make "xxx" dynamic depending on which tenant is making the request.
Is it possible to pass this parameter or do I need to have separate classes?
Or maybe the best solution is just to have one instance running per customer and have the #NamedDatabase as a runtime config parameter?
I found DBApi where there is a getter for Database.
public class JavaNamedDatabase {
private DBApi dbApi;
private DatabaseExecutionContext executionContext;
private static final Logger.ALogger LOGGER = Logger.of(JavaNamedDatabase.class);
#Inject
public JavaNamedDatabase(
DBApi dbApi, DatabaseExecutionContext executionContext) {
this.dbApi = dbApi;
this.executionContext = executionContext;
}
public CompletionStage<Integer> addGenreToPlayItem(Integer playItemId, String genre) {
return CompletableFuture.supplyAsync(
() ->
dbApi.getDatabase("xxx").withConnection(...```

I can't load a ready database in my room database although I put it in Assets folder

Room.databaseBuilder(getApplicationContext(), UserDatabase.class,"users_db")
.createFromAsset("users.db")
.build();
UserDatabase db = Room.databaseBuilder(getApplicationContext(),
UserDatabase.class,"users_db").allowMainThreadQueries()
.fallbackToDestructiveMigration()
.addMigrations()
.build();
Even I tried to add as a file to prepopulate as "createFromFile" with the following code
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
.createFromFile(new File("mypath"))
.build();
If the above is the only code then unless you try to access the database, the database will not be created. That is just getting an instance of the built database just creates an instance that is ready to open the database.
So if you have, for example :-
UserDatabase db = Room.databaseBuilder(getApplicationContext(),
UserDatabase.class,"users_db").allowMainThreadQueries()
.createFromAsset("users.db")
.build();
Then it is not until, for example, you did
SupportSQLiteDatabase accessedDatabase = db.getOpenHelper().getWritableDatabase();
That an attempt to open the database (in the case of no database existing, then an attempt to create the database, and in the case of createFromAsset the create database will then attempt the copy from the asset).
Typically you would not get a SupportSQliteDatabase but would simply try to access the database via the Dao's ( i.e. the methods in the interfaces or abstract classes that are annotated with #Dao).
Demonstration
Consider the following :-
An existing database from an external source (Navicat in the example) :-
just a single table and therefore #Entity annotated class will be required.
as can be seen it has 3 users.
IMPORTANT the schema MUST adhere to what Room expects, which has limitations
The database is copied to the assets folder as users.db :-
An #Entity annotated class User (so the table name will be User) :-
#Entity
class User {
#PrimaryKey
Long id = null;
String username;
}
2 columns
id which is the Primary Key and as the type is Long then the column type must be INTEGER
username, as it's a String then column type MUST be TEXT, as there is no #NonNUll annotation then NOT NULL should not be coded (if it were then the schema room expects would not be as above)
An #Dao annotated class UserDao :-
#Dao
interface UserDao {
#Insert
long insert(User user);
#Query("SELECT * FROM user")
List<User> getAllUsers();
}
An #Database annotated class UserDatabase :-
#Database(entities = {User.class}, version = 1,exportSchema = false)
abstract class UserDatabase extends RoomDatabase {
abstract UserDao getUserDao();
private static volatile UserDatabase instance = null;
static UserDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(context,UserDatabase.class,"users_db")
.allowMainThreadQueries()
.createFromAsset("users.db")
.build();
}
return instance;
}
}
note the getInstance() method (see the activity code that follows)
Putting it all together in an activity
First a shortened version that does not access the database but just gets an instance of the UserDatabase :-
public class MainActivity extends AppCompatActivity {
UserDatabase db;
UserDao dao;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
db = Room.databaseBuilder(this,UserDatabase.class,"users_db")
.allowMainThreadQueries()
.createFromAsset("users.db")
.build();
dao = db.getUserDao(); //<<<<< WILL NOT OPEN THE DATABASE
}
}
When the above is run AppInspection shows nothing.
However, using :-
public class MainActivity extends AppCompatActivity {
UserDatabase db;
UserDao dao;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
db = Room.databaseBuilder(this,UserDatabase.class,"users_db")
.allowMainThreadQueries()
.createFromAsset("users.db")
.build();
dao = db.getUserDao(); //<<<<< WILL NOT OPEN THE DATABASE
//db.getOpenHelper().getWritableDatabase(); //<<<<< Force an open of the database
//<<<<< WILl OPEN THE DATABASE
for(User u: dao.getAllUsers()) {
Log.d("USERINFO","UserName is" + u.username + " ID is " + u.id);
}
}
/*
Example of using the singleton (not called)
*/
void unusedFromExample() {
UserDatabase db = UserDatabase.getInstance(this);
UserDao dao = db.getUserDao();
/// etc
}
}
and the log includes (as expected) :-
D/USERINFO: UserName isuser1 ID is 1
D/USERINFO: UserName isuser2 ID is 2
D/USERINFO: UserName isuser3 ID is 2000
And AppInspection shows the database and that it has ben copied.

Spring data with LDAP (Active Directory) returns WILL NOT PERFORM in any writing operation

I'm currently trying to implement an simple CRUD integrated with Active Directory via LDAP, using Spring Data, for managing my internal users.
The problem is, while the reading works as expected, any writing on AD (creating or editing a user, for example) results in a generic error message, shown below:
[LDAP: error code 53 - 0000209A: SvcErr: DSID-031A107A, problem 5003 (WILL_NOT_PERFORM), data 0\n\u0000]; remaining name 'DC=company, DC=com'
The ldap connection is being made using LDAPS with an admin user. I can even work with the same credentials without any issues in a simple nodejs test application. So I'm probably making some mistake with Spring Data.
The relevant source code is attached below.
Entity class:
// Person.java (Entity model)
#Data
#Entry(
base = "ou=Employees,dc=company,dc=com",
objectClasses = {"person", "top"}
)
public class Person {
#Id
private Name dn;
#Attribute(name = "cn")
private String commonName;
#Attribute(name = "sAMAccountName")
private String accountName;
#Attribute(name = "userPrincipalName")
private String username;
#Attribute(name = "mail")
private String mail;
#Attribute(name = "userPassword")
private String password;
#Attribute(name = "description")
private String desc;
#Attribute(name = "memberOf")
private List<String> groups;
#Attribute(name = "company")
private String company;
#Attribute(name = "objectClass")
private List<String> objectClasses;
#Attribute(name = "objectCategory")
private String objectCategory;
}
Repository class:
// PersonRepository.java
#Repository
public interface PersonRepository extends LdapRepository<Person> {
Person findByMailIgnoreCase(String mail);
}
Service class:
#Service
public class UserService {
#Autowired
private PersonRepository personRepository;
/**
* Save the user at AD.
*
* #param username the user login name
* #param name the user name and surename
* #param companyExtName the company external name
* #param email the user email
* #param description the user description
* #return the newly created user
*/
public Person createPerson(String username, String name, String companyExtName,
String email, String description) {
final Person user = new Person();
user.setAccountName(username);
user.setCommonName(name);
user.setCompany(companyExtName);
user.setMail(email);
user.setUsername(email);
String tempPass = RandomStringUtils.randomAscii(10);
user.setPassword(digestSHA(tempPass));
user.setDn(LdapNameBuilder.newInstance("DC=company, DC=com")
.build());
List<String> objClasses = new ArrayList<>();
objClasses.add("person");
objClasses.add("top");
user.setObjectClasses(objClasses);
user.setObjectCategory("CN=Person,CN=Schema,CN=Configuration,DC=company,DC=com");
List<String> groups = new ArrayList<>();
groups.add("CN=Administrators,CN=Builtin,DC=company,DC=com");
user.setGroups(groups);
if (description != null && !description.isEmpty()) {
user.setDesc(description);
}
return personRepository.save(user);
}
/**
* Encodes the user password as it is used at Active Directory
*
* #param plain the plain text password
* #return the password hash
*/
private static String digestSHA(String plain) {
try {
MessageDigest digester = MessageDigest.getInstance("SHA-256");
digester.update(plain.getBytes());
return String.format("{SHA}%s", Base64.getEncoder().encodeToString(digester.digest()));
} catch (NoSuchAlgorithmException ex) {
return null;
}
}
The exception is thrown when I call personRepository.save(user);
As a addtional information, I've already tried a few variations of the code attached -- tried to remove almost all user data beforing saving it, different password encodings and hashing -- but the result is always the same.
Any help on this will be greatly appreciated.
Thanks!
EDIT:
Investigation indicates that the cause is probably something related with the way I'm sending my user DN.
Anyway, I'm still wrestling with this issue.
I was able to create/edit my Active Directory users with a workaround.
In my UserService, instead of using the Spring Data Ldap repository, I've used the LdapTemplate methods, like shown below.
// UserService.java
public void createPerson() {
Name userDn = LdapNameBuilder
.newInstance()
.add("ou", ou)
.add("cn", accountName)
.build();
DirContextAdapter context = new DirContextAdapter(userDn);
context.setAttributeValue("cn", accountName);
context.setAttributeValue("sn", accountName);
context.setAttributeValue("userPassword", digestSHA(password));
context.setAttributeValue("company", company);
context.setAttributeValue("description", desc);
context.setAttributeValue("mail", mail);
context.setAttributeValue("sAMAccountName", accountName);
context.setAttributeValue("userPrincipalName", username);
context.setAttributeValue("objectCategory", objectCategory);
context.setAttributeValues("objectClass", objectClasses.toArray());
DirContextAdapter context = user.getLdapContext("Users");
ldapTemplate.bind(context);
}
Since I used the same values for user creation with both Spring Data and LdapTemplate, my original issue is probably related to some treatment Spring does before sending the data to my Active Directory server.
Since the method above is currently working for me, I'll follow with it. When I have some spare time I'll go back to this to find out what I was doing wrong with Spring.
For future use, I believe it is related to memberOf attribute. This attribute must be set after the user is created, but it seems that Spring Data is filling this property with an empty string even if I set the attribute to null when creating the user.
Lots of articles says that it is because of a lack of SSL connection to a LDAP server.
Here are some links:
stackoverflow.com/questions/17290539
stackoverflow.com/questions/6797955
forum.spring.io/forum/spring-projects/data/ldap
community.oracle.com/thread/2177638

GAE + JPA + updating entity is losing other entity relation

Hi I´m new using GAE and JPA, and I´m having some problems trying to update an entity. I copy next a code example:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
private String userName;
#ManyToOne(fetch=FetchType.LAZY, cascade = CascadeType.ALL)
private Address address;
}
When I save on datastore a User instance it's store without problems.
After this, I retrive this instance from the datastore, then I set a new userName and try to update it.
public User updateUser(User user) {
EntityManager mgr = getEntityManager();
if (!containsUser(user)) {
throw new EntityNotFoundException("Object does not exist");
}
mgr.persist(user);
}
The update is performed, the new userName is stored in the dataStore, but as the Address field has FetchType.LAZY I'm losing the persisted value on the update.
How can I make an update of some fields without losing other values?
Try getting and saving the entity in the same method, otherwise you will lost the session and the manager will recognize the lazy attributes as null ones.
Hope it helps!

GAE/J OneToMany JPA Persistence Failure When Deployed

I am using Google App Engine with JPA to implement a one-to-many bidirectional relationship. Everything works just fine when I debug and test my application on my machine at home, but after I deploy it to the App Engine persistence seems to break down.
This is the model I have (stripped down for simplicity):
User.java:
#Entity
class User implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key id;
#OneToMany(mappedBy = "owner",
cascade = CascadeType.ALL,
fetch = FetchType.EAGER)
private List<Book> books;
public getBooks() { return this.books; }
}
Book.java:
#Entity
class Book implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key id;
#ManyToOne(fetch = FetchType.LAZY)
private User owner;
private String name;
}
To create a new User:
User user = new User()
// This is done just for testing. It works fine.
user.getBooks().add(new Book("TEST"))
EntityManager em = /* ... */
EntityTransaction transaction = em.getTransaction();
try
{
transaction.begin();
em.persist(user);
transaction.commit();
}
/* Exceptions handling. */
finally
{
if (transaction.isActive())
transaction.rollback();
em.close();
}
And to add a book:
User user = /* ... */
Book book = new Book("A new book");
user.getBooks().add(book);
EntityManager em = /* ... */
EntityTransaction transaction = em.getTransaction();
try
{
transaction.begin();
/* user.getBooks().add(book); - placing this here doesn't change anything */
em.merge(user);
transaction.commit();
}
/* Exceptions handling. */
finally
{
if (transaction.isActive())
transaction.rollback();
em.close();
}
What I saw, before I added the 'test' book which is created along with the User, is that creation of the first book entity works just fine, but whenever I create another one, the previous one is somehow removed from the Datastore and is replaced by the new one which I just created (I can tell because of the books' names). So I cannot create more than one book for the same user.
I tried to see if I somehow messed up persistence of the Book entity, and for that reason I added the 'TEST' book. The problem persists, only that now I have the first book ("TEST") and I keep replacing the second book on the list whenever I try to add a new one.
Again, this doesn't happen when I debug my application, only after I deploy.
I tried calling em.persist(book) before the call to em.merge(user), but that caused an exception saying the book's owner was already set when it was persisted and cannot be changed. I tried setting the relationship myself (like in this thread), but that caused a failed transaction when adding a book.
I'm not sure if its relevant, but the type of the 'books' column I see is datastore_types.Key.from_path, as in:
[datastore_types.Key.from_path(u'User', 9001L, u'Book', 1L, _app=u's~myapp'),
datastore_types.Key.from_path(u'User', 9001L, u'Book', 2001L, _app=u's~myapp')]
Any help would be appreciated,
Thank you!
Can you try below:
User user = /* ... */
Book book = new Book("A new book");
book.setOwner(user);
EntityManager em = /* ... */
EntityTransaction transaction = em.getTransaction();
try{
transaction.begin();
em.persist(user);
transaction.commit();
}finally{
if (transaction.isActive())
transaction.rollback();
em.close();
}

Resources