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