View Javadoc

1   /*
2    * Copyright 2000-2005 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.portals.graffito.jcr.mapper.model;
17  
18  
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.HashMap;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.portals.graffito.jcr.exception.JcrMappingException;
29  import org.apache.portals.graffito.jcr.reflection.ReflectionUtils;
30  
31  /***
32   *
33   * ClassDescriptor is used by the mapper to read general information on a class
34   *
35   * @author <a href="mailto:christophe.lombart@sword-technologies.com">Lombart Christophe </a>
36   * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
37   */
38  public class ClassDescriptor {
39  	
40  	private static final Log log = LogFactory.getLog(ClassDescriptor.class);
41  	
42      private static final String NODETYPE_PER_HIERARCHY = "nodetypeperhierarchy";
43      private static final String NODETYPE_PER_CONCRETECLASS = "nodetypeperconcreteclass";
44  
45      private MappingDescriptor mappingDescriptor;
46      private ClassDescriptor superClassDescriptor;
47      private Collection descendantClassDescriptors = new ArrayList();
48  
49      private String className;
50      private String jcrNodeType;
51      private String jcrSuperTypes;
52      private String[] jcrMixinTypes = new String[0];
53      private FieldDescriptor idFieldDescriptor;
54      private FieldDescriptor pathFieldDescriptor;
55  
56      private Map fieldDescriptors = new HashMap();    
57      private Map beanDescriptors = new HashMap();        
58      private Map collectionDescriptors = new HashMap();
59          
60      private Map fieldNames = new HashMap();
61  
62      private String superClassName;
63      private String extendsStrategy;    
64      private boolean isAbstract = false;    
65      private boolean hasDescendant = false;
66      private boolean hasDiscriminator = true; 
67     
68         
69      private boolean isInterface=false;
70      private List interfaces = new ArrayList();
71      
72      public void setAbstract(boolean flag) {
73          this.isAbstract = flag;
74      }
75  
76      public boolean isAbstract() {
77          return this.isAbstract;
78      }
79  
80      public void setInterface(boolean flag) {
81      	   this.isInterface = flag;
82      }
83         
84      public boolean isInterface() {
85      	    return isInterface;
86      }
87      
88      public boolean hasInterfaces()
89      {
90      	   return this.interfaces.size() > 0;
91      }
92  
93      public void setDiscriminator(boolean flag)
94      {
95          this.hasDiscriminator = flag;	
96      }
97      
98      public boolean hasDiscriminator() {        
99   	   return this.hasDiscriminator;
100  }    
101     
102     public boolean usesNodeTypePerHierarchyStrategy() {
103         return NODETYPE_PER_HIERARCHY.equals(this.extendsStrategy);
104     }
105 
106     public boolean usesNodeTypePerConcreteClassStrategy() {
107         return NODETYPE_PER_CONCRETECLASS.equals(this.extendsStrategy);
108     }
109     /***
110      * @return Returns the className.
111      */
112     public String getClassName() {
113         return className;
114     }
115 
116     /***
117      * @param className The className to set.
118      */
119     public void setClassName(String className) {    	   
120         this.className = className;
121     }
122 
123     /***
124      * @return Returns the jcrNodeType.
125      */
126     public String getJcrNodeType() {
127         return jcrNodeType;
128     }
129 
130     /***
131      * @param jcrNodeType The jcrNodeType to set.
132      */
133     public void setJcrNodeType(String jcrNodeType) {
134         this.jcrNodeType = jcrNodeType;
135     }
136 
137     /***
138      * Add a new FielDescriptor
139      * @param fieldDescriptor the new field descriptor to add
140      */
141     public void addFieldDescriptor(FieldDescriptor fieldDescriptor) {
142         fieldDescriptor.setClassDescriptor(this);
143         if (fieldDescriptor.isId()) {
144             this.idFieldDescriptor = fieldDescriptor;
145         }
146         if (fieldDescriptor.isPath()) {
147             this.pathFieldDescriptor = fieldDescriptor;
148         }
149 
150         fieldDescriptors.put(fieldDescriptor.getFieldName(), fieldDescriptor);
151         fieldNames.put(fieldDescriptor.getFieldName(), fieldDescriptor.getJcrName());
152     }
153 
154     public void addImplementDescriptor(ImplementDescriptor implementDescriptor)
155     {
156         interfaces.add(implementDescriptor.getInterfaceName());	
157     }
158     
159     /***
160      * Get the FieldDescriptor to used for a specific java bean attribute
161      * @param fieldName The java bean attribute name
162      *
163      * @return the {@link FieldDescriptor} found or null
164      */
165     public FieldDescriptor getFieldDescriptor(String fieldName) {
166         return (FieldDescriptor) this.fieldDescriptors.get(fieldName);
167     }
168 
169     /***
170      *
171      * @return all {@link FieldDescriptor} defined in this ClassDescriptor
172      */
173     public Collection getFieldDescriptors() {
174         return this.fieldDescriptors.values();
175     }
176 
177     /***
178      * Add a new BeanDescriptor
179      * @param beanDescriptor the new bean descriptor to add
180      */
181 
182     public void addBeanDescriptor(BeanDescriptor beanDescriptor) {
183         beanDescriptor.setClassDescriptor(this);
184         beanDescriptors.put(beanDescriptor.getFieldName(), beanDescriptor);
185         fieldNames.put(beanDescriptor.getFieldName(), beanDescriptor.getJcrName());
186     }
187 
188     /***
189      * Get the BeanDescriptor to used for a specific java bean attribute
190      * @param fieldName The java bean attribute name
191      *
192      * @return the {@link BeanDescriptor} found or null
193      */
194     public BeanDescriptor getBeanDescriptor(String fieldName) {
195         return (BeanDescriptor) this.beanDescriptors.get(fieldName);
196     }
197 
198     /***
199      * @return all {@link BeanDescriptor} defined in this ClassDescriptor
200      */
201     public Collection getBeanDescriptors() {
202         return this.beanDescriptors.values();
203     }
204 
205     /***
206      * Add a new CollectionDescriptor
207      * @param collectionDescriptor the new collection descriptor to add
208      */
209 
210     public void addCollectionDescriptor(CollectionDescriptor collectionDescriptor) {
211         collectionDescriptor.setClassDescriptor(this);
212         collectionDescriptors.put(collectionDescriptor.getFieldName(), collectionDescriptor);
213         fieldNames.put(collectionDescriptor.getFieldName(), collectionDescriptor.getJcrName());
214     }
215 
216     /***
217      * Get the CollectionDescriptor to used for a specific java bean attribute
218      * @param fieldName The java bean attribute name
219      *
220      * @return the {@link CollectionDescriptor} found or null
221      */
222     public CollectionDescriptor getCollectionDescriptor(String fieldName) {
223         return (CollectionDescriptor) this.collectionDescriptors.get(fieldName);
224     }
225 
226     /***
227      * @return all {@link BeanDescriptor} defined in this ClassDescriptor
228      */
229     public Collection getCollectionDescriptors() {
230         return this.collectionDescriptors.values();
231     }
232 
233     /***
234      * @return the fieldDescriptor ID
235      */
236     public FieldDescriptor getIdFieldDescriptor() {
237         return idFieldDescriptor;
238     }
239 
240     /***
241      * @return the fieldDescriptor path
242      */
243     public FieldDescriptor getPathFieldDescriptor() {
244         if (null != this.pathFieldDescriptor) {
245             return this.pathFieldDescriptor;
246         }
247 
248         if (null != this.superClassDescriptor) {
249             return this.superClassDescriptor.getPathFieldDescriptor();
250         }
251 
252         return null;
253     }
254 
255 
256     /***
257      * Check if this class has an ID
258      * @return true if the class has an ID
259      */
260     public boolean hasIdField() {        
261         return (this.idFieldDescriptor != null && ! this.idFieldDescriptor.equals(""));
262     }
263 
264     /***
265      * Get the JCR name used for one of the object attributes
266      * @param fieldName the object attribute name (can be an atomic field, bean field or a collection field)
267      * @return the JCR name found
268      */
269     public String getJcrName(String fieldName) {
270         String jcrName =  (String) this.fieldNames.get(fieldName);
271         if (this.isInterface && jcrName == null)
272         {
273             return this.getJcrNameFromDescendants(this, fieldName);          
274         }
275         
276         return jcrName;
277     }
278     
279     private String getJcrNameFromDescendants(ClassDescriptor classDescriptor, String fieldName )
280     {
281         Iterator  descendants = classDescriptor.getDescendantClassDescriptors().iterator();
282         while (descendants.hasNext())
283         {
284         	    ClassDescriptor descendant = (ClassDescriptor) descendants.next();
285         	    String jcrName =  (String) descendant.fieldNames.get(fieldName);
286         	    if(jcrName != null)
287         	    {
288         	    	   return jcrName;
289         	    }
290         	    return this.getJcrNameFromDescendants(descendant, fieldName);
291         }
292         return null;
293 
294     	
295     }
296     
297     public Map getFieldNames() {
298         return this.fieldNames;
299     }
300 
301     /*** Get the JCR node super types.
302      *
303      * @return jcrSuperTypes
304      */
305     public String getJcrSuperTypes() {
306         return jcrSuperTypes;
307     }
308 
309     /*** Setter for JCR super types.
310      *
311      * @param superTypes Comma separated list of JCR node super types
312      */
313     public void setJcrSuperTypes(String superTypes) {
314         this.jcrSuperTypes = superTypes;
315     }
316 
317     /***
318      * Retrieve the mixin types.
319      *
320      * @return array of mixin types
321      */
322     public String[] getJcrMixinTypes() {
323         return this.jcrMixinTypes;
324     }
325 
326     /***
327      * Sets a comma separated list of mixin types.
328      *
329      * @param mixinTypes command separated list of mixins
330      */
331     public void setJcrMixinTypes(String[] mixinTypes) {
332         if (null != mixinTypes && mixinTypes.length == 1) {
333             jcrMixinTypes = mixinTypes[0].split(" *, *");
334         }
335     }
336 
337     /***
338      * @return Returns the mappingDescriptor.
339      */
340     public MappingDescriptor getMappingDescriptor() {
341         return mappingDescriptor;
342     }
343 
344     /***
345      * @param mappingDescriptor The mappingDescriptor to set.
346      */
347     public void setMappingDescriptor(MappingDescriptor mappingDescriptor) {
348         this.mappingDescriptor = mappingDescriptor;
349     }
350 
351     /***
352      * Revisit information in this descriptor and fills in more.
353      */
354     public void afterPropertiesSet() {
355         validateClassName();   
356         validateBeanFields();
357         lookupSuperDescriptor();
358         lookupInheritanceSettings();
359 //        validateInheritanceSettings();
360     }
361 
362 	private void validateClassName() {
363 		try {
364             ReflectionUtils.forName(this.className);
365 		} catch (JcrMappingException e) {			
366 			 throw new JcrMappingException("Class used in descriptor not found : " + className);
367 		}
368 	}
369 
370 	private void validateBeanFields()
371 	{
372 	         Iterator beanDescriptorIterator  =  beanDescriptors.values().iterator();
373 	         while (beanDescriptorIterator.hasNext()) {
374               	        BeanDescriptor beanDescriptor = (BeanDescriptor) beanDescriptorIterator.next();
375               	        if (beanDescriptor.isProxy() && beanDescriptor.isInline())
376               	        {
377               	        	     throw new JcrMappingException("Bean field can not be proxy and inline - class : " + this.className + " - bean field :" + beanDescriptor.getFieldName());	
378               	        }
379 				       
380 			}
381 
382 	}
383 	
384 	private void lookupSuperDescriptor() {
385         if (null != superClassDescriptor) {
386             this.hasDiscriminator = superClassDescriptor.hasDiscriminator();
387             if (! this.isInterface)
388             {
389                 this.fieldDescriptors = mergeFields(this.fieldDescriptors, this.superClassDescriptor.getFieldDescriptors());
390                 this.beanDescriptors = mergeBeans(this.beanDescriptors, this.superClassDescriptor.getBeanDescriptors());
391                 this.collectionDescriptors = mergeCollections(this.collectionDescriptors, this.superClassDescriptor.getCollectionDescriptors());            
392                 this.fieldNames.putAll(this.superClassDescriptor.getFieldNames());
393             }
394         
395         }
396     }
397 
398     private void lookupInheritanceSettings() {
399         if ((null != this.superClassDescriptor) || (this.hasDescendants()) || this.hasInterfaces()) {
400             if (this.hasDiscriminator()) {
401                 this.extendsStrategy = NODETYPE_PER_HIERARCHY;
402             }
403             else {
404                 this.extendsStrategy = NODETYPE_PER_CONCRETECLASS;
405             }
406         }
407     }
408 	
409 
410     /***
411      * @return return the super class name if defined in mapping, or
412      * <tt>null</tt> if not set
413      */
414     public String getExtend() {
415         return this.superClassName;
416     }
417 
418     /***
419      * @param className
420      */
421     public void setExtend(String className) {
422         this.superClassName = className;
423     }
424 
425     /***
426      * @return Returns the superClassDescriptor.
427      */
428     public ClassDescriptor getSuperClassDescriptor() {
429         return superClassDescriptor;
430     }
431     
432     public Collection getDescendantClassDescriptors() {
433     	     return this.descendantClassDescriptors;
434     }
435     
436     /***
437      * If the node type per concrete class strategy is used, we need to find a descendant class descriptor assigned to a node type
438      * This method is not used in other situation.
439      * 
440      * @param nodeType the node type for which the classdescriptor is required
441      * @return the classdescriptor found or null
442      * 
443      * @todo : maybe we have to review this implementation to have better performance. 
444      */
445     public ClassDescriptor getDescendantClassDescriptor(String nodeType) {
446         Iterator iterator = this.descendantClassDescriptors.iterator();
447         while (iterator.hasNext()) {
448             ClassDescriptor descendantClassDescriptor = (ClassDescriptor) iterator.next();
449   
450             if (descendantClassDescriptor.getJcrNodeType().equals(nodeType)) {
451                 return descendantClassDescriptor;
452             }
453   
454             if (descendantClassDescriptor.hasDescendants()) {
455                 ClassDescriptor classDescriptor = descendantClassDescriptor.getDescendantClassDescriptor(nodeType);
456                 if (classDescriptor != null) {
457                     return classDescriptor;
458                 }
459             }
460         }
461         return null;
462     }
463     
464     public void addDescendantClassDescriptor(ClassDescriptor classDescriptor) {
465     	     this.descendantClassDescriptors.add(classDescriptor);
466     	     this.hasDescendant = true;
467     }
468     
469     public boolean hasDescendants() {
470     	    return this.hasDescendant;
471     }
472 
473     /***
474      * @param superClassDescriptor The superClassDescriptor to set.
475      */
476     public void setSuperClassDescriptor(ClassDescriptor superClassDescriptor) {
477         this.superClassDescriptor= superClassDescriptor;
478         superClassDescriptor.addDescendantClassDescriptor(this);
479     }
480    
481 
482     public Collection getImplements()
483     {
484     	    return interfaces;
485     }
486     
487     private Map mergeFields(Map existing, Collection superSource) {
488         if (null == superSource) {
489             return existing;
490         }
491 
492         Map merged = new HashMap(existing);
493         for(Iterator it = superSource.iterator(); it.hasNext();) {
494             FieldDescriptor fieldDescriptor = (FieldDescriptor) it.next();
495             if (!merged.containsKey(fieldDescriptor.getFieldName())) {
496                 merged.put(fieldDescriptor.getFieldName(), fieldDescriptor);
497             }
498 //            else {
499 //                log.warn("Field name conflict in " + this.className + " - field : " +fieldDescriptor.getFieldName() + " -  this  field name is also defined  in the ancestor class : " + this.getExtend());
500 //            }
501         }
502 
503         return merged;
504     }
505 
506     
507     private Map mergeBeans(Map existing, Collection superSource) {
508         if (null == superSource) {
509             return existing;
510         }
511 
512         Map merged = new HashMap(existing);
513         for(Iterator it = superSource.iterator(); it.hasNext();) {
514             BeanDescriptor beanDescriptor = (BeanDescriptor) it.next();
515             if (!merged.containsKey(beanDescriptor.getFieldName())) {
516                 merged.put(beanDescriptor.getFieldName(), beanDescriptor);
517             }
518 //            else {
519 //                log.warn("Bean name conflict in " + this.className + " - field : " +beanDescriptor.getFieldName() + " -  this  field name is also defined  in the ancestor class : " + this.getExtend());
520 //            }
521         }
522 
523         return merged;
524     }
525     
526     private Map mergeCollections(Map existing, Collection superSource) {
527         if (null == superSource) {
528             return existing;
529         }
530 
531         Map merged = new HashMap(existing);
532         for(Iterator it = superSource.iterator(); it.hasNext();) {
533             CollectionDescriptor collectionDescriptor = (CollectionDescriptor) it.next();
534             if (!merged.containsKey(collectionDescriptor.getFieldName())) {
535                 merged.put(collectionDescriptor.getFieldName(), collectionDescriptor);
536             }
537 //            else {
538 //                log.warn("Collection name conflict in " + this.className + " - field : " +collectionDescriptor.getFieldName() + " -  this  field name is also defined  in the ancestor class : " + this.getExtend());
539 //            }
540         }
541 
542         return merged;
543     }    
544     
545     
546 //    private List mergeInterfaces(List  existing, Collection superSource) {
547 //        if (null == superSource) {
548 //            return existing;
549 //        }
550 //
551 //        ArrayList merged = new ArrayList(existing);
552 //        for (Iterator it = superSource.iterator(); it.hasNext();)
553 //        {
554 //             String interfaceName = (String) it.next();
555 //             if (! merged.contains(interfaceName))
556 //             {
557 //            	     merged.add(interfaceName);
558 //             }            	 
559 //        }
560 //        
561 //        return merged;
562 //    }    
563     
564     
565 	public String toString() {
566 		return "Class Descriptor : " +  this.getClassName();
567 	}
568 }