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