Liferay7 BPM门户开发之42: Liferay核心JSP定制扩展

Liferay最大的好处是不仅接口强大,利于扩展,就连JSP定制扩展都提供了3种方式。

修改核心jsp代码,有3种修改方式:
1、暴力修改
直接修改(位于portal-web/docroot/html),编译部署,会带来风险,而且不能同步更新。


2、全量扩展修改
热部署jsp文件(替代原有jsp),这是v7.0下的OSGi方式,实现方式非常优雅。


3、CustomJspBag Hook方式
实现CustomJspBag接口,做jsp片段式的修改,同样是增量热部署,也是v7.0下的OSGi方式(需要增加依赖org.osgi.service.component.annotations.Activate和org.osgi.service.component.annotations.Component;),实现方式可以说更加优雅。

第一种方式不讲了。

2、全量扩展修改

需要新建fragment module。只需要注意2点:
1、在module工程的OSGi定义(bnd.bnd文件)中指定Fragment-Host声明
如下:
Bundle-Version: 1.0.0
Fragment-Host: com.liferay.login.web;bundle-version="1.0.0"
-sources: true

2、把你修改后的JSP文件放在module工程的resources目录,比如
你的module工程\src\main\resources\META-INF\resources\login.jsp

login.jsp例子:

<%--
/**
 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 */
--%>

