0

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 を介してリンクされたレコードをフェッチしようとします。

私たちの側に何らかのエラーがあるかどうか、それが既知の問題であるかどうか、または何か役立つ可能性があるかどうかを誰か説明できますか?

前もって感謝します。

4

1 に答える 1

1

EMF は共有リソースをラップするため、EMF コンテキストがこの持続性ユニットに存在する限り、ベース EMF がベース ログイン プロパティで使用されます。ここで説明されているように、共有接続ではなく排他的接続を使用するように EclipseLink をセットアップする必要がありますhttp://wiki.eclipse.org/EclipseLink/Examples/JPA/Auditing

于 2013-10-09T18:35:47.197 に答える