001: /*
002: * This file is part of the GeOxygene project source files.
003: *
004: * GeOxygene aims at providing an open framework which implements OGC/ISO specifications for
005: * the development and deployment of geographic (GIS) applications. It is a open source
006: * contribution of the COGIT laboratory at the Institut Géographique National (the French
007: * National Mapping Agency).
008: *
009: * See: http://oxygene-project.sourceforge.net
010: *
011: * Copyright (C) 2005 Institut Géographique National
012: *
013: * This library is free software; you can redistribute it and/or modify it under the terms
014: * of the GNU Lesser General Public License as published by the Free Software Foundation;
015: * either version 2.1 of the License, or any later version.
016: *
017: * This library is distributed in the hope that it will be useful, but WITHOUT ANY
018: * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
019: * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
020: *
021: * You should have received a copy of the GNU Lesser General Public License along with
022: * this library (see file LICENSE if present); if not, write to the Free Software
023: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024: *
025: */
026:
027: package fr.ign.cogit.geoxygene.datatools.ojb;
028:
029: import java.lang.reflect.Field;
030: import java.lang.reflect.Method;
031: import java.lang.reflect.Proxy;
032: import java.sql.Connection;
033: import java.util.StringTokenizer;
034:
035: import org.apache.commons.lang.SystemUtils;
036: import org.apache.ojb.broker.Identity;
037: import org.apache.ojb.broker.PBKey;
038: import org.apache.ojb.broker.PersistenceBroker;
039: import org.apache.ojb.broker.PersistenceBrokerException;
040: import org.apache.ojb.broker.VirtualProxy;
041: import org.apache.ojb.broker.accesslayer.IndirectionHandler;
042: import org.apache.ojb.broker.core.ValueContainer;
043: import org.apache.ojb.broker.metadata.ClassDescriptor;
044: import org.apache.ojb.broker.metadata.FieldDescriptor;
045: import org.apache.ojb.broker.metadata.MetadataException;
046: import org.apache.ojb.broker.metadata.MetadataManager;
047: import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
048: import org.apache.ojb.broker.platforms.Platform;
049: import org.apache.ojb.broker.platforms.PlatformFactory;
050: import org.apache.ojb.broker.platforms.PlatformOracle9iImpl;
051: import org.apache.ojb.broker.platforms.PlatformOracleImpl;
052: import org.apache.ojb.broker.platforms.PlatformPostgreSQLImpl;
053: import org.apache.ojb.broker.query.Criteria;
054: import org.apache.ojb.broker.query.MtoNQuery;
055: import org.apache.ojb.broker.query.Query;
056: import org.apache.ojb.broker.query.QueryByCriteria;
057: import org.apache.ojb.broker.query.QueryBySQL;
058: import org.apache.ojb.broker.query.ReportQueryByCriteria;
059: import org.apache.ojb.broker.query.ReportQueryByMtoNCriteria;
060: import org.apache.ojb.broker.util.sequence.SequenceManagerException;
061:
062: //import fr.ign.cogit.geoxygene.datatools.oracle.ArrayGeOxygene2Oracle;
063: //import fr.ign.cogit.geoxygene.datatools.oracle.GeomGeOxygene2Oracle;
064: //#else
065: /*
066: import com.develop.java.lang.reflect.Proxy;
067: */
068: //#endif
069: /**
070: * Redefinition de la classe org.apache.ojb.util.BrokerHelper d'OJB,
071: * permettant d'appeler une methode "javaToSql(Object, Connection)
072: * pour ecrire les structures dans Oracle.
073: * Par rapport a la version originale de BrokerHelper :
074: * les imports ont ete reorganises,
075: * le constructeur renomme,
076: * un parametre connection ajoute dans la signature de getValuesForObject (ligne 352),
077: * un ajout dans la methode getValuesForObject (ligne 375),
078: * un parametre connection ajoute dans getAllRwValues,
079: * un parametre connection ajoute dans getNonKeyRwValues,
080: * un parametre connection ajoute dans getKeyValues,
081: * un parametre connection ajoute dans getKeyValues.
082: * Les 4 dernieres modifs se font suite a des erreurs de compile,
083: * suite au premier ajout dans getValuesForObject.
084: *
085: * AB 11 juillet 2005 :
086: * <br> Utilisation des noms de classes et de la réflection pour permettre la compilation sépérée pour Oracle.
087: * <br> Patch pour permettre l'utilisation de la meme classe de "FieldConversion" pour Oracle et Postgis.
088: *
089: * @author Thierry Badard & Arnaud Braun
090: * @version 1.1
091: *
092: */
093: public class GeOxygeneBrokerHelper {
094:
095: // AJOUT pour GeOxygene ---------------------------------------------------
096: // Nom des classes relatives à Oracle,
097: //en String pour permettre la compilation séparée
098: private final String GeomGeOxygene2Oracle_CLASS_NAME = "fr.ign.cogit.geoxygene.datatools.oracle.GeomGeOxygene2Oracle";
099: private final String GeomGeOxygene2Postgis_CLASS_NAME = "fr.ign.cogit.geoxygene.datatools.postgis.GeomGeOxygene2Postgis";
100: private Method geomGeOxygene2OracleMethod;
101: private Method geomGeOxygene2PostgisMethod;
102: // SGBD
103: private Platform m_platform;
104: // FIN AJOUT pour GeOxygene ---------------------------------------------------
105:
106: public static final String REPOSITORY_NAME_SEPARATOR = "#";
107: private PersistenceBroker m_broker;
108:
109: public GeOxygeneBrokerHelper(PersistenceBroker broker) {
110: this .m_broker = broker;
111:
112: // AJOUT pour GeOxygene -----------------------------------------------------------
113: // Definition du SGBD
114: m_platform = PlatformFactory.getPlatformFor(m_broker
115: .serviceConnectionManager().getConnectionDescriptor());
116:
117: // ORACLE
118: if (m_platform instanceof PlatformOracle9iImpl
119: || m_platform instanceof PlatformOracleImpl)
120: try {
121: Class geomGeOxygene2OracleClass = Class
122: .forName(GeomGeOxygene2Oracle_CLASS_NAME);
123: geomGeOxygene2OracleMethod = geomGeOxygene2OracleClass
124: .getMethod("javaToSql", new Class[] {
125: Object.class, Connection.class });
126: } catch (Exception e) {
127: e.printStackTrace();
128: }
129:
130: // POSTGIS
131: else if (m_platform instanceof PlatformPostgreSQLImpl)
132: try {
133: Class geomGeOxygene2PostgisClass = Class
134: .forName(GeomGeOxygene2Postgis_CLASS_NAME);
135: geomGeOxygene2PostgisMethod = geomGeOxygene2PostgisClass
136: .getMethod("javaToSql",
137: new Class[] { Object.class });
138: } catch (Exception e) {
139: e.printStackTrace();
140: }
141:
142: // AUTRE DBMS
143: else {
144: System.out
145: .println("## Le SGBD n'est ni Oracle, ni PostgreSQL ##");
146: System.out.println("## Le programme s'arrête ##");
147: System.exit(0);
148: }
149: // FIN AJOUT pour GeOxygene ---------------------------------------------------
150:
151: }
152:
153: /**
154: * splits up the name string and extract db url,
155: * user name and password and build a new PBKey
156: * instance - the token '#' is used to separate
157: * the substrings.
158: * @throws PersistenceBrokerException if given name was <code>null</code>
159: */
160: public static PBKey extractAllTokens(String name) {
161: if (name == null) {
162: throw new PersistenceBrokerException(
163: "Could not extract PBKey, given argument is 'null'");
164: }
165: String user = null;
166: String passwd = null;
167: StringTokenizer tok = new StringTokenizer(name,
168: REPOSITORY_NAME_SEPARATOR);
169: String dbName = tok.nextToken();
170: if (tok.hasMoreTokens()) {
171: user = tok.nextToken();
172: if (user != null && user.trim().equals("")) {
173: user = null;
174: }
175: }
176: if (tok.hasMoreTokens()) {
177: if (user != null)
178: passwd = tok.nextToken();
179: }
180: if (user != null && passwd == null) {
181: passwd = "";
182: }
183: PBKey key = new PBKey(dbName, user, passwd);
184: return key;
185: }
186:
187: /**
188: * Check if the user of the given PBKey was <code>null</code>, if so we try to
189: * get user/password from the jdbc-connection-descriptor matching the given
190: * PBKey.getAlias().
191: */
192: public static PBKey crossCheckPBKey(PBKey key) {
193: if (key.getUser() == null) {
194: PBKey defKey = MetadataManager.getInstance()
195: .connectionRepository()
196: .getStandardPBKeyForJcdAlias(key.getAlias());
197: if (defKey != null) {
198: return defKey;
199: }
200: }
201: return key;
202: }
203:
204: /**
205: * Answer the real ClassDescriptor for anObj
206: * ie. aCld may be an Interface of anObj, so the cld for anObj is returned
207: */
208: protected ClassDescriptor getRealClassDescriptor(
209: ClassDescriptor aCld, Object anObj) {
210: ClassDescriptor result;
211:
212: if (aCld.getClassOfObject() == anObj.getClass()) {
213: result = aCld;
214: } else {
215: result = aCld.getRepository().getDescriptorFor(
216: anObj.getClass());
217: }
218:
219: return result;
220: }
221:
222: /**
223: * returns an Array with an Objects PK VALUES if convertToSql is true, any
224: * associated java-to-sql conversions are applied. If the Object is a Proxy
225: * or a VirtualProxy NO conversion is necessary.
226: *
227: * @param objectOrProxy
228: * @param convertToSql
229: * @return Object[]
230: * @throws PersistenceBrokerException
231: */
232: public ValueContainer[] getKeyValues(ClassDescriptor cld,
233: Object objectOrProxy, boolean convertToSql, Connection conn)
234: throws PersistenceBrokerException {
235: /*
236: arminw
237: Check it out. Because the isProxyClass method is costly and most objects
238: aren't proxies, I add a instanceof check before. Is every Proxy a instance of Proxy?
239: */
240: if ((objectOrProxy instanceof Proxy)
241: && Proxy.isProxyClass(objectOrProxy.getClass())) {
242: IndirectionHandler handler;
243: handler = (IndirectionHandler) Proxy
244: .getInvocationHandler(objectOrProxy);
245: return getKeyValues(cld, handler.getIdentity(),
246: convertToSql); //BRJ: convert Identity
247: } else if (objectOrProxy instanceof VirtualProxy) {
248: IndirectionHandler handler;
249: handler = VirtualProxy
250: .getIndirectionHandler((VirtualProxy) objectOrProxy);
251: return getKeyValues(cld, handler.getIdentity(),
252: convertToSql); //BRJ: convert Identity
253: } else {
254: ClassDescriptor realCld = getRealClassDescriptor(cld,
255: objectOrProxy);
256: return getValuesForObject(realCld.getPkFields(),
257: objectOrProxy, convertToSql, conn);
258: }
259: }
260:
261: /**
262: * Return key Values of an Identity
263: * @param cld
264: * @param oid
265: * @return Object[]
266: * @throws PersistenceBrokerException
267: */
268: public ValueContainer[] getKeyValues(ClassDescriptor cld,
269: Identity oid) throws PersistenceBrokerException {
270: return getKeyValues(cld, oid, true);
271: }
272:
273: /**
274: * Return key Values of an Identity
275: * @param cld
276: * @param oid
277: * @param convertToSql
278: * @return Object[]
279: * @throws PersistenceBrokerException
280: */
281: public ValueContainer[] getKeyValues(ClassDescriptor cld,
282: Identity oid, boolean convertToSql)
283: throws PersistenceBrokerException {
284: FieldDescriptor[] pkFields = cld.getPkFields();
285: ValueContainer[] result = new ValueContainer[pkFields.length];
286: Object[] pkValues = oid.getPrimaryKeyValues();
287:
288: try {
289: for (int i = 0; i < result.length; i++) {
290: FieldDescriptor fd = pkFields[i];
291: Object cv = pkValues[i];
292: if (convertToSql) {
293: // BRJ : apply type and value mapping
294: cv = fd.getFieldConversion().javaToSql(cv);
295: }
296: result[i] = new ValueContainer(cv, fd.getJdbcType());
297: }
298: } catch (Exception e) {
299: throw new PersistenceBrokerException(
300: "Can't generate primary key values for given Identity "
301: + oid, e);
302: }
303: return result;
304: }
305:
306: /**
307: * returns an Array with an Objects PK VALUES, with any java-to-sql
308: * FieldConversion applied. If the Object is a Proxy or a VirtualProxy NO
309: * conversion is necessary.
310: *
311: * @param objectOrProxy
312: * @return Object[]
313: * @throws PersistenceBrokerException
314: */
315: public ValueContainer[] getKeyValues(ClassDescriptor cld,
316: Object objectOrProxy, Connection conn)
317: throws PersistenceBrokerException {
318: return getKeyValues(cld, objectOrProxy, true, conn);
319: }
320:
321: /**
322: * Return true if aValue is regarded as null<br>
323: * null, Number(0) or empty String
324: * @param aValue
325: * @return
326: */
327: private boolean isNull(Object aValue) {
328: return ((aValue == null)
329: || ((aValue instanceof Number) && (((Number) aValue)
330: .longValue() == 0)) || ((aValue instanceof String) && (((String) aValue)
331: .length() == 0)));
332: }
333:
334: /**
335: * Get an autoincremented value that has already
336: * had a field conversion run on it.
337: * <p>
338: * The data type of the value that is returned by this method is
339: * compatible with the java-world. The return value has <b>NOT</b>
340: * been run through a field conversion and converted to a corresponding
341: * sql-type.
342: *
343: * @throws MetadataException if there is an erros accessing obj field values
344: */
345: protected Object getAutoIncrementValue(FieldDescriptor fd,
346: Object obj, Object cv) {
347: if (isNull(cv)) {
348: PersistentField f = fd.getPersistentField();
349: try {
350: // lookup SeqMan for a value matching db column an
351: // fieldconversion
352: Object result = m_broker.serviceSequenceManager()
353: .getUniqueValue(fd);
354: // reflect autoincrement value back into object
355: f.set(obj, result);
356: return result;
357: } catch (MetadataException e) {
358: throw new PersistenceBrokerException(
359: "Error while trying to autoincrement field "
360: + f.getDeclaringClass() + "#"
361: + f.getName(), e);
362: } catch (SequenceManagerException e) {
363: throw new PersistenceBrokerException(
364: "Could not get key value", e);
365: }
366: } else {
367: return cv;
368: }
369: }
370:
371: /**
372: * Get the values of the fields for an obj
373: * @param fields
374: * @param obj
375: * @throws PersistenceBrokerException
376: */
377: public ValueContainer[] getValuesForObject(
378: FieldDescriptor[] fields, Object obj, boolean convertToSql,
379: Connection conn) throws PersistenceBrokerException {
380: ValueContainer[] result = new ValueContainer[fields.length];
381:
382: for (int i = 0; i < fields.length; i++) {
383: FieldDescriptor fd = fields[i];
384: Object cv = fd.getPersistentField().get(obj);
385:
386: // handle autoincrement attributes if not filled
387: if (fd.isAutoIncrement()) {
388: // getAutoIncrementValue returns a value that is
389: // properly typed for the java-world. This value
390: // needs to be converted to it's corresponding
391: // sql type so that the entire result array contains
392: // objects that are properly typed for sql.
393: cv = getAutoIncrementValue(fd, obj, cv);
394: }
395: if (convertToSql) {
396: // apply type and value conversion
397:
398: // DEBUT AJOUT POUR GeOxygene -------------------------------------------------------------
399: // Gestion des géométrie
400: if (fd.getFieldConversion() instanceof GeomGeOxygene2Dbms) {
401: // ORACLE
402: if (m_platform instanceof PlatformOracle9iImpl
403: || m_platform instanceof PlatformOracleImpl) {
404: try {
405: cv = geomGeOxygene2OracleMethod.invoke(fd
406: .getFieldConversion(),
407: new Object[] { cv, conn });
408: } catch (Exception e) {
409: e.printStackTrace();
410: }
411: } // POSTGIS
412: if (m_platform instanceof PlatformPostgreSQLImpl) {
413: try {
414: cv = geomGeOxygene2PostgisMethod.invoke(fd
415: .getFieldConversion(),
416: new Object[] { cv });
417: } catch (Exception e) {
418: e.printStackTrace();
419: }
420: }
421:
422: } else
423: // FIN AJOUT POUR GeOxygene----------------------------------------------------------------
424: // Types non géométriques
425:
426: cv = fd.getFieldConversion().javaToSql(cv);
427: }
428: // create ValueContainer
429: result[i] = new ValueContainer(cv, fd.getJdbcType());
430: }
431: return result;
432: }
433:
434: /**
435: * returns an Array with an Objects NON-PK VALUES (READ/WRITE only)
436: * @throws MetadataException if there is an erros accessing o field values
437: */
438: public ValueContainer[] getNonKeyRwValues(ClassDescriptor cld,
439: Object obj, Connection conn)
440: throws PersistenceBrokerException {
441: ClassDescriptor realCld = getRealClassDescriptor(cld, obj);
442: return getValuesForObject(realCld.getNonPkRwFields(), obj,
443: true, conn);
444: }
445:
446: /**
447: * returns an array containing values for all the Objects attribute (READ/WRITE only)
448: * @throws MetadataException if there is an erros accessing obj field values
449: */
450: public ValueContainer[] getAllRwValues(ClassDescriptor cld,
451: Object obj, Connection conn)
452: throws PersistenceBrokerException {
453: ClassDescriptor realCld = getRealClassDescriptor(cld, obj);
454: return getValuesForObject(realCld.getAllRwFields(), obj, true,
455: conn);
456: }
457:
458: /**
459: * Extract a value array of the given {@link ValueContainer} array.
460: * @param containers
461: * @return a value array
462: */
463: public Object[] extractValueArray(ValueContainer[] containers) {
464: Object[] result = new Object[containers.length];
465: for (int i = 0; i < containers.length; i++) {
466: result[i] = containers[i].getValue();
467: }
468: return result;
469: }
470:
471: /**
472: * returns true if the primary key fields are valid, else false.
473: * PK fields are valid if each of them is either an OJB managed
474: * attribute (autoincrement or locking) or if it contains
475: * a valid non-null value
476: * @param fieldDescriptors the array of PK fielddescriptors
477: * @param pkValues the array of PK values
478: * @return boolean
479: */
480: public boolean assertValidPkFields(
481: FieldDescriptor[] fieldDescriptors, Object[] pkValues) {
482: int fieldDescriptorSize = fieldDescriptors.length;
483: for (int i = 0; i < fieldDescriptorSize; i++) {
484: /**
485: * a pk field is valid if it is either managed by OJB
486: * (autoincrement or locking) or if it does contain a
487: * valid non-null value.
488: */
489: if (!(fieldDescriptors[i].isAutoIncrement()
490: || fieldDescriptors[i].isLocking() || assertValidPkValue(pkValues[i]))) {
491: return false;
492: }
493: }
494: return true;
495: }
496:
497: /**
498: * returns true if a value is non-null, STring instances are also checked,
499: * if they are non-empty.
500: * @param pkValue the value to check
501: * @return boolean
502: */
503: private boolean assertValidPkValue(Object pkValue) {
504: // null as value of a primary key is not acceptable
505: if (pkValue == null) {
506: return false;
507: }
508: if (pkValue instanceof String) {
509: // the toString() method on a String-object is maybe faster
510: // than the downcast to String. Also use length() to test
511: // if a String empty or not, this is faster than the comparing
512: // a String-object with an empty string using the equals()-method.
513: if (pkValue.toString().trim().length() == 0) {
514: return false;
515: }
516: }
517: return true;
518: }
519:
520: /**
521: * Build a Count-Query based on aQuery
522: * @param aQuery
523: * @return a Query
524: */
525: public Query getCountQuery(Query aQuery) {
526: if (aQuery instanceof QueryBySQL) {
527: return getCountQuery((QueryBySQL) aQuery);
528: } else {
529: return getCountQuery((QueryByCriteria) aQuery);
530: }
531: }
532:
533: /**
534: * Create a Count-Query for QueryBySQL
535: * @param aQuery
536: * @return
537: */
538: private Query getCountQuery(QueryBySQL aQuery) {
539: String countSql = aQuery.getSql();
540:
541: int fromPos = countSql.toUpperCase().indexOf(" FROM ");
542: if (fromPos >= 0) {
543: countSql = "select count(*)" + countSql.substring(fromPos);
544: }
545:
546: int orderPos = countSql.toUpperCase().indexOf(" ORDER BY ");
547: if (orderPos >= 0) {
548: countSql = countSql.substring(0, orderPos);
549: }
550:
551: return new QueryBySQL(aQuery.getSearchClass(), countSql);
552: }
553:
554: /**
555: * Create a Count-Query for QueryByCriteria
556: * @param aQuery
557: * @return
558: */
559: private Query getCountQuery(QueryByCriteria aQuery) {
560: Class searchClass = aQuery.getSearchClass();
561: ReportQueryByCriteria countQuery;
562: Criteria countCrit = null;
563: FieldDescriptor[] pkFields = m_broker.getClassDescriptor(
564: searchClass).getPkFields();
565: String[] columns = new String[pkFields.length];
566:
567: // build a ReportQuery based on query orderby needs to be cleared
568: if (aQuery.getCriteria() != null) {
569: countCrit = aQuery.getCriteria().copy(false, false, false);
570: }
571:
572: // BRJ: add a column for each pkField, make it distinct if query is distinct
573: // TBD check if it really works for multiple keys ?
574: for (int i = 0; i < pkFields.length; i++) {
575: if (aQuery.isDistinct()) {
576: columns[i] = "count(distinct "
577: + pkFields[i].getAttributeName() + ")";
578: } else {
579: columns[i] = "count(" + pkFields[i].getAttributeName()
580: + ")";
581: }
582: }
583:
584: // BRJ: we have to preserve indirection table !
585: if (aQuery instanceof MtoNQuery) {
586: MtoNQuery mnQuery = (MtoNQuery) aQuery;
587: ReportQueryByMtoNCriteria mnReportQuery = new ReportQueryByMtoNCriteria(
588: searchClass, columns, countCrit);
589: mnReportQuery.setIndirectionTable(mnQuery
590: .getIndirectionTable());
591:
592: countQuery = mnReportQuery;
593: } else {
594: countQuery = new ReportQueryByCriteria(searchClass,
595: columns, countCrit);
596: }
597:
598: return countQuery;
599: }
600:
601: public static String buildMessageString(Object obj, Object value,
602: Field field) {
603: String eol = SystemUtils.LINE_SEPARATOR;
604: StringBuffer buf = new StringBuffer();
605: buf.append(
606: eol
607: + "object class[ "
608: + (obj != null ? obj.getClass().getName()
609: : null)).append(
610: eol + "target field: " + field.getName()).append(
611: eol + "target field type: "
612: + (field != null ? field.getType() : null))
613: .append(
614: eol
615: + "object value class: "
616: + (value != null ? value.getClass()
617: .getName() : null)).append(
618: eol + "object value: "
619: + (value != null ? value : null))
620: .append("]");
621: return buf.toString();
622: }
623: }
|