001: /***************************************************************
002: * This file is part of the [fleXive](R) project.
003: *
004: * Copyright (c) 1999-2007
005: * UCS - unique computing solutions gmbh (http://www.ucs.at)
006: * All rights reserved
007: *
008: * The [fleXive](R) project is free software; you can redistribute
009: * it and/or modify it under the terms of the GNU General Public
010: * License as published by the Free Software Foundation;
011: * either version 2 of the License, or (at your option) any
012: * later version.
013: *
014: * The GNU General Public License can be found at
015: * http://www.gnu.org/copyleft/gpl.html.
016: * A copy is found in the textfile GPL.txt and important notices to the
017: * license from the author are found in LICENSE.txt distributed with
018: * these libraries.
019: *
020: * This library is distributed in the hope that it will be useful,
021: * but WITHOUT ANY WARRANTY; without even the implied warranty of
022: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023: * GNU General Public License for more details.
024: *
025: * For further information about UCS - unique computing solutions gmbh,
026: * please see the company website: http://www.ucs.at
027: *
028: * For further information about [fleXive](R), please see the
029: * project website: http://www.flexive.org
030: *
031: *
032: * This copyright notice MUST APPEAR in all copies of the file!
033: ***************************************************************/package com.flexive.core.search;
034:
035: import com.flexive.shared.structure.*;
036: import com.flexive.shared.exceptions.FxSqlSearchException;
037: import com.flexive.shared.exceptions.FxRuntimeException;
038: import com.flexive.shared.exceptions.FxNotFoundException;
039: import com.flexive.shared.value.*;
040: import com.flexive.shared.FxLanguage;
041: import com.flexive.shared.CacheAdmin;
042: import com.flexive.shared.search.FxPaths;
043: import com.flexive.shared.content.FxPK;
044: import com.flexive.core.DatabaseConst;
045: import com.flexive.core.storage.ContentStorage;
046: import com.flexive.sqlParser.Property;
047: import org.apache.commons.lang.StringUtils;
048: import org.apache.commons.logging.Log;
049: import org.apache.commons.logging.LogFactory;
050:
051: import java.sql.ResultSet;
052: import java.sql.Timestamp;
053: import java.sql.SQLException;
054: import java.util.Date;
055:
056: /**
057: * <p>
058: * A single entry of the property resolver, i.e. a selected property. A new entry is instantiated for every
059: * column of a search query, it also stores context information like the column index in the SQL result set.
060: * </p>
061: * <p>
062: * To create a new property entry type (e.g. a new virtual property), you have to:
063: * <ol>
064: * <li>Register a new {@link PropertyEntry.Type} that matches the property name</li>
065: * <li>Create a new subclass of {@link PropertyEntry} that selects the columns and specifies
066: * a method to read the value from the result set</li>
067: * <li>Extend the factory method {@link PropertyEntry.Type#createEntry()}</li>
068: * <li>Only if you need database-specific procedure calls: extend the {@link DataSelector}
069: * implementation</li>
070: * </ol>
071: * </p>
072: *
073: * @author Gregor Schober (gregor.schober@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
074: * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
075: * @version $Rev$
076: */
077: public class PropertyEntry {
078: private static final transient Log LOG = LogFactory
079: .getLog(PropertyEntry.class);
080:
081: /**
082: * Property entry types. A type is either a generic property selector (e.g. {@link #PROPERTY_REF}),
083: * or a custom resolver like the primary key or tree path "virtual" properties.
084: */
085: public static enum Type {
086: /**
087: * A common property reference, e.g. co.caption.
088: */
089: PROPERTY_REF(null),
090: /**
091: * A primary key (@pk) column.
092: */
093: PK("@pk"),
094:
095: /**
096: * A tree node path (@path) column.
097: */
098: PATH("@path"),
099:
100: /**
101: * A tree node position (@node_position) column.
102: */
103: NODE_POSITION("@node_position");
104:
105: private final String propertyName;
106:
107: Type(String propertyName) {
108: this .propertyName = propertyName;
109: }
110:
111: /**
112: * Returns the property name this type applies for. If it is a generic type
113: * (i.e. {@link #PROPERTY_REF}, this method returns null.
114: *
115: * @return the property name this type applies for
116: */
117: public String getPropertyName() {
118: return propertyName;
119: }
120:
121: /**
122: * Returns true if this type matches the given property name (e.g. "@pk").
123: * For generic types (i.e. {@link #PROPERTY_REF}), this method always returns false.
124: *
125: * @param name the property name to be matched
126: * @return true if this type matches the given property name (e.g. "@pk").
127: */
128: public boolean matchesProperty(String name) {
129: return StringUtils.equalsIgnoreCase(propertyName, name);
130: }
131:
132: /**
133: * Create a new {@link PropertyEntry} instance for this property type. Does not
134: * work for generic entries (i.e. {@link #PROPERTY_REF}), these have to be created
135: * manually by creating a new instance of the generic {@link PropertyEntry} class.
136: *
137: * @return a new {@link PropertyEntry} instance for this property type.
138: */
139: public PropertyEntry createEntry() {
140: switch (this ) {
141: case PK:
142: return new PkEntry();
143: case NODE_POSITION:
144: return new NodePositionEntry();
145: case PATH:
146: return new PathEntry();
147: case PROPERTY_REF:
148: throw new FxSqlSearchException(LOG,
149: "ex.sqlSearch.entry.virtual")
150: .asRuntimeException();
151: default:
152: throw new IllegalStateException();
153: }
154: }
155: }
156:
157: /**
158: * The primary key resolver (@pk)
159: */
160: private static class PkEntry extends PropertyEntry {
161: private PkEntry() {
162: super (Type.PK, PropertyResolver.Table.T_CONTENT,
163: new String[] { "ID", "VERSION" }, null, false, null);
164: }
165:
166: @Override
167: public Object getResultValue(ResultSet rs, FxLanguage language)
168: throws FxSqlSearchException {
169: try {
170: final long id = rs.getLong(positionInResultSet);
171: final int ver = rs.getInt(positionInResultSet + 1);
172: return new FxPK(id, ver);
173: } catch (SQLException e) {
174: throw new FxSqlSearchException(LOG, e);
175: }
176: }
177: }
178:
179: /**
180: * The tree path resolver (@path)
181: */
182: private static class PathEntry extends PropertyEntry {
183: private PathEntry() {
184: super (Type.PATH, PropertyResolver.Table.T_CONTENT,
185: new String[] { "" }, // select one column (function will be inserted by DB adapter)
186: null, false, null);
187: }
188:
189: @Override
190: public Object getResultValue(ResultSet rs, FxLanguage language)
191: throws FxSqlSearchException {
192: try {
193: return new FxPaths(rs.getString(positionInResultSet));
194: } catch (SQLException e) {
195: throw new FxSqlSearchException(LOG, e);
196: }
197: }
198: }
199:
200: /**
201: * The tree node position resolver (@node_position)
202: */
203: private static class NodePositionEntry extends PropertyEntry {
204: private NodePositionEntry() {
205: super (Type.NODE_POSITION, PropertyResolver.Table.T_CONTENT,
206: new String[] { "" }, // select one column (function will be inserted by DB adapter)
207: null, false, FxDataType.Number);
208: }
209:
210: @Override
211: public Object getResultValue(ResultSet rs, FxLanguage language)
212: throws FxSqlSearchException {
213: try {
214: return rs.getLong(positionInResultSet);
215: } catch (SQLException e) {
216: throw new FxSqlSearchException(LOG, e);
217: }
218: }
219: }
220:
221: protected final String[] readColumns;
222: protected final String filterColumn;
223: protected final String tableName;
224: protected final FxProperty property;
225: protected final PropertyResolver.Table tbl;
226: protected final Type type;
227: protected final boolean multilanguage;
228: protected int positionInResultSet = -1;
229: protected FxPropertyAssignment assignment;
230: protected FxDataType overrideDataType;
231:
232: /**
233: * Create a new instance based on the given (search) property.
234: *
235: * @param searchProperty the search property
236: * @param storage the storage instance
237: * @param ignoreCase whether case should be ignored for this column
238: * @throws FxSqlSearchException if the entry could not be created
239: */
240: public PropertyEntry(Property searchProperty,
241: ContentStorage storage, boolean ignoreCase)
242: throws FxSqlSearchException {
243: this .type = Type.PROPERTY_REF;
244:
245: if (searchProperty.isAssignment()) {
246: try {
247: if (searchProperty.getPropertyName().indexOf('/') > 0) {
248: //XPath
249: this .assignment = (FxPropertyAssignment) CacheAdmin
250: .getEnvironment().getAssignment(
251: searchProperty.getPropertyName());
252: } else {
253: //#<id>
254: this .assignment = (FxPropertyAssignment) CacheAdmin
255: .getEnvironment().getAssignment(
256: Long.valueOf(searchProperty
257: .getPropertyName()
258: .substring(1)));
259: }
260: } catch (ClassCastException ce) {
261: throw new FxSqlSearchException(LOG, ce,
262: "ex.sqlSearch.query.unknownAssignment",
263: searchProperty.getPropertyName());
264: } catch (FxRuntimeException e) {
265: if (e.getConverted() instanceof FxNotFoundException) {
266: throw new FxSqlSearchException(LOG, e,
267: "ex.sqlSearch.query.unknownAssignment",
268: searchProperty.getPropertyName());
269: } else {
270: throw new FxSqlSearchException(
271: LOG,
272: e,
273: "ex.sqlSearch.query.failedToResolveAssignment",
274: searchProperty.getPropertyName(), e
275: .getMessage());
276: }
277: }
278: this .property = assignment.getProperty();
279: } else {
280: this .assignment = null;
281: this .property = CacheAdmin.getEnvironment().getProperty(
282: searchProperty.getPropertyName());
283: }
284:
285: this .readColumns = storage.getColumns(this .property);
286: String fcol = ignoreCase ? storage
287: .getUppercaseColumn(this .property)
288: : this .readColumns[0];
289: if (fcol == null) {
290: fcol = this .readColumns == null ? null
291: : this .readColumns[0];
292: }
293: this .filterColumn = fcol;
294:
295: if (this .filterColumn == null) {
296: throw new FxSqlSearchException(
297: LOG,
298: "ex.sqlSearch.init.propertyDoesNotHaveColumnMapping",
299: searchProperty.getPropertyName());
300: }
301:
302: this .tableName = storage.getTableName(this .property);
303: if (this .tableName.equalsIgnoreCase(DatabaseConst.TBL_CONTENT)) {
304: this .tbl = PropertyResolver.Table.T_CONTENT;
305: } else if (this .tableName
306: .equalsIgnoreCase(DatabaseConst.TBL_CONTENT_DATA)) {
307: this .tbl = PropertyResolver.Table.T_CONTENT_DATA;
308: } else {
309: throw new FxSqlSearchException(LOG,
310: "ex.sqlSearch.err.unknownPropertyTable",
311: searchProperty, this .tableName);
312: }
313: this .multilanguage = this .property.isMultiLang();
314: }
315:
316: protected PropertyEntry(Type type, PropertyResolver.Table tbl,
317: String[] readColumns, String filterColumn,
318: boolean multilanguage, FxDataType overrideDataType) {
319: this .readColumns = readColumns;
320: this .filterColumn = filterColumn;
321: this .tbl = tbl;
322: this .type = type;
323: this .multilanguage = multilanguage;
324: this .overrideDataType = overrideDataType;
325: this .property = null;
326: this .tableName = null;
327: }
328:
329: /**
330: * Return the result value of this property entry in a given result set.
331: *
332: * @param rs the SQL result set
333: * @param language the result language
334: * @return the value of this property (column) in the result set
335: * @throws FxSqlSearchException if the database cannot read the value
336: */
337: public Object getResultValue(ResultSet rs, FxLanguage language)
338: throws FxSqlSearchException {
339: final FxValue result;
340: int pos = positionInResultSet;
341: // Handle by type
342: try {
343: switch (overrideDataType == null ? property.getDataType()
344: : overrideDataType) {
345: case DateTime:
346: if (rs.getMetaData().getColumnType(pos) == java.sql.Types.BIGINT) {
347: result = new FxDateTime(multilanguage,
348: FxLanguage.SYSTEM_ID, new Date(rs
349: .getLong(pos)));
350: break;
351: }
352: Timestamp dttstp = rs.getTimestamp(pos);
353: Date _dtdate = new Date(dttstp.getTime());
354: result = new FxDateTime(multilanguage,
355: FxLanguage.SYSTEM_ID, _dtdate);
356: break;
357: case Date:
358: Timestamp tstp = rs.getTimestamp(pos);
359: Date _date = new Date(tstp.getTime());
360: result = new FxDate(multilanguage,
361: FxLanguage.SYSTEM_ID, _date);
362: break;
363: case DateRange:
364: final Date from = new Date(rs.getTimestamp(pos)
365: .getTime()); // FDATE1
366: final Date to = new Date(rs.getTimestamp(pos + 4)
367: .getTime()); // FDATE2
368: result = new FxDateRange(new DateRange(from, to));
369: break;
370: case DateTimeRange:
371: final Date from2 = new Date(rs.getTimestamp(pos)
372: .getTime()); // FDATE1
373: final Date to2 = new Date(rs.getTimestamp(pos + 7)
374: .getTime()); // FDATE2
375: result = new FxDateTimeRange(new DateRange(from2, to2));
376: break;
377: case HTML:
378: result = new FxHTML(multilanguage,
379: FxLanguage.SYSTEM_ID, rs.getString(pos));
380: break;
381: case String1024:
382: case Text:
383: result = new FxString(multilanguage,
384: FxLanguage.SYSTEM_ID, rs.getString(pos));
385: break;
386: case LargeNumber:
387: result = new FxLargeNumber(multilanguage,
388: FxLanguage.SYSTEM_ID, rs.getLong(pos));
389: break;
390: case Number:
391: result = new FxNumber(multilanguage,
392: FxLanguage.SYSTEM_ID, rs.getInt(pos));
393: break;
394: case Float:
395: result = new FxFloat(multilanguage,
396: FxLanguage.SYSTEM_ID, rs.getFloat(pos));
397: break;
398: case Boolean:
399: result = new FxBoolean(multilanguage,
400: FxLanguage.SYSTEM_ID, rs.getBoolean(pos));
401: break;
402: case Double:
403: result = new FxDouble(multilanguage,
404: FxLanguage.SYSTEM_ID, rs.getDouble(pos));
405: break;
406: case Reference:
407: result = new FxReference(new ReferencedContent(
408: new FxPK(rs.getLong(pos), FxPK.MAX))); // TODO!!
409: break;
410: case SelectOne:
411: FxSelectListItem oneItem = CacheAdmin.getEnvironment()
412: .getSelectListItem(rs.getLong(pos));
413: result = new FxSelectOne(multilanguage,
414: FxLanguage.SYSTEM_ID, oneItem);
415: break;
416: case SelectMany:
417: FxSelectListItem manyItem = CacheAdmin.getEnvironment()
418: .getSelectListItem(rs.getLong(pos));
419: SelectMany valueMany = new SelectMany(manyItem
420: .getList());
421: valueMany.selectFromList(rs.getString(pos + 1));
422: result = new FxSelectMany(multilanguage,
423: FxLanguage.SYSTEM_ID, valueMany);
424: break;
425: case Binary:
426: result = new FxBinary(multilanguage,
427: FxLanguage.SYSTEM_ID, new BinaryDescriptor());
428: break;
429: default:
430: throw new FxSqlSearchException(LOG,
431: "ex.sqlSearch.reader.UnknownColumnType", String
432: .valueOf(getProperty().getDataType()));
433: }
434:
435: if (rs.wasNull()) {
436: result.setEmpty(language.getId());
437: }
438:
439: // Get the XPATH if we are reading from the content data table
440: if (getTableType() == PropertyResolver.Table.T_CONTENT_DATA) {
441: result.setXPath(rs.getString(positionInResultSet
442: + getReadColumns().length));
443: }
444:
445: return result;
446: } catch (SQLException e) {
447: throw new FxSqlSearchException(e);
448: }
449: }
450:
451: /**
452: * Return the entry type.
453: *
454: * @return the entry type.
455: */
456: public Type getType() {
457: return type;
458: }
459:
460: /**
461: * Overrides the data type this entry represents, e.g. by selectors that load property
462: * fields from an external table like the {@link com.flexive.core.search.mysql.MySQLACLSelector}.
463: *
464: * @param type the data type of this entry
465: */
466: public void overrideDataType(FxDataType type) {
467: this .overrideDataType = type;
468: }
469:
470: /**
471: * Set the entry's result set index while the query is being built.
472: *
473: * @param positionInResultSet the entry's result set index
474: */
475: void setPositionInResultSet(int positionInResultSet) {
476: this .positionInResultSet = positionInResultSet;
477: }
478:
479: /**
480: * Return the table type if it's a predefined table (like FX_CONTENT), null otherwise.
481: *
482: * @return the table type if it's a predefined table (like FX_CONTENT), null otherwise.
483: */
484: public PropertyResolver.Table getTableType() {
485: return tbl;
486: }
487:
488: /**
489: * The column(s) to read the result from.
490: *
491: * @return the column(s) to read the result from
492: */
493: public String[] getReadColumns() {
494: return readColumns;
495: }
496:
497: /**
498: * Return the column name to be used for filtering (i.e. in the 'WHERE' clause of the query).
499: *
500: * @return the column name to be used for filtering
501: */
502: public String getFilterColumn() {
503: return filterColumn;
504: }
505:
506: /**
507: * Return the database table name to be used for selecting/filtering, e.g.
508: * FX_CONTENT_DATA.
509: *
510: * @return the database table name to be used for selecting/filtering
511: */
512: public String getTableName() {
513: return StringUtils.defaultString(tableName, tbl.getTableName());
514: }
515:
516: /**
517: * Returns the structure property, may be null if this entry does not actually represent
518: * a structure element.
519: *
520: * @return the structure property
521: */
522: public FxProperty getProperty() {
523: return property;
524: }
525:
526: /**
527: * Returns true if this entry represents a structure (property) assignment.
528: *
529: * @return true if this entry represents a structure (property) assignment.
530: */
531: public boolean isAssignment() {
532: return assignment != null;
533: }
534:
535: /**
536: * Returns the (property) assignment. May be null if this entry does not represent
537: * a structure element.
538: *
539: * @return the property assignment
540: */
541: public FxPropertyAssignment getAssignment() {
542: return assignment;
543: }
544: }
|