]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
604f2f28f0184fc038a66c1ce5dfd081a69ec899
[tmp/jakarta-migration.git] /
1 /**     
2  * QueryManagerNuxeoImpl.java
3  *
4  * {Purpose of This Class}
5  *
6  * {Other Notes Relating to This Class (Optional)}
7  *
8  * $LastChangedBy: $
9  * $LastChangedRevision: $
10  * $LastChangedDate: $
11  *
12  * This document is a part of the source code and related artifacts
13  * for CollectionSpace, an open source collections management system
14  * for museums and related institutions:
15  *
16  * http://www.collectionspace.org
17  * http://wiki.collectionspace.org
18  *
19  * Copyright © 2009 {Contributing Institution}
20  *
21  * Licensed under the Educational Community License (ECL), Version 2.0.
22  * You may not use this file except in compliance with this License.
23  *
24  * You may obtain a copy of the ECL 2.0 License at
25  * https://source.collectionspace.org/collection-space/LICENSE.txt
26  */
27 package org.collectionspace.services.common.query.nuxeo;
28
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
34
35 //import org.nuxeo.ecm.core.client.NuxeoClient;
36
37 import org.collectionspace.services.jaxb.InvocableJAXBSchema;
38 //import org.collectionspace.services.nuxeo.client.java.NuxeoConnector;
39 //import org.collectionspace.services.nuxeo.client.java.NxConnect;
40
41 import org.collectionspace.services.client.IQueryManager;
42 import org.collectionspace.services.common.invocable.InvocableUtils;
43 import org.collectionspace.services.common.storage.DatabaseProductType;
44 import org.collectionspace.services.common.storage.JDBCTools;
45
46 public class QueryManagerNuxeoImpl implements IQueryManager {
47
48         private static String ECM_FULLTEXT_LIKE = "ecm:fulltext"
49                         + SEARCH_TERM_SEPARATOR + IQueryManager.SEARCH_LIKE;
50         private static String SEARCH_LIKE_FORM = null;
51
52         private final Logger logger = LoggerFactory
53                         .getLogger(QueryManagerNuxeoImpl.class);
54
55         // Consider that letters, letter-markers, numbers, '_' and apostrophe are
56         // words
57         private static Pattern nonWordChars = Pattern
58                         .compile("[^\\p{L}\\p{M}\\p{N}_']");
59         private static Pattern kwdTokenizer = Pattern.compile("(?:(['\"])(.*?)(?<!\\\\)(?>\\\\\\\\)*\\1|([^ ]+))");
60         private static Pattern unescapedDblQuotes = Pattern.compile("(?<!\\\\)\"");
61         private static Pattern unescapedSingleQuote = Pattern.compile("(?<!\\\\)'");
62         //private static Pattern kwdSearchProblemChars = Pattern.compile("[\\:\\(\\)\\*\\%]");
63         // HACK to work around Nuxeo regression that tokenizes on '.'. 
64         private static Pattern kwdSearchProblemChars = Pattern.compile("[\\:\\(\\)\\*\\%\\.]");
65         private static Pattern kwdSearchHyphen = Pattern.compile(" - ");
66         private static Pattern advSearchSqlWildcard = Pattern.compile(".*?[I]*LIKE\\s*\\\"\\%\\\".*?");
67
68
69         private static String getLikeForm(String dataSourceName, String repositoryName, String cspaceInstanceId) {
70                 if (SEARCH_LIKE_FORM == null) {
71                         try {
72                                 DatabaseProductType type = JDBCTools.getDatabaseProductType(dataSourceName, repositoryName, cspaceInstanceId);
73                                 if (type == DatabaseProductType.MYSQL) {
74                                         SEARCH_LIKE_FORM = IQueryManager.SEARCH_LIKE;
75                                 } else if (type == DatabaseProductType.POSTGRESQL) {
76                                         SEARCH_LIKE_FORM = IQueryManager.SEARCH_ILIKE;
77                                 }
78                         } catch (Exception e) {
79                                 SEARCH_LIKE_FORM = IQueryManager.SEARCH_LIKE;
80                         }
81                 }
82                 return SEARCH_LIKE_FORM;
83         }
84
85         @Override
86         public String getDatasourceName() {
87                 return JDBCTools.NUXEO_DATASOURCE_NAME;
88         }
89         
90         // TODO: This is currently just an example fixed query. This should
91         // eventually be
92         // removed or replaced with a more generic method.
93         /*
94          * (non-Javadoc)
95          * 
96          * @see
97          * org.collectionspace.services.common.query.IQueryManager#execQuery(java
98          * .lang.String)
99          */
100         @Override
101         @Deprecated
102         public void execQuery(String queryString) {
103                 // Intentionally left blank
104         }
105
106         @Override
107         public String createWhereClauseFromAdvancedSearch(String advancedSearch) {
108                 String result = null;
109                 //
110                 // Process search term.  FIXME: REM - Do we need to perform any string filtering here?
111                 //
112                 if (advancedSearch != null && !advancedSearch.isEmpty()) {
113                         // Filtering of advanced searches on a single '%' char, per CSPACE-5828
114                         Matcher regexMatcher = advSearchSqlWildcard.matcher(advancedSearch.trim());
115                         if (regexMatcher.matches()) {
116                             return "";
117                         }
118                         StringBuffer advancedSearchWhereClause = new StringBuffer(
119                                         advancedSearch);
120                         result = advancedSearchWhereClause.toString();
121                 }
122                 
123                 return result;
124         }
125
126         /*
127          * (non-Javadoc)
128          * 
129          * @see org.collectionspace.services.common.query.IQueryManager#
130          * createWhereClauseFromKeywords(java.lang.String)
131          */
132         // TODO handle keywords containing escaped punctuation chars, then we need
133         // to qualify the
134         // search by matching on the fulltext.simpletext field.
135         // TODO handle keywords containing unescaped double quotes by matching the
136         // phrase
137         // against the fulltext.simpletext field.
138         // Both these require using JDBC, since we cannot get to the fulltext table
139         // in NXQL
140         @Override
141         public String createWhereClauseFromKeywords(String keywords) {
142                 String result = null;
143                 StringBuffer fullTextWhereClause = new StringBuffer();
144                 // Split on unescaped double quotes to handle phrases
145                 Matcher regexMatcher = kwdTokenizer.matcher(keywords.trim());
146                 boolean addNOT = false;
147                 boolean newWordSet = true;
148                 while (regexMatcher.find()) {
149                         String phrase = regexMatcher.group();
150                         // Not needed - already trimmed by split: 
151                         // String trimmed = phrase.trim();
152                         // Ignore empty strings from match, or goofy input
153                         if (phrase.isEmpty())
154                                 continue;
155                         // Note we let OR through as is
156                         if("AND".equalsIgnoreCase(phrase)) {
157                                 continue;       // AND is default
158                         } else if("NOT".equalsIgnoreCase(phrase)) {
159                                 addNOT = true;
160                                 continue;
161                         }
162                         // Next comment block of questionable value...
163                         
164                         // ignore the special chars except single quote here - can't hurt
165                         // TODO this should become a special function that strips things the
166                         // fulltext will ignore, including non-word chars and too-short
167                         // words,
168                         // and escaping single quotes. Can return a boolean for anything
169                         // stripped,
170                         // which triggers the back-up search. We can think about whether
171                         // stripping
172                         // short words not in a quoted phrase should trigger the backup.
173                         String escapedAndTrimmed = unescapedSingleQuote.matcher(phrase).replaceAll("\\\\'");
174                         // If there are non-word chars in the phrase, we need to match the
175                         // phrase exactly against the fulltext table for this object
176                         // if(nonWordChars.matcher(trimmed).matches()) {
177                         // }
178                         // Replace problem chars with spaces. Patches CSPACE-4147,
179                         // CSPACE-4106
180                         escapedAndTrimmed = kwdSearchProblemChars.matcher(escapedAndTrimmed).replaceAll(" ").trim();
181                         escapedAndTrimmed = kwdSearchHyphen.matcher(escapedAndTrimmed).replaceAll(" ").trim();
182                         if(escapedAndTrimmed.isEmpty()) {
183                                 if (logger.isDebugEnabled() == true) {
184                                         logger.debug("Phrase reduced to empty after replacements: " + phrase);
185                                 }
186                                 continue;
187                         }
188
189                         if (fullTextWhereClause.length()==0) {
190                                 fullTextWhereClause.append(SEARCH_GROUP_OPEN);
191                         }
192                         if (newWordSet) {
193                                 fullTextWhereClause.append(ECM_FULLTEXT_LIKE + "'");
194                                 newWordSet = false;
195                         } else {
196                                 fullTextWhereClause.append(SEARCH_TERM_SEPARATOR);
197                         }
198                         if(addNOT) {
199                                 fullTextWhereClause.append("-");        // Negate the next term
200                                 addNOT = false;
201                         }
202                         fullTextWhereClause.append(escapedAndTrimmed);
203                         
204                         if (logger.isTraceEnabled() == true) {
205                                 logger.trace("Current built whereClause is: "
206                                                 + fullTextWhereClause.toString());
207                         }
208                 }
209                 if (fullTextWhereClause.length()==0) {
210                         if (logger.isDebugEnabled() == true) {
211                                 logger.debug("No usable keywords specified in string:[" + keywords + "]");
212                         }
213                 } else {
214                         fullTextWhereClause.append("'" + SEARCH_GROUP_CLOSE);
215                 }
216
217                 result = fullTextWhereClause.toString();
218                 if (logger.isDebugEnabled()) {
219                         logger.debug("Final built WHERE clause is: " + result);
220                 }
221
222                 return result;
223         }
224
225         /*
226          * (non-Javadoc)
227          * 
228          * @see org.collectionspace.services.common.query.IQueryManager#
229          * createWhereClauseFromKeywords(java.lang.String)
230          */
231         // TODO handle keywords containing escaped punctuation chars, then we need
232         // to qualify the
233         // search by matching on the fulltext.simpletext field.
234         // TODO handle keywords containing unescaped double quotes by matching the
235         // phrase
236         // against the fulltext.simpletext field.
237         // Both these require using JDBC, since we cannot get to the fulltext table
238         // in NXQL
239         @Override
240         public String createWhereClauseForPartialMatch(String dataSourceName,
241                         String repositoryName,
242                         String cspaceInstanceId,
243                         String field,
244                         boolean startingWildcard,
245                         String partialTerm) {
246                 String trimmed = (partialTerm == null) ? "" : partialTerm.trim();
247                 if (trimmed.isEmpty()) {
248                         throw new RuntimeException("No partialTerm specified.");
249                 }
250                 if(trimmed.charAt(0) == '*') {
251                         if(trimmed.length() == 1) { // only a star is not enough
252                                 throw new RuntimeException("No partialTerm specified.");
253                         }
254                         trimmed = trimmed.substring(1);
255                         startingWildcard = true;                // force a starting wildcard match
256                 }
257                 if (field == null || field.isEmpty()) {
258                         throw new RuntimeException("No match field specified.");
259                 }
260                         
261                 StringBuilder ptClause = new StringBuilder(trimmed.length()+field.length()+20);
262                 ptClause.append(field);
263                 ptClause.append(getLikeForm(dataSourceName, repositoryName, cspaceInstanceId));
264                 ptClause.append(startingWildcard?"'%":"'");
265                 ptClause.append(unescapedSingleQuote.matcher(trimmed).replaceAll("\\\\'"));
266                 ptClause.append("%'");
267                 return ptClause.toString();
268         }
269
270         /**
271          * Creates a filtering where clause from docType, for invocables.
272          * 
273          * @param docType
274          *            the docType
275          * 
276          * @return the string
277          */
278         @Override
279         public String createWhereClauseForInvocableByDocType(String schema,
280                         String docType) {
281                 String trimmed = (docType == null) ? "" : docType.trim();
282                 if (trimmed.isEmpty()) {
283                         throw new RuntimeException("No docType specified.");
284                 }
285                 if (schema == null || schema.isEmpty()) {
286                         throw new RuntimeException("No match schema specified.");
287                 }
288                 String wClause = schema + ":" + InvocableJAXBSchema.FOR_DOC_TYPES
289                                 + " = '" + trimmed + "'";
290                 return wClause;
291         }
292
293         /**
294          * Creates a filtering where clause from invocation mode, for invocables.
295          * 
296          * @param mode
297          *            the mode
298          * 
299          * @return the string
300          */
301         @Override
302         public String createWhereClauseForInvocableByMode(String schema, String mode) {
303                 String trimmed = (mode == null) ? "" : mode.trim();
304                 if (trimmed.isEmpty()) {
305                         throw new RuntimeException("No docType specified.");
306                 }
307                 if (schema == null || schema.isEmpty()) {
308                         throw new RuntimeException("No match schema specified.");
309                 }
310                 String wClause = InvocableUtils.getPropertyNameForInvocationMode(
311                                 schema, trimmed) + " != 0";
312                 return wClause;
313         }
314
315         /**
316          * @param input
317          * @return true if there were any chars filtered, that will require a backup
318          *         qualifying search on the actual text.
319          */
320         private boolean filterForFullText(String input) {
321                 boolean fFilteredChars = false;
322
323                 return fFilteredChars;
324         }
325         
326         /**
327          * Creates a query to filter a qualified (string) field according to a list of string values. 
328          * @param qualifiedField The schema-qualified field to filter on
329          * @param filterTerms the list of one or more strings to filter on
330          * @param fExclude If true, will require qualifiedField NOT match the filters strings.
331          *                                      If false, will require qualifiedField does match one of the filters strings.
332          * @return queryString
333          */
334         @Override
335         public String createWhereClauseToFilterFromStringList(String qualifiedField, String[] filterTerms, boolean fExclude) {
336         // Start with the qualified termStatus field
337         StringBuilder filterClause = new StringBuilder(qualifiedField);
338         if (filterTerms.length == 1) {
339                 filterClause.append(fExclude?" <> '":" = '");
340                 filterClause.append(filterTerms[0]);
341                 filterClause.append('\'');  
342         } else {
343                 filterClause.append(fExclude?" NOT IN (":" IN (");
344                 for(int i=0; i<filterTerms.length; i++) {
345                         if(i>0) {
346                                 filterClause.append(',');
347                         }
348                         filterClause.append('\'');  
349                         filterClause.append(filterTerms[i]);
350                         filterClause.append('\'');  
351                 }
352                 filterClause.append(')');  
353         }
354         return filterClause.toString();
355         }
356
357 }