Apache Pluto による簡易ポータル開発(動的ページ構成編)

前回の続き。

データベースからページ・ポートレットの構造を取得し、ログインユーザのロールごとに異なるページ構成を見せられるようにしてみる。

データベースの更新

前回作成したデータベースに、さらにページ・ポートレット関連の情報を追加する。

CREATE TABLE PAGES
(
	ID INTEGER NOT NULL,
	NAME TEXT NOT NULL UNIQUE,
	URI TEXT NOT NULL,
	PRIMARY KEY (ID)
);


CREATE TABLE PORTLETS
(
	ID INTEGER NOT NULL,
	NAME TEXT NOT NULL UNIQUE,
	CONTEXT TEXT NOT NULL,
	PRIMARY KEY (ID)
);


CREATE TABLE PAGES_PORTLETS
(
	PAGE_ID INTEGER NOT NULL,
	POSITION INTEGER NOT NULL,
	PORTLET_ID INTEGER NOT NULL,
	PRIMARY KEY (PAGE_ID, POSITION)
);


CREATE TABLE ROLES_PAGES
(
	ROLE_ID INTEGER NOT NULL,
	POSITION INTEGER NOT NULL,
	PAGE_ID INTEGER NOT NULL,
	PRIMARY KEY (ROLE_ID, POSITION)
);

-- ユーザ用ページと管理者用ページを作成
INSERT INTO "PAGES" VALUES(1,'Users Page','/WEB-INF/themes/pluto-default-theme.jsp');
INSERT INTO "PAGES" VALUES(2,'Admin Page','/WEB-INF/themes/pluto-default-theme.jsp');

-- AboutポートレットとPageAdminポートレットを作成
-- ポートレットの定義自体は src/main/webapp/WEB-INF/portlet.xml
INSERT INTO "PORTLETS" VALUES(1,'AboutPortlet','/portal-example');
INSERT INTO "PORTLETS" VALUES(2,'PlutoPageAdmin','/portal-example');

-- ロールとページの関連付け
INSERT INTO "ROLES_PAGES" VALUES(1,1,1);
INSERT INTO "ROLES_PAGES" VALUES(2,1,1);
INSERT INTO "ROLES_PAGES" VALUES(2,2,2);

-- ページとポートレットの関連付け
INSERT INTO "PAGES_PORTLETS" VALUES(1,1,1);
INSERT INTO "PAGES_PORTLETS" VALUES(1,2,1);
INSERT INTO "PAGES_PORTLETS" VALUES(2,1,1);
INSERT INTO "PAGES_PORTLETS" VALUES(2,2,2);

データは基本的に pluto-portal-driver-config.xml ファイルの render-config をデータベースに移したような構造になっている。

RenderConfigService の差し替え

Pluto Portal アプリケーションのデフォルトでは、ポータルのページ構成は org.apache.pluto.driver.services.portal.RenderConfigService インタフェースを通してフレームワーク側に返されることになっている。

さらに、デフォルトでは、実装クラスとして RenderConfigServiceImpl クラスが src/main/webapp/WEB-INF/pluto-portal-driver-services-config.xml ファイルで以下のように指定されている。

    <bean id="RenderConfigService"
          class="org.apache.pluto.driver.services.impl.resource.RenderConfigServiceImpl"
          singleton="true">
    </bean>

RenderConfigServiceImpl クラスは、pluto-portal-driver-config.xml ファイルの render-config 要素で静的に定義された内容を返すだけのクラスであるため動的にページ構成を定義することは難しい。そこで、この部分を、Spring Security のログイン情報に格納されているロールをもとに動的にページ構成を返すような別の RenderConfigService 実装に差し替えてしまうことにする。

そのために、まず上記の部分を以下のように修正する。

    <bean id="RenderConfigService"
          class="com.example.pluto.SpringRoleBasedRenderConfigServiceImpl"
          singleton="true">
          <property name="portalStructureDao" ref="portalStructureDao" />
    </bean>

その上で、この SpringRoleBasedRenderConfigServiceImpl を実装するために、以下のファイルを作成・修正する。

src/main/java/com/example/pluto/SpringRoleBasedRenderConfigServiceImpl.java

上で指定した新しい RenderConfigService 実装。初期化時にDBからロールごとのページ構成を読み込み、実行時にログインユーザのロール名から対応するページ構成を返す。

package com.example.pluto;

import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;

import org.apache.pluto.driver.config.DriverConfigurationException;import org.apache.pluto.driver.services.portal.PageConfig;
import org.apache.pluto.driver.services.portal.RenderConfig;
import org.apache.pluto.driver.services.portal.RenderConfigService;
import org.apache.pluto.driver.services.portal.admin.RenderConfigAdminService;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.context.SecurityContextHolder;

