/**
* 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.cell;
import java.io.IOException;
import java.io.Reader;
import java.util.regex.Pattern;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.core.Response;
import org.apache.wink.webdav.WebDAVMethod.PROPFIND;
import org.apache.wink.webdav.WebDAVMethod.PROPPATCH;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fujitsu.dc.common.utils.DcCoreUtils;
import com.fujitsu.dc.core.DcCoreException;
import com.fujitsu.dc.core.auth.AccessContext;
import com.fujitsu.dc.core.auth.CellPrivilege;
import com.fujitsu.dc.core.eventbus.DcEventBus;
import com.fujitsu.dc.core.eventbus.JSONEvent;
import com.fujitsu.dc.core.model.Cell;
import com.fujitsu.dc.core.model.DavRsCmp;
import com.fujitsu.dc.core.model.ctl.Event;
/**
* イベントバス用JAX-RS Resource.
*/
public class EventResource {
Cell cell;
AccessContext accessContext;
DavRsCmp davRsCmp;
static Logger log = LoggerFactory.getLogger(EventResource.class);
static final int MAXREQUEST_KEY_LENGTH = 128;
static final String REQEUST_KEY_DEFAULT_FORMAT = "PCS-%d";
static final Pattern REQUEST_KEY_PATTERN = Pattern.compile("[\\p{Alpha}\\p{Digit}_-]*");
/**
* constructor.
* @param cell Cell
* @param accessContext AccessContext
* @param davRsCmp DavRsCmp
*/
public EventResource(final Cell cell, final AccessContext accessContext, final DavRsCmp davRsCmp) {
this.accessContext = accessContext;
this.cell = cell;
this.davRsCmp = davRsCmp;
}
/**
* イベントの受付.
* @param reader リクエストボディ
* @param version X-Dc-Versionヘッダー値
* @param requestKey X-Dc-RequestKeyヘッダー値
* @return JAXRS Response
*/
@POST
public final Response receiveEvent(final Reader reader,
@HeaderParam(DcCoreUtils.HttpHeaders.X_DC_VERSION) final String version,
@HeaderParam(DcCoreUtils.HttpHeaders.X_DC_REQUESTKEY) String requestKey) {
// TODO findBugs対策↓
log.debug(this.cell.getName());
log.debug(this.accessContext.getBaseUri());
log.debug(this.davRsCmp.getUrl());
// アクセス制御
this.davRsCmp.checkAccessContext(this.davRsCmp.getAccessContext(), CellPrivilege.EVENT);
// X-Dc-RequestKeyの解析(指定なしの場合にデフォルト値を補充)
requestKey = validateXDcRequestKey(requestKey);
// TODO findBugs対策↓
log.debug(requestKey);
// リクエストボディを解析してEventオブジェクトを取得する
JSONEvent reqBody = getRequestBody(reader);
validateEventProperties(reqBody);
// TODO イベントバス系のデータロック
// TODO 新規のイベント受付かどうかをESへ検索(current/default.logのデータ検索)
// TODO ESへCollectionとLogDavFileを登録/更新(新規:CREATE、更新:uのみPUT)
// TODO ログ出力用のデフォルト設定情報を取得
// ログファイル出力
DcEventBus eventBus = new DcEventBus(this.cell);
Event event = createEvent(reqBody, requestKey);
eventBus.outputEventLog(event);
// レスポンス返却
return Response.ok().build();
}
/**
* ログ設定更新.
* @return レスポンス
*/
@PROPPATCH
public final Response updateLogSettings() {
// TODO アクセス制御
// this.davRsCmp.checkAccessContext(this.davRsCmp.getAccessContext(), CellPrivilege.LOG);
throw DcCoreException.Misc.METHOD_NOT_IMPLEMENTED;
}
/**
* ログ設定取得.
* @return レスポンス
*/
@PROPFIND
public final Response getLogSettings() {
// TODO アクセス制御
// this.davRsCmp.checkAccessContext(this.davRsCmp.getAccessContext(), CellPrivilege.LOG_READ);
throw DcCoreException.Misc.METHOD_NOT_IMPLEMENTED;
}
/**
* リクエストヘッダの X-Dc-RequestKey の正当性チェックを行う. <br/>
* 不正な場合には例外を発する. <br/>
* 未指定時には、デフォルト値を補充する.
* @param requestKey リクエストヘッダ
* @return 正当性チェック通過後の X-Dc-RequetKeyの値
*/
public static String validateXDcRequestKey(String requestKey) {
if (null == requestKey) {
requestKey = String.format(REQEUST_KEY_DEFAULT_FORMAT, System.currentTimeMillis());
}
if (MAXREQUEST_KEY_LENGTH < requestKey.length()) {
throw DcCoreException.Event.X_DC_REQUESTKEY_INVALID;
}
if (!REQUEST_KEY_PATTERN.matcher(requestKey).matches()) {
throw DcCoreException.Event.X_DC_REQUESTKEY_INVALID;
}
return requestKey;
}
/**
* リクエストボディを解析してEventオブジェクトを取得する.
* @param reader Http入力ストリーム
* @return 解析したEventオブジェクト
*/
protected JSONEvent getRequestBody(final Reader reader) {
JSONEvent event = null;
JsonParser jp = null;
ObjectMapper mapper = new ObjectMapper();
JsonFactory f = new JsonFactory();
try {
jp = f.createJsonParser(reader);
JsonToken token = jp.nextToken(); // JSONルート要素("{")
if (token == JsonToken.START_OBJECT) {
event = mapper.readValue(jp, JSONEvent.class);
} else {
throw DcCoreException.Event.JSON_PARSE_ERROR;
}
} catch (IOException e) {
throw DcCoreException.Event.JSON_PARSE_ERROR;
}
return event;
}
/**
* Event内の各プロパティ値をバリデートする.
* @param event Eventオブジェクト
*/
protected void validateEventProperties(final JSONEvent event) {
Event.LEVEL level = event.getLevel();
if (level == null) {
throw DcCoreException.Event.INPUT_REQUIRED_FIELD_MISSING.params("level");
} else if (!JSONEvent.validateLevel(level)) {
throw DcCoreException.Event.REQUEST_FIELD_FORMAT_ERROR.params("level");
}
String action = event.getAction();
if (action == null) {
throw DcCoreException.Event.INPUT_REQUIRED_FIELD_MISSING.params("action");
} else if (!JSONEvent.validateAction(action)) {
throw DcCoreException.Event.REQUEST_FIELD_FORMAT_ERROR.params("action");
}
String object = event.getObject();
if (object == null) {
throw DcCoreException.Event.INPUT_REQUIRED_FIELD_MISSING.params("object");
} else if (!JSONEvent.validateObject(object)) {
throw DcCoreException.Event.REQUEST_FIELD_FORMAT_ERROR.params("object");
}
String result = event.getResult();
if (result == null) {
throw DcCoreException.Event.INPUT_REQUIRED_FIELD_MISSING.params("result");
} else if (!JSONEvent.validateResult(result)) {
throw DcCoreException.Event.REQUEST_FIELD_FORMAT_ERROR.params("result");
}
}
/**
* 外部イベントログ出力用Eventオブジェクトを生成.
* @param reqBody JSON表現リクエストボディ
* @param requestKey RequestKeyヘッダの値
* @return Eventオブジェクト
*/
protected Event createEvent(JSONEvent reqBody, String requestKey) {
Event event = new Event();
event.setLevel(reqBody.getLevel());
event.setAction(reqBody.getAction());
event.setObject(reqBody.getObject());
event.setResult(reqBody.getResult());
event.setRequestKey(requestKey);
event.setName("client");
event.setSchema(accessContext.getSchema());
event.setSubject(accessContext.getSubject());
return event;
}
/**
* 内部イベントログ出力用Eventオブジェクトを生成.
* @param reqBody JSON表現リクエストボディ
* @param requestKey RequestKeyヘッダの値
* @param accessContext accessContext
* @return Eventオブジェクト
*/
public static Event createEvent(JSONEvent reqBody, String requestKey, AccessContext accessContext) {
Event event = new Event();
event.setLevel(reqBody.getLevel());
event.setAction(reqBody.getAction());
event.setObject(reqBody.getObject());
event.setResult(reqBody.getResult());
event.setRequestKey(requestKey);
event.setName("server");
event.setSchema(accessContext.getSchema());
event.setSubject(accessContext.getSubject());
return event;
}
}