/**
* 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.core.rs.box;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.UriInfo;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.wink.webdav.WebDAVMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fujitsu.dc.common.utils.DcCoreUtils;
import com.fujitsu.dc.core.DcCoreConfig;
import com.fujitsu.dc.core.DcCoreException;
import com.fujitsu.dc.core.annotations.ACL;
import com.fujitsu.dc.core.auth.BoxPrivilege;
import com.fujitsu.dc.core.model.DavCmp;
import com.fujitsu.dc.core.model.DavMoveResource;
import com.fujitsu.dc.core.model.DavRsCmp;
import com.fujitsu.dc.core.model.impl.es.DavCmpEsImpl;
import com.fujitsu.dc.core.model.impl.fs.DavCmpFsImpl;
/**
* DcEngineSvcCollectionResourceを担当するJAX-RSリソース.
*/
public final class DcEngineSvcCollectionResource {
private static Logger log = LoggerFactory.getLogger(DcEngineSvcCollectionResource.class);
DavCmp davCmp = null;
DavCollectionResource dcr = null;
DavRsCmp davRsCmp;
/**
* constructor.
* @param parent DavRsCmp
* @param davCmp DavCmp
*/
public DcEngineSvcCollectionResource(final DavRsCmp parent, final DavCmp davCmp) {
this.davCmp = davCmp;
this.dcr = new DavCollectionResource(parent, davCmp);
this.davRsCmp = new DavRsCmp(parent, davCmp);
}
/**
* PROPFINDの処理.
* @param requestBodyXml リクエストボディ
* @param depth Depthヘッダ
* @param contentLength Content-Length ヘッダ
* @param transferEncoding Transfer-Encoding ヘッダ
* @return JAX-RS Response
*/
@WebDAVMethod.PROPFIND
public Response propfind(final Reader requestBodyXml,
@HeaderParam(DcCoreUtils.HttpHeaders.DEPTH) final String depth,
@HeaderParam(HttpHeaders.CONTENT_LENGTH) final Long contentLength,
@HeaderParam("Transfer-Encoding") final String transferEncoding) {
return this.davRsCmp.doPropfind(requestBodyXml, depth, contentLength, transferEncoding,
BoxPrivilege.READ_PROPERTIES, BoxPrivilege.READ_ACL);
}
/**
* DELETEメソッドを処理してこのリソースを削除します.
* @return JAX-RS応答オブジェクト
*/
@DELETE
public Response delete() {
// アクセス制御
// DavEngineSvcCollectionResourceは必ず親(最上位はBox)を持つため、this.davRsCmp.getParent()の結果がnullになることはない
this.davRsCmp.getParent().checkAccessContext(this.davRsCmp.getAccessContext(), BoxPrivilege.WRITE);
if (!this.davRsCmp.getDavCmp().isEmpty()) {
throw DcCoreException.Dav.HAS_CHILDREN;
}
return this.davCmp.delete(null, false).build();
}
/**
* PROPPATCHの処理.
* @param requestBodyXml リクエストボディ
* @return JAX-RS Response
*/
@WebDAVMethod.PROPPATCH
public Response proppatch(final Reader requestBodyXml) {
// アクセス制御
this.davRsCmp.checkAccessContext(
this.davRsCmp.getAccessContext(), BoxPrivilege.WRITE_PROPERTIES);
return this.davRsCmp.doProppatch(requestBodyXml);
}
/**
* ACLメソッドの処理. ACLの設定を行う.
* @param reader 設定XML
* @return JAX-RS Response
*/
@ACL
public Response acl(final Reader reader) {
// アクセス制御
this.davRsCmp.checkAccessContext(this.davRsCmp.getAccessContext(), BoxPrivilege.WRITE_ACL);
return this.davCmp.acl(reader).build();
}
/**
* OPTIONSメソッド.
* @return JAX-RS Response
*/
@OPTIONS
public Response options() {
// アクセス制御
this.davRsCmp.checkAccessContext(this.davRsCmp.getAccessContext(), BoxPrivilege.READ);
return DcCoreUtils.responseBuilderForOptions(
HttpMethod.DELETE,
com.fujitsu.dc.common.utils.DcCoreUtils.HttpMethod.MOVE,
com.fujitsu.dc.common.utils.DcCoreUtils.HttpMethod.PROPFIND,
com.fujitsu.dc.common.utils.DcCoreUtils.HttpMethod.PROPPATCH,
com.fujitsu.dc.common.utils.DcCoreUtils.HttpMethod.ACL
).build();
}
/**
* サービスソースを担当するJax-RSリソースを返す.
* @return DavFileResource
*/
@Path("__src")
public DcEngineSourceCollection src() {
DavCmp nextCmp = this.davCmp.getChild(DavCmp.SERVICE_SRC_COLLECTION);
if (nextCmp.exists()) {
return new DcEngineSourceCollection(this.davRsCmp, nextCmp);
} else {
// サービスソースコレクションが存在しないため404エラーとする
throw DcCoreException.Dav.RESOURCE_NOT_FOUND;
}
}
/**
* relay_GETメソッド.
* @param path パス名
* @param uriInfo URI
* @param headers ヘッダ
* @return JAX-RS Response
*/
@Path("{path}")
@GET
public Response relayget(@PathParam("path") String path,
@Context final UriInfo uriInfo,
@Context HttpHeaders headers) {
// アクセス制御
this.davRsCmp.checkAccessContext(this.davRsCmp.getAccessContext(), BoxPrivilege.EXEC);
return relaycommon(HttpMethod.GET, uriInfo, path, headers, null);
}
/**
* relay_POSTメソッド.
* @param path パス名
* @param uriInfo URI
* @param headers ヘッダ
* @param is リクエストボディ
* @return JAX-RS Response
*/
@Path("{path}")
@POST
public Response relaypost(@PathParam("path") String path,
@Context final UriInfo uriInfo,
@Context HttpHeaders headers,
final InputStream is) {
// アクセス制御
this.davRsCmp.checkAccessContext(this.davRsCmp.getAccessContext(), BoxPrivilege.EXEC);
return relaycommon(HttpMethod.POST, uriInfo, path, headers, is);
}
/**
* relay_PUTメソッド.
* @param path パス名
* @param uriInfo URI
* @param headers ヘッダ
* @param is リクエストボディ
* @return JAX-RS Response
*/
@Path("{path}")
@PUT
public Response relayput(@PathParam("path") String path,
@Context final UriInfo uriInfo,
@Context HttpHeaders headers,
final InputStream is) {
// アクセス制御
this.davRsCmp.checkAccessContext(this.davRsCmp.getAccessContext(), BoxPrivilege.EXEC);
return relaycommon(HttpMethod.PUT, uriInfo, path, headers, is);
}
/**
* relay_DELETEメソッド.
* @param path パス名
* @param uriInfo URI
* @param headers ヘッダ
* @return JAX-RS Response
*/
@Path("{path}")
@DELETE
public Response relaydelete(@PathParam("path") String path,
@Context final UriInfo uriInfo,
@Context HttpHeaders headers) {
// アクセス制御
this.davRsCmp.checkAccessContext(this.davRsCmp.getAccessContext(), BoxPrivilege.EXEC);
return relaycommon(HttpMethod.DELETE, uriInfo, path, headers, null);
}
/**
* relay共通処理のメソッド.
* @param method メソッド
* @param uriInfo URI
* @param path パス名
* @param headers ヘッダ
* @param is リクエストボディ
* @return JAX-RS Response
*/
public Response relaycommon(
String method,
UriInfo uriInfo,
String path,
HttpHeaders headers,
InputStream is) {
String cellName = this.davRsCmp.getCell().getName();
String boxName = this.davRsCmp.getBox().getName();
String requestUrl = String.format("http://%s:%s/%s/%s/%s/service/%s", DcCoreConfig.getEngineHost(),
DcCoreConfig.getEnginePort(), DcCoreConfig.getEnginePath(), cellName, boxName, path);
// baseUrlを取得
String baseUrl = uriInfo.getBaseUri().toString();
// リクエストヘッダを取得し、以下内容を追加
HttpClient client = new DefaultHttpClient();
HttpUriRequest req = null;
if (method.equals(HttpMethod.POST)) {
HttpPost post = new HttpPost(requestUrl);
InputStreamEntity ise = new InputStreamEntity(is, -1);
ise.setChunked(true);
post.setEntity(ise);
req = post;
} else if (method.equals(HttpMethod.PUT)) {
HttpPut put = new HttpPut(requestUrl);
InputStreamEntity ise = new InputStreamEntity(is, -1);
ise.setChunked(true);
put.setEntity(ise);
req = put;
} else if (method.equals(HttpMethod.DELETE)) {
HttpDelete delete = new HttpDelete(requestUrl);
req = delete;
} else {
HttpGet get = new HttpGet(requestUrl);
req = get;
}
req.addHeader("X-Baseurl", baseUrl);
req.addHeader("X-Request-Uri", uriInfo.getRequestUri().toString());
if (davCmp instanceof DavCmpFsImpl) {
DavCmpFsImpl dcmp = (DavCmpFsImpl) davCmp;
req.addHeader("X-Dc-Fs-Path", dcmp.getFsPath());
} else if (davCmp instanceof DavCmpEsImpl) {
DavCmpEsImpl dcmp = (DavCmpEsImpl) davCmp;
req.addHeader("X-Dc-Es-Index", dcmp.getEsColType().getIndex().getName());
req.addHeader("X-Dc-Es-Id", dcmp.getNodeId());
req.addHeader("X-Dc-Es-Type", dcmp.getEsColType().getType());
req.addHeader("X-Dc-Es-Routing-Id", this.davRsCmp.getCell().getId());
}
req.addHeader("X-Dc-Box-Schema", this.davRsCmp.getBox().getSchema());
// リレイまでのヘッダを追加
MultivaluedMap<String, String> multivalueHeaders = headers.getRequestHeaders();
for (Iterator<Entry<String, List<String>>> it = multivalueHeaders.entrySet().iterator(); it.hasNext();) {
Entry<String, List<String>> entry = it.next();
String key = (String) entry.getKey();
if (key.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH)) {
continue;
}
List<String> valueList = (List<String>) entry.getValue();
for (Iterator<String> i = valueList.iterator(); i.hasNext();) {
String value = (String) i.next();
req.setHeader(key, value);
}
}
if (log.isDebugEnabled()) {
log.debug("【EngineRelay】 " + req.getMethod() + " " + req.getURI());
Header[] reqHeaders = req.getAllHeaders();
for (int i = 0; i < reqHeaders.length; i++) {
log.debug("RelayHeader[" + reqHeaders[i].getName() + "] : " + reqHeaders[i].getValue());
}
}
// Engineにリクエストを投げる
HttpResponse objResponse = null;
try {
objResponse = client.execute(req);
} catch (ClientProtocolException e) {
throw DcCoreException.ServiceCollection.SC_INVALID_HTTP_RESPONSE_ERROR;
} catch (Exception ioe) {
throw DcCoreException.ServiceCollection.SC_ENGINE_CONNECTION_ERROR.reason(ioe);
}
// ステータスコードを追加
ResponseBuilder res = Response.status(objResponse.getStatusLine().getStatusCode());
Header[] headersResEngine = objResponse.getAllHeaders();
// レスポンスヘッダを追加
for (int i = 0; i < headersResEngine.length; i++) {
// Engineから返却されたTransfer-Encodingはリレーしない。
// 後続のMWにてレスポンスの長さに応じてContent-LengthまたはTransfer-Encodingが付加されるので
// 2重に付加されてしまうのを防ぐため、ここでは外しておく。
if ("Transfer-Encoding".equalsIgnoreCase(headersResEngine[i].getName())) {
continue;
}
// Engineから返却されたDateはリレーしない。
// WebサーバのMWがJettyの場合は2重に付加されてしまうため。
if (HttpHeaders.DATE.equalsIgnoreCase(headersResEngine[i].getName())) {
continue;
}
res.header(headersResEngine[i].getName(), headersResEngine[i].getValue());
}
InputStream isResBody = null;
// レスポンスボディを追加
HttpEntity entity = objResponse.getEntity();
if (entity != null) {
try {
isResBody = entity.getContent();
} catch (IllegalStateException e) {
throw DcCoreException.ServiceCollection.SC_UNKNOWN_ERROR.reason(e);
} catch (IOException e) {
throw DcCoreException.ServiceCollection.SC_ENGINE_CONNECTION_ERROR.reason(e);
}
final InputStream isInvariable = isResBody;
// 処理結果を出力
StreamingOutput strOutput = new StreamingOutput() {
@Override
public void write(final OutputStream os) throws IOException {
int chr;
try {
while ((chr = isInvariable.read()) != -1) {
os.write(chr);
}
} finally {
isInvariable.close();
}
}
};
res.entity(strOutput);
}
// レスポンス返却
return res.build();
}
/**
* MOVEメソッドの処理.
* @param headers ヘッダ情報
* @return JAX-RS応答オブジェクト
*/
@WebDAVMethod.MOVE
public Response move(
@Context HttpHeaders headers) {
// 移動元に対するアクセス制御(親の権限をチェックする)
this.davRsCmp.getParent().checkAccessContext(this.davRsCmp.getAccessContext(), BoxPrivilege.WRITE);
return new DavMoveResource(this.davRsCmp.getParent(), this.davRsCmp.getDavCmp(), headers).doMove();
}
}