KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > storage > search > implementation > database > informix > excalibur > EtxSqlHandler


1 /*
2
3 This software is OSI Certified Open Source Software.
4 OSI Certified is a certification mark of the Open Source Initiative.
5
6 The license (Mozilla version 1.0) can be read at the MMBase site.
7 See http://www.MMBase.org/license
8
9 */

10 package org.mmbase.storage.search.implementation.database.informix.excalibur;
11
12 import java.io.*;
13 import java.util.*;
14
15 import org.mmbase.bridge.Field;
16 import org.mmbase.module.core.*;
17 import org.mmbase.storage.StorageManagerFactory;
18 import org.mmbase.storage.search.*;
19 import org.mmbase.storage.search.implementation.database.*;
20 import org.mmbase.util.logging.*;
21 import org.w3c.dom.*;
22 import org.xml.sax.*;
23
24 /**
25  * The Etx query handler adds support for Excalibur Text Search constraints,
26  * when used with an Informix database and an Excalibur Text Search datablade.
27  * This class is provided as a coding example of a ChainedSqlHandler.
28  * <p>
29  * On initialization, the handler reads a list of etx-indices from a
30  * configuration file.
31  * This configurationfile must be named <em>etxindices.xml</em> and located
32  * inside the <em>databases</em> configuration directory.
33  * It's dtd is located in the directory
34  * <code>org.mmbase.storage.search.implementation.database.informix.excalibur.resources</code>
35  * in the MMBase source tree and
36  * <a HREF="http://www.mmbase.org/dtd/etxindices.dtd">here</a> online.
37  *
38  * @author Rob van Maris
39  * @version $Id: EtxSqlHandler.java,v 1.7 2005/10/05 12:26:11 michiel Exp $
40  * @since MMBase-1.7
41  */

