001: package org.apache.ojb.broker.metadata;
002:
003: /* Copyright 2002-2005 The Apache Software Foundation
004: *
005: * Licensed under the Apache License, Version 2.0 (the "License");
006: * you may not use this file except in compliance with the License.
007: * You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: import java.util.Hashtable;
019: import java.util.Iterator;
020: import java.util.Vector;
021:
022: import org.apache.commons.lang.builder.ToStringBuilder;
023: import org.apache.ojb.broker.OJBRuntimeException;
024: import org.apache.ojb.broker.PersistenceBrokerException;
025: import org.apache.ojb.broker.core.proxy.ProxyHelper;
026: import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
027:
028: /**
029: * Describes a Field containing a reference to another class. Provides handling for foreign keys etc.
030: * <br>
031: * Note: Be careful when use references of this class or caching instances of this class,
032: * because instances could become invalid (see {@link MetadataManager}).
033: *
034: * @author <a href="mailto:thma@apache.org">Thomas Mahler<a>
035: *
036: */
037: public class ObjectReferenceDescriptor extends AttributeDescriptorBase
038: implements XmlCapable {
039: private static final long serialVersionUID = 5561562217150972131L;
040:
041: public static final int CASCADE_NONE = 17;
042: public static final int CASCADE_LINK = 19;
043: public static final int CASCADE_OBJECT = 23;
044:
045: private Class m_ClassOfItems = null;
046: private Vector m_ForeignKeyFields = new Vector();
047: private boolean m_CascadeRetrieve = true;
048: private int m_CascadeStore = CASCADE_NONE;
049: private int m_CascadeDelete = CASCADE_NONE;
050: private int m_ProxyPrefetchingLimit = 50;
051:
052: private Class m_ProxyOfItems = null;
053: private boolean m_LookedUpProxy = false;
054: private boolean m_OtmDependent = false;
055:
056: /**
057: * holds the foreign-key field descriptor array for a specified class
058: */
059: private Hashtable fkFieldMap = new Hashtable();
060: /**
061: * define loading strategy of the resulting object
062: */
063: private boolean lazy = false;
064: /**
065: * if true relationship is refreshed when owner is found in cache
066: */
067: private boolean refresh = false;
068:
069: /**
070: *
071: */
072: public ObjectReferenceDescriptor(ClassDescriptor descriptor) {
073: super (descriptor);
074: }
075:
076: /**
077: *
078: */
079: public Class getItemProxyClass() throws PersistenceBrokerException {
080: if (!m_LookedUpProxy) {
081: m_ProxyOfItems = getClassDescriptor().getRepository()
082: .getDescriptorFor(m_ClassOfItems).getProxyClass();
083: m_LookedUpProxy = true;
084: }
085: return m_ProxyOfItems;
086: }
087:
088: /**
089: *
090: */
091: public FieldDescriptor[] getForeignKeyFieldDescriptors(
092: ClassDescriptor cld) {
093: FieldDescriptor[] foreignKeyFieldDescriptors;
094: if ((foreignKeyFieldDescriptors = (FieldDescriptor[]) fkFieldMap
095: .get(cld)) == null) {
096: // 1. collect vector of indices of Fk-Fields
097: Vector v = getForeignKeyFields();
098: // 2. get FieldDescriptor for each index from Class-descriptor
099: // 2A. In a many-to-many relationship foreignkeyfields vector will be null.
100: if (v != null) {
101: Vector ret;
102: if (cld.isInterface()) {
103: //exchange interface class descriptor with first concrete
104: //class
105: Vector extents = cld.getExtentClasses();
106: Class firstConcreteClass = (Class) extents.get(0);
107: cld = getClassDescriptor().getRepository()
108: .getDescriptorFor(firstConcreteClass);
109: }
110: ret = new Vector();
111:
112: Iterator iter = v.iterator();
113: while (iter.hasNext()) {
114: Object fk = iter.next();
115: FieldDescriptor fkfd = null;
116: /*
117: OJB-55
118: it's possible that the FK field is declared in the super classes of this object,
119: so we can search for a valid field in super class-descriptor
120: */
121: ClassDescriptor tmp = cld;
122: while (tmp != null) {
123: if (fk instanceof Integer) {
124: Integer index = (Integer) fk;
125: fkfd = cld.getFieldDescriptorByIndex(index
126: .intValue());
127: } else {
128: fkfd = tmp
129: .getFieldDescriptorByName((String) fk);
130: }
131: if (fkfd != null) {
132: break;
133: } else {
134: tmp = tmp.getSuperClassDescriptor();
135: }
136: }
137:
138: if (fkfd == null) {
139: throw new OJBRuntimeException(
140: "Incorrect or not found field reference name '"
141: + fk
142: + "' in descriptor "
143: + this
144: + " for class-descriptor '"
145: + (cld != null ? cld
146: .getClassNameOfObject()
147: + "'" : "'null'"));
148: }
149: ret.add(fkfd);
150: }
151: foreignKeyFieldDescriptors = (FieldDescriptor[]) ret
152: .toArray(new FieldDescriptor[ret.size()]);
153: fkFieldMap.put(cld, foreignKeyFieldDescriptors);
154: }
155: }
156: return foreignKeyFieldDescriptors;
157: }
158:
159: /**
160: * Returns an Object array of all FK field values of the specified object.
161: * If the specified object is an unmaterialized Proxy, it will be materialized
162: * to read the FK values.
163: *
164: * @throws MetadataException if an error occours while accessing ForeingKey values on obj
165: */
166: public Object[] getForeignKeyValues(Object obj, ClassDescriptor mif)
167: throws PersistenceBrokerException {
168: FieldDescriptor[] fks = getForeignKeyFieldDescriptors(mif);
169: // materialize object only if FK fields are declared
170: if (fks.length > 0)
171: obj = ProxyHelper.getRealObject(obj);
172: Object[] result = new Object[fks.length];
173: for (int i = 0; i < result.length; i++) {
174: FieldDescriptor fmd = fks[i];
175: PersistentField f = fmd.getPersistentField();
176:
177: // BRJ: do NOT convert.
178: // conversion is done when binding the sql-statement
179: //
180: // FieldConversion fc = fmd.getFieldConversion();
181: // Object val = fc.javaToSql(f.get(obj));
182:
183: result[i] = f.get(obj);
184: }
185: return result;
186: }
187:
188: /**
189: *
190: */
191: public Class getItemClass() {
192: return m_ClassOfItems;
193: }
194:
195: /**
196: * @return the fully qualified name of the item class for this descriptor.
197: */
198: public String getItemClassName() {
199: return this .m_ClassOfItems != null ? this .m_ClassOfItems
200: .getName() : null;
201: }
202:
203: /**
204: * sets the item class
205: * @param c the items class object
206: */
207: public void setItemClass(Class c) {
208: m_ClassOfItems = c;
209: }
210:
211: /**
212: *
213: */
214: public Vector getForeignKeyFields() {
215: return m_ForeignKeyFields;
216: }
217:
218: /**
219: *
220: */
221: public void setForeignKeyFields(Vector vec) {
222: m_ForeignKeyFields = vec;
223: }
224:
225: /**
226: * add a foreign key field ID
227: */
228: public void addForeignKeyField(int newId) {
229: if (m_ForeignKeyFields == null) {
230: m_ForeignKeyFields = new Vector();
231: }
232: m_ForeignKeyFields.add(new Integer(newId));
233: }
234:
235: /**
236: * add a foreign key field
237: */
238: public void addForeignKeyField(String newField) {
239: if (m_ForeignKeyFields == null) {
240: m_ForeignKeyFields = new Vector();
241: }
242: m_ForeignKeyFields.add(newField);
243: }
244:
245: /**
246: * Gets the refresh.
247: * @return Returns a boolean
248: */
249: public boolean isRefresh() {
250: return refresh;
251: }
252:
253: /**
254: * Sets the refresh.
255: * @param refresh The refresh to set
256: */
257: public void setRefresh(boolean refresh) {
258: this .refresh = refresh;
259: }
260:
261: /**
262: * Gets the lazy.
263: * @return Returns a boolean
264: */
265: public boolean isLazy() {
266: return lazy;
267: }
268:
269: /**
270: * Sets the lazy.
271: * @param lazy The lazy to set
272: */
273: public void setLazy(boolean lazy) {
274: this .lazy = lazy;
275: }
276:
277: /**
278: *
279: */
280: public boolean getCascadeRetrieve() {
281: return m_CascadeRetrieve;
282: }
283:
284: /**
285: *
286: */
287: public void setCascadeRetrieve(boolean b) {
288: m_CascadeRetrieve = b;
289: }
290:
291: /**
292: *
293: */
294: public int getCascadingStore() {
295: return m_CascadeStore;
296: }
297:
298: /**
299: *
300: */
301: public void setCascadingStore(int cascade) {
302: m_CascadeStore = cascade;
303: }
304:
305: public void setCascadingStore(String value) {
306: setCascadingStore(getCascadeStoreValue(value));
307: }
308:
309: /**
310: * @deprecated use {@link #getCascadingStore} instead.
311: */
312: public boolean getCascadeStore() {
313: return getCascadingStore() == CASCADE_OBJECT;
314: }
315:
316: /**
317: * @deprecated use {@link #setCascadingStore(int)} instead.
318: */
319: public void setCascadeStore(boolean cascade) {
320: if (cascade) {
321: setCascadingStore(getCascadeStoreValue("true"));
322: } else {
323: setCascadingStore(getCascadeStoreValue("false"));
324: }
325: }
326:
327: /**
328: *
329: */
330: public int getCascadingDelete() {
331: return m_CascadeDelete;
332: }
333:
334: /**
335: *
336: */
337: public void setCascadingDelete(int cascade) {
338: m_CascadeDelete = cascade;
339: }
340:
341: public void setCascadingDelete(String value) {
342: setCascadingDelete(getCascadeDeleteValue(value));
343: }
344:
345: /**
346: * @deprecated use {@link #getCascadingDelete} instead.
347: */
348: public boolean getCascadeDelete() {
349: return getCascadingDelete() == CASCADE_OBJECT;
350: }
351:
352: /**
353: * @deprecated use {@link #setCascadingDelete(int)}
354: */
355: public void setCascadeDelete(boolean cascade) {
356: if (cascade) {
357: setCascadingDelete(getCascadeDeleteValue("true"));
358: } else {
359: setCascadingDelete(getCascadeDeleteValue("false"));
360: }
361: }
362:
363: protected int getCascadeStoreValue(String cascade) {
364: if (cascade.equalsIgnoreCase(RepositoryTags.CASCADE_NONE_STR)) {
365: return CASCADE_NONE;
366: } else if (cascade
367: .equalsIgnoreCase(RepositoryTags.CASCADE_LINK_STR)) {
368: return CASCADE_LINK;
369: } else if (cascade
370: .equalsIgnoreCase(RepositoryTags.CASCADE_OBJECT_STR)) {
371: return CASCADE_OBJECT;
372: } else if (cascade.equalsIgnoreCase("true")) {
373: return CASCADE_OBJECT;
374: } else if (cascade.equalsIgnoreCase("false")) {
375: /*
376: in old implementation the FK values of an 1:1 relation are always
377: set. Thus we choose 'link' instead of 'none'
378: The CollectionDescriptor should override this behaviour.
379: */
380: return CASCADE_LINK;
381: } else {
382: throw new OJBRuntimeException(
383: "Invalid value! Given value was '"
384: + cascade
385: + "', expected values are: "
386: + RepositoryTags.CASCADE_NONE_STR
387: + ", "
388: + RepositoryTags.CASCADE_LINK_STR
389: + ", "
390: + RepositoryTags.CASCADE_OBJECT_STR
391: + " ('false' and 'true' are deprecated but still valid)");
392: }
393: }
394:
395: protected int getCascadeDeleteValue(String cascade) {
396: if (cascade.equalsIgnoreCase(RepositoryTags.CASCADE_NONE_STR)) {
397: return CASCADE_NONE;
398: } else if (cascade
399: .equalsIgnoreCase(RepositoryTags.CASCADE_LINK_STR)) {
400: return CASCADE_LINK;
401: } else if (cascade
402: .equalsIgnoreCase(RepositoryTags.CASCADE_OBJECT_STR)) {
403: return CASCADE_OBJECT;
404: } else if (cascade.equalsIgnoreCase("true")) {
405: return CASCADE_OBJECT;
406: } else if (cascade.equalsIgnoreCase("false")) {
407: return CASCADE_NONE;
408: } else {
409: throw new OJBRuntimeException(
410: "Invalid value! Given value was '"
411: + cascade
412: + "', expected values are: "
413: + RepositoryTags.CASCADE_NONE_STR
414: + ", "
415: + RepositoryTags.CASCADE_LINK_STR
416: + ", "
417: + RepositoryTags.CASCADE_OBJECT_STR
418: + " ('false' and 'true' are deprecated but still valid)");
419: }
420: }
421:
422: public String getCascadeAsString(int cascade) {
423: String result = null;
424: switch (cascade) {
425: case CASCADE_NONE:
426: result = RepositoryTags.CASCADE_NONE_STR;
427: break;
428: case CASCADE_LINK:
429: result = RepositoryTags.CASCADE_LINK_STR;
430: break;
431: case CASCADE_OBJECT:
432: result = RepositoryTags.CASCADE_OBJECT_STR;
433: break;
434: }
435: return result;
436: }
437:
438: public int getProxyPrefetchingLimit() {
439: return m_ProxyPrefetchingLimit;
440: }
441:
442: public void setProxyPrefetchingLimit(int proxyPrefetchingLimit) {
443: m_ProxyPrefetchingLimit = proxyPrefetchingLimit;
444: }
445:
446: /**
447: *
448: */
449: public boolean getOtmDependent() {
450: return m_OtmDependent;
451: }
452:
453: /**
454: *
455: */
456: public void setOtmDependent(boolean b) {
457: m_OtmDependent = b;
458: }
459:
460: /**
461: * Returns <code>true</code> if this descriptor was used to
462: * describe a reference to a super class of an object.
463: *
464: * @return always <code>false</code> for this instance.
465: */
466: public boolean isSuperReferenceDescriptor() {
467: return false;
468: }
469:
470: /**
471: * Returns <em>true</em> if a foreign key constraint to the referenced object is
472: * declared, else <em>false</em> is returned.
473: */
474: public boolean hasConstraint() {
475: /*
476: arminw: Currently we don't have a ForeignKey descriptor object and
477: a official xml-element to support FK settings. As a workaround I introduce
478: a custom-attribute to handle FK settings in collection-/reference-decriptor
479: */
480: String result = getAttribute("constraint");
481: return result != null && result.equalsIgnoreCase("true");
482: }
483:
484: /**
485: * Set a foreign key constraint flag for this reference - see {@link #hasConstraint()}
486: * @param constraint If set <em>true</em>, signals a foreign key constraint in database.
487: */
488: public void setConstraint(boolean constraint) {
489: addAttribute("constraint", constraint ? "true" : "false");
490: }
491:
492: public String toString() {
493: return new ToStringBuilder(this ).append("cascade_retrieve",
494: getCascadeRetrieve()).append("cascade_store",
495: getCascadeAsString(m_CascadeStore)).append(
496: "cascade_delete", getCascadeAsString(m_CascadeDelete))
497: .append("is_lazy", lazy).append("class_of_Items",
498: m_ClassOfItems).toString();
499: }
500:
501: /*
502: * @see XmlCapable#toXML()
503: */
504: public String toXML() {
505: RepositoryTags tags = RepositoryTags.getInstance();
506: String eol = System.getProperty("line.separator");
507:
508: // opening tag
509: StringBuffer result = new StringBuffer(1024);
510: result.append(" ");
511: result.append(tags
512: .getOpeningTagNonClosingById(REFERENCE_DESCRIPTOR));
513: result.append(eol);
514:
515: // attributes
516: // name
517: String name = this .getAttributeName();
518: if (name == null) {
519: name = RepositoryElements.TAG_SUPER;
520: }
521: result.append(" ");
522: result.append(tags.getAttribute(FIELD_NAME, name));
523: result.append(eol);
524:
525: // class-ref
526: result.append(" ");
527: result.append(tags.getAttribute(REFERENCED_CLASS, this
528: .getItemClassName()));
529: result.append(eol);
530:
531: // proxyReference is optional
532: if (isLazy()) {
533: result.append(" ");
534: result.append(tags.getAttribute(PROXY_REFERENCE, "true"));
535: result.append(eol);
536: result.append(" ");
537: result.append(tags.getAttribute(PROXY_PREFETCHING_LIMIT, ""
538: + this .getProxyPrefetchingLimit()));
539: result.append(eol);
540: }
541:
542: //reference refresh is optional, disabled by default
543: if (isRefresh()) {
544: result.append(" ");
545: result.append(tags.getAttribute(REFRESH, "true"));
546: result.append(eol);
547: }
548:
549: //auto retrieve
550: result.append(" ");
551: result.append(tags.getAttribute(AUTO_RETRIEVE, ""
552: + getCascadeRetrieve()));
553: result.append(eol);
554:
555: //auto update
556: result.append(" ");
557: result.append(tags.getAttribute(AUTO_UPDATE,
558: getCascadeAsString(getCascadingStore())));
559: result.append(eol);
560:
561: //auto delete
562: result.append(" ");
563: result.append(tags.getAttribute(AUTO_DELETE,
564: getCascadeAsString(getCascadingDelete())));
565: result.append(eol);
566:
567: //otm-dependent is optional, disabled by default
568: if (getOtmDependent()) {
569: result.append(" ");
570: result.append(tags.getAttribute(OTM_DEPENDENT, "true"));
571: result.append(eol);
572: }
573:
574: // close opening tag
575: result.append(" >");
576: result.append(eol);
577:
578: // elements
579: // write foreignkey elements
580: for (int i = 0; i < getForeignKeyFields().size(); i++) {
581: Object obj = getForeignKeyFields().get(i);
582: if (obj instanceof Integer) {
583: String fkId = obj.toString();
584: result.append(" ");
585: result.append(tags
586: .getOpeningTagNonClosingById(FOREIGN_KEY));
587: result.append(" ");
588: result.append(tags.getAttribute(FIELD_ID_REF, fkId));
589: result.append("/>");
590: result.append(eol);
591: } else {
592: String fk = (String) obj;
593: result.append(" ");
594: result.append(tags
595: .getOpeningTagNonClosingById(FOREIGN_KEY));
596: result.append(" ");
597: result.append(tags.getAttribute(FIELD_REF, fk));
598: result.append("/>");
599: result.append(eol);
600: }
601: }
602:
603: // closing tag
604: result.append(" ");
605: result.append(tags.getClosingTagById(REFERENCE_DESCRIPTOR));
606: result.append(eol);
607: return result.toString();
608: }
609: }
|