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    }