本篇內容大幅度參考自網路文章
但是因為這篇文章寫得較為詳細 + 語言是英文,所以我看了很多次才終於成功實作出來,
因此特別紀錄一下,操作步驟以免以後忘記,又要重來一遍
(建議卡關的話可以本文跟這篇文章交叉比對,會有助於debug)
假設觀看本文章的人已經有基本能力可以建立一個普通的liferay MVC "Hello World 網頁專案" & 對 spring mvc 專案也有基礎了解,
是需要把原本使用 portlet URL 改成可以使用 rest api 的形式
本篇希望讀者建立liferay plugin project後
可以使用 copy paste 就建立出一個簡單的 liferay + spring mvc + rest api 的專案
基本上粗體字就是需要修改或者建立的檔案
本身環境是 liferay 6.2 版本,
一開始project 建好後裡應會有,
portlet.xml
liferay-plugin-package.properties
web.xml
等基本設定黨
liferay-plugin-package.properties
打開後,開啟Source 頁籤
在最底端加上
portal-dependency-jars=\
aopalliance.jar,\
commons-io.jar,\
commons-logging.jar,\
commons-codec.jar,\
jstl-api.jar,\
jstl-impl.jar,\
log4j.jar,\
slf4j-api.jar,\
spring-aop.jar,\
spring-asm.jar,\
spring-beans.jar,\
spring-context-support.jar,\
spring-context.jar,\
spring-core.jar,\
spring-expression.jar,\
spring-web-portlet.jar,\
spring-web-servlet.jar,\
spring-web-struts.jar,\
spring-web.jar,\
jackson-core-asl-1.4.2.jar,\
jackson-mapper-asl-1.4.2.jar,\
poi.jar,\
poi-ooxml.jar,\
poi-ooxml-schemas.jar,\
poi-scratchpad.jar
這個步驟是引入所需要的jar檔,他會跑到 tomcat\webapps\ROOT\WEB-INF\lib 抓所需要用到的檔案
當然,如果你是用 MVN or Gradle 在管理相依 lib 就依照自己的方式去引入這些 jar 檔也沒差
這邊附上
portlet.xml
把要轉變為 resp api 的 portlet 修改設定
<portlet-class>com.test.NewPortlet</portlet-class>
改為
<portlet-class>org.springframework.web.portlet.DispatcherPortlet</portlet-class>
,
<init-param>
<name>view-template</name>
<value>/html/new/view.jsp</value>
</init-param>
改為
<init-param>
<name>contextConfigLocation</name>
<!-- spring 設定檔的存放位置 -->
<value>/WEB-INF/spring/test-context.xml</value>
</init-param>
web.xml
在 <web-app...> </web-app> 之間 加入
<listener>
<listener-class>com.liferay.portal.kernel.servlet.PluginContextListener</listener-class>
</listener>
<context-param>
<param-name>portalListenerClasses</param-name>
<param-value>com.liferay.portal.kernel.servlet.SerializableSessionAttributeListener,org.springframework.web.util.Log4jConfigListener,org.springframework.web.context.ContextLoaderListener</param-value>
</context-param>
<servlet>
<servlet-name>Portlet Servlet</servlet-name>
<servlet-class>com.liferay.portal.kernel.servlet.PortletServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Portlet Servlet</servlet-name>
<url-pattern>/portlet-servlet/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>restful</servlet-name>
<servlet-class>
com.liferay.portal.kernel.servlet.PortalDelegateServlet
</servlet-class>
<init-param>
<param-name>servlet-class</param-name>
<param-value>
org.springframework.web.servlet.DispatcherServlet
</param-value>
</init-param>
<init-param>
<param-name>sub-context</param-name>
<param-value>restful</param-value>
</init-param>
<!-- 等第一次有人呼叫這個portlet的時候,才建立的一些會使用到的背景執行程式-->
<load-on-startup>-1</load-on-startup>
</servlet>
<servlet>
<servlet-name>view-servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.ViewRendererServlet</servlet-class>
<load-on-startup>-1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>restful</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>view-servlet</servlet-name>
<url-pattern>/WEB-INF/servlet/view</url-pattern>
</servlet-mapping>
<jsp-config>
<taglib>
<taglib-uri>http://liferay.com/tld/theme</taglib-uri>
<taglib-location>/WEB-INF/tld/liferay-theme.tld</taglib-location>
</taglib>
</jsp-config>
restful-servlet.xml (在...\WEB-INF\restful-servlet.xml自行建立)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<!--之後用來接收rest api 的實際位置-->
<context:component-scan base-package="com.test.rest" />
<mvc:annotation-driven />
<mvc:interceptors>
<mvc:interceptor >
<mvc:mapping path="/**"/>
<bean class="com.test.interceptor.SecureRequestHandlerInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="mediaTypes">
<map>
<entry key="html" value="text/html"/>
<entry key="json" value="application/json"/>
</map>
</property>
<property name="viewResolvers">
<list>
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/html/"/>
<property name="suffix" value=".jsp"/>
</bean>
</list>
</property>
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
<property name="prefixJson" value="true"/>
</bean>
</list>
</property>
</bean>
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="objectMapper" ref="jacksonObjectMapper" />
</bean>
</list>
</property>
</bean>
<bean id="jacksonObjectMapper" class="com.test.rest.json.LiferayObjectMapper" >
</bean>
</beans>
test-context.xml(在.../WEB-INF/spring 下自行建立,連spring 資料夾都要自己建立唷)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/html" />
<property name="suffix" value=".html" />
<property name="contentType" value="text/html;charset=UTF-8"/>
</bean>
<!--用來接受 liferay spring mvc 呼叫的位置 (PortalUtil Request)-->
<bean class="com.test.mvc.TestController" />
</beans>
Portlet Controller
package com.test.mvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("VIEW")
public class TestController {
@RequestMapping
public String initRESTFulApp() {
return "defaultView"; // docroot/html下應該要有一個 defaultView.html
}
}
RESTFul Controller
package com.test.rest;
import java.util.ArrayList;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.liferay.portal.kernel.exception.SystemException;
@Controller
public class SampleRESTFullController {
@RequestMapping(value = "/helloSample", method = RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
public @ResponseBody List<String> helloSample() throws SystemException {
List<String> result = new ArrayList<String>();
result.add("Hello");
result.add("World");
return result;
}
}
SecureRequestHandlerInterceptor.java
package com.test.interceptor;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.servlet.HttpHeaders;
import com.liferay.portal.kernel.servlet.ProtectedServletRequest;
import com.liferay.portal.kernel.util.CookieKeys;
import com.liferay.portal.kernel.util.Digester;
import com.liferay.portal.kernel.util.DigesterUtil;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.Http;
import com.liferay.portal.kernel.util.HttpUtil;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.kernel.util.WebKeys;
import com.liferay.portal.model.Company;
import com.liferay.portal.model.User;
import com.liferay.portal.security.auth.CompanyThreadLocal;
import com.liferay.portal.security.auth.PrincipalThreadLocal;
import com.liferay.portal.security.permission.PermissionChecker;
import com.liferay.portal.security.permission.PermissionCheckerFactoryUtil;
import com.liferay.portal.security.permission.PermissionThreadLocal;
import com.liferay.portal.service.CompanyLocalServiceUtil;
import com.liferay.portal.service.UserLocalServiceUtil;
import com.liferay.portal.util.Portal;
import com.liferay.portal.util.PortalUtil;
import com.liferay.util.Encryptor;
import com.liferay.util.EncryptorException;
public class SecureRequestHandlerInterceptor extends HandlerInterceptorAdapter{
//當 request 為 "POST", "PUT", "DELETE" 需要做安全上的確認
public final static List<String> METHODS_TO_CHECK = Collections.unmodifiableList(Arrays.asList("POST", "PUT", "DELETE"));
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
if(METHODS_TO_CHECK.contains(request.getMethod().toUpperCase())){
if (_log.isDebugEnabled()) {
if (_httpsRequired) {
_log.debug("https is required");
}
else {
_log.debug("https is not required");
}
}
if (_httpsRequired && !request.isSecure()) {
if (_log.isDebugEnabled()) {
String completeURL = HttpUtil.getCompleteURL(request);
_log.debug("Securing " + completeURL);
}
StringBundler redirectURL = new StringBundler(5);
redirectURL.append(Http.HTTPS_WITH_SLASH);
redirectURL.append(request.getServerName());
redirectURL.append(request.getServletPath());
String queryString = request.getQueryString();
if (Validator.isNotNull(queryString)) {
redirectURL.append(StringPool.QUESTION);
redirectURL.append(request.getQueryString());
}
if (_log.isDebugEnabled()) {
_log.debug("Redirect to " + redirectURL);
}
response.sendRedirect(redirectURL.toString());
return false;
}
else {
if (_log.isDebugEnabled()) {
String completeURL = HttpUtil.getCompleteURL(request);
_log.debug("Not securing " + completeURL);
}
User user = PortalUtil.getUser(request);
if(user==null){
user = getUserFromCookies(request);
}
if ((user != null) && !user.isDefaultUser()) {
request = setCredentials(
request, request.getSession(), user.getUserId(), null);
return true;
}
else {
if (_digestAuthEnabled) {
return digestAuth(request, response)!=null?true:false;
}
else if (_basicAuthEnabled) {
return basicAuth(request, response)!=null?true:false;
}
return false;
}
}
}
return true;
}
private User getUserFromCookies(HttpServletRequest request) {
// Getting the cookies from the servlet request
Cookie[] cookies = request.getCookies();
String userId = null;
String uuid = CookieKeys.getCookie(request, CookieKeys.USER_UUID);
String companyId = CookieKeys.getCookie(request, CookieKeys.COMPANY_ID);
if (uuid != null && companyId != null) {
try {
Company company = CompanyLocalServiceUtil.getCompany(Long.parseLong(companyId));
String _userid = Encryptor.decrypt(company.getKeyObj(), uuid);
userId = _userid.substring(0,_userid.indexOf(StringPool.PERIOD));
try {
// liferay 預設管理者,模擬其他用戶時,必須置換session 中 USER 的資訊
String referrer = request.getHeader("referer");
URI url = new URI(referrer);
String queryStr = url.getQuery();
if (queryStr != null) {
String[] params = queryStr.split("&");
for (String param: params) {
String key = param.substring(0, param.indexOf("="));
if ("doAsUserId".equals(key)) {
String doAsUserId = param.substring(param.indexOf('=') + 1);
String disguiseUserId = Encryptor.decrypt(PortalUtil
.getCompany(request).getKeyObj(), doAsUserId);
return UserLocalServiceUtil.getUser(Long.parseLong(disguiseUserId));
}
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return UserLocalServiceUtil.getUser(Long.parseLong(userId));
} catch (NumberFormatException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (PortalException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SystemException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (EncryptorException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return null;
}
public String hexStringToStringByAscii(String hexString) {
byte[] bytes = new byte[hexString.length()/2];
for (int i = 0; i < hexString.length() / 2; i++) {
String oneHexa = hexString.substring(i * 2, i * 2 + 2);
bytes[i] = Byte.parseByte(oneHexa, 16);
}
try {
return new String(bytes, "ASCII");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
protected HttpServletRequest basicAuth(
HttpServletRequest request, HttpServletResponse response)
throws Exception {
HttpSession session = request.getSession();
session.setAttribute("BASIC_AUTH_ENABLED", Boolean.TRUE);
long userId = GetterUtil.getLong(
(String)session.getAttribute(_AUTHENTICATED_USER));
if (userId > 0) {
request = new ProtectedServletRequest(
request, String.valueOf(userId), HttpServletRequest.BASIC_AUTH);
initThreadLocals(request);
}
else {
try {
userId = PortalUtil.getBasicAuthUserId(request);
if(userId > 0){
initThreadLocals(userId);
}
}
catch (Exception e) {
_log.error(e, e);
}
if (userId > 0) {
request = setCredentials(
request, session, userId, HttpServletRequest.BASIC_AUTH);
}
else {
response.setHeader(HttpHeaders.WWW_AUTHENTICATE, _BASIC_REALM);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return null;
}
}
return request;
}
protected HttpServletRequest setCredentials(
HttpServletRequest request, HttpSession session, long userId,
String authType)
throws Exception {
User user = UserLocalServiceUtil.getUser(userId);
String userIdString = String.valueOf(userId);
request = new ProtectedServletRequest(request, userIdString, authType);
session.setAttribute(WebKeys.USER, user);
session.setAttribute(_AUTHENTICATED_USER, userIdString);
initThreadLocals(request);
return request;
}
private String generateNonce(long companyId, String remoteAddress) {
String companyKey = null;
try {
Company company = CompanyLocalServiceUtil.getCompanyById(companyId);
companyKey = company.getKey();
}
catch (Exception e) {
throw new RuntimeException("Invalid companyId " + companyId, e);
}
long timestamp = System.currentTimeMillis();
String nonce = DigesterUtil.digestHex(
Digester.MD5, remoteAddress, String.valueOf(timestamp), companyKey);
return nonce;
}
protected void initThreadLocals(HttpServletRequest request)
throws Exception {
HttpSession session = request.getSession();
User user = (User)session.getAttribute(WebKeys.USER);
CompanyThreadLocal.setCompanyId(user.getCompanyId());
PrincipalThreadLocal.setName(user.getUserId());
PrincipalThreadLocal.setPassword(PortalUtil.getUserPassword(request));
if (!_usePermissionChecker) {
return;
}
PermissionChecker permissionChecker =
PermissionCheckerFactoryUtil.create(user);
PermissionThreadLocal.setPermissionChecker(permissionChecker);
}
protected void initThreadLocals(long userId)
throws Exception {
User user = UserLocalServiceUtil.getUser(userId);
CompanyThreadLocal.setCompanyId(user.getCompanyId());
PrincipalThreadLocal.setName(user.getUserId());
if (!_usePermissionChecker) {
return;
}
PermissionChecker permissionChecker =
PermissionCheckerFactoryUtil.create(user);
PermissionThreadLocal.setPermissionChecker(permissionChecker);
}
protected HttpServletRequest digestAuth(
HttpServletRequest request, HttpServletResponse response)
throws Exception {
HttpSession session = request.getSession();
long userId = GetterUtil.getLong(
(String)session.getAttribute(_AUTHENTICATED_USER));
if (userId > 0) {
request = new ProtectedServletRequest(
request, String.valueOf(userId),
HttpServletRequest.DIGEST_AUTH);
initThreadLocals(request);
}
else {
try {
userId = PortalUtil.getDigestAuthUserId(request);
}
catch (Exception e) {
_log.error(e, e);
}
if (userId > 0) {
request = setCredentials(
request, session, userId, HttpServletRequest.DIGEST_AUTH);
}
else {
// Must generate a new nonce for each 401 (RFC2617, 3.2.1)
long companyId = CompanyLocalServiceUtil.getCompanies(0, 1).get(0).getCompanyId();
String remoteAddress = request.getRemoteAddr();
String nonce = generateNonce(companyId, remoteAddress);
StringBundler sb = new StringBundler(4);
sb.append(_DIGEST_REALM);
sb.append(", nonce=\"");
sb.append(nonce);
sb.append("\"");
response.setHeader(HttpHeaders.WWW_AUTHENTICATE, sb.toString());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return null;
}
}
return request;
}
public void setBasicAuthEnabled(boolean basicAuthEnabled) {
this._basicAuthEnabled = basicAuthEnabled;
}
public void setDigestAuthEnabled(boolean digestAuthEnabled) {
this._digestAuthEnabled = digestAuthEnabled;
}
public void setHttpsRequired(boolean httpsRequired) {
this._httpsRequired = httpsRequired;
}
public void setUsePermissionChecker(boolean usePermissionChecker) {
this._usePermissionChecker = usePermissionChecker;
}
private static final String _AUTHENTICATED_USER =
SecureRequestHandlerInterceptor.class + "_AUTHENTICATED_USER";
private static final String _BASIC_REALM =
"Basic realm=\"" + Portal.PORTAL_REALM + "\"";
private static final String _DIGEST_REALM =
"Digest realm=\"" + Portal.PORTAL_REALM + "\"";
private static Log _log = LogFactoryUtil.getLog(SecureRequestHandlerInterceptor.class);
private boolean _basicAuthEnabled=true;
private boolean _digestAuthEnabled;
private boolean _httpsRequired;
private boolean _usePermissionChecker=true;
}
LiferayObjectMapper.java
package com.test.rest.json;
import com.liferay.portal.model.BaseModel;
import com.liferay.portal.service.ServiceContext;
import com.liferay.portlet.expando.model.ExpandoBridge;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.DeserializerProvider;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.SerializerProvider;
import org.codehaus.jackson.type.JavaType;
/***************************************************************************
* <b>Description:</b> An ObjectMapper to add mixIn annotations
* This will make all the service builder entities valid for jackson
* deserialization
* <b>Created:</b>20 Feb 2014 17:48:09 @author Vitor Silva
**************************************************************************/
public class LiferayObjectMapper extends ObjectMapper {
public LiferayObjectMapper() {
super();
}
public LiferayObjectMapper(JsonFactory jf, SerializerProvider sp,
DeserializerProvider dp, SerializationConfig sconfig,
DeserializationConfig dconfig) {
super(jf, sp, dp, sconfig, dconfig);
}
public LiferayObjectMapper(JsonFactory jf, SerializerProvider sp,
DeserializerProvider dp) {
super(jf, sp, dp);
}
public LiferayObjectMapper(JsonFactory jf) {
super(jf);
}
@Override
public boolean canDeserialize(JavaType type) {
DeserializationConfig desConfig = copyDeserializationConfig();
addMixInAnnotations(desConfig, type);
return _deserializerProvider.hasValueDeserializerFor(desConfig, type);
}
/***************************************************************************
* <b>Description:</b> Adds mix in annotations to filter out
* entity internal fields like expando that prevent deserialization
*
* <b>Created:</b>20 Feb 2014 16:57:31 @author Vitor Silva
* @param desConfig
**************************************************************************/
protected void addMixInAnnotations(DeserializationConfig desConfig, JavaType type) {
desConfig.addMixInAnnotations(type.getClass(), IgnoreExpandoAttributesMixIn.class);
}
abstract class IgnoreExpandoAttributesMixIn
{
@JsonIgnore public abstract void setExpandoBridgeAttributes(ServiceContext serviceContext);
@JsonIgnore public abstract void setExpandoBridgeAttributes(BaseModel<?> baseModel);
@JsonIgnore public abstract void setExpandoBridgeAttributes(ExpandoBridge expandoBridge);
}
}
啟動Tomcat 後就可以使用 postman 測試了
localhost:8080/Origin-portlet/services/helloSample , method = GET 去測試是否架設成功了
以上
文章標籤
全站熱搜
