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 implements XmlCapable
038 {
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 {
074 super(descriptor);
075 }
076
077 /**
078 *
079 */
080 public Class getItemProxyClass() throws PersistenceBrokerException
081 {
082 if (!m_LookedUpProxy)
083 {
084 m_ProxyOfItems = getClassDescriptor().getRepository().
085 getDescriptorFor(m_ClassOfItems).getProxyClass();
086 m_LookedUpProxy = true;
087 }
088 return m_ProxyOfItems;
089 }
090
091 /**
092 *
093 */
094 public FieldDescriptor[] getForeignKeyFieldDescriptors(ClassDescriptor cld)
095 {
096 FieldDescriptor[] foreignKeyFieldDescriptors;
097 if ((foreignKeyFieldDescriptors = (FieldDescriptor[]) fkFieldMap.get(cld)) == null)
098 {
099 // 1. collect vector of indices of Fk-Fields
100 Vector v = getForeignKeyFields();
101 // 2. get FieldDescriptor for each index from Class-descriptor
102 // 2A. In a many-to-many relationship foreignkeyfields vector will be null.
103 if (v != null)
104 {
105 Vector ret;
106 if (cld.isInterface())
107 {
108 //exchange interface class descriptor with first concrete
109 //class
110 Vector extents = cld.getExtentClasses();
111 Class firstConcreteClass = (Class) extents.get(0);
112 cld = getClassDescriptor().getRepository().getDescriptorFor(firstConcreteClass);
113 }
114 ret = new Vector();
115
116 Iterator iter = v.iterator();
117 while (iter.hasNext())
118 {
119 Object fk = iter.next();
120 FieldDescriptor fkfd = null;
121 /*
122 OJB-55
123 it's possible that the FK field is declared in the super classes of this object,
124 so we can search for a valid field in super class-descriptor
125 */
126 ClassDescriptor tmp = cld;
127 while(tmp != null)
128 {
129 if (fk instanceof Integer)
130 {
131 Integer index = (Integer) fk;
132 fkfd = cld.getFieldDescriptorByIndex(index.intValue());
133 }
134 else
135 {
136 fkfd = tmp.getFieldDescriptorByName((String) fk);
137 }
138 if(fkfd != null)
139 {
140 break;
141 }
142 else
143 {
144 tmp = tmp.getSuperClassDescriptor();
145 }
146 }
147
148 if (fkfd == null)
149 {
150 throw new OJBRuntimeException("Incorrect or not found field reference name '"
151 + fk + "' in descriptor " + this + " for class-descriptor '"
152 + (cld != null ? cld.getClassNameOfObject() + "'" : "'null'"));
153 }
154 ret.add(fkfd);
155 }
156 foreignKeyFieldDescriptors = (FieldDescriptor[]) ret.toArray(new FieldDescriptor[ret.size()]);
157 fkFieldMap.put(cld, foreignKeyFieldDescriptors);
158 }
159 }
160 return foreignKeyFieldDescriptors;
161 }
162
163 /**
164 * Returns an Object array of all FK field values of the specified object.
165 * If the specified object is an unmaterialized Proxy, it will be materialized
166 * to read the FK values.
167 *
168 * @throws MetadataException if an error occours while accessing ForeingKey values on obj
169 */
170 public Object[] getForeignKeyValues(Object obj, ClassDescriptor mif)
171 throws PersistenceBrokerException
172 {
173 FieldDescriptor[] fks = getForeignKeyFieldDescriptors(mif);
174 // materialize object only if FK fields are declared
175 if(fks.length > 0) obj = ProxyHelper.getRealObject(obj);
176 Object[] result = new Object[fks.length];
177 for (int i = 0; i < result.length; i++)
178 {
179 FieldDescriptor fmd = fks[i];
180 PersistentField f = fmd.getPersistentField();
181
182 // BRJ: do NOT convert.
183 // conversion is done when binding the sql-statement
184 //
185 // FieldConversion fc = fmd.getFieldConversion();
186 // Object val = fc.javaToSql(f.get(obj));
187
188 result[i] = f.get(obj);
189 }
190 return result;
191 }
192
193 /**
194 *
195 */
196 public Class getItemClass()
197 {
198 return m_ClassOfItems;
199 }
200
201 /**
202 * @return the fully qualified name of the item class for this descriptor.
203 */
204 public String getItemClassName()
205 {
206 return this.m_ClassOfItems != null ? this.m_ClassOfItems.getName() : null;
207 }
208
209 /**
210 * sets the item class
211 * @param c the items class object
212 */
213 public void setItemClass(Class c)
214 {
215 m_ClassOfItems = c;
216 }
217
218 /**
219 *
220 */
221 public Vector getForeignKeyFields()
222 {
223 return m_ForeignKeyFields;
224 }
225
226 /**
227 *
228 */
229 public void setForeignKeyFields(Vector vec)
230 {
231 m_ForeignKeyFields = vec;
232 }
233
234 /**
235 * add a foreign key field ID
236 */
237 public void addForeignKeyField(int newId)
238 {
239 if (m_ForeignKeyFields == null)
240 {
241 m_ForeignKeyFields = new Vector();
242 }
243 m_ForeignKeyFields.add(new Integer(newId));
244 }
245
246 /**
247 * add a foreign key field
248 */
249 public void addForeignKeyField(String newField)
250 {
251 if (m_ForeignKeyFields == null)
252 {
253 m_ForeignKeyFields = new Vector();
254 }
255 m_ForeignKeyFields.add(newField);
256 }
257
258 /**
259 * Gets the refresh.
260 * @return Returns a boolean
261 */
262 public boolean isRefresh()
263 {
264 return refresh;
265 }
266
267 /**
268 * Sets the refresh.
269 * @param refresh The refresh to set
270 */
271 public void setRefresh(boolean refresh)
272 {
273 this.refresh = refresh;
274 }
275
276 /**
277 * Gets the lazy.
278 * @return Returns a boolean
279 */
280 public boolean isLazy()
281 {
282 return lazy;
283 }
284
285 /**
286 * Sets the lazy.
287 * @param lazy The lazy to set
288 */
289 public void setLazy(boolean lazy)
290 {
291 this.lazy = lazy;
292 }
293
294 /**
295 *
296 */
297 public boolean getCascadeRetrieve()
298 {
299 return m_CascadeRetrieve;
300 }
301
302 /**
303 *
304 */
305 public void setCascadeRetrieve(boolean b)
306 {
307 m_CascadeRetrieve = b;
308 }
309
310 /**
311 *
312 */
313 public int getCascadingStore()
314 {
315 return m_CascadeStore;
316 }
317
318 /**
319 *
320 */
321 public void setCascadingStore(int cascade)
322 {
323 m_CascadeStore = cascade;
324 }
325
326 public void setCascadingStore(String value)
327 {
328 setCascadingStore(getCascadeStoreValue(value));
329 }
330
331 /**
332 * @deprecated use {@link #getCascadingStore} instead.
333 */
334 public boolean getCascadeStore()
335 {
336 return getCascadingStore() == CASCADE_OBJECT;
337 }
338
339 /**
340 * @deprecated use {@link #setCascadingStore(int)} instead.
341 */
342 public void setCascadeStore(boolean cascade)
343 {
344 if(cascade)
345 {
346 setCascadingStore(getCascadeStoreValue("true"));
347 }
348 else
349 {
350 setCascadingStore(getCascadeStoreValue("false"));
351 }
352 }
353
354 /**
355 *
356 */
357 public int getCascadingDelete()
358 {
359 return m_CascadeDelete;
360 }
361
362 /**
363 *
364 */
365 public void setCascadingDelete(int cascade)
366 {
367 m_CascadeDelete = cascade;
368 }
369
370 public void setCascadingDelete(String value)
371 {
372 setCascadingDelete(getCascadeDeleteValue(value));
373 }
374
375 /**
376 * @deprecated use {@link #getCascadingDelete} instead.
377 */
378 public boolean getCascadeDelete()
379 {
380 return getCascadingDelete() == CASCADE_OBJECT;
381 }
382
383 /**
384 * @deprecated use {@link #setCascadingDelete(int)}
385 */
386 public void setCascadeDelete(boolean cascade)
387 {
388 if(cascade)
389 {
390 setCascadingDelete(getCascadeDeleteValue("true"));
391 }
392 else
393 {
394 setCascadingDelete(getCascadeDeleteValue("false"));
395 }
396 }
397
398 protected int getCascadeStoreValue(String cascade)
399 {
400 if(cascade.equalsIgnoreCase(RepositoryTags.CASCADE_NONE_STR))
401 {
402 return CASCADE_NONE;
403 }
404 else if(cascade.equalsIgnoreCase(RepositoryTags.CASCADE_LINK_STR))
405 {
406 return CASCADE_LINK;
407 }
408 else if(cascade.equalsIgnoreCase(RepositoryTags.CASCADE_OBJECT_STR))
409 {
410 return CASCADE_OBJECT;
411 }
412 else if(cascade.equalsIgnoreCase("true"))
413 {
414 return CASCADE_OBJECT;
415 }
416 else if(cascade.equalsIgnoreCase("false"))
417 {
418 /*
419 in old implementation the FK values of an 1:1 relation are always
420 set. Thus we choose 'link' instead of 'none'
421 The CollectionDescriptor should override this behaviour.
422 */
423 return CASCADE_LINK;
424 }
425 else
426 {
427 throw new OJBRuntimeException("Invalid value! Given value was '" + cascade
428 + "', expected values are: " + RepositoryTags.CASCADE_NONE_STR + ", "
429 + RepositoryTags.CASCADE_LINK_STR + ", " + RepositoryTags.CASCADE_OBJECT_STR
430 + " ('false' and 'true' are deprecated but still valid)");
431 }
432 }
433
434 protected int getCascadeDeleteValue(String cascade)
435 {
436 if(cascade.equalsIgnoreCase(RepositoryTags.CASCADE_NONE_STR))
437 {
438 return CASCADE_NONE;
439 }
440 else if(cascade.equalsIgnoreCase(RepositoryTags.CASCADE_LINK_STR))
441 {
442 return CASCADE_LINK;
443 }
444 else if(cascade.equalsIgnoreCase(RepositoryTags.CASCADE_OBJECT_STR))
445 {
446 return CASCADE_OBJECT;
447 }
448 else if(cascade.equalsIgnoreCase("true"))
449 {
450 return CASCADE_OBJECT;
451 }
452 else if(cascade.equalsIgnoreCase("false"))
453 {
454 return CASCADE_NONE;
455 }
456 else
457 {
458 throw new OJBRuntimeException("Invalid value! Given value was '" + cascade
459 + "', expected values are: " + RepositoryTags.CASCADE_NONE_STR + ", "
460 + RepositoryTags.CASCADE_LINK_STR + ", " + RepositoryTags.CASCADE_OBJECT_STR
461 + " ('false' and 'true' are deprecated but still valid)");
462 }
463 }
464
465 public String getCascadeAsString(int cascade)
466 {
467 String result = null;
468 switch(cascade)
469 {
470 case CASCADE_NONE:
471 result = RepositoryTags.CASCADE_NONE_STR;
472 break;
473 case CASCADE_LINK:
474 result = RepositoryTags.CASCADE_LINK_STR;
475 break;
476 case CASCADE_OBJECT:
477 result = RepositoryTags.CASCADE_OBJECT_STR;
478 break;
479 }
480 return result;
481 }
482
483 public int getProxyPrefetchingLimit()
484 {
485 return m_ProxyPrefetchingLimit;
486 }
487
488 public void setProxyPrefetchingLimit(int proxyPrefetchingLimit)
489 {
490 m_ProxyPrefetchingLimit = proxyPrefetchingLimit;
491 }
492
493 /**
494 *
495 */
496 public boolean getOtmDependent()
497 {
498 return m_OtmDependent;
499 }
500
501 /**
502 *
503 */
504 public void setOtmDependent(boolean b)
505 {
506 m_OtmDependent = b;
507 }
508
509 /**
510 * Returns <code>true</code> if this descriptor was used to
511 * describe a reference to a super class of an object.
512 *
513 * @return always <code>false</code> for this instance.
514 */
515 public boolean isSuperReferenceDescriptor()
516 {
517 return false;
518 }
519
520 /**
521 * Returns <em>true</em> if a foreign key constraint to the referenced object is
522 * declared, else <em>false</em> is returned.
523 */
524 public boolean hasConstraint()
525 {
526 /*
527 arminw: Currently we don't have a ForeignKey descriptor object and
528 a official xml-element to support FK settings. As a workaround I introduce
529 a custom-attribute to handle FK settings in collection-/reference-decriptor
530 */
531 String result = getAttribute("constraint");
532 return result != null && result.equalsIgnoreCase("true");
533 }
534
535 /**
536 * Set a foreign key constraint flag for this reference - see {@link #hasConstraint()}
537 * @param constraint If set <em>true</em>, signals a foreign key constraint in database.
538 */
539 public void setConstraint(boolean constraint)
540 {
541 addAttribute("constraint", constraint ? "true" : "false");
542 }
543
544 public String toString()
545 {
546 return new ToStringBuilder(this)
547 .append("cascade_retrieve", getCascadeRetrieve())
548 .append("cascade_store", getCascadeAsString(m_CascadeStore))
549 .append("cascade_delete", getCascadeAsString(m_CascadeDelete))
550 .append("is_lazy", lazy)
551 .append("class_of_Items", m_ClassOfItems)
552 .toString();
553 }
554
555 /*
556 * @see XmlCapable#toXML()
557 */
558 public String toXML()
559 {
560 RepositoryTags tags = RepositoryTags.getInstance();
561 String eol = System.getProperty( "line.separator" );
562
563 // opening tag
564 StringBuffer result = new StringBuffer( 1024 );
565 result.append( " " );
566 result.append( tags.getOpeningTagNonClosingById( REFERENCE_DESCRIPTOR ) );
567 result.append( eol );
568
569 // attributes
570 // name
571 String name = this.getAttributeName();
572 if( name == null )
573 {
574 name = RepositoryElements.TAG_SUPER;
575 }
576 result.append( " " );
577 result.append( tags.getAttribute( FIELD_NAME, name ) );
578 result.append( eol );
579
580 // class-ref
581 result.append( " " );
582 result.append( tags.getAttribute( REFERENCED_CLASS, this.getItemClassName() ) );
583 result.append( eol );
584
585 // proxyReference is optional
586 if( isLazy() )
587 {
588 result.append( " " );
589 result.append( tags.getAttribute( PROXY_REFERENCE, "true" ) );
590 result.append( eol );
591 result.append( " " );
592 result.append( tags.getAttribute( PROXY_PREFETCHING_LIMIT, "" + this.getProxyPrefetchingLimit() ) );
593 result.append( eol );
594 }
595
596 //reference refresh is optional, disabled by default
597 if( isRefresh() )
598 {
599 result.append( " " );
600 result.append( tags.getAttribute( REFRESH, "true" ) );
601 result.append( eol );
602 }
603
604 //auto retrieve
605 result.append( " " );
606 result.append( tags.getAttribute( AUTO_RETRIEVE, "" + getCascadeRetrieve() ) );
607 result.append( eol );
608
609 //auto update
610 result.append( " " );
611 result.append( tags.getAttribute( AUTO_UPDATE, getCascadeAsString( getCascadingStore() ) ) );
612 result.append( eol );
613
614 //auto delete
615 result.append( " " );
616 result.append( tags.getAttribute( AUTO_DELETE, getCascadeAsString( getCascadingDelete() ) ) );
617 result.append( eol );
618
619 //otm-dependent is optional, disabled by default
620 if( getOtmDependent() )
621 {
622 result.append( " " );
623 result.append( tags.getAttribute( OTM_DEPENDENT, "true" ) );
624 result.append( eol );
625 }
626
627 // close opening tag
628 result.append( " >" );
629 result.append( eol );
630
631 // elements
632 // write foreignkey elements
633 for( int i = 0; i < getForeignKeyFields().size(); i++ )
634 {
635 Object obj = getForeignKeyFields().get( i );
636 if( obj instanceof Integer )
637 {
638 String fkId = obj.toString();
639 result.append( " " );
640 result.append( tags.getOpeningTagNonClosingById( FOREIGN_KEY ) );
641 result.append( " " );
642 result.append( tags.getAttribute( FIELD_ID_REF, fkId ) );
643 result.append( "/>" );
644 result.append( eol );
645 }
646 else
647 {
648 String fk = ( String ) obj;
649 result.append( " " );
650 result.append( tags.getOpeningTagNonClosingById( FOREIGN_KEY ) );
651 result.append( " " );
652 result.append( tags.getAttribute( FIELD_REF, fk ) );
653 result.append( "/>" );
654 result.append( eol );
655 }
656 }
657
658 // closing tag
659 result.append( " " );
660 result.append( tags.getClosingTagById( REFERENCE_DESCRIPTOR ) );
661 result.append( eol );
662 return result.toString();
663 }
664 }