<%@ include file="/init.jsp" %>
<p style="color: red">changed</p>
<c:choose>
    <c:when test="<%= themeDisplay.isSignedIn() %>">

        <%
        String signedInAs = HtmlUtil.escape(user.getFullName());

        if (themeDisplay.isShowMyAccountIcon() && (themeDisplay.getURLMyAccount() != null)) {
            String myAccountURL = String.valueOf(themeDisplay.getURLMyAccount());

            signedInAs = "<a class=\"signed-in\" href=\"" + HtmlUtil.escape(myAccountURL) + "\">" + signedInAs + "</a>";
        }
        %>

        <liferay-ui:message arguments="<%= signedInAs %>" key="you-are-signed-in-as-x" translateArguments="<%= false %>" />
    </c:when>
    <c:otherwise>

        <%
        String redirect = ParamUtil.getString(request, "redirect");

        String login = LoginUtil.getLogin(request, "login", company);
        String password = StringPool.BLANK;
        boolean rememberMe = ParamUtil.getBoolean(request, "rememberMe");

        if (Validator.isNull(authType)) {
            authType = company.getAuthType();
        }
        %>

        <portlet:actionURL name="/login/login" secure="<%= PropsValues.COMPANY_SECURITY_AUTH_REQUIRES_HTTPS || request.isSecure() %>" var="loginURL" />

        <aui:form action="<%= loginURL %>" autocomplete='<%= PropsValues.COMPANY_SECURITY_LOGIN_FORM_AUTOCOMPLETE ? "on" : "off" %>' cssClass="sign-in-form" method="post" name="fm" onSubmit="event.preventDefault();">
            <aui:input name="saveLastPath" type="hidden" value="<%= false %>" />
            <aui:input name="redirect" type="hidden" value="<%= redirect %>" />
            <aui:input name="doActionAfterLogin" type="hidden" value="<%= portletName.equals(PortletKeys.FAST_LOGIN) ? true : false %>" />

            <c:choose>
                <c:when test='<%= SessionMessages.contains(request, "userAdded") %>'>

                    <%
                    String userEmailAddress = (String)SessionMessages.get(request, "userAdded");
                    String userPassword = (String)SessionMessages.get(request, "userAddedPassword");
                    %>

                    <div class="alert alert-success">
                        <c:choose>
                            <c:when test="<%= company.isStrangersVerify() || Validator.isNull(userPassword) %>">
                                <liferay-ui:message key="thank-you-for-creating-an-account" />

                                <c:if test="<%= company.isStrangersVerify() %>">
                                    <liferay-ui:message arguments="<%= userEmailAddress %>" key="your-email-verification-code-has-been-sent-to-x" translateArguments="<%= false %>" />
                                </c:if>
                            </c:when>
                            <c:otherwise>
                                <liferay-ui:message arguments="<%= userPassword %>" key="thank-you-for-creating-an-account.-your-password-is-x" translateArguments="<%= false %>" />
                            </c:otherwise>
                        </c:choose>

                        <c:if test="<%= PrefsPropsUtil.getBoolean(company.getCompanyId(), PropsKeys.ADMIN_EMAIL_USER_ADDED_ENABLED) %>">
                            <liferay-ui:message arguments="<%= userEmailAddress %>" key="your-password-has-been-sent-to-x" translateArguments="<%= false %>" />
                        </c:if>
                    </div>
                </c:when>
                <c:when test='<%= SessionMessages.contains(request, "userPending") %>'>

                    <%
                    String userEmailAddress = (String)SessionMessages.get(request, "userPending");
                    %>

                    <div class="alert alert-success">
                        <liferay-ui:message arguments="<%= userEmailAddress %>" key="thank-you-for-creating-an-account.-you-will-be-notified-via-email-at-x-when-your-account-has-been-approved" translateArguments="<%= false %>" />
                    </div>
                </c:when>
            </c:choose>

            <liferay-ui:error exception="<%= AuthException.class %>" message="authentication-failed" />
            <liferay-ui:error exception="<%= CompanyMaxUsersException.class %>" message="unable-to-log-in-because-the-maximum-number-of-users-has-been-reached" />
            <liferay-ui:error exception="<%= CookieNotSupportedException.class %>" message="authentication-failed-please-enable-browser-cookies" />
            <liferay-ui:error exception="<%= NoSuchUserException.class %>" message="authentication-failed" />
            <liferay-ui:error exception="<%= PasswordExpiredException.class %>" message="your-password-has-expired" />
            <liferay-ui:error exception="<%= UserEmailAddressException.MustNotBeNull.class %>" message="please-enter-an-email-address" />
            <liferay-ui:error exception="<%= UserLockoutException.LDAPLockout.class %>" message="this-account-is-locked" />

            <liferay-ui:error exception="<%= UserLockoutException.PasswordPolicyLockout.class %>">

                <%
                UserLockoutException.PasswordPolicyLockout ule = (UserLockoutException.PasswordPolicyLockout)errorException;
                %>

                <liferay-ui:message arguments="<%= ule.user.getUnlockDate() %>" key="this-account-is-locked-until-x" translateArguments="<%= false %>" />
            </liferay-ui:error>

            <liferay-ui:error exception="<%= UserPasswordException.class %>" message="authentication-failed" />
            <liferay-ui:error exception="<%= UserScreenNameException.MustNotBeNull.class %>" message="the-screen-name-cannot-be-blank" />

            <aui:fieldset>

                <%
                String loginLabel = null;

                if (authType.equals(CompanyConstants.AUTH_TYPE_EA)) {
                    loginLabel = "email-address";
                }
                else if (authType.equals(CompanyConstants.AUTH_TYPE_SN)) {
                    loginLabel = "screen-name";
                }
                else if (authType.equals(CompanyConstants.AUTH_TYPE_ID)) {
                    loginLabel = "id";
                }
                %>

                <aui:input autoFocus="<%= windowState.equals(LiferayWindowState.EXCLUSIVE) || windowState.equals(WindowState.MAXIMIZED) %>" cssClass="clearable" label="<%= loginLabel %>" name="login" showRequiredLabel="<%= false %>" type="text" value="<%= login %>">
                    <aui:validator name="required" />
                </aui:input>

                <aui:input name="password" showRequiredLabel="<%= false %>" type="password" value="<%= password %>">
                    <aui:validator name="required" />
                </aui:input>

                <span id="<portlet:namespace />passwordCapsLockSpan" style="display: none;"><liferay-ui:message key="caps-lock-is-on" /></span>

                <c:if test="<%= company.isAutoLogin() && !PropsValues.SESSION_DISABLED %>">
                    <aui:input checked="<%= rememberMe %>" name="rememberMe" type="checkbox" />
                </c:if>
            </aui:fieldset>

            <aui:button-row>
                <aui:button type="submit" value="sign-in" />
            </aui:button-row>
        </aui:form>

        <liferay-util:include page="/navigation.jsp" servletContext="<%= application %>" />

        <aui:script sandbox="<%= true %>">
            var form = AUI.$(document.<portlet:namespace />fm);

            form.on(
                'submit',
                function(event) {
                    var redirect = form.fm('redirect');

                    if (redirect) {
                        var redirectVal = redirect.val();

                        redirect.val(redirectVal + window.location.hash);
                    }

                    submitForm(form);
                }
            );

            form.fm('password').on(
                'keypress',
                function(event) {
                    Liferay.Util.showCapsLock(event, '<portlet:namespace />passwordCapsLockSpan');
                }
            );
        </aui:script>
    </c:otherwise>
