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