// // 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.data; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.regex.Pattern; import org.apache.commons.lang.StringUtils; import org.chililog.server.common.TextTokenizer; import org.chililog.server.data.RepositoryEntryBO.Severity; import com.mongodb.BasicDBObject; /** * Criteria for selecting repository log entries * * @author vibul * */ public class RepositoryEntryListCriteria extends ListCriteria { private Date _from = null; private Date _to = null; private String _fields = null; private String _conditions = null; private String _keywords = null; private String _severity = null; private String _host = null; private String _source = null; private KeywordUsage _keywordUsage = KeywordUsage.All; private String _orderBy = null; private String _initial = null; private String _reduceFunction = null; private String _finalizeFunction = null; public static final Pattern DATE_PATTERN = Pattern .compile("^([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}Z)$"); public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; public static final Pattern LONG_NUMBER_PATTERN = Pattern.compile("^NumberLong\\(([0-9]+)\\)$"); /** * Basic constructor */ public RepositoryEntryListCriteria() { return; } /** * Returns the timestamp from which the search should start. If set, this is added to the condition. */ public Date getFrom() { return _from; } public void setFrom(Date from) { _from = from; } public void setFrom(String from) throws ParseException { if (StringUtils.isBlank(from)) { return; } SimpleDateFormat sf = new SimpleDateFormat(DATE_FORMAT); if (from.endsWith("Z")) { // Simple date format does not recognise Z time zone so make it GMT from = from.substring(0, from.length() - 1) + "GMT"; } _from = sf.parse(from); } /** * Returns the timestamp from which the search should stop. If set, this is added to the condition. */ public Date getTo() { return _to; } public void setTo(Date to) { _to = to; } public void setTo(String to) throws ParseException { if (StringUtils.isBlank(to)) { return; } SimpleDateFormat sf = new SimpleDateFormat(DATE_FORMAT); if (to.endsWith("Z")) { // Simple date format does not recognise Z time zone so make it GMT to = to.substring(0, to.length() - 1) + "GMT"; } _to = sf.parse(to); } /** * <p> * The fields to retrieve. * </p> * <p> * For example, to retrieve field "a" but not field "b": <code>{ "a" : 1, "b" : 0 }</code>. * </p> * <p> * See http://www.mongodb.org/display/DOCS/Retrieving+a+Subset+of+Fields. * </p> */ public String getFields() { return _fields; } public void setFields(String fields) { _fields = fields; } /** * Returns the fields as DBObject */ public BasicDBObject getFieldsDbObject() { if (StringUtils.isBlank(_fields)) { return null; } MongoJsonParser parser = new MongoJsonParser(_fields); BasicDBObject queryParameters = (BasicDBObject) parser.parse(); return queryParameters; } /** * <p> * The conditions used to filter entries. * </p> * <p> * For example, to retrieve entries where "j" is not equals to 3 and "k" is greater than 10: * <code>{"j": {"$ne": 3}, "k": {"$gt": 10} }</code>. * </p> * <p> * For dates, the format "yyyy-MM-dd'T'HH:mm:ssZ" is used. For example, "2011-01-01T23:01:02Z". It assumes the * timezone is UTC. * </p> * <p> * For long numbers, a string like "LongNumber(888)" is converted into a long number of value 888. If a JSON number * is more 10 digits long, it is also converted to a long integer. * </p> * <p> * See http://www.mongodb.org/display/DOCS/Advanced+Queries. * </p> */ public String getConditions() { return _conditions; } public void setConditions(String conditions) { _conditions = conditions; } /** * <p> * The keywords to append to the conditions property (if any). Keywords will be parsed and normalized * </p> */ public String getKeywords() { return _keywords; } public void setKeywords(String keywords) { _keywords = keywords; } /** * <p> * How the keywords are to be used in the search criteria. All or any keywords are to be used * </p> */ public KeywordUsage getKeywordUsage() { return _keywordUsage; } public void setKeywordUsage(KeywordUsage keywordUsage) { _keywordUsage = keywordUsage; } /** * The severity code; i.e 0-7. */ public String getSeverity() { return _severity; } public void setSeverity(String severity) { _severity = severity; } /** * Matching host entry */ public String getHost() { return _host; } public void setHost(String host) { _host = host; } /** * Matching source entry */ public String getSource() { return _source; } public void setSource(String source) { _source = source; } /** * Returns the conditions as DBObject * * @throws IOException */ public BasicDBObject getConditionsDbObject() throws IOException { BasicDBObject o = null; // Other conditions if (StringUtils.isBlank(_conditions) || _conditions.equals("{}")) { o = new BasicDBObject(); } else { MongoJsonParser parser = new MongoJsonParser(_conditions, DATE_PATTERN, DATE_FORMAT, LONG_NUMBER_PATTERN); o = (BasicDBObject) parser.parse(); } // Timestamp if (_from != null || _to != null) { BasicDBObject ts = new BasicDBObject(); if (_from != null) { ts.put("$gte", _from); } if (_to != null) { ts.put("$lte", _to); } o.put(RepositoryEntryBO.TIMESTAMP_FIELD_NAME, ts); } // Severity - need range query so we miss out on index lookup. If debug, leave out criteria because // we want everything returned. Hopefully this speed things up because mongo wont have to scan resultset // to check if severity is valid if (!StringUtils.isBlank(_severity) && !_severity.equals(Severity.Debug.toCode().toString())) { o.put(RepositoryEntryBO.SEVERITY_FIELD_NAME, new BasicDBObject("$lte", Integer.parseInt(_severity))); } // Keywords ArrayList<String> keywordsList = new ArrayList<String>(); if (!StringUtils.isBlank(_keywords)) { ArrayList<String> l = TextTokenizer.getInstance().tokenize(_keywords, 200); keywordsList.addAll(l); } // EntryParser.parseKeywords() puts source, severity and host into the keywords so that they are indexed ArrayList<String> shList = new ArrayList<String>(); if (!StringUtils.isBlank(_source)) { shList.add("s=" + _source); } if (!StringUtils.isBlank(_host)) { shList.add("h=" + _host); } // Keywords if (_keywordUsage == KeywordUsage.All) { keywordsList.addAll(shList); if (keywordsList.size() > 0) { o.put(RepositoryEntryBO.KEYWORDS_FIELD_NAME, new BasicDBObject("$all", keywordsList)); } } else { BasicDBObject kw = new BasicDBObject(); if (keywordsList.size() > 0) { kw.put("$in", keywordsList); } if (shList.size() > 0) { kw.put("$all", shList); } o.put(RepositoryEntryBO.KEYWORDS_FIELD_NAME, kw); } return o; } /** * <p> * Field to use to sort the result set. * </p> * <p> * For example, by 'name' ascending, then 'age' descending: <code>{ "name" : 1, "age" : -1 }</code>. * </p> * <p> * See http://www.mongodb.org/display/DOCS/Sorting+and+Natural+Order * </p> */ public String getOrderBy() { return _orderBy; } public void setOrderBy(String orderBy) { _orderBy = orderBy; } /** * Returns the order by fields as DBObject */ public BasicDBObject getOrderByDbObject() { if (StringUtils.isBlank(_orderBy)) { return null; } MongoJsonParser parser = new MongoJsonParser(_orderBy); BasicDBObject queryParameters = (BasicDBObject) parser.parse(); return queryParameters; } /** * <p> * Initial values of the aggregation counter object. * </p> * <p> * For example, to initialize, the count and total time: <code> {"count": 0, "total_time":0}</code>. * </p> * <p> * For dates, the format "yyyy-MM-dd'T'HH:mm:ssZ" is used. For example, "2011-01-01T23:01:02Z". It assumes the * timezone is UTC. * </p> * <p> * For long numbers, a string like "LongNumber(888)" is converted into a long number of value 888. If a JSON number * is more 10 digits long, it is also converted to a long integer. * </p> * <p> * See http://www.mongodb.org/display/DOCS/Advanced+Queries. * </p> */ public String getInitial() { return _initial; } public void setInitial(String initial) { _initial = initial; } /** * Returns the initial values as DBObject */ public BasicDBObject getIntialDbObject() { if (StringUtils.isBlank(_initial)) { return null; } MongoJsonParser parser = new MongoJsonParser(_initial, DATE_PATTERN, DATE_FORMAT, LONG_NUMBER_PATTERN); BasicDBObject queryParameters = (BasicDBObject) parser.parse(); return queryParameters; } /** * <p> * The reduce function aggregates (reduces) the objects iterated. Typical operations of a reduce function include * summing and counting. Reduce takes two arguments: the current entry (document) being iterated over and the * aggregation counter object. * </p> * <p> * For example, * </p> * * <pre> * function(entry, aggregation_counter){ aggregation_counter.count++; aggregation_counter.total_time+=entry.response_time } * </pre> * * <p> * See http://www.mongodb.org/display/DOCS/Advanced+Queries. * </p> */ public String getReduceFunction() { return _reduceFunction; } public void setReduceFunction(String reduce) { _reduceFunction = reduce; } /** * <p> * An optional function to be run on each item in the result set just before the item is returned. Can either modify * the item (e.g., add an average field given a count and a total) or return a replacement object (returning a new * object with just _id and average fields). * </p> * <p> * For example, * </p> * * <pre> * function(aggregation_counter){ aggregation_countert.avg_time = aggregation_counter.total_time / aggregation_counter.count } * </pre> * * <p> * See http://www.mongodb.org/display/DOCS/Advanced+Queries. * </p> */ public String getFinalizeFunction() { return _finalizeFunction; } public void setFinalizeFunction(String finalize) { _finalizeFunction = finalize; } /** * The type of query that can be performed */ public static enum QueryType { /** * <p> * Find and return matching entries. See http://www.mongodb.org/display/DOCS/Advanced+Queries. * </p> * <p> * The following parameters will be used. * <dl> * <dt>Fields</dt> * <dd>Optional list of fields in the entry to return. If not supplied, all fields will be returned.</dd> * * <dt>Conditions</dt> * <dd>Optional list of condition to use to filter entries. If not supplied, all entries will be returned.</dd> * * <dt>OrderBy</dt> * <dd>Optional list of fields to use in ordering the results</dd> * </dl> * </p> */ FIND, /** * <p> * Counts the number of matching entries. See http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Count * </p> * <p> * The following parameter will be used. * <dl> * <dt>Conditions</dt> * <dd>Optional list of condition to use to filter entries. If not supplied, all entries will be returned.</dd> * </dl> * </p> */ COUNT, /** * <p> * Returns a list of distinct values for the specified field. See * http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Distinct * </p> * <p> * The following parameters will be used. * <dl> * <dt>Fields</dt> * <dd>One and only 1 field must be supplied. The distinct value of this field will be returned.</dd> * * <dt>Conditions</dt> * <dd>Optional list of condition to use to filter entries. If not supplied, the distinct values of the * specified field for all entries will be returned.</dd> * </dl> * </p> */ DISTINCT, /** * <p> * Returns an array of grouped items like the SQL group by. See * http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Group. * </p> * <p> * The following parameters will be used. * <dl> * <dt>Fields</dt> * <dd>Fields to group by. If not supplied, only the aggregation counter will be returned.</dd> * * <dt>Conditions</dt> * <dd>Optional list of condition to use to filter entries. If not supplied, all entries will be grouped.</dd> * * <dt>Initial</dt> * <dd>Initial value of the aggregation counter object.</dd> * * <dt>Reduce</dt> * <dd>The reduce function aggregates (reduces) the objects iterated. Typical operations of a reduce function * include summing and counting. reduce takes two arguments: the current document being iterated over and the * aggregation counter object.</dd> * * <dt>Finalize</dt> * <dd>An optional function to be run on each item in the result set just before the item is returned. Can * either modify the item (e.g., add an average field given a count and a total) or return a replacement object * (returning a new object with just _id and average fields).</dd> * </dl> * </p> */ GROUP } /** * How the keywords are to be used as conditions * * @author vibul * */ public static enum KeywordUsage { /** * Any (one or more) keywords must exist */ Any, /** * All keywords must exist */ All } }