Glassfish 4 サーバーで実行するアプリケーションをセットアップしようとしています。データの取得には、JPA 2.0 (EclipseLink Kepler) を使用しています。いくつかの理由から、通常のユーザー管理に加えて、DMBS ユーザー管理を介してユーザーを承認する必要もあります。
これを実現するために、ユーザーのログイン時にユーザー固有の JpaEntityManager を作成します (ログイン自体を確認するために、DBMS 資格情報が persistence.xml にある一般的なサーバー側の JpaEntityManager もあります)。ここにいくつかのコード:
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import org.eclipse.persistence.jpa.JpaEntityManager;
import org.eclipse.persistence.jpa.JpaHelper;
import application.auth.general.Credentials;
import application.config.Config;
import application.data.model.JpaSession;
import application.logger.Logger;
/**
*/
public class EntityManagerBase {
/**
* Singleton instance
*/
private static EntityManagerBase instance = new EntityManagerBase();
/**
* Hashtable for storage of user specific EntityManagers
*
* @param String userName
* @param EntityManager corresponding EntityManager
*/
private Hashtable<Integer, JpaEntityManager> jpaEms = new Hashtable<>();
/**
* Default constructor for singleton, creates single jpaEm instance for
* user ID -1, rights are defined in server-side persistence.xml
*/
private EntityManagerBase() {
String persistenceUnitName = Config.get("pvapp.data.persistence.unitName");
EntityManagerFactory emf = Persistence.createEntityManagerFactory(persistenceUnitName);
EntityManager em = emf.createEntityManager();
JpaEntityManager jpaEm = JpaHelper.getEntityManager(em);
jpaEms.put(-1, jpaEm);
}
public static EntityManagerBase getInstance() {
return instance;
}
/**
* Prevent cloning of singleton instance
*/
@Override
protected Object clone() throws CloneNotSupportedException {
String name = this.getClass().getCanonicalName();
throw new CloneNotSupportedException(name
+ " does not support clone(). Use " + name
+ ".getInstance() instead.");
}
public void createJpaEntityManager(JpaSession session, Credentials credentials) {
String persistenceUnitName = Config.get("pvapp.data.persistence.unitName");
EntityManagerFactory emf = Persistence.createEntityManagerFactory(persistenceUnitName);
HashMap<String, String> properties = new HashMap<>();
properties.put("javax.persistence.jdbc.user", credentials.getUserName());
properties.put("javax.persistence.jdbc.password", credentials.getPassword());
EntityManager em = emf.createEntityManager(properties);
JpaEntityManager jpaEm = JpaHelper.getEntityManager(em);
jpaEms.put(session.getUser().getId(), jpaEm);
}
public JpaEntityManager getJpaEntityManager(JpaSession session) {
return this.jpaEms.get(session.getUser().getId());
}
/**
* Get a JPA entity manager for a numeric user id
*
* @param id
* @return
*/
public JpaEntityManager getJpaEntityManager(int id) {
return this.jpaEms.get(id);
}
}
そのため、ユーザーがログインすると、createJpaEntityManager が呼び出され、新しく作成された JpaEntityManager がユーザー ID とともに Hashtable に格納されます。これは、デフォルトの POJO を取得する場合には問題なく機能しますが、他のオブジェクトとの関係がある場合は次のようになります。
import java.io.Serializable;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.xml.bind.annotation.XmlRootElement;
import application.auth.general.Authorizable;
import static javax.persistence.FetchType.EAGER;
/**
* The persistent class for the modulesignature database table.
*
*/
@Entity(name="JpaModuleSignature")
@Table(name="modulesignature")
@XmlRootElement
@NamedQueries({
@NamedQuery(name="JpaModuleSignature.findAll", query="SELECT m FROM JpaModuleSignature m"),
@NamedQuery(name="JpaModuleSignature.findById", query="SELECT m FROM JpaModuleSignature m WHERE m.id = :id")
})
public class JpaModuleSignature implements Serializable, Authorizable, JpaRecord {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="id")
private int id;
@Column(name="authObjId")
private int authObjId;
@Column(name="className")
private String className;
@Column(name="version")
private int version;
//bi-directional many-to-one association to JpaMenuItem
@OneToMany(mappedBy="moduleSignature", fetch = EAGER)
private List<JpaMenuItem> menuItems;
public JpaModuleSignature() {
}
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
public int getAuthObjId() {
return this.authObjId;
}
public void setAuthObjId(int authObjId) {
this.authObjId = authObjId;
}
public String getClassName() {
return this.className;
}
public void setClassName(String className) {
this.className = className;
}
public int getVersion() {
return this.version;
}
public void setVersion(int version) {
this.version = version;
}
public List<JpaMenuItem> getMenuItems() {
return this.menuItems;
}
public void setMenuItems(List<JpaMenuItem> menuItems) {
this.menuItems = menuItems;
}
public JpaMenuItem addMenuItem(JpaMenuItem menuItem) {
getMenuItems().add(menuItem);
menuItem.setModuleSignature(this);
return menuItem;
}
public JpaMenuItem removeMenuItem(JpaMenuItem menuItem) {
getMenuItems().remove(menuItem);
menuItem.setModuleSignature(null);
return menuItem;
}
}
次のように進めたとしましょう - ユーザーの ID を決定し、正しい JpaEntityManager をフェッチします (残念ながら、そのためにはさらにコードが必要です)。
まず、Jersey 2.0-Webservice ですが、これは目立たないものです。
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
import application.data.model.JpaMenuItem;
import application.data.model.JpaModuleSignature;
import application.data.model.JpaSession;
import application.logger.Logger;
import application.server.storage.GenericStorage;
/**
*/
@Provider
@Path("JpaModuleSignature")
public class ModuleSignatureService extends AbstractService {
/**
* Storage unit dealing with storage of the JPA entities
*/
//private ModuleSignatureStorage storage = ModuleSignatureStorage.getInstance();
private GenericStorage<JpaModuleSignature> storage =
GenericStorage.createInstance(JpaModuleSignature.class);
/**
* Include application.data.model classes
*/
public ModuleSignatureService() {
super();
}
/**
* Get a list of all ModuleSignatures in the database
*
* @return list of ModuleSignatures
*/
@Produces(MediaType.APPLICATION_XML)
@Path("list")
@GET
public JpaModuleSignature[] getModuleSignatures(@QueryParam("sessionId") String sessionId) {
Logger.getInstance().setVerbosity((byte) 3);
JpaSession session = this.getSession(sessionId);
List<JpaModuleSignature> ms = storage.getList(session);
Logger.getInstance().log("-----3-----");
for (int i = 0; i < ms.size(); i++) {
Logger.getInstance().log("signature #" + i + ": " + ms.get(i).getTitle());
List<JpaMenuItem> menuItems = ms.get(i).getMenuItems();
for (int j = 0; j < menuItems.size(); j++) {
Logger.getInstance().log("menu item #" + i + "-" + j + ": " + menuItems.get(j));
}
}
Logger.getInstance().log("-----4-----");
JpaModuleSignature ret[] = new JpaModuleSignature[0];
return ms.toArray(ret);
}
}
ユーザー固有の JpaEntityManager への呼び出しを含む汎用ストレージへのコールバックがあります (getList 内の StorageBase が静的に呼び出される 1 行を見てください:
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.Query;
import org.eclipse.persistence.jpa.JpaEntityManager;
import application.auth.general.Authorizable;
import application.data.model.JpaRecord;
import application.data.model.JpaSession;
import application.logger.Logger;
import application.server.auth.AuthorizationManager;
public class GenericStorage<T> extends StorageBase {
/**
* This is an internal variable in order to store the class
* of the generic object. It is used as a helper later in the code
*/
private final Class<T> storageClass;
/**
* The constructor has only "protected" visibility so the formal type
* parameter has to be specified via createClient
*
* @param clientClass
*/
protected GenericStorage(Class<T> storageClass) {
this.storageClass = storageClass;
}
/**
* Static method for creating instances of GenericStorage.
*
* @param className
* @return
*/
public static <U> GenericStorage<U> createInstance(Class<U> className) {
return new GenericStorage<U>(className);
}
/**
* Get a list of all items
*
* @return
*/
public List<T> getList(JpaSession session) {
return this.getList(session, null);
}
public List<T> getList(JpaSession session, Hashtable<String,Object> parameters) {
Entity e = (Entity) this.storageClass.getAnnotation(Entity.class);
String entityName = e.name();
String queryString = "SELECT r FROM " + entityName + " r";
if (parameters != null && parameters.size() > 0) {
String where = " WHERE ";
Set<String> paramKeys = parameters.keySet();
Iterator<String> i = paramKeys.iterator();
while (i.hasNext()) {
String key = i.next();
where += key + " = :" + key;
}
queryString += where;
}
// GET USER-SPECIFIC JpaEntityManager HERE:
JpaEntityManager jpaEm = StorageBase.getJpaEM(session);
Query query = jpaEm.createQuery(queryString);
Logger.getInstance().log("-----1-----");
if (parameters != null && parameters.size() > 0) {
Set<String> paramKeys = parameters.keySet();
Iterator<String> i = paramKeys.iterator();
while (i.hasNext()) {
String key = i.next();
query.setParameter(key, parameters.get(key));
}
}
List<T> L = (List<T>) query.getResultList();
Logger.getInstance().log("-----2----- (" + entityName + ")");
return L;
}
}
StorageBase のコード:
import org.eclipse.persistence.jpa.JpaEntityManager;
import application.data.model.JpaSession;
/**
* Implements general functionality for retrieval of
* user specific JpaEntityManager instances using the
* EntityManagerBase
*/
abstract class StorageBase {
public static JpaEntityManager getJpaEM(JpaSession session) {
return EntityManagerBase.getInstance().getJpaEntityManager(session);
}
public static JpaEntityManager getJpaEM(int id) {
return EntityManagerBase.getInstance().getJpaEntityManager(id);
}
}
「AbstractService」が何をするかについて質問がある場合は、実際に読む必要はありませんが、Jersey の「this.packages(..)」を呼び出し、ユーザーが指定したセッション ID で JPA セッション オブジェクトを取得するメソッドを提供します。
問題は、ModuleSignatureService の getModuleSignatures を実行すると、奇妙なことが起こることです。JpaModuleSignature は基本的に正しい JpaEntityManager を介して取得できますが、リンクされた属性「menuItems」にアクセスしようとすると、次のエラーが発生します。
INFO: [EL Severe]: 2013-10-09 16:50:24.34--ServerSession(1625769026)--Exception [EclispseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipe.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLException: Access denied for user 'PERSISTENCE.XML-USER'@'localhost' (using password: YES)
Error Code: 1045
もちろん、実際のユーザー名は PERSISTENCE.XML-USER ではありませんが、persistence.xml で定義されているものです。元のレコードが、データベース システムに対して承認された正しいユーザーによって取得されていることを再確認しました。ただし、JPA は明らかに間違った JpaEntityManager を介してリンクされたレコードをフェッチしようとします。
私たちの側に何らかのエラーがあるかどうか、それが既知の問題であるかどうか、または何か役立つ可能性があるかどうかを誰か説明できますか?
前もって感謝します。