/** * personium.io * Copyright 2014 FUJITSU LIMITED * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.fujitsu.dc.engine.rs; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.Collections; import java.util.Enumeration; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.PathParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.fujitsu.dc.engine.DcEngineContext; import com.fujitsu.dc.engine.DcEngineException; import com.fujitsu.dc.engine.source.ISourceManager; import com.fujitsu.dc.engine.utils.DcEngineConfig; /** * 抽象Serviceクラス. */ public abstract class AbstractService { /** ログオブジェクト. */ private static Log log = LogFactory.getLog(AbstractService.class); /** デバッグ実行時のローカルパス. */ private String sourcePath = ""; /** Service名. */ private String serviceName = ""; /** サーブレットコンテキストオブジェクト. */ @Context private ServletContext context; /** 基底URLが格納されているヘッダ名. */ private static final String KEY_HEADER_BASEURL = "X-Baseurl"; /** リクエストURLが格納されているヘッダ名. */ private static final String KEY_HEADER_REQUEST_URI = "X-Request-Uri"; /** HTTPポート. */ private static final int PORT_HTTP = 80; /** HTTPSポート. */ private static final int PORT_HTTPS = 443; /** リクエストヘッダから取得する インデックス. */ @HeaderParam("X-Dc-Es-Index") private String index; /** リクエストヘッダから取得するタイプ. */ @HeaderParam("X-Dc-Es-Type") private String type; /** リクエストヘッダから取得するID. */ @HeaderParam("X-Dc-Es-Id") private String id; /** リクエストヘッダから取得するRoutingID. */ @HeaderParam("X-Dc-Es-Routing-Id") private String routingId; /** リクエストヘッダから取得するRoutingID. */ @HeaderParam("X-Dc-Fs-Path") protected String fsPath; /** サービスサブジェクト. */ String serviceSubject; /** ソース情報管理. */ ISourceManager sourceManager; /** * デバッグ実行時のローカルパスの取得. * @return ローカルパス */ public final String getSourcePath() { return this.sourcePath; } /** * デバッグ実行時のローカルパスの設定. * @param value ローカルパス */ public final void setSourcePath(final String value) { this.sourcePath = value; } /** * Service名を取得する. * @return Service名 */ public final String getServiceName() { return this.serviceName; } /** * Service名を設定する. * @param value Service名 */ public final void setServiceName(final String value) { this.serviceName = value; } /** * インデックスを取得する. * @return the index */ public final String getIndex() { return index; } /** * タイプを取得する. * @return the type */ public final String getType() { return type; } /** * IDを取得する. * @return the id */ public final String getId() { return id; } /** * RoutingIDを取得する. * @return the routingId */ public final String getRoutingId() { return routingId; } /** * GETメソッド. * @param cell Cell名 * @param scheme データスキーマURI * @param svcName サービス名 * @param request リクエストオブジェクト * @param response レスポンスオブジェクト * @param is リクエストストリームオブジェクト * @return Responseオブジェクト */ @GET public final Response evalJsgiForGet(@PathParam("cell") final String cell, @PathParam("scheme") final String scheme, @PathParam("id") final String svcName, @Context final HttpServletRequest request, @Context final HttpServletResponse response, final InputStream is) { return run(cell, scheme, svcName, request, response, is); } /** * POSTメソッド. * @param cell Cell名 * @param scheme データスキーマURI * @param svcName サービス名 * @param request リクエストオブジェクト * @param response レスポンスオブジェクト * @param is リクエストストリームオブジェクト * @return Responseオブジェクト */ @POST public final Response evalJsgiForPost(@PathParam("cell") final String cell, @PathParam("scheme") final String scheme, @PathParam("id") final String svcName, @Context final HttpServletRequest request, @Context final HttpServletResponse response, final InputStream is) { return run(cell, scheme, svcName, request, response, is); } /** * PUTメソッド. * @param cell Cell名 * @param scheme データスキーマURI * @param svcName サービス名 * @param request リクエストオブジェクト * @param response レスポンスオブジェクト * @param is リクエストストリームオブジェクト * @return Responseオブジェクト */ @PUT public final Response evalJsgiForPutMethod(@PathParam("cell") final String cell, @PathParam("scheme") final String scheme, @PathParam("id") final String svcName, @Context final HttpServletRequest request, @Context final HttpServletResponse response, final InputStream is) { return run(cell, scheme, svcName, request, response, is); } /** * DELETEメソッド. * @param cell Cell名 * @param scheme データスキーマURI * @param svcName サービス名 * @param request リクエストオブジェクト * @param response レスポンスオブジェクト * @param is リクエストストリームオブジェクト * @return Responseオブジェクト */ @DELETE public final Response evalJsgiForDeleteMethod(@PathParam("cell") final String cell, @PathParam("scheme") final String scheme, @PathParam("id") final String svcName, @Context final HttpServletRequest request, @Context final HttpServletResponse response, final InputStream is) { return run(cell, scheme, svcName, request, response, is); } /** * 使用するサーブレットコンテキストオブジェクトの指定. * @param value サーブレットコンテキストオブジェクト */ public final void setServletContext(final ServletContext value) { this.context = value; } /** * サーブレットコンテキストオブジェクトを取得. * @return サーブレットコンテキストオブジェクト */ public final ServletContext getServletContext() { return this.context; } /** * Service実行. * @param cell Cell名 * @param scheme データスキーマURI * @param svcName サーバー名 * @param req Requestオブジェクト * @param res Responseオブジェクト * @param is リクエストストリームオブジェクト * @return Response */ public final Response run(final String cell, final String scheme, final String svcName, final HttpServletRequest req, final HttpServletResponse res, final InputStream is) { StringBuilder msg = new StringBuilder(); msg.append("[" + DcEngineConfig.getVersion() + "] " + ">>> Request Started "); msg.append(" method:"); msg.append(req.getMethod()); msg.append(" method:"); msg.append(req.getRequestURL()); msg.append(" url:"); msg.append(cell); msg.append(" schema:"); msg.append(scheme); msg.append(" svcName:"); msg.append(svcName); log.info(msg); // デバッグ用 すべてのヘッダをログ出力 Enumeration<String> multiheaders = req.getHeaderNames(); for (String headerName : Collections.list(multiheaders)) { Enumeration<String> headers = req.getHeaders(headerName); for (String header : Collections.list(headers)) { log.debug("RequestHeader['" + headerName + "'] = " + header); } } this.setServiceName(svcName); // サービスのURL情報の取得。 String targetCell = cell; if (cell == null) { targetCell = getCell(); } String targetScheme = scheme; if (scheme == null) { targetScheme = getScheme(); } String targetServiceName = svcName; String baseUrl; try { baseUrl = parseRequestUri(req, res); } catch (MalformedURLException e) { // URLに異常があった場合のエラー return makeErrorResponse("Server Error", DcEngineException.STATUSCODE_SERVER_ERROR); } Response response = null; DcEngineContext dcContext = null; try { try { dcContext = new DcEngineContext(); } catch (DcEngineException e) { return errorResponse(e); } // ソースに関する情報を取得 try { this.sourceManager = this.getServiceCollectionManager(); dcContext.setSourceManager(this.sourceManager); this.serviceSubject = this.sourceManager.getServiceSubject(); } catch (DcEngineException e) { return errorResponse(e); } // グローバルオブジェクトのロード dcContext.loadGlobalObject(baseUrl, targetCell, targetScheme, targetScheme, targetServiceName); // ユーザスクリプトを取得(設定及びソース) String source = ""; try { String sourceName = this.sourceManager.getScriptNameForServicePath(targetServiceName); source = this.sourceManager.getSource(sourceName); } catch (DcEngineException e) { return errorResponse(e); } catch (Exception e) { log.info("User Script not found to targetCell(" + targetCell + ", targetScheme(" + targetScheme + "), targetServiceName(" + targetServiceName + ")"); log.info(e.getMessage(), e); return errorResponse(new DcEngineException("404 Not Found (User Script)", DcEngineException.STATUSCODE_NOTFOUND)); } // JSGI実行 try { response = dcContext.runJsgi(source, req, res, is, this.serviceSubject); } catch (DcEngineException e) { return errorResponse(e); } catch (Exception e) { log.warn(" unknown Exception(" + e.getMessage() + ")"); return errorResponse(new DcEngineException("404 Not Found (Service Excute Error)", DcEngineException.STATUSCODE_NOTFOUND)); } } finally { IOUtils.closeQuietly(dcContext); } return response; } /** * エラーレスポンス生成. * @param e Exceptionオブジェクト * @return Response */ final Response errorResponse(final DcEngineException e) { return makeErrorResponse(e.getMessage(), e.getStatusCode()); } /** * エラー時のレスポンスを生成. * @param msg メッセージ本文 * @param code ステータスコード * @return Responseオブジェクト */ private Response makeErrorResponse(final String msg, final int code) { return Response.status(code) .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML) .header(HttpHeaders.CONTENT_LENGTH, msg.getBytes().length) .entity(msg).build(); } /** * Cell名取得. * @return Cell名 */ public abstract String getCell(); /** * データスキーマURI取得. * @return データスキーマURI */ public abstract String getScheme(); /** * リクエストURLの解析. * @param req Requestオブジェクト * @param res Responseオブジェクト * @return 基底URL文字列 * @throws MalformedURLException 例外 */ public final String parseRequestUri(final HttpServletRequest req, final HttpServletResponse res) throws MalformedURLException { String baseUrl = ""; // リクエストURIを取得する。リクエストURI=>ホスト名よりあとのクエリ含んだ文字列。 // 例) /dc-engine/engine-test/ds-engine-test/service/hello?a=b&c=d // (通常動作)DCから送られてきたURIがヘッダに指定されていたらそれを利用する。 // (デバッグ動作)DC-Engine呼び出しのURIから生成する。 String requestUri = req.getHeader(KEY_HEADER_REQUEST_URI); if (requestUri == null || requestUri.length() == 0) { requestUri = req.getRequestURI(); String query = req.getQueryString(); if (query != null && query.length() > 0) { requestUri += "?" + query; } } // リクエストURIからクエリ部分を切り取って、スクリプト名を抜き出す。 // 例)/dc-engine/engine-test/ds-engine-test/service/hello int indexQ = requestUri.indexOf("?"); String scriptName = requestUri; if (indexQ > 0) { scriptName = requestUri.substring(0, indexQ); } // サーブレットリクエストオブジェクトに値を保存。あとでJSGIオブジェクトに設定するため。 req.setAttribute("env.requestUri", requestUri); req.setAttribute("scriptName", scriptName); // クライアントから受け付けたリクエストURL(ホスト?)の取得 // (通常動作)DCから送られてきたURLがヘッダに指定されていたらそれを利用する。 // (デバッグ動作)プロパティファイルに設定されている値を利用する。 String defaultBaseUrl = DcEngineConfig.getDefaultBaseUrl(); baseUrl = req.getHeader(KEY_HEADER_BASEURL); if (baseUrl == null || baseUrl.length() == 0) { baseUrl = defaultBaseUrl; } // サーブレットリクエストオブジェクトに値を保存。あとでJSGIオブジェクトに設定するため。 URL baseUrlObj = new URL(baseUrl); int port = baseUrlObj.getPort(); String proto = baseUrlObj.getProtocol(); String host = baseUrlObj.getHost(); String hostHeader = host; if (port < 0) { if ("http".endsWith(proto)) { port = PORT_HTTP; } if ("https".endsWith(proto)) { port = PORT_HTTPS; } } else { hostHeader += ":" + port; } req.setAttribute("HostHeader", hostHeader); req.setAttribute("host", baseUrlObj.getHost()); req.setAttribute("port", port); req.setAttribute("scheme", proto); return baseUrl; } /** * ServiceCollectionManager取得. * @return ISourceManager * @throws DcEngineException DcEngineException */ public abstract ISourceManager getServiceCollectionManager() throws DcEngineException; }