Project

General

Profile

1
package eu.dnetlib.oai.parser;
2

    
3
import java.io.IOException;
4
import java.util.List;
5

    
6
import com.google.common.collect.Lists;
7
import com.mongodb.BasicDBObject;
8
import com.mongodb.BasicDBObjectBuilder;
9
import eu.dnetlib.data.information.oai.publisher.OaiPublisherRuntimeException;
10
import eu.dnetlib.data.information.oai.publisher.conf.OAIConfigurationReader;
11
import eu.dnetlib.functionality.index.parse.Relation;
12
import eu.dnetlib.functionality.index.parse.Relations;
13
import org.apache.commons.lang.StringUtils;
14
import org.apache.commons.logging.Log;
15
import org.apache.commons.logging.LogFactory;
16
import org.bson.conversions.Bson;
17
import org.bson.types.ObjectId;
18
import org.joda.time.DateTime;
19
import org.joda.time.format.DateTimeFormat;
20
import org.joda.time.format.DateTimeFormatter;
21
import org.joda.time.format.ISODateTimeFormat;
22
import org.z3950.zing.cql.*;
23

    
24
/**
25
 * Instances of this class parse query string into mongo DBObject query.
26
 *
27
 * @author alessia
28
 */
29
public class MongoQueryParser {
30

    
31
	private static final Log log = LogFactory.getLog(MongoQueryParser.class); // NOPMD by marko on 11/24/08 5:02 PM
32

    
33
	/**
34
	 * Parses the given query string into a mongo DBObject.
35
	 *
36
	 * @param query String to parse
37
	 * @return DBObject corresponding to the query string
38
	 */
39
	public Bson parse(final String query) {
40
		log.debug("PARSING: " + query);
41
		if (StringUtils.isBlank(query)) return new BasicDBObject();
42
		Bson parsed = toMongo(query);
43
		log.debug(parsed);
44
		return parsed;
45
	}
46

    
47
	private Bson toMongo(final String query) {
48
		CQLParser parser = new CQLParser();
49
		CQLNode root;
50
		try {
51
			root = parser.parse(query);
52
			return this.toMongo(root);
53
		} catch (CQLParseException e) {
54
			throw new OaiPublisherRuntimeException(e);
55
		} catch (IOException e) {
56
			throw new OaiPublisherRuntimeException(e);
57
		}
58
	}
59

    
60
	private Bson toMongo(final CQLNode node) {
61
		if (node instanceof CQLTermNode) return doTranslate((CQLTermNode) node);
62
		if (node instanceof CQLBooleanNode) return doTranslate((CQLBooleanNode) node);
63

    
64
		throw new RuntimeException("error choice for CQLNode " + node.getClass());
65
	}
66

    
67
	private Bson doTranslate(final CQLTermNode termNode) {
68
		if (termNode.getTerm().equals("*")) return new BasicDBObject();
69
		String relation = termNode.getRelation().getBase();
70
		Relation rel = Relations.get(relation);
71
		return this.handleRelationNode(rel, termNode);
72
	}
73

    
74
	private Bson handleRelationNode(final Relation rel, final CQLTermNode termNode) {
75
		BasicDBObject mongoQueryObject = new BasicDBObject();
76
		String term = termNode.getTerm();
77
		String indexName = termNode.getIndex();
78
		Object termObj = term;
79
		if (indexName.equals("_id")) {
80
			termObj = new ObjectId(term);
81
		} else {
82
			if (indexName.equals(OAIConfigurationReader.DATESTAMP_FIELD) || indexName.equals(OAIConfigurationReader.LAST_COLLECTION_DATE_FIELD)) {
83
				// termObj = this.parseDate(term);
84
				OAIDate termDate = this.parseDate(term);
85
				return handleDateRelationNode(indexName, rel, termDate);
86
			}
87
		}
88
		switch (rel) {
89
		case EQUAL:
90
		case EXACT:
91
			mongoQueryObject.put(indexName, termObj);
92
			break;
93
		case NOT:
94
			mongoQueryObject.put(indexName, new BasicDBObject("$ne", termObj));
95
			break;
96
		case GT:
97
			mongoQueryObject.put(indexName, new BasicDBObject("$gt", termObj));
98
			break;
99
		case GTE:
100
			mongoQueryObject.put(indexName, new BasicDBObject("$gte", termObj));
101
			break;
102
		case LT:
103
			mongoQueryObject.put(indexName, new BasicDBObject("$lt", termObj));
104
			break;
105
		case LTE:
106
			mongoQueryObject.put(indexName, new BasicDBObject("$lte", termObj));
107
			break;
108
		default:
109
			throw new OaiPublisherRuntimeException("Can't parse query: relation " + rel + " not supported!");
110
		}
111
		return mongoQueryObject;
112
	}
113

    
114
	/**
115
	 * The construction of the query changes based on the granularity of the date to handle.
116
	 * <p>
117
	 * If the date has yyyy-MM-ddThh:mm:ssZ granularity we have to create a range query because in mongo we have milliseconds, hence an
118
	 * exact match will return nothing.
119
	 * </p>
120
	 * <p>
121
	 * If the date has yyyy-MM-dd granularity then we have to trick the query. If we are interested in the date 2013-10-28, the date has
122
	 * been converted into 2013-10-28T00:00:00Z : if we ask for datestamp = 2013-10-28T00:00:00Z, we'll get nothing: we have to ask for
123
	 * records whose day is the one specified by the date.
124
	 * <p>
125
	 * </p>
126
	 *
127
	 * @param indexName
128
	 * @param rel
129
	 * @param date
130
	 * @return
131
	 */
132
	private Bson handleDateRelationNode(final String indexName, final Relation rel, final OAIDate date) {
133
		BasicDBObject mongoQueryObject = new BasicDBObject();
134
		DateTime fromDate = date.date;
135
		switch (rel) {
136
		case EQUAL:
137
		case EXACT:
138
			if (date.onlyDate) {
139
				DateTime endDate = date.date.plusDays(1);
140
				mongoQueryObject.put(indexName, BasicDBObjectBuilder.start("$gte", fromDate.toDate()).append("$lt", endDate.toDate()).get());
141
			} else {
142
				DateTime endDate = date.date.plusSeconds(1);
143
				mongoQueryObject.put(indexName, BasicDBObjectBuilder.start("$gte", fromDate.toDate()).append("$lt", endDate.toDate()).get());
144
			}
145
			break;
146
		case NOT:
147
			mongoQueryObject.put(indexName, new BasicDBObject("$ne", fromDate.toDate()));
148
			break;
149
		case GT:
150
			mongoQueryObject.put(indexName, new BasicDBObject("$gt", fromDate.toDate()));
151
			break;
152
		case GTE:
153
			mongoQueryObject.put(indexName, new BasicDBObject("$gte", fromDate.toDate()));
154
			break;
155
		case LT:
156
			mongoQueryObject.put(indexName, new BasicDBObject("$lt", fromDate.toDate()));
157
			break;
158
		case LTE:
159
			/*
160
			If the request is date <= YYYY-MM-DD then we need to change the date.
161
			The parseDate returned YYYY-MM-DDT00:00:00Z, but we need YYYY-MM-DDT23:59:59Z.
162
			To simplify we can add one day and perform < instead of <=.
163
			 */
164
			if (date.onlyDate) {
165
				fromDate = date.date.plusDays(1);
166
				mongoQueryObject.put(indexName, new BasicDBObject("$lt", fromDate.toDate()));
167
			} else {
168
				mongoQueryObject.put(indexName, new BasicDBObject("$lte", fromDate.toDate()));
169
			}
170
			break;
171
		default:
172
			throw new OaiPublisherRuntimeException("Can't parse query: relation " + rel + " not supported!");
173
		}
174
		return mongoQueryObject;
175
	}
176

    
177
	private Bson doTranslate(final CQLBooleanNode node) {
178
		if (node instanceof CQLAndNode) return getBooleanQuery("$and", node);
179
		if (node instanceof CQLOrNode) return getBooleanQuery("$or", node);
180
		if (node instanceof CQLNotNode) return getNotQuery((CQLNotNode) node);
181
		throw new RuntimeException("error choice for CQLBooleanNode " + node.getClass());
182
	}
183

    
184
	private Bson getBooleanQuery(final String mongoOperator, final CQLBooleanNode node) {
185
		Bson left = this.toMongo(node.left);
186
		Bson right = this.toMongo(node.right);
187
		BasicDBObject opQuery = new BasicDBObject();
188
		List<Bson> termList = Lists.newArrayList(left, right);
189
		opQuery.put(mongoOperator, termList);
190
		return opQuery;
191
	}
192

    
193
	private Bson getNotQuery(final CQLNotNode node) {
194
		Bson left = this.toMongo(node.left);
195
		Bson right = this.toMongo(node.right);
196
		Bson notRight = new BasicDBObject("$not", right);
197
		BasicDBObject andQuery = new BasicDBObject();
198
		List<Bson> termList = Lists.newArrayList(left, notRight);
199
		andQuery.put("$and", termList);
200
		return andQuery;
201
	}
202

    
203
	private OAIDate parseDate(final String date) {
204
		DateTimeFormatter dateNoTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd").withZoneUTC();
205
		DateTimeFormatter iso8601NoMsTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ssZ").withZoneUTC();
206
		DateTimeFormatter iso8601Formatter = ISODateTimeFormat.dateTime().withZoneUTC();
207
		OAIDate res = null;
208
		try {
209
			log.debug("Using default " + iso8601Formatter.getClass());
210
			DateTime dt = iso8601Formatter.parseDateTime(date);
211
			res = new OAIDate(dt, false);
212
		} catch (Exception e) {
213
			try {
214
				log.debug("Switching to ISO with no millisecond date formatter: yyyy-MM-dd'T'HH:mm:ssZ");
215
				DateTime dt = iso8601NoMsTimeFormatter.parseDateTime(date);
216
				res = new OAIDate(dt, false);
217
			} catch (Exception ex) {
218
				log.debug("Switching to simple date formatter: yyyy-MM-dd");
219
				DateTime dt = dateNoTimeFormatter.parseDateTime(date);
220
				res = new OAIDate(dt, true);
221
			}
222
		}
223
		return res;
224
	}
225

    
226
	class OAIDate {
227

    
228
		DateTime date;
229
		boolean onlyDate;
230

    
231
		OAIDate(final DateTime date, final boolean onlyDate) {
232
			this.date = date;
233
			this.onlyDate = onlyDate;
234
		}
235

    
236
	}
237

    
238
}
(1-1/2)