42 // TODO RvM: (later) add javadoc, elaborate on overwritten methods.
43
public class EtxSqlHandler extends ChainedSqlHandler implements SqlHandler {
44
45     /** Logger instance. */
46     private static Logger log
47     = Logging.getLoggerInstance(EtxSqlHandler.class.getName());
48
49     /**
50      * The indexed fields, stored as {@link #BuilderField BuilderField}
51      * instances.
52      */

53     private Set indexedFields = new HashSet();
54
55     /**
56      * Creates a new instance of EtxueryHandler.
57      *
58      * @param successor Successor in chain or responsibility.
59      */

60     public EtxSqlHandler(SqlHandler successor) throws IOException {
61         super(successor);
62         init();
63     }
64
65     // javadoc is inherited
66
public void appendConstraintToSql(StringBuffer JavaDoc sb, Constraint constraint,
67     SearchQuery query, boolean inverse, boolean inComposite)
68     throws SearchQueryException {
69         // Net effect of inverse setting with constraint inverse property.
70
boolean overallInverse = inverse ^ constraint.isInverse();
71
72         if (constraint instanceof StringSearchConstraint) {
73             // TODO: test for support, else throw exception
74
// TODO: support maxNumber for query with etx constraint.
75
StringSearchConstraint stringSearchConstraint
76                 = (StringSearchConstraint) constraint;
77             StepField field = stringSearchConstraint.getField();
78             Map parameters = stringSearchConstraint.getParameters();
79
80             // TODO: how to implement inverse,
81
// it is actually more complicated than this:
82
if (overallInverse) {
83                 sb.append("NOT ");
84             }
85             sb.append("etx_contains(").
86             append(getAllowedValue(field.getStep().getAlias())).
87             append(".").
88             append(getAllowedValue(field.getFieldName())).
89             append(", Row('");
90
91             Iterator iSearchTerms
92                 = stringSearchConstraint.getSearchTerms().iterator();
93             while (iSearchTerms.hasNext()) {
94                 String JavaDoc searchTerm = (String JavaDoc) iSearchTerms.next();
95                 sb.append(searchTerm);
96                 if (iSearchTerms.hasNext()) {
97                     sb.append(" ");
98                 }
99             }
100             sb.append("', '");
101             switch (stringSearchConstraint.getSearchType()) {
102                 case StringSearchConstraint.SEARCH_TYPE_WORD_ORIENTED:
103                     sb.append("SEARCH_TYPE = WORD");
104                     break;
105
106                 case StringSearchConstraint.SEARCH_TYPE_PHRASE_ORIENTED:
107                     sb.append("SEARCH_TYPE = PHRASE_EXACT");
108                     break;
109
110                 case StringSearchConstraint.SEARCH_TYPE_PROXIMITY_ORIENTED:
111                     Integer JavaDoc proximityLimit
112                         = (Integer JavaDoc) parameters.
113                             get(StringSearchConstraint.PARAM_PROXIMITY_LIMIT);
114                     if (proximityLimit == null) {
115                         throw new IllegalStateException JavaDoc(
116                         "Parameter PARAM_PROXIMITY_LIMIT not set " +
117                         "while trying to perform proximity oriented search.");
118                     }
119                     sb.append("SEARCH_TYPE = PROX_SEARCH(").append(proximityLimit).append(")");
120                     break;
121
122                 default:
123                     throw new IllegalStateException JavaDoc("Invalid searchtype value: "
124                         + stringSearchConstraint.getSearchType());
125             }
126
127             switch(stringSearchConstraint.getMatchType()) {
128                 case StringSearchConstraint.MATCH_TYPE_FUZZY:
129                     Float JavaDoc fuzziness =
130                         (Float JavaDoc) parameters.get(StringSearchConstraint.PARAM_FUZZINESS);
131                     int wordScore = Math.round(100 * fuzziness.floatValue());
132                     sb.append(" & PATTERN_ALL & WORD_SCORE = ").append(wordScore);
133                     break;
134
135                 case StringSearchConstraint.MATCH_TYPE_LITERAL:
136                     break;
137
138                 case StringSearchConstraint.MATCH_TYPE_SYNONYM:
139                     log.warn("Synonym matching not supported. Executing this query with literal matching instead: " + query);
140                     break;
141
142                 default:
143                     throw new IllegalStateException JavaDoc("Invalid matchtype value: "
144                         + stringSearchConstraint.getMatchType());
145             }
146
147             sb.append("'))");
148
149         } else {
150             getSuccessor().appendConstraintToSql(sb, constraint, query,
151             inverse, inComposite);
152         }
153     }
154
155     // javadoc is inherited
156
public int getSupportLevel(int feature, SearchQuery query) throws SearchQueryException {
157         int support;
158         switch (feature) {
159             case SearchQueryHandler.FEATURE_MAX_NUMBER:
160                 // optimal with etx index on field, and constraint is
161
// StringSearchConstraint, with no additonal constraints.
162
Constraint constraint = query.getConstraint();
163                 if (constraint != null
164                         && constraint instanceof StringSearchConstraint
165                         && hasEtxIndex(((StringSearchConstraint) constraint).getField())
166                         && !hasAdditionalConstraints(query)) {
167                     support=SearchQueryHandler.SUPPORT_OPTIMAL;
168                 } else {
169                     support = getSuccessor().getSupportLevel(feature, query);
170                 }
171                 break;
172             default:
173                 support = getSuccessor().getSupportLevel(feature, query);
174         }
175         return support;
176     }
177
178     // javadoc is inherited
179
public int getSupportLevel(Constraint constraint, SearchQuery query)
180     throws SearchQueryException {
181         int support;
182
183         if (constraint instanceof StringSearchConstraint
184                 && hasEtxIndex(((StringSearchConstraint) constraint).getField())) {
185             StringSearchConstraint stringSearchConstraint =
186                 (StringSearchConstraint) constraint;
187             // StringSearchConstraint on field with etx index:
188
// - none if matchtype = MATCH_TYPE_SYNONYM
189
// - otherwise: weak support if other stringsearch constraints are present
190
// - otherwise: optimal support
191
if (stringSearchConstraint.getMatchType()
192                     == StringSearchConstraint.MATCH_TYPE_SYNONYM) {
193                 support = SearchQueryHandler.SUPPORT_NONE;
194             } else if (containsOtherStringSearchConstraints(
195                     query.getConstraint(), stringSearchConstraint)) {
196                 support = SearchQueryHandler.SUPPORT_WEAK;
197             } else {
198                 support = SearchQueryHandler.SUPPORT_OPTIMAL;
199             }
200         } else {
201             support = getSuccessor().getSupportLevel(constraint, query);
202         }
203         return support;
204     }
205
206     /**
207      * Tests if an Excelibur Text Search index has been made for this field.
208      *
209      * @param field the field.
210      * @return true if an Excelibur Text Search index has been made for this field,
211      * false otherwise.
212      */

213     public boolean hasEtxIndex(StepField field) {
214         boolean result = false;
215         if (field.getType() == Field.TYPE_STRING
216         || field.getType() == Field.TYPE_XML) {
217             result = indexedFields.contains(
218             field.getStep().getTableName() + "." + field.getFieldName());
219         }
220         return result;
221     }
222
223     /**
224      * Tests if the query contains additional constraints, i.e. on relations
225      * or nodes.
226      *
227      * @param query the query.
228      * @return true if the query containts additional constraints,
229      * false otherwise.
230      */

231     protected boolean hasAdditionalConstraints(SearchQuery query) {
232         Iterator iSteps = query.getSteps().iterator();
233         while (iSteps.hasNext()) {
234             Step step = (Step) iSteps.next();
235             if (step instanceof RelationStep || step.getNodes().size() > 0) {
236                 // Additional constraints on relations or nodes.
237
return true;
238             }
239         }
240         // No additonal constraints:
241
return false;
242     }
243
244     /**
245      * Tests if a constaint is/contains another stringsearch constraint than
246      * the specified one. Recursively seaches through all childs of composite
247      * constraints.
248      *
249      * @param constraint the constraint.
250      * @param searchConstraint the stringsearch constraint.
251      * @return true if the constraint is/contains another stringsearch constraint
252      * than the given one, false otherwise.
253      */

254     protected boolean containsOtherStringSearchConstraints(
255     Constraint constraint,
256     StringSearchConstraint searchConstraint) {
257         if (constraint instanceof CompositeConstraint) {
258             // Composite constraint.
259
Iterator iChildConstraints
260             = ((CompositeConstraint) constraint).getChilds().iterator();
261             while (iChildConstraints.hasNext()) {
262                 Constraint childConstraint
263                 = (Constraint) iChildConstraints.next();
264                 if (containsOtherStringSearchConstraints(
265                 childConstraint, searchConstraint)) {
266                     // Another stringsearch constraint found in childs.
267
return true;
268                 }
269             }
270             // No other stringsearch constraint found in childs.
271
return false;
272
273         } else if (constraint instanceof StringSearchConstraint
274         && constraint != searchConstraint) {
275             // Anther stringsearch constraint.
276
return true;
277
278         } else {
279             // Not another stringsearch constraint and not a composite.
280
return false;
281         }
282     }
283
284     /**
285      * Initializes the handler by reading the etxindices configuration file
286      * to determine which fields have a etx index.
287      * <p>
288      * The configurationfile must be named <em>etxindices.xml</em> and located
289      * inside the <em>databases</em> configuration directory.
290      *
291      * @throw IOException When a failure occurred while trying to read the
292      * configuration file.
293      */

294     private void init() throws IOException {
295         File etxConfigFile = new File(
296             MMBaseContext.getConfigPath() + "/databases/etxindices.xml");
297         XmlEtxIndicesReader configReader =
298             new XmlEtxIndicesReader(
299                 new InputSource(
300                     new BufferedReader(
301                         new FileReader(etxConfigFile))));
302
303         for (Iterator eSbspaces = configReader.getSbspaceElements(); eSbspaces.hasNext();) {
304             Element sbspace = (Element) eSbspaces.next();
305             
306             for (Iterator eEtxIndices = configReader.getEtxindexElements(sbspace); eEtxIndices.hasNext();) {
307                 Element etxIndex = (Element) eEtxIndices.next();
308                 String JavaDoc table = configReader.getEtxindexTable(etxIndex);
309                 String JavaDoc field = configReader.getEtxindexField(etxIndex);
310                 String JavaDoc index = configReader.getEtxindexValue(etxIndex);
311                 try {
312                     String JavaDoc builderField = toBuilderField(table, field);
313                     indexedFields.add(builderField);
314                     log.service("Registered etx index \"" + index +
315                     "\" for builderfield " + builderField);
316                 } catch (IllegalArgumentException JavaDoc e) {
317                     log.error("Failed to register etx index \"" +
318                     index + "\": " + e);
319                 }
320             }
321         }
322     }
323
324     /**
325      * Finds builderfield corresponding to the database table and field names.
326      *
327      * @param dbTable The tablename used in the database.
328      * @param dbField The fieldname used in the database.
329      * @return The corresponding builderfield represented by a string of the
330      * form &lt;buildername&gt;.&lt;fieldname&gt;.
331      * @throws IllegalArgumentException when an invalid argument is supplied.
332      */

333     static String JavaDoc toBuilderField(String JavaDoc dbTable, String JavaDoc dbField) {
334         // package visibility!
335
MMBase mmbase = MMBase.getMMBase();
336         StorageManagerFactory factory = mmbase.getStorageManagerFactory();
337         String JavaDoc tablePrefix = mmbase.getBaseName() + "_";
338
339         if (!dbTable.startsWith(tablePrefix)) {
340             throw new IllegalArgumentException JavaDoc(
341             "Invalid tablename: \"" + dbTable + "\". " +
342             "It should start with the prefix \"" + tablePrefix + "\".");
343         }
344
345         String JavaDoc builderName = dbTable.substring(tablePrefix.length());
346         MMObjectBuilder builder;
347         try {
348             builder = mmbase.getBuilder(builderName);
349         } catch (BuilderConfigurationException e){
350             // Unknown builder.
351
builder = null;
352         }
353
354         if (builder == null) {
355             throw new IllegalArgumentException JavaDoc(
356             "Unknown builder: \"" + builderName + "\".");
357         }
358
359         Iterator iFieldNames = builder.getFieldNames().iterator();
360         while (iFieldNames.hasNext()) {
361             String JavaDoc fieldName = (String JavaDoc) iFieldNames.next();
362             if (factory.getStorageIdentifier(fieldName).equals(dbField)) {
363                 return builderName + "." + fieldName;
364             }
365         }
366
367         throw new IllegalArgumentException JavaDoc(
368         "No field corresponding to database field \"" + dbField
369         + "\" found in builder \"" + builderName + "\".");
370     }
371
372 }
373
Popular Tags