</c:choose>
View Code

 

3、CustomJspBag Hook方式


这是一种覆盖原有XXXX-ext.jsp的方式,XXXX-ext.jsp是空文件,下面的例子用来覆盖 bottom.jsp ,插入了新定义 bottom-ext.jsp 片段进来
文件位于 src/main/resources/META-INF/jsps/html/common/themes/bottom-ext.jsp
bottom-ext.jsp 只有一行

<h2>HERE I AM!!!!!</h2>

然后实现CustomJspBag接口,实现类YourCustomJspBag ,有2点需要特别注意

service.ranking是优先级的意思,数值越大越优先,在例子中是100,当有另外的CustomJspBag实现类,比如定义为200,那么定义为200的类的优先级更高。

关键方法是activate,作用是为所有的需要自定义的核心JSPs添加URL(目录路径)到List列表,List列表的用途有些过滤器的含义,例子中是添加"META-INF/jsps/"下的所有文件。

import com.liferay.portal.deploy.hot.CustomJspBag;
import com.liferay.portal.kernel.url.URLContainer;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;


@Component(
    immediate = true,
    property = {
        "context.id=YourCustomJspBag",
        "context.name=Custom JSP Bag",
        "service.ranking:Integer=100"
    }
)
public class YourCustomJspBag implements CustomJspBag {


    @Override
    public String getCustomJspDir() {
        return "META-INF/jsps/";
    }

    /**
     * 返回自定义JSP URL paths列表.
     */
    @Override
    public List<String> getCustomJsps() {
        return _customJsps;
    }

    @Override
    public URLContainer getURLContainer() {
        return _urlContainer;
    }

    @Override
    public boolean isCustomJspGlobal() {
        return true;
    }

    //osgi激活时触发的动作
    @Activate
    protected void activate(BundleContext bundleContext) {
        _bundle = bundleContext.getBundle();

        _customJsps = new ArrayList<>();

        Enumeration<URL> entries = _bundle.findEntries(
            getCustomJspDir(), "*.jsp", true);

        while (entries.hasMoreElements()) {
            URL url = entries.nextElement();
            //*.jsp全部添加进JSP URL paths
            _customJsps.add(url.getPath());
        }
    }

    private Bundle _bundle;
    private List<String> _customJsps;

    private final URLContainer _urlContainer = new URLContainer() {

        @Override
        public URL getResource(String name) {
            return _bundle.getEntry(name);
        }

        @Override
        public Set<String> getResources(String path) {
            Set<String> paths = new HashSet<>();

            for (String entry : _customJsps) {
                if (entry.startsWith(path)) {
                    paths.add(entry);
                }
            }

            return paths;
        }

    };

}

 

gradle的配置文件build.gradle

dependencies {
compile 'com.liferay.portal:com.liferay.portal.kernel:2.0.0'
compile 'com.liferay.portal:com.liferay.portal.impl:2.0.0'
compile 'org.osgi:org.osgi.core:6.0.0'
compile 'org.osgi:org.osgi.service.component.annotations:1.3.0'
}

version = '1.0.0'

部署后的界面:


CustomJspBag后面的秘密

在portal-web/docroot/html/common/themes/bottom.jsp 文件,在其最下方,有以下代码:

<liferay-util:include page="/html/common/themes/bottom-ext.jsp" />

原来必须要依靠原来的JSP包括了一个空的bottom-ext.jsp文件,这是前提
实际上它只是覆盖了bottom-ext.jsp,而不是它的宿主bottom.jsp

即所有类似XXXX-ext.jsp这样的文件,都是可以做定制的。

那么看到这里就很清晰第三种方式(CustomJspBag Hook)和第二种方式(全量扩展修改)之间的区别了,即 片段覆盖全量覆盖的区别,这需要您根据需求来做选择。
Liferay给开发者这两种选择,目的很清晰,即通过CustomJspBag Hook来降低风险,做合理的分离设计。

 

posted @ 2016-11-30 11:15  昕友软件开发  阅读(796)  评论(0编辑  收藏  举报
欢迎访问我的开源项目:xyIM企业即时通讯