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