首页 > 代码库 > Liferay7 BPM门户开发之42: Liferay核心JSP定制扩展

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=http://www.mamicode.com/"" + 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="http://www.mamicode.com/" />            <aui:input name="redirect" type="hidden" value="http://www.mamicode.com/" />            <aui:input name="doActionAfterLogin" type="hidden" value="http://www.mamicode.com/" />            <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="http://www.mamicode.com/">                    <aui:validator name="required" />                </aui:input>                <aui:input name="password" showRequiredLabel="<%= false %>" type="password" value="http://www.mamicode.com/">                    <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="http://www.mamicode.com/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来降低风险,做合理的分离设计。

 

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