Project

General

Profile

1
package eu.dnetlib.oai.parser;
2

    
3
import java.io.IOException;
4
import java.time.LocalDateTime;
5
import java.time.format.DateTimeFormatter;
6
import java.util.List;
7

    
8
import org.apache.commons.lang3.StringUtils;
9
import org.apache.commons.logging.Log;
10
import org.apache.commons.logging.LogFactory;
11
import org.bson.conversions.Bson;
12
import org.bson.types.ObjectId;
13
import org.z3950.zing.cql.CQLAndNode;
14
import org.z3950.zing.cql.CQLBooleanNode;
15
import org.z3950.zing.cql.CQLNode;
16
import org.z3950.zing.cql.CQLNotNode;
17
import org.z3950.zing.cql.CQLOrNode;
18
import org.z3950.zing.cql.CQLParseException;
19
import org.z3950.zing.cql.CQLParser;
20
import org.z3950.zing.cql.CQLTermNode;
21

    
22
import com.google.common.collect.Lists;
23
import com.mongodb.BasicDBObject;
24
import com.mongodb.BasicDBObjectBuilder;
25

    
26
import eu.dnetlib.index.parse.Relation;
27
import eu.dnetlib.index.parse.Relations;
28
import eu.dnetlib.oai.conf.OAIConfigurationReader;
29
import eu.dnetlib.rmi.provision.OaiPublisherRuntimeException;
30

    
31
/**
32
 * Instances of this class parse query string into mongo DBObject query.
33
 *
34
 * @author alessia
35
 */
