View Javadoc

1   package org.apache.ojb.broker.metadata;
2   
3   /* Copyright 2002-2005 The Apache Software Foundation
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  import java.util.Hashtable;
19  import java.util.Iterator;
20  import java.util.Vector;
21  
22  import org.apache.commons.lang.builder.ToStringBuilder;
23  import org.apache.ojb.broker.OJBRuntimeException;
24  import org.apache.ojb.broker.PersistenceBrokerException;
25  import org.apache.ojb.broker.core.proxy.ProxyHelper;
26  import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
27  
28  /**
29   * Describes a Field containing a reference to another class. Provides handling for foreign keys etc.
30   * <br>
31   * Note: Be careful when use references of this class or caching instances of this class,
32   * because instances could become invalid (see {@link MetadataManager}).
33   *
34   * @author <a href="mailto:thma@apache.org">Thomas Mahler<a>
35   *
36   */
37  public class ObjectReferenceDescriptor extends AttributeDescriptorBase implements XmlCapable
38  {
39      private static final long serialVersionUID = 5561562217150972131L;
40  
41      public static final int CASCADE_NONE = 17;
42      public static final int CASCADE_LINK = 19;
43      public static final int CASCADE_OBJECT = 23;
44  
45      private Class m_ClassOfItems = null;
46      private Vector m_ForeignKeyFields = new Vector();
47      private boolean m_CascadeRetrieve = true;
48      private int m_CascadeStore = CASCADE_NONE;
49      private int m_CascadeDelete = CASCADE_NONE;
50      private int m_ProxyPrefetchingLimit = 50;
51  
52      private Class m_ProxyOfItems = null;
53      private boolean m_LookedUpProxy = false;
54      private boolean m_OtmDependent = false;
55  
56      /**
57       * holds the foreign-key field descriptor array for a specified class
58       */
59      private Hashtable fkFieldMap = new Hashtable();
60      /**
61       * define loading strategy of the resulting object
62       */
63      private boolean lazy = false;
64      /**
65       * if true relationship is refreshed when owner is found in cache
66       */
67      private boolean refresh = false;
68  
69      /**
70       *
71       */
72      public ObjectReferenceDescriptor(ClassDescriptor descriptor)
73      {
74          super(descriptor);
75      }
76  
77      /**
78       *
79       */
80      public Class getItemProxyClass() throws PersistenceBrokerException
81      {
82          if (!m_LookedUpProxy)
83          {
84              m_ProxyOfItems = getClassDescriptor().getRepository().
85                                  getDescriptorFor(m_ClassOfItems).getProxyClass();
86              m_LookedUpProxy = true;
87          }
88          return m_ProxyOfItems;
89      }
90  
91      /**
92       *
93       */
94  	public FieldDescriptor[] getForeignKeyFieldDescriptors(ClassDescriptor cld)
95  	{
96  		FieldDescriptor[] foreignKeyFieldDescriptors;
97  		if ((foreignKeyFieldDescriptors = (FieldDescriptor[]) fkFieldMap.get(cld)) == null)
98  		{
99  			// 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 }