Liferay中外部war方式整合portlet的处理流程

  本文介绍如何以外部war应用的方式向liferay portal集成portlet, 还是以sample-jsp-portlet为例来说明。

一,portlet如何被liferay portal调用?

在web-xml定义中,可以看到这样的一个servlet定义:  

 〈servlet〉
  〈servlet-name〉sample_jsp_portlet〈/servlet-name〉
  〈servlet-class〉com.liferay.portal.kernel.servlet.PortletServlet〈/servlet-class〉
  〈init-param〉
   〈param-name〉portlet-class〈/param-name〉
   〈param-value〉com.sample.jsp.portlet.JSPPortlet〈/param-value〉
  〈/init-param〉
  〈load-on-startup〉0〈/load-on-startup〉
 〈/servlet〉
 〈servlet-mapping〉
  〈servlet-name〉sample_jsp_portlet〈/servlet-name〉
  〈url-pattern〉/sample_jsp_portlet/*〈/url-pattern〉
 〈/servlet-mapping〉

在[Liferay中action的处理流程]一文中,分析了当portlet是一个war时,liferay portal将转发请求到war应用,代码如下:
  CachePortlet
    private void _invoke(
   PortletRequest req, PortletResponse res, boolean action)
 throws IOException, PortletException {

 if (_portletConfig.isWARFile()) {
  String path =
   StringPool.SLASH + _portletConfig.getPortletName() + "/invoke";
  RequestDispatcher rd =
   _portletCtx.getServletContext().getRequestDispatcher(path);

  // 一些初始化代码....
  httpReq.setAttribute(WebKeys.JAVAX_PORTLET_PORTLET, _portlet);
  rd.include(httpReq, httpRes);
 }
 else {
  // 略...
 }
  }
  在本文中portletName为sample_jsp_portlet,则path为/sample_jsp_portlet/invoke,这个url对应的servlet就是PortletServlet了,
  这样liferay portlet就将请求转发到了sample-jsp-portlet应用。
  这里要注意的是servlet-name与url-pattern的名称一定要保持一致!

// PortletServlet
 public class PortletServlet extends HttpServlet {
 public static final String JAVAX_PORTLET_PORTLET = "javax.portlet.portlet";
 public static final String JAVAX_PORTLET_REQUEST = "javax.portlet.request";
 public static final String JAVAX_PORTLET_RESPONSE = "javax.portlet.response";
 public static final String PORTLET_CLASS_LOADER = "PORTLET_CLASS_LOADER";

 public void service(HttpServletRequest req, HttpServletResponse res)
  throws IOException, ServletException {

  PortletRequest portletReq =
   (PortletRequest)req.getAttribute(JAVAX_PORTLET_REQUEST);
  PortletResponse portletRes =
   (PortletResponse)req.getAttribute(JAVAX_PORTLET_RESPONSE);
  Portlet portlet = (Portlet)req.getAttribute(JAVAX_PORTLET_PORTLET);

  LiferayPortletSession portletSes =
   (LiferayPortletSession)portletReq.getPortletSession();
  portletSes.setHttpSession(req.getSession());

  if (portletReq instanceof ActionRequest) {
   ActionRequest actionReq = (ActionRequest)portletReq;
   ActionResponse actionRes = (ActionResponse)portletRes;

   portlet.processAction(actionReq, actionRes);
  }
  else {
   RenderRequest renderReq = (RenderRequest)portletReq;
   RenderResponse renderRes = (RenderResponse)portletRes;

   portlet.render(renderReq, renderRes);
  }
 }
 }
PortletServlet很简单,它根据PortletRequest的类型来决定是让portlet执行processAction还是render,
portlet由servlet定义的init-param参数指定,它必须继承自javax.portlet.GenericPortlet类, 这是jsr168规范所指定的,
本文中为com.sample.jsp.portlet.JSPPortlet。

// JSPPortlet.
 public class JSPPortlet extends GenericPortlet {

 public void init() throws PortletException {
  editJSP = getInitParameter("edit-jsp");
  helpJSP = getInitParameter("help-jsp");
  viewJSP = getInitParameter("view-jsp");
 }

 public void doDispatch(RenderRequest req, RenderResponse res)
  throws IOException, PortletException {
  // 略...
 }

 public void doEdit(RenderRequest req, RenderResponse res)
  throws IOException, PortletException {
  // 略...
 }

 public void doHelp(RenderRequest req, RenderResponse res)
  throws IOException, PortletException {
  include(helpJSP, req, res);
 }

 public void doView(RenderRequest req, RenderResponse res)
  throws IOException, PortletException {
  include(viewJSP, req, res);
 }

 public void processAction(ActionRequest req, ActionResponse res)
  throws IOException, PortletException {
 }

 protected void include(String path, RenderRequest req, RenderResponse res)
  throws IOException, PortletException {
  PortletRequestDispatcher prd =
   getPortletContext().getRequestDispatcher(path);

  if (prd == null) {
   _log.error(path + " is not a valid include");
  }
  else {
   prd.include(req, res);
  }
 }
 }
这里一个很简单的portlet实现,它直接包含jsp文件。

二,portlet中的预定义对象.

portlet 页面上能使用的对象.
首先在portlet页面上加入如下tag定义,
<portlet:defineobjects></portlet:defineobjects>

 DefineObjectsTag.doStartTag() {
 ClassLoader contextClassLoader =
  Thread.currentThread().getContextClassLoader();

 try {
  // 切换ClassLoader,对于采用War方式的portlet,这是必须的?
  Thread.currentThread().setContextClassLoader(
   PortalClassLoaderUtil.getClassLoader());

  MethodWrapper methodWrapper = new MethodWrapper(
   _TAG_CLASS, _TAG_DO_START_METHOD, new Object[] {pageContext});

  MethodInvoker.invoke(methodWrapper);
 }
 catch (Exception e) {
  throw new JspException(e);
 }
 finally {
  Thread.currentThread().setContextClassLoader(contextClassLoader);
 }

 return SKIP_BODY; 
 }

 private static final String _TAG_CLASS =
  "com.liferay.portlet.DefineObjectsTagUtil";
 private static final String _TAG_DO_START_METHOD = "doStartTag";


 DefineObjectsTag.doStartTag(PageContext pageContext) {
 ServletRequest req = pageContext.getRequest();

 PortletConfigImpl portletConfig =
  (PortletConfigImpl)req.getAttribute(WebKeys.JAVAX_PORTLET_CONFIG);

 if (portletConfig != null) {
  pageContext.setAttribute("portletConfig", portletConfig);
  pageContext.setAttribute(
   "portletName", portletConfig.getPortletName());
 }

 RenderRequest renderRequest =
  (RenderRequest)req.getAttribute(WebKeys.JAVAX_PORTLET_REQUEST);

 if (renderRequest != null) {
  pageContext.setAttribute("renderRequest", renderRequest);
  pageContext.setAttribute(
   "portletPreferences", renderRequest.getPreferences());
  pageContext.setAttribute(
   "portletSession", renderRequest.getPortletSession());
 }

 RenderResponse renderResponse =
  (RenderResponse)req.getAttribute(WebKeys.JAVAX_PORTLET_RESPONSE);

 if (renderResponse != null) {
  pageContext.setAttribute("renderResponse", renderResponse);
 }

 if (portletConfig.isWARFile() && ServerDetector.isWebLogic()) {
  PortletSessionImpl portletSession =
   (PortletSessionImpl)renderRequest.getPortletSession();

  PortletSessionPool.put(
   portletSession.getPortalSessionId(), pageContext.getSession());
 }
 }

从上面的代码可以看出,在portlet的页面上可以直接使用的对象有:
portletConfig  // portlet配置.
portletName   // portlet名称.
renderRequest  // portlet request.
portletPreferences // portlet 首选项.
portletSession // portlet 会话.
renderResponse // portlet response.


三,portlets如何部署到liferay portal中?

在portlets应用的web.xml中加入如下listener定义:
  <listener></listener>
     <listener-class></listener-class> com.liferay.portal.kernel.servlet.PortletContextListener
 

加入了此listener后,当portlets应用启动时,将会向liferay portal注册此应用中的所有定义的portlet;
当portlets应用停止时,会向liferay portal取消所有定义的portlet,  portlet定义当然是由portlet.xml文件给出。

 PortletContextListener实现了ServletContextListener接口.

   public class PortletContextListener implements ServletContextListener {
 public void contextInitialized(ServletContextEvent sce) {
  HotDeployUtil.fireDeployEvent(
   new HotDeployEvent(
    sce.getServletContext(),
    Thread.currentThread().getContextClassLoader()));
 }

 public void contextDestroyed(ServletContextEvent sce) {
  HotDeployUtil.fireUndeployEvent(
   new HotDeployEvent(
    sce.getServletContext(),
    Thread.currentThread().getContextClassLoader()));
 }
  }
  上面的两个方法直接调用HotDeployUtil的fireDeployEvent和fireUndeployEvent方法,HotDeployUtil是Liferay portal的热部署工具类,

  下面来着重看看fireDeployEvent是如何执行portlet注册的,

  public class HotDeployUtil {

 // 构造一个实例,singlton模式.
 private static HotDeployUtil _instance = new HotDeployUtil();

 // 触发部署事件,通知HotDeployListener进行部署。
 private synchronized void _fireDeployEvent(HotDeployEvent event) {
 
             // 当_events不为空时,说明liferay portal还没启动,此时只是简单的保存event.
      if (_events != null) {              
  _events.add(event);
  return;
      }

       Iterator itr = _listeners.iterator();

       // 遍历HotDeployListener,通知它们进行部署.
      while (itr.hasNext()) {
  HotDeployListener listener = (HotDeployListener)itr.next();

  try {
     listener.invokeDeploy(event);
  }  catch (HotDeployException hde) {
     _log.error(StackTraceUtil.getStackTrace(hde));
  }
     }
 }

        // 在liferay portal启动后被调用,此设计的目的主要用于解决portals应用在liferay portal应用之前启动的情况。
 private synchronized void _flushEvents() {
     for (int i = 0; i < _events.size(); i++) {
      HotDeployEvent event = (HotDeployEvent)_events.get(i);

       Iterator itr = _listeners.iterator();

          while (itr.hasNext()) {
  HotDeployListener listener = (HotDeployListener)itr.next();

  try {
     listener.invokeDeploy(event);
  }
  catch (HotDeployException hde) {
     _log.error(StackTraceUtil.getStackTrace(hde));
  }
      }
    }
    _events = null;
 }

 // 注册HotDeployListener
 private void _registerListener(HotDeployListener listener) {
  _listeners.add(listener);
 }

 private List _listeners;
 private List _events;
  }
  这里就有个问题了,HotDeployListener是在哪里加入进来的了?答案是在liferay portal的初始化时加入的。

// liferay portal初始化
  public class MainServlet extends ActionServlet {
      public void init() {        
          // 其它初始化处理......
          try {
                // 执行全局启动事件.
  EventsProcessor.process(PropsUtil.getArray(
   PropsUtil.GLOBAL_STARTUP_EVENTS), true);
  // 执行应用程序启动事件.
  EventsProcessor.process(PropsUtil.getArray(
   PropsUtil.APPLICATION_STARTUP_EVENTS),
     new String[] {_companyId});
  }
  catch (Exception e) {
  _log.error(e);
  }
      PortalInstances.init(_companyId);
      } 
   }
   EventsProcessor是liferay portal中的事件处理器,liferay portal中定义了一系列的事件点,
   像上面的APPLICATION_STARTUP_EVENTS,就是应用程序启动事件点.
   通过配置文件,就可以将我们的自定义事件加入到这些事件点里去执行了。
   关于EventProcessor更详细的内容,将用另外一篇文章来详细介绍,这里只需要知道有两个事件被触发了。

// 全局启动事件。
  public class GlobalStartupAction extends SimpleAction {

      public void run(String[] ids) throws ActionException {

      // JCR 处理...

      // Portal initable
      PortalInitableUtil.flushInitables();

      // Hot deploy
      // 注册layoutTemplate监听器.
      HotDeployUtil.registerListener(new HotDeployLayoutTemplateListener());
      // 注册portlet监听器.
      HotDeployUtil.registerListener(new HotDeployPortletListener());
      // 注册theme监听器.
      HotDeployUtil.registerListener(new HotDeployThemeListener());
      // 清理所有事件.
      HotDeployUtil.flushEvents();

      // Auto deploy 自动部署处理.....
  }

// 部署Portlets

 public class HotDeployPortletListener implements HotDeployListener {

 public void invokeDeploy(HotDeployEvent event) throws HotDeployException {
  String servletContextName = null;

  try {
   // Servlet context
   ServletContext ctx = event.getServletContext();
   servletContextName = ctx.getServletContextName();

   // Company ids
   String[] companyIds = StringUtil.split(
    ctx.getInitParameter("company_id"));
   if ((companyIds.length == 1) && (companyIds[0].equals("*"))) {
    companyIds = PortalInstances.getCompanyIds();
   }

   // Initialize portlets, 初始化portlets
   String[] xmls = new String[] {
    Http.URLtoString(ctx.getResource("/WEB-INF/portlet.xml")),
    Http.URLtoString(ctx.getResource(
     "/WEB-INF/liferay-portlet.xml")),
    Http.URLtoString(ctx.getResource("/WEB-INF/web.xml"))
   };
   List portlets = PortletLocalServiceUtil.initWAR(
    servletContextName, xmls);

   // Class loader, 类装载器
   ClassLoader portletClassLoader = event.getContextClassLoader();
   ctx.setAttribute(
    PortletServlet.PORTLET_CLASS_LOADER, portletClassLoader);

   // portlet context wrapper, portlet上下文包装器.

   boolean strutsBridges = false;

   Iterator itr1 = portlets.iterator();

   while (itr1.hasNext()) {
    Portlet portlet = (Portlet)itr1.next();

    // 构造portlet实例.
    Class portletClass = portletClassLoader.loadClass(
      portlet.getPortletClass());
    javax.portlet.Portlet portletInstance =
     (javax.portlet.Portlet)portletClass.newInstance();

    // 检查是否struts桥接portlet.
    if (ClassUtil.isSubclass(portletClass,
     StrutsPortlet.class.getName())) {
     strutsBridges = true;
    }

    // 构造lucene indexer实例.
    Indexer indexerInstance = null;
    if (Validator.isNotNull(portlet.getIndexerClass())) {
     indexerInstance = (Indexer)portletClassLoader.loadClass(
      portlet.getIndexerClass()).newInstance();
    }

    // 构造scheduler实例.
    Scheduler schedulerInstance = null;

    if (Validator.isNotNull(portlet.getSchedulerClass())) {
     schedulerInstance = (Scheduler)portletClassLoader.loadClass(
      portlet.getSchedulerClass()).newInstance();
    }

    // 验证preferences.
    PreferencesValidator prefsValidator = null;

    if (Validator.isNotNull(portlet.getPreferencesValidator())) {
     prefsValidator =
      (PreferencesValidator)portletClassLoader.loadClass(
       portlet.getPreferencesValidator()).newInstance();

     try {
      if (GetterUtil.getBoolean(PropsUtil.get(
        PropsUtil.PREFERENCE_VALIDATE_ON_STARTUP))) {

       prefsValidator.validate(
        PortletPreferencesSerializer.fromDefaultXML(
         portlet.getDefaultPreferences()));
      }
     }
     catch (Exception e1) {
      _log.warn(
       "Portlet with the name " + portlet.getPortletId() +
        " does not have valid default preferences");
     }
    }

    // 资源处理.
    Map resourceBundles = null;

    if (Validator.isNotNull(portlet.getResourceBundle())) {
     resourceBundles = CollectionFactory.getHashMap();

     Iterator itr2 = portlet.getSupportedLocales().iterator();

     while (itr2.hasNext()) {
      String supportedLocale = (String)itr2.next();

      Locale locale = new Locale(supportedLocale);

      try {
       ResourceBundle resourceBundle =
        ResourceBundle.getBundle(
         portlet.getResourceBundle(), locale,
         portletClassLoader);

       resourceBundles.put(
        locale.getLanguage(), resourceBundle);
      }
      catch (MissingResourceException mre) {
       _log.warn(mre.getMessage());
      }
     }
    }

    // 定制用户特性。
    Map customUserAttributes = CollectionFactory.getHashMap();

    Iterator itr2 =
     portlet.getCustomUserAttributes().entrySet().iterator();

    while (itr2.hasNext()) {
     Map.Entry entry = (Map.Entry)itr2.next();

     String attrCustomClass = (String)entry.getValue();

     customUserAttributes.put(
      attrCustomClass,
      portletClassLoader.loadClass(
       attrCustomClass).newInstance());
    }

    PortletContextWrapper pcw = new PortletContextWrapper(
     portlet.getPortletId(), ctx, portletInstance,
     indexerInstance, schedulerInstance, prefsValidator,
     resourceBundles, customUserAttributes);

    PortletContextPool.put(portlet.getPortletId(), pcw);
   }

   // Struts bridges

   if (strutsBridges) {
    ctx.setAttribute(
     ServletContextProvider.STRUTS_BRIDGES_CONTEXT_PROVIDER,
     new LiferayServletContextProvider());
   }

   // Portlet display,portlet展示处理,显示在portlet添加面板上位置.

   String xml = Http.URLtoString(ctx.getResource(
    "/WEB-INF/liferay-display.xml"));
   PortletCategory newPortletCategory =
    PortletLocalServiceUtil.getWARDisplay(servletContextName, xml);

   for (int i = 0; i < companyIds.length; i++) {
    String companyId = companyIds[i];

    PortletCategory portletCategory =
     (PortletCategory)WebAppPool.get(
      companyId, WebKeys.PORTLET_CATEGORY);

    if (portletCategory != null) {
     portletCategory.merge(newPortletCategory);
    }
    else {
     _log.error(
      "Unable to register portlet for company " + companyId +
       " because it does not exist");
    }
   }

   // Variables

   _vars.put(
    servletContextName, new ObjectValuePair(companyIds, portlets));

   if (_log.isInfoEnabled()) {
    _log.info(
     "Portlets for " + servletContextName +
      " registered successfully");
   }
  }
  catch (Exception e2) {
   throw new HotDeployException(
    "Error registering portlets for " + servletContextName, e2);
  }
 }
 }

关于layoutTemplates和theme的部署这里就不列出了,请自行参考源代码。

你可能感兴趣的:(应用服务器,jsp,servlet,struts,企业应用)