36
public class MongoQueryParser {
37

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

    
40
	/**
41
	 * Parses the given query string into a mongo DBObject.
42
	 *
43
	 * @param query
44
	 *            String to parse
45
	 * @return DBObject corresponding to the query string
46
	 */
47
	public Bson parse(final String query) {
48
		log.debug("PARSING: " + query);
49
		if (StringUtils.isBlank(query)) { return new BasicDBObject(); }
50
		final Bson parsed = toMongo(query);
51
		log.debug(parsed);
52
		return parsed;
53
	}
54

    
55
	private Bson toMongo(final String query) {
56
		final CQLParser parser = new CQLParser();
57
		CQLNode root;
58
		try {
59
			root = parser.parse(query);
60
			return this.toMongo(root);
61
		} catch (final CQLParseException e) {
62
			throw new OaiPublisherRuntimeException(e);
63
		} catch (final IOException e) {
64
			throw new OaiPublisherRuntimeException(e);
65
		}
66
	}
67

    
68
	private Bson toMongo(final CQLNode node) {
69
		if (node instanceof CQLTermNode) { return doTranslate((CQLTermNode) node); }
70
		if (node instanceof CQLBooleanNode) { return doTranslate((CQLBooleanNode) node); }
71

    
72
		throw new RuntimeException("error choice for CQLNode " + node.getClass());
73
	}
74

    
75
	private Bson doTranslate(final CQLTermNode termNode) {
76
		if (termNode.getTerm().equals("*")) { return new BasicDBObject(); }
77
		final String relation = termNode.getRelation().getBase();
78
		final Relation rel = Relations.get(relation);
79
		return this.handleRelationNode(rel, termNode);
80
	}
81

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

    
122
	/**
123
	 * The construction of the query changes based on the granularity of the date to handle.
124
	 * <p>
125
	 * 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
126
	 * exact match will return nothing.
127
	 * </p>
128
	 * <p>
129
	 * 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
130
	 * 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
131
	 * records whose day is the one specified by the date.
132
	 * <p>
133
	 * </p>
134
	 *
135
	 * @param indexName
136
	 * @param rel
137
	 * @param date
138
	 * @return
139
	 */
140
	private Bson handleDateRelationNode(final String indexName, final Relation rel, final OAIDate date) {
141
		final BasicDBObject mongoQueryObject = new BasicDBObject();
142

    
143
		LocalDateTime fromDate = date.date;
144
		switch (rel) {
145
		case EQUAL:
146
		case EXACT:
147
			if (date.onlyDate) {
148

    
149
				final LocalDateTime endDate = date.date.plusDays(1);
150
				mongoQueryObject.put(indexName, BasicDBObjectBuilder.start("$gte", fromDate.toLocalDate()).append("$lt", endDate.toLocalDate()).get());
151
			} else {
152
				final LocalDateTime endDate = date.date.plusSeconds(1);
153
				mongoQueryObject.put(indexName, BasicDBObjectBuilder.start("$gte", fromDate.toLocalDate()).append("$lt", endDate.toLocalDate()).get());
154
			}
155
			break;
156
		case NOT:
157
			mongoQueryObject.put(indexName, new BasicDBObject("$ne", fromDate.toLocalDate()));
158
			break;
159
		case GT:
160
			mongoQueryObject.put(indexName, new BasicDBObject("$gt", fromDate.toLocalDate()));
161
			break;
162
		case GTE:
163
			mongoQueryObject.put(indexName, new BasicDBObject("$gte", fromDate.toLocalDate()));
164
			break;
165
		case LT:
166
			mongoQueryObject.put(indexName, new BasicDBObject("$lt", fromDate.toLocalDate()));
167
			break;
168
		case LTE:
169
			/*
170
			 * If the request is date <= YYYY-MM-DD then we need to change the date. The parseDate returned YYYY-MM-DDT00:00:00Z, but we
171
			 * need YYYY-MM-DDT23:59:59Z. To simplify we can add one day and perform < instead of <=.
172
			 */
173
			if (date.onlyDate) {
174
				fromDate = date.date.plusDays(1);
175
				mongoQueryObject.put(indexName, new BasicDBObject("$lt", fromDate.toLocalDate()));
176
			} else {
177
				mongoQueryObject.put(indexName, new BasicDBObject("$lte", fromDate.toLocalDate()));
178
			}
179
			break;
180
		default:
181
			throw new OaiPublisherRuntimeException("Can't parse query: relation " + rel + " not supported!");
182
		}
183
		return mongoQueryObject;
184
	}
185

    
186
	private Bson doTranslate(final CQLBooleanNode node) {
187
		if (node instanceof CQLAndNode) { return getBooleanQuery("$and", node); }
188
		if (node instanceof CQLOrNode) { return getBooleanQuery("$or", node); }
189
		if (node instanceof CQLNotNode) { return getNotQuery((CQLNotNode) node); }
190
		throw new RuntimeException("error choice for CQLBooleanNode " + node.getClass());
191
	}
192

    
193
	private Bson getBooleanQuery(final String mongoOperator, final CQLBooleanNode node) {
194
		final Bson left = this.toMongo(node.left);
195
		final Bson right = this.toMongo(node.right);
196
		final BasicDBObject opQuery = new BasicDBObject();
197
		final List<Bson> termList = Lists.newArrayList(left, right);
198
		opQuery.put(mongoOperator, termList);
199
		return opQuery;
200
	}
201

    
202
	private Bson getNotQuery(final CQLNotNode node) {
203
		final Bson left = this.toMongo(node.left);
204
		final Bson right = this.toMongo(node.right);
205
		final Bson notRight = new BasicDBObject("$not", right);
206
		final BasicDBObject andQuery = new BasicDBObject();
207
		final List<Bson> termList = Lists.newArrayList(left, notRight);
208
		andQuery.put("$and", termList);
209
		return andQuery;
210
	}
211

    
212
	private OAIDate parseDate(final String date) {
213

    
214
		final DateTimeFormatter dateNoTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
215
		final DateTimeFormatter iso8601NoMsTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
216
		final DateTimeFormatter iso8601Formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
217
		OAIDate res = null;
218
		try {
219
			log.debug("Using default " + iso8601Formatter.getClass());
220

    
221
			final LocalDateTime dt = LocalDateTime.parse(date, iso8601Formatter);
222
			res = new OAIDate(dt, false);
223
		} catch (final Exception e) {
224
			try {
225
				log.debug("Switching to ISO with no millisecond date formatter: yyyy-MM-dd'T'HH:mm:ssZ");
226
				final LocalDateTime dt = LocalDateTime.parse(date, iso8601NoMsTimeFormatter);
227
				res = new OAIDate(dt, false);
228
			} catch (final Exception ex) {
229
				log.debug("Switching to simple date formatter: yyyy-MM-dd");
230
				final LocalDateTime dt = LocalDateTime.parse(date, dateNoTimeFormatter);
231
				res = new OAIDate(dt, true);
232
			}
233
		}
234
		return res;
235
	}
236

    
237
	class OAIDate {
238

    
239
		LocalDateTime date;
240
		boolean onlyDate;
241

    
242
		OAIDate(final LocalDateTime date, final boolean onlyDate) {
243
			this.date = date;
244
			this.onlyDate = onlyDate;
245
		}
246

    
247
	}
248

    
249
}
(1-1/2)