public class SpringRoleBasedRenderConfigServiceImpl
implements RenderConfigService, RenderConfigAdminService {

    protected Map<String, RenderConfig> configMap;

    protected RenderConfig renderConfig;

    protected PortalStructureDao portalStructureDao;

    public void setPortalStructureDao(PortalStructureDao portalStructureDao) {
        this.portalStructureDao = portalStructureDao;
    }

    protected RenderConfig getRenderConfig() {
        GrantedAuthority auth = SecurityContextHolder.getContext().getAuthentication().getAuthorities()[0];
        return configMap.get(auth.getAuthority());
    }

    public void init(ServletContext ctx) {
        configMap = portalStructureDao.getRenderConfigMap();
    }

    @Override
    public PageConfig getDefaultPage() {
        return getRenderConfig().getPageConfig(null);
    }

    @Override
    public PageConfig getPage(String pageId) {
        return getRenderConfig().getPageConfig(pageId);
    }

    @SuppressWarnings("unchecked")
    @Override
    public List getPages() {
        return getRenderConfig().getPages();
    }

    @Override
    public void addPage(PageConfig config) {
        getRenderConfig().addPage(config);
    }

    @Override
    public void removePage(PageConfig config) {
        getRenderConfig().removePage(config);
    }

    @Override
    public void destroy() throws DriverConfigurationException {
        configMap = null;
    }
}
src/main/java/com/example/pluto/PortalStructureDao.java

ページ構成を取得するためのインタフェース。あまり意味はない。

package com.example.pluto;

import java.util.Map;

import org.apache.pluto.driver.services.portal.RenderConfig;

public interface PortalStructureDao {

    Map<String, RenderConfig> getRenderConfigMap();
}
src/main/java/com/example/pluto/PortalStructureDaoImpl.java

ページ構成をDBから取得するためのDAO。iBATISを使用している。

package com.example.pluto;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.pluto.driver.services.portal.PageConfig;
import org.apache.pluto.driver.services.portal.RenderConfig;
import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;

public class PortalStructureDaoImpl extends SqlMapClientDaoSupport implements PortalStructureDao {

    public Map<String, RenderConfig> getRenderConfigMap() {

        @SuppressWarnings("unchecked")
        List<String> roleNames =
            (List<String>) getSqlMapClientTemplate().queryForList("portal.findRoleNames");

        Map<String, RenderConfig> result = new HashMap<String, RenderConfig>();

        for (String roleName : roleNames) {
            RenderConfig config = new RenderConfig();

            List<? extends PageConfig> pages = getPagesByRoleName(roleName);
            for (PageConfig page : pages) {
                config.addPage(page);
            }

            // とりあえず最初のページ
            config.setDefaultPageId(pages.get(0).getName());

            result.put(roleName, config);
        }

        return result;
    }

    protected List<? extends PageConfig> getPagesByRoleName(String role) {

        @SuppressWarnings("unchecked")
        List<MyPageConfig> pages = (List<MyPageConfig>) getSqlMapClientTemplate()
            .queryForList("portal.findPagesByRoleName", role);

        for (MyPageConfig page : pages) {

            @SuppressWarnings("unchecked")
            List<Map<String, String>> portlets =
                (List<Map<String, String>>) getSqlMapClientTemplate()
                    .queryForList("portal.findPortletsByPageId", page.getId());

            for (Map<String, String> portlet : portlets) {
                page.addPortlet(portlet.get("CONTEXT"), portlet.get("NAME"));
            }
        }

        return pages;
    }
}
src/main/java/com/example/pluto/MyPageConfig.java

上のDAOの都合上必要なページ設定クラス。

package com.example.pluto;

import org.apache.pluto.driver.services.portal.PageConfig;

public class MyPageConfig extends PageConfig {

    private Integer id;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }
}
src/main/resources/sqlmap-portal.xml

PortalStructureDaoImpl で使用するSQLの設定を追加する。

<sqlMap namespace="portal">

  <!-- 追加 -->
  <resultMap id="pageResult" class="com.example.pluto.MyPageConfig">
    <result property="id" column="ID" />
    <result property="name" column="NAME" />
    <result property="uri" column="URI" />
  </resultMap>

  (中略)

  <!-- 追加 -->
  <select id="findPagesByRoleName" parameterClass="string" resultMap="pageResult">
    SELECT pages.* FROM pages
      JOIN roles_pages ON roles_pages.page_id = pages.id
      JOIN roles ON roles.id = roles_pages.role_id
    WHERE roles.name = #value#
    ORDER BY roles_pages.position ASC
  </select>

  <!-- 追加 -->
  <select id="findPortletsByPageId" parameterClass="Integer" resultClass="java.util.HashMap">
    SELECT portlets.* FROM portlets
      JOIN pages_portlets ON pages_portlets.portlet_id = portlets.id
    WHERE pages_portlets.page_id = #value#
    ORDER BY pages_portlets.position ASC
  </select>

  <!-- 追加 -->
  <select id="findRoleNames" resultClass="string">
    SELECT name FROM roles
  </select>
src/main/resources/applicationContext.xml

最初に pluto-portal-driver-services-config.xml ファイルで指定した、 poortalStructureDao というbeanの定義を追加。

  <bean id="portalStructureDao" class="com.example.pluto.PortalStructureDaoImpl">
    <property name="sqlMapClient" ref="sqlMapClient" />
  </bean>

実行確認

http://localhost:8080/portal-example/ にアクセスし、

  • user/password でログインすると、「Users Page」というページ1つだけ
  • admin/password でログインすると、「Users Page」と「Admin Page」という2つのページが表示される

ことを確認する。