1 package net.sf.jldapbeans.lang;
2
3 import static java.util.Collections.addAll;
4
5 import java.util.Collection;
6 import java.util.Vector;
7
8 import net.sf.cglib.proxy.Enhancer;
9 import net.sf.jldapbeans.lang.annotation.LdapBean;
10 import net.sf.jldapbeans.lang.annotation.LdapModifier;
11
12 import org.apache.commons.beanutils.BeanUtils;
13 import org.apache.commons.lang.builder.EqualsBuilder;
14 import org.apache.commons.lang.builder.HashCodeBuilder;
15 import org.apache.commons.lang.builder.ToStringBuilder;
16 import org.apache.commons.logging.Log;
17 import org.apache.commons.logging.LogFactory;
18
19 /***
20 * Representation of a jLDAPBeans' class.
21 * <p>
22 * The <code>LdapClass</code> is used as a LDAP bean instantiator. We can use it in many forms
23 * such as:
24 * <ul>
25 * <li>To instantiate a bean from a string array of <code>objectClass</code>es.
26 * <li>To identify which of the classes from an object are LDAP bean definitions.
27 * <li>Aggregate attributes merging <code>Class</code>es or <code>LdapClass</code>es objects.
28 * </ul>
29 *
30 * @author <a href="mailto:alonsoft@users.sf.net">A. Alonso Domínguez</a>
31 * @version 1.0 $ Revision 13-nov-2005 :: alonso $
32 */
33 public final class LdapClass {
34 public static final BeanRepository BEAN_REPOSITORY = BeanRepositoryFactory.getRepository();
35
36 private static final Log log = LogFactory.getLog(LdapClass.class);
37
38 /***
39 * Aggregates an array of any kind of java classes to an LDAP bean instance.
40 * <p>
41 * There is one thing that must be in mind of people who use this method to aggregate
42 * <tt>objectClass</tt>es to an already existent instance and that things is that the
43 * properties of the classes aggregated are not initialized by this method.
44 *
45 * @param instance An already existent instance of a LDAP bean.
46 * @param classes Any kind of java classes to be aggregated to the instance
47 * @return A new instance which is a copy of the one received but which has new properties
48 * received from the java classes aggregated.
49 * @throws LdapClassException If it is not possible aggregate classes.
50 */
51 public static Object aggregate(Object obj, Class<?>... classes) throws LdapClassException {
52 Vector<Class<?>> classVector = new Vector<Class<?>>();
53 addAll(classVector, classOf(obj).getClasses());
54 addAll(classVector, filterBeanClasses(classes));
55
56 LdapClass ldapClass = merge(classVector.toArray(new Class<?>[classVector.size()]));
57 Object result = ldapClass.newInstance();
58 try {
59 BeanUtils.copyProperties(obj, result);
60 }
61 catch(Exception e) {
62 throw new LdapClassException(e.getMessage(), e);
63 }
64 return result;
65 }
66
67 /***
68 * Aggregates an array of any kind of LDAP classes to an LDAP bean instance.
69 * <p>
70 * There is one thing that must be in mind of people who use this method to aggregate
71 * <tt>objectClass</tt>es to an already existent instance and that things is that the
72 * properties of the classes aggregated are not initialized by this method.
73 *
74 * @param instance An already existent instance of a LDAP bean.
75 * @param classes Any kind of LDAP classes to be aggregated to the instance
76 * @return A new instance which is a copy of the one received but which has new properties
77 * received from the java classes aggregated.
78 * @throws LdapClassException If it is not possible aggregate classes.
79 */
80 public static Object aggregate(Object obj, LdapClass... ldapClasses) throws LdapClassException {
81 Vector<LdapClass> classVector = new Vector<LdapClass>();
82 classVector.addElement(classOf(obj));
83 addAll(classVector, ldapClasses);
84
85 LdapClass ldapClass = merge(classVector.toArray(new LdapClass[classVector.size()]));
86 Object result = ldapClass.newInstance();
87 try {
88 BeanUtils.copyProperties(obj, result);
89 }
90 catch(Exception e) {
91 throw new LdapClassException(e.getMessage(), e);
92 }
93 return result;
94 }
95
96 /***
97 * Obtains a <tt>LdapClass</tt> instance for an object that may be a LDAP bean.
98 *
99 * @param obj The object where search for LDAP classes.
100 * @return A new <code>LdapClass</code> instance that represents the LDAP classes of the
101 * object specified.
102 * @throws LdapClassException If the object doesn't implement any LDAP class
103 */
104 public static LdapClass classOf(Object obj) throws LdapClassException {
105 return merge(getClassesOfBean(obj));
106 }
107
108 private static Class<?>[] filterBeanClasses(Class<?>... classes) {
109 Vector<Class<?>> classVector = new Vector<Class<?>>();
110
111 for(Class<?> clazz : classes) {
112 if(!clazz.isAnnotationPresent(LdapBean.class)) continue;
113 if(!clazz.isInterface()) {
114 log.warn("Class [" + clazz.getName() + "] ignored because it is not an interface.");
115 continue;
116 }
117 if(!classVector.contains(clazz))
118 classVector.addElement(clazz);
119 }
120
121 return classVector.toArray(new Class<?>[classVector.size()]);
122 }
123
124 /***
125 * Searches in <tt>BeanRepository</tt> for the names specified as <tt>objectClass</tt>
126 * maps each one as a <tt>java.lang.Class</tt>.
127 *
128 * @param objectClasses Array of <tt>objectClass</tt>es we need to search for.
129 * @return A new <tt>LdapClass</tt> instance that represents all the <tt>objectClass</tt>es
130 * specified.
131 * @throws LdapClassException If we can not found anyone of the <tt>objectClass</tt>es.
132 * @throws ClassNotFoundException If we can not fond anyone of the java classes.
133 */
134 public static LdapClass forName(String... objectClasses)
135 throws LdapClassException, ClassNotFoundException {
136 Vector<Class<?>> classVector = new Vector<Class<?>>();
137 for(String objectClass : objectClasses) {
138 String className = BEAN_REPOSITORY.getObjectClass(objectClass);
139 if(className == null) throw new ObjectClassNotFoundException(objectClass);
140
141 log.trace("Mapped objectClass [" + objectClass + "] as [" + className + "]");
142 Class<?> clazz = Class.forName(className);
143 classVector.addElement(clazz);
144 }
145
146 return merge(classVector.toArray(new Class<?>[classVector.size()]));
147 }
148
149 private static Class<?>[] getClassesOfBean(Object obj) {
150 Vector<Class<?>> classVector = new Vector<Class<?>>();
151 Class<?>[] classes = filterBeanClasses((Class<?>[]) obj.getClass().getInterfaces());
152
153 addAll(classVector, classes);
154 for(Class<?> clazz : classes) {
155 log.trace("Found LdapJB class name [" + clazz.getName() + "]");
156 scanParentClasses(clazz.getSuperclass(), classVector);
157 }
158 return classVector.toArray(new Class<?>[classVector.size()]);
159 }
160
161 /***
162 * Identifies if a given object is an instance of an LdapJB
163 *
164 * @param obj A possible LdapJB instance
165 * @return <tt>true</tt> if it's a bean; <tt>false</tt> otherwise.
166 */
167 public static boolean isLdapBean(Object obj) {
168 Class<?>[] classes = getClassesOfBean(obj);
169 if(classes.length == 0) return false;
170 return true;
171 }
172
173 /***
174 * Merges an array of any kind of java class into a <tt>LdapClass</tt> instance indentifying
175 * which of the classes received are valid LDAP bean classes.
176 *
177 * @param classes An array of any kind of java class.
178 * @return A new instance of a <tt>LdapClass</tt> which will be conformant with the parameters
179 * received.
180 * @throws LdapClassException If there is not any valid LDAP bean class.
181 */
182 public static LdapClass merge(Class<?>... classes) throws LdapClassException {
183 Class<?>[] clazzes = filterBeanClasses(classes);
184 if(clazzes.length == 0)
185 throw new LdapClassException("Didn't found any valid LJB class.");
186 return new LdapClass(clazzes);
187 }
188
189 /***
190 * Merges an array of instances of <tt>LdapClass</tt>
191 *
192 * @param beanClasses A <tt>LdapClass</tt> array
193 * @return A new <tt>LdapClass</tt> that represents the <tt>LdapClass</tt>es received
194 * @throws LdapClassException
195 */
196 public static LdapClass merge(LdapClass... ldapClasses) throws LdapClassException {
197 Vector<Class<?>> classVector = new Vector<Class<?>>();
198 for(LdapClass ldapClass : ldapClasses)
199 addAll(classVector, ldapClass.getClasses());
200 return merge(classVector.toArray(new Class<?>[classVector.size()]));
201 }
202
203 /***
204 * Merges a <tt>LdapClass</tt> instance with an array of any kind of java classes.
205 *
206 * @param ldapClass Any <tt>LdapClass</tt> instance
207 * @param classes An array of any kind of java classes
208 * @return A new instance of a <tt>LdapClass</tt> which will be an aggegation of
209 * <tt>ldapClass</tt> and the valid java classes found in the array
210 * @throws LdapClassException If the is not any valid LDAP bean class in the array.
211 */
212 public static LdapClass merge(LdapClass ldapClass, Class<?>... classes) throws LdapClassException {
213 Vector<Class<?>> classVector = new Vector<Class<?>>();
214 addAll(classVector, ldapClass.getClasses());
215 addAll(classVector, filterBeanClasses(classes));
216 return merge(classVector.toArray(new Class<?>[classVector.size()]));
217 }
218
219 private static void scanParentClasses(Class<?> clazz, Collection<Class<?>> classes) {
220 if(clazz == null) return;
221 if(!classes.contains(clazz)) {
222 if(!clazz.isAnnotationPresent(LdapBean.class)) return;
223
224 log.trace("Found LdapJB class name [" + clazz.getName() + "]");
225 classes.add(clazz);
226 scanParentClasses(clazz.getSuperclass(), classes);
227 }
228 }
229
230 private Class<?>[] m_classes;
231
232 private LdapClass(Class<?>... classes) {
233 if(classes == null)
234 throw new NullPointerException("classes");
235 if(classes.length == 0)
236 throw new IllegalArgumentException("Argument classes can not be an empty array.");
237
238 m_classes = classes;
239 }
240
241 public boolean equals(Object o) {
242 if(this == o) return true;
243 if(!(o instanceof LdapClass)) return false;
244
245 final LdapClass other = (LdapClass) o;
246 return new EqualsBuilder()
247 .append(m_classes, other.m_classes)
248 .isEquals();
249 }
250
251 /***
252 * Obtains an array of java classes that reflect the ABSTRACT objectClasses
253 * used in this <tt>LdapClass</tt>
254 *
255 * @return An array of LdapJB interfaces
256 */
257 public Class<?>[] getAbstractClasses() {
258 Vector<Class<?>> classVector = new Vector<Class<?>>();
259 for(Class<?> clazz : m_classes) {
260 LdapBean beanMeta = clazz.getAnnotation(LdapBean.class);
261 if(beanMeta.value().equals(LdapModifier.ABSTRACT))
262 classVector.addElement(clazz);
263 }
264 return classVector.toArray(new Class<?>[classVector.size()]);
265 }
266
267 /***
268 * Obtains an array of java classes that reflect the AUXILIARY objectClasses
269 * used in this <tt>LdapClass</tt>.
270 *
271 * @return An array of LdapJB interfaces
272 */
273 public Class<?>[] getAuxiliaryClasses() {
274 Vector<Class<?>> classVector = new Vector<Class<?>>();
275 for(Class<?> clazz : m_classes) {
276 LdapBean beanMeta = clazz.getAnnotation(LdapBean.class);
277 if(beanMeta.value().equals(LdapModifier.AUXILIARY))
278 classVector.addElement(clazz);
279 }
280 return classVector.toArray(new Class<?>[classVector.size()]);
281 }
282
283 /***
284 * Accesses the class array used used inside this <tt>LdapClass</tt> instance.
285 *
286 * @return An array of LdapJB interfaces
287 */
288 protected Class<?>[] getClasses() {
289 return m_classes;
290 }
291
292 /***
293 * Obtains an array of java classes that reflect the STRUCTURAL objectClasses
294 * used in this <tt>LdapClass</tt>.
295 *
296 * @return An array of LdapJB interfaces
297 */
298 public Class<?>[] getStructuralClasses() {
299 Vector<Class<?>> classVector = new Vector<Class<?>>();
300 for(Class<?> clazz : m_classes) {
301 LdapBean beanMeta = clazz.getAnnotation(LdapBean.class);
302 if(beanMeta.value().equals(LdapModifier.STRUCTURAL))
303 classVector.addElement(clazz);
304 }
305 return classVector.toArray(new Class<?>[classVector.size()]);
306 }
307
308 public int hashCode() {
309 return new HashCodeBuilder()
310 .append(m_classes)
311 .toHashCode();
312 }
313
314 /***
315 * Creates a new instance of a LDAP bean using a <tt>net.sf.cglib.proxy.Enhancer</tt> to
316 * support the implementation of every java class that the bean must support.
317 *
318 * @return An instance of a LDAP bean.
319 * @throws BeanInstantiationException If some exception is catched during the instantiation
320 * process.
321 */
322 public Object newInstance() throws BeanInstantiationException {
323 return LdapClassHelper.newInstance(this);
324 }
325
326 public String toString() {
327 return new ToStringBuilder(this)
328 .append(m_classes)
329 .toString();
330 }
331
332 }