//
// Copyright 2010 Cinch Logic Pty Ltd.
//
// http://www.chililog.com
//
// 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 org.chililog.server.workbench.workers;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.naming.OperationNotSupportedException;
import org.apache.commons.lang.StringUtils;
import org.bson.types.ObjectId;
import org.chililog.server.common.ChiliLogException;
import org.chililog.server.data.MongoConnection;
import org.chililog.server.data.MongoJsonSerializer;
import org.chililog.server.data.RepositoryConfigBO.Status;
import org.chililog.server.data.RepositoryEntryController;
import org.chililog.server.data.RepositoryEntryListCriteria;
import org.chililog.server.data.UserBO;
import org.chililog.server.data.RepositoryEntryListCriteria.QueryType;
import org.chililog.server.engine.Repository;
import org.chililog.server.engine.RepositoryService;
import org.chililog.server.workbench.Strings;
import org.jboss.netty.handler.codec.http.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBObject;
/**
* <p>
* Repository Runtime Info worker provides the following services to manage repositories at run time:
* <ul>
* <li>start all - HTTP POST /api/repositories?action=start</li>
* <li>start one - HTTP POST /api/repositories/{id}?action=start</li>
* <li>stop all - HTTP POST /api/repositories?action=stop</li>
* <li>stop one - HTTP POST /api/repositories/{id}?action=stop</li>
* <li>reload all - HTTP POST /api/repositories?action=reload</li>
* <li>read all - HTTP GET /api/repositories</li>
* <li>read one - HTTP GET /api/repositories/{id}</li>
* <li>read entry - HTTP GET /api/repositories/{id}/entries?query_type=find</li>
* </p>
* <p>
* Runtime information refers to the current status of an instance of a repository.
* </p>
*/
public class RepositoryRuntimeWorker extends Worker {
public static final String ACTION_URI_QUERYSTRING_PARAMETER_NAME = "action";
public static final String ONLINE_OPERATION = "online";
public static final String READONLY_OPERATION = "readonly";
public static final String OFFLINE_OPERATION = "offline";
public static final String ENTRY_QUERY_TYPE_QUERYSTRING_PARAMETER_NAME = "query_type";
public static final String ENTRY_QUERY_FIELDS_QUERYSTRING_PARAMETER_NAME = "fields";
public static final String ENTRY_QUERY_FROM_TIMESTAMP_QUERYSTRING_PARAMETER_NAME = "from";
public static final String ENTRY_QUERY_TO_TIMESTAMP_QUERYSTRING_PARAMETER_NAME = "to";
public static final String ENTRY_QUERY_KEYWORD_USAGE_QUERYSTRING_PARAMETER_NAME = "keyword_usage";
public static final String ENTRY_QUERY_KEYWORDS_QUERYSTRING_PARAMETER_NAME = "keywords";
public static final String ENTRY_QUERY_SEVERITY_QUERYSTRING_PARAMETER_NAME = "severity";
public static final String ENTRY_QUERY_HOST_QUERYSTRING_PARAMETER_NAME = "host";
public static final String ENTRY_QUERY_SOURCE_QUERYSTRING_PARAMETER_NAME = "source";
public static final String ENTRY_QUERY_CONDITIONS_QUERYSTRING_PARAMETER_NAME = "conditions";
public static final String ENTRY_QUERY_ORDER_BY_QUERYSTRING_PARAMETER_NAME = "order_by";
public static final String ENTRY_QUERY_INITIAL_QUERYSTRING_PARAMETER_NAME = "initial";
public static final String ENTRY_QUERY_REDUCE_QUERYSTRING_PARAMETER_NAME = "reduce";
public static final String ENTRY_QUERY_FINALIZE_QUERYSTRING_PARAMETER_NAME = "finalize";
public static final String ENTRY_QUERY_TYPE_HEADER_NAME = "X-ChiliLog-Query-Type";
public static final String ENTRY_QUERY_FIELDS_HEADER_NAME = "X-ChiliLog-Fields";
public static final String ENTRY_QUERY_FROM_TIMESTAMP_HEADER_NAME = "X-ChiliLog-From";
public static final String ENTRY_QUERY_TO_TIMESTAMP_HEADER_NAME = "X-ChiliLog-To";
public static final String ENTRY_QUERY_KEYWORD_USAGE_HEADER_NAME = "X-ChiliLog-Keywords-Usage";
public static final String ENTRY_QUERY_KEYWORDS_HEADER_NAME = "X-ChiliLog-Keywords";
public static final String ENTRY_QUERY_SEVERITY_HEADER_NAME = "X-ChiliLog-Severity";
public static final String ENTRY_QUERY_HOST_HEADER_NAME = "X-ChiliLog-Host";
public static final String ENTRY_QUERY_SOURCE_HEADER_NAME = "X-ChiliLog-Source";
public static final String ENTRY_QUERY_CONDITIONS_HEADER_NAME = "X-ChiliLog-Conditions";
public static final String ENTRY_QUERY_ORDER_BY_HEADER_NAME = "X-ChiliLog-Order-By";
public static final String ENTRY_QUERY_INITIAL_HEADER_NAME = "X-ChiliLog-Initial";
public static final String ENTRY_QUERY_REDUCE_HEADER_NAME = "X-ChiliLog-Reduce";
public static final String ENTRY_QUERY_FINALIZE_HEADER_NAME = "X-ChiliLog-Finalize";
/**
* Constructor
*/
public RepositoryRuntimeWorker(HttpRequest request) {
super(request);
return;
}
/**
* Can only create and delete sessions
*/
@Override
public HttpMethod[] getSupportedMethods() {
return new HttpMethod[] { HttpMethod.GET, HttpMethod.POST };
}
/**
* Let's validate if the user is able to access these functions
*/
@Override
protected ApiResult validateAuthenticatedUserRole() {
// Do checks when we execute
return new ApiResult();
}
/**
* Start
*
* @throws Exception
*/
@Override
public ApiResult processPost(Object requestContent) throws Exception {
try {
UserBO user = this.getAuthenticatedUser();
String action = this.getUriQueryStringParameter(ACTION_URI_QUERYSTRING_PARAMETER_NAME, false);
Object responseContent = null;
if (this.getUriPathParameters() == null || this.getUriPathParameters().length == 0) {
// Start/Stop/Reload all
// Only available to system administrators
if (!user.isSystemAdministrator()) {
return new ApiResult(HttpResponseStatus.UNAUTHORIZED, new ChiliLogException(
Strings.NOT_AUTHORIZED_ERROR));
}
if (action.equalsIgnoreCase(ONLINE_OPERATION)) {
RepositoryService.getInstance().bringAllRepositoriesOnline();
} else if (action.equalsIgnoreCase(OFFLINE_OPERATION)) {
RepositoryService.getInstance().takeAllRepositoriesOffline();
} else {
throw new UnsupportedOperationException(String.format("Action '%s' not supported.", action));
}
Repository[] list = RepositoryService.getInstance().getRepositories();
if (list != null && list.length > 0) {
ArrayList<RepositoryStatusAO> aoList = new ArrayList<RepositoryStatusAO>();
for (Repository repo : list) {
aoList.add(new RepositoryStatusAO(repo));
}
if (!aoList.isEmpty()) {
responseContent = aoList.toArray(new RepositoryStatusAO[] {});
}
}
} else {
// Online/ReadOnly/Offline specific one
// Only available to system administrators and repo admin
String id = this.getUriPathParameters()[ID_URI_PATH_PARAMETER_INDEX];
ObjectId objectId = parseDocumentObjectID(id);
Repository repo = RepositoryService.getInstance().getRepository(objectId);
if (!user.isSystemAdministrator() && !user.hasRole(repo.getRepoConfig().getAdministratorRoleName())) {
return new ApiResult(HttpResponseStatus.UNAUTHORIZED, new ChiliLogException(
Strings.NOT_AUTHORIZED_ERROR));
}
if (action.equalsIgnoreCase(ONLINE_OPERATION)) {
repo = RepositoryService.getInstance().bringRepositoryOnline(repo.getRepoConfig().getDocumentID());
responseContent = new RepositoryStatusAO(repo);
} else if (action.equalsIgnoreCase(READONLY_OPERATION)) {
repo = RepositoryService.getInstance().makeRepositoryReadOnly(repo.getRepoConfig().getDocumentID());
responseContent = new RepositoryStatusAO(repo);
} else if (action.equalsIgnoreCase(OFFLINE_OPERATION)) {
RepositoryService.getInstance().takeRepositoryOffline(repo.getRepoConfig().getDocumentID());
repo = RepositoryService.getInstance().getRepository(objectId);
responseContent = new RepositoryStatusAO(repo);
} else {
throw new UnsupportedOperationException(String.format("Action '%s' not supported.", action));
}
}
// Return response
return new ApiResult(this.getAuthenticationToken(), JSON_CONTENT_TYPE, responseContent);
} catch (Exception ex) {
return new ApiResult(HttpResponseStatus.BAD_REQUEST, ex);
}
}
/**
* Read
*
* @throws Exception
*/
@SuppressWarnings("rawtypes")
@Override
public ApiResult processGet() throws Exception {
try {
UserBO user = this.getAuthenticatedUser();
List<String> allowedRepositories = Arrays.asList(this.getAuthenticatedUserAllowedRepository());
DB db = MongoConnection.getInstance().getConnection();
Object responseContent = null;
// Get info on all repositories
// HTTP GET /api/repositories
if (this.getUriPathParameters() == null || this.getUriPathParameters().length == 0) {
Repository[] list = RepositoryService.getInstance().getRepositories();
if (list != null && list.length > 0) {
ArrayList<RepositoryStatusAO> aoList = new ArrayList<RepositoryStatusAO>();
for (Repository repo : list) {
if (user.isSystemAdministrator()
|| allowedRepositories.contains(repo.getRepoConfig().getName())) {
aoList.add(new RepositoryStatusAO(repo));
}
}
if (!aoList.isEmpty()) {
responseContent = aoList.toArray(new RepositoryStatusAO[] {});
}
}
} else if (this.getUriPathParameters().length == 1) {
// Get info on specified repository
// HTTP GET /api/repositories/{id}
String id = this.getUriPathParameters()[ID_URI_PATH_PARAMETER_INDEX];
ObjectId objectId = parseDocumentObjectID(id);
Repository repo = RepositoryService.getInstance().getRepository(objectId);
if (user.isSystemAdministrator() || allowedRepositories.contains(repo.getRepoConfig().getName())) {
responseContent = new RepositoryStatusAO(repo);
} else {
// Assume not found
throw new ChiliLogException(Strings.REPOSITORY_NOT_FOUND_ERROR, id);
}
} else if (this.getUriPathParameters().length == 2) {
// HTTP GET /api/repositories/{id}/entries?query_type=find
// Get entries for a specific repository
String id = this.getUriPathParameters()[ID_URI_PATH_PARAMETER_INDEX];
ObjectId objectId = parseDocumentObjectID(id);
Repository repo = RepositoryService.getInstance().getRepository(objectId);
if (!user.isSystemAdministrator() && !allowedRepositories.contains(repo.getRepoConfig().getName())) {
// Assume not found
throw new ChiliLogException(Strings.REPOSITORY_NOT_FOUND_ERROR, id);
} else if (repo.getStatus() == Status.OFFLINE) {
// Cannot search if repository is offline
throw new ChiliLogException(Strings.REPOSITORY_OFFLINE_ERROR, id);
}
// Load criteria
QueryType queryType = Enum.valueOf(
QueryType.class,
this.getQueryStringOrHeaderValue(ENTRY_QUERY_TYPE_QUERYSTRING_PARAMETER_NAME,
ENTRY_QUERY_TYPE_HEADER_NAME, false).toUpperCase());
RepositoryEntryListCriteria criteria = loadCriteria();
// Convert to JSON ourselves because this is not a simple AO object.
// mongoDB object JSON serialization required
StringBuilder json = new StringBuilder();
// Get controller and execute query
RepositoryEntryController controller = RepositoryEntryController.getInstance(repo.getRepoConfig());
if (queryType == QueryType.FIND) {
ArrayList<DBObject> list = controller.executeFindQuery(db, criteria);
if (list != null && !list.isEmpty()) {
MongoJsonSerializer.serialize(new BasicDBObject("find", list), json);
}
} else if (queryType == QueryType.COUNT) {
int count = controller.executeCountQuery(db, criteria);
MongoJsonSerializer.serialize(new BasicDBObject("count", count), json);
} else if (queryType == QueryType.DISTINCT) {
List l = controller.executeDistinctQuery(db, criteria);
MongoJsonSerializer.serialize(new BasicDBObject("distinct", l), json);
} else if (queryType == QueryType.GROUP) {
DBObject groupObject = controller.executeGroupQuery(db, criteria);
MongoJsonSerializer.serialize(new BasicDBObject("group", groupObject), json);
} else {
throw new OperationNotSupportedException("Unsupported query type: " + queryType.toString());
}
// If there is no json, skip this and a 204 No Content will be returned
if (json.length() > 0) {
responseContent = json.toString().getBytes(Worker.JSON_CHARSET);
ApiResult result = new ApiResult(this.getAuthenticationToken(), JSON_CONTENT_TYPE, responseContent);
if (criteria.getDoPageCount()) {
result.getHeaders().put(PAGE_COUNT_HEADER, new Integer(criteria.getPageCount()).toString());
}
return result;
}
}
// Return response
return new ApiResult(this.getAuthenticationToken(), JSON_CONTENT_TYPE, responseContent);
} catch (Exception ex) {
return new ApiResult(HttpResponseStatus.BAD_REQUEST, ex);
}
}
/**
* Parse a string repository document id
*
* @param id
* id string
* @return ObjectID
* @throws ChiliLogException
*/
private ObjectId parseDocumentObjectID(String id) throws ChiliLogException {
try {
return new ObjectId(id);
} catch (Exception ex) {
throw new ChiliLogException(Strings.REPOSITORY_NOT_FOUND_ERROR, id);
}
}
/**
* Load our criteria from query string and headers (in case it is too big for query string)
*
* @returns query criteria
* @throws ChiliLogException
* @throws ParseException
*/
private RepositoryEntryListCriteria loadCriteria() throws ChiliLogException, ParseException {
String s;
RepositoryEntryListCriteria criteria = new RepositoryEntryListCriteria();
this.loadBaseListCriteriaParameters(criteria);
s = this.getQueryStringOrHeaderValue(ENTRY_QUERY_FIELDS_QUERYSTRING_PARAMETER_NAME,
ENTRY_QUERY_FIELDS_HEADER_NAME, true);
if (!StringUtils.isBlank(s)) {
criteria.setFields(s);
}
s = this.getQueryStringOrHeaderValue(ENTRY_QUERY_FROM_TIMESTAMP_QUERYSTRING_PARAMETER_NAME,
ENTRY_QUERY_FROM_TIMESTAMP_HEADER_NAME, true);
if (!StringUtils.isBlank(s)) {
criteria.setFrom(s);
}
s = this.getQueryStringOrHeaderValue(ENTRY_QUERY_TO_TIMESTAMP_QUERYSTRING_PARAMETER_NAME,
ENTRY_QUERY_TO_TIMESTAMP_HEADER_NAME, true);
if (!StringUtils.isBlank(s)) {
criteria.setTo(s);
}
s = this.getQueryStringOrHeaderValue(ENTRY_QUERY_KEYWORD_USAGE_QUERYSTRING_PARAMETER_NAME,
ENTRY_QUERY_KEYWORD_USAGE_HEADER_NAME, true);
if (!StringUtils.isBlank(s)) {
criteria.setKeywordUsage(Enum.valueOf(RepositoryEntryListCriteria.KeywordUsage.class, s));
}
s = this.getQueryStringOrHeaderValue(ENTRY_QUERY_CONDITIONS_QUERYSTRING_PARAMETER_NAME,
ENTRY_QUERY_CONDITIONS_HEADER_NAME, true);
if (!StringUtils.isBlank(s)) {
criteria.setConditions(s);
}
s = this.getQueryStringOrHeaderValue(ENTRY_QUERY_KEYWORD_USAGE_QUERYSTRING_PARAMETER_NAME,
ENTRY_QUERY_KEYWORD_USAGE_HEADER_NAME, true);
if (!StringUtils.isBlank(s)) {
criteria.setKeywordUsage(Enum.valueOf(RepositoryEntryListCriteria.KeywordUsage.class, s));
}
s = this.getQueryStringOrHeaderValue(ENTRY_QUERY_KEYWORDS_QUERYSTRING_PARAMETER_NAME,
ENTRY_QUERY_KEYWORDS_HEADER_NAME, true);
if (!StringUtils.isBlank(s)) {
criteria.setKeywords(s);
}
s = this.getQueryStringOrHeaderValue(ENTRY_QUERY_SEVERITY_QUERYSTRING_PARAMETER_NAME,
ENTRY_QUERY_SEVERITY_HEADER_NAME, true);
if (!StringUtils.isBlank(s)) {
criteria.setSeverity(s);
}
s = this.getQueryStringOrHeaderValue(ENTRY_QUERY_HOST_QUERYSTRING_PARAMETER_NAME,
ENTRY_QUERY_HOST_HEADER_NAME, true);
if (!StringUtils.isBlank(s)) {
criteria.setHost(s);
}
s = this.getQueryStringOrHeaderValue(ENTRY_QUERY_SOURCE_QUERYSTRING_PARAMETER_NAME,
ENTRY_QUERY_SOURCE_HEADER_NAME, true);
if (!StringUtils.isBlank(s)) {
criteria.setSource(s);
}
s = this.getQueryStringOrHeaderValue(ENTRY_QUERY_CONDITIONS_QUERYSTRING_PARAMETER_NAME,
ENTRY_QUERY_CONDITIONS_HEADER_NAME, true);
if (!StringUtils.isBlank(s)) {
criteria.setConditions(s);
}
s = this.getQueryStringOrHeaderValue(ENTRY_QUERY_ORDER_BY_QUERYSTRING_PARAMETER_NAME,
ENTRY_QUERY_ORDER_BY_HEADER_NAME, true);
if (!StringUtils.isBlank(s)) {
criteria.setOrderBy(s.trim());
}
s = this.getQueryStringOrHeaderValue(ENTRY_QUERY_INITIAL_QUERYSTRING_PARAMETER_NAME,
ENTRY_QUERY_INITIAL_HEADER_NAME, true);
if (!StringUtils.isBlank(s)) {
criteria.setInitial(s);
}
s = this.getQueryStringOrHeaderValue(ENTRY_QUERY_INITIAL_HEADER_NAME, ENTRY_QUERY_REDUCE_HEADER_NAME, true);
if (!StringUtils.isBlank(s)) {
criteria.setReduceFunction(s);
}
s = this.getQueryStringOrHeaderValue(ENTRY_QUERY_FINALIZE_QUERYSTRING_PARAMETER_NAME,
ENTRY_QUERY_FINALIZE_HEADER_NAME, true);
if (!StringUtils.isBlank(s)) {
criteria.setFinalizeFunction(s);
}
return criteria;
}
}