001    /*
002     * Copyright 2009-2016 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2009-2016 UnboundID Corp.
007     *
008     * This program is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License (GPLv2 only)
010     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011     * as published by the Free Software Foundation.
012     *
013     * This program is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program; if not, see <http://www.gnu.org/licenses>.
020     */
021    package com.unboundid.ldap.sdk.persist;
022    
023    
024    
025    import java.io.Serializable;
026    import java.lang.reflect.Method;
027    import java.lang.reflect.Modifier;
028    import java.lang.reflect.Type;
029    
030    import com.unboundid.ldap.sdk.Attribute;
031    import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
032    import com.unboundid.util.NotMutable;
033    import com.unboundid.util.ThreadSafety;
034    import com.unboundid.util.ThreadSafetyLevel;
035    
036    import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
037    import static com.unboundid.util.Debug.*;
038    import static com.unboundid.util.StaticUtils.*;
039    import static com.unboundid.util.Validator.*;
040    
041    
042    
043    /**
044     * This class provides a data structure that holds information about an
045     * annotated getter method.
046     */
047    @NotMutable()
048    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
049    public final class GetterInfo
050           implements Serializable
051    {
052      /**
053       * The serial version UID for this serializable class.
054       */
055      private static final long serialVersionUID = 1578187843924054389L;
056    
057    
058    
059      // Indicates whether the associated method value should be included in the
060      // entry created for an add operation.
061      private final boolean includeInAdd;
062    
063      // Indicates whether the associated method value should be considered for
064      // inclusion in the set of modifications used for modify operations.
065      private final boolean includeInModify;
066    
067      // Indicates whether the associated method value is part of the RDN.
068      private final boolean includeInRDN;
069    
070      // The class that contains the associated method.
071      private final Class<?> containingClass;
072    
073      // The filter usage for the associated method.
074      private final FilterUsage filterUsage;
075    
076      // The method with which this object is associated.
077      private final Method method;
078    
079      // The encoder used for this method.
080      private final ObjectEncoder encoder;
081    
082      // The name of the associated attribute type.
083      private final String attributeName;
084    
085      // The names of the object classes for the associated attribute.
086      private final String[] objectClasses;
087    
088    
089    
090      /**
091       * Creates a new getter info object from the provided method.
092       *
093       * @param  m  The method to use to create this object.
094       * @param  c  The class which holds the method.
095       *
096       * @throws  LDAPPersistException  If a problem occurs while processing the
097       *                                given method.
098       */
099      GetterInfo(final Method m, final Class<?> c)
100           throws LDAPPersistException
101      {
102        ensureNotNull(m, c);
103    
104        method = m;
105        m.setAccessible(true);
106    
107        final LDAPGetter  a = m.getAnnotation(LDAPGetter.class);
108        if (a == null)
109        {
110          throw new LDAPPersistException(ERR_GETTER_INFO_METHOD_NOT_ANNOTATED.get(
111               m.getName(), c.getName()));
112        }
113    
114        final LDAPObject o = c.getAnnotation(LDAPObject.class);
115        if (o == null)
116        {
117          throw new LDAPPersistException(ERR_GETTER_INFO_CLASS_NOT_ANNOTATED.get(
118               c.getName()));
119        }
120    
121        containingClass = c;
122        includeInRDN    = a.inRDN();
123        includeInAdd    = (includeInRDN || a.inAdd());
124        includeInModify = ((! includeInRDN) && a.inModify());
125        filterUsage     = a.filterUsage();
126    
127        final int modifiers = m.getModifiers();
128        if (Modifier.isStatic(modifiers))
129        {
130          throw new LDAPPersistException(ERR_GETTER_INFO_METHOD_STATIC.get(
131               m.getName(), c.getName()));
132        }
133    
134        final Type[] params = m.getGenericParameterTypes();
135        if (params.length > 0)
136        {
137          throw new LDAPPersistException(ERR_GETTER_INFO_METHOD_TAKES_ARGUMENTS.get(
138               m.getName(), c.getName()));
139        }
140    
141        try
142        {
143          encoder = a.encoderClass().newInstance();
144        }
145        catch (Exception e)
146        {
147          debugException(e);
148          throw new LDAPPersistException(ERR_GETTER_INFO_CANNOT_GET_ENCODER.get(
149               a.encoderClass().getName(), m.getName(), c.getName(),
150               getExceptionMessage(e)), e);
151        }
152    
153        if (! encoder.supportsType(m.getGenericReturnType()))
154        {
155          throw new LDAPPersistException(
156               ERR_GETTER_INFO_ENCODER_UNSUPPORTED_TYPE.get(
157                    encoder.getClass().getName(), m.getName(), c.getName(),
158                    String.valueOf(m.getGenericReturnType())));
159        }
160    
161        final String structuralClass;
162        if (o.structuralClass().length() == 0)
163        {
164          structuralClass = getUnqualifiedClassName(c);
165        }
166        else
167        {
168          structuralClass = o.structuralClass();
169        }
170    
171        final String[] ocs = a.objectClass();
172        if ((ocs == null) || (ocs.length == 0))
173        {
174          objectClasses = new String[] { structuralClass };
175        }
176        else
177        {
178          objectClasses = ocs;
179        }
180    
181        for (final String s : objectClasses)
182        {
183          if (! s.equalsIgnoreCase(structuralClass))
184          {
185            boolean found = false;
186            for (final String oc : o.auxiliaryClass())
187            {
188              if (s.equalsIgnoreCase(oc))
189              {
190                found = true;
191                break;
192              }
193            }
194    
195            if (! found)
196            {
197              throw new LDAPPersistException(ERR_GETTER_INFO_INVALID_OC.get(
198                   m.getName(), c.getName(), s));
199            }
200          }
201        }
202    
203        final String attrName = a.attribute();
204        if ((attrName == null) || (attrName.length() == 0))
205        {
206          final String methodName = m.getName();
207          if (methodName.startsWith("get") && (methodName.length() >= 4))
208          {
209            attributeName = toInitialLowerCase(methodName.substring(3));
210          }
211          else
212          {
213            throw new LDAPPersistException(ERR_GETTER_INFO_CANNOT_INFER_ATTR.get(
214                 methodName, c.getName()));
215          }
216        }
217        else
218        {
219          attributeName = attrName;
220        }
221      }
222    
223    
224    
225      /**
226       * Retrieves the method with which this object is associated.
227       *
228       * @return  The method with which this object is associated.
229       */
230      public Method getMethod()
231      {
232        return method;
233      }
234    
235    
236    
237      /**
238       * Retrieves the class that is marked with the {@link LDAPObject} annotation
239       * and contains the associated field.
240       *
241       * @return  The class that contains the associated field.
242       */
243      public Class<?> getContainingClass()
244      {
245        return containingClass;
246      }
247    
248    
249    
250      /**
251       * Indicates whether the associated method value should be included in entries
252       * generated for add operations.  Note that the value returned from this
253       * method may be {@code true} even when the annotation has a value of
254       * {@code false} if the associated field is to be included in entry RDNs.
255       *
256       * @return  {@code true} if the associated method value should be included in
257       *          entries generated for add operations, or {@code false} if not.
258       */
259      public boolean includeInAdd()
260      {
261        return includeInAdd;
262      }
263    
264    
265    
266      /**
267       * Indicates whether the associated method value should be considered for
268       * inclusion in the set of modifications generated for modify operations.
269       * Note that the value returned from this method may be {@code false} even
270       * when the annotation have a value of {@code true} if the associated field is
271       * to be included in entry RDNs.
272       *
273       * @return  {@code true} if the associated method value should be considered
274       *          for inclusion in the set of modifications generated for modify
275       *          operations, or {@code false} if not.
276       */
277      public boolean includeInModify()
278      {
279        return includeInModify;
280      }
281    
282    
283    
284      /**
285       * Indicates whether the associated method value should be used to generate
286       * entry RDNs.
287       *
288       * @return  {@code true} if the associated method value should be used to
289       *          generate entry RDNs, or {@code false} if not.
290       */
291      public boolean includeInRDN()
292      {
293        return includeInRDN;
294      }
295    
296    
297    
298      /**
299       * Retrieves the filter usage for the associated method.
300       *
301       * @return  The filter usage for the associated method.
302       */
303      public FilterUsage getFilterUsage()
304      {
305        return filterUsage;
306      }
307    
308    
309    
310      /**
311       * Retrieves the encoder that should be used for the associated method.
312       *
313       * @return  The encoder that should be used for the associated method.
314       */
315      public ObjectEncoder getEncoder()
316      {
317        return encoder;
318      }
319    
320    
321    
322      /**
323       * Retrieves the name of the LDAP attribute used to hold values for the
324       * associated method.
325       *
326       * @return  The name of the LDAP attribute used to hold values for the
327       *          associated method.
328       */
329      public String getAttributeName()
330      {
331        return attributeName;
332      }
333    
334    
335    
336      /**
337       * Retrieves the names of the object classes containing the associated
338       * attribute.
339       *
340       * @return  The names of the object classes containing the associated
341       *          attribute.
342       */
343      public String[] getObjectClasses()
344      {
345        return objectClasses;
346      }
347    
348    
349    
350      /**
351       * Constructs a definition for an LDAP attribute type which may be added to
352       * the directory server schema to allow it to hold the value of the associated
353       * method.  Note that the object identifier used for the constructed attribute
354       * type definition is not required to be valid or unique.
355       *
356       * @return  The constructed attribute type definition.
357       *
358       * @throws  LDAPPersistException  If the object encoder does not support
359       *                                encoding values for the associated field
360       *                                type.
361       */
362      AttributeTypeDefinition constructAttributeType()
363           throws LDAPPersistException
364      {
365        return constructAttributeType(DefaultOIDAllocator.getInstance());
366      }
367    
368    
369    
370      /**
371       * Constructs a definition for an LDAP attribute type which may be added to
372       * the directory server schema to allow it to hold the value of the associated
373       * method.  Note that the object identifier used for the constructed attribute
374       * type definition is not required to be valid or unique.
375       *
376       * @param  a  The OID allocator to use to generate the object identifier.  It
377       *            must not be {@code null}.
378       *
379       * @return  The constructed attribute type definition.
380       *
381       * @throws  LDAPPersistException  If the object encoder does not support
382       *                                encoding values for the associated method
383       *                                type.
384       */
385      AttributeTypeDefinition constructAttributeType(final OIDAllocator a)
386           throws LDAPPersistException
387      {
388        return encoder.constructAttributeType(method, a);
389      }
390    
391    
392    
393      /**
394       * Creates an attribute with the value returned by invoking the associated
395       * method on the provided object.
396       *
397       * @param  o  The object for which to invoke the associated method.
398       *
399       * @return  The attribute containing the encoded representation of the method
400       *          value, or {@code null} if the method returned {@code null}.
401       *
402       * @throws  LDAPPersistException  If a problem occurs while encoding the
403       *                                value of the associated field for the
404       *                                provided object.
405       */
406      Attribute encode(final Object o)
407                throws LDAPPersistException
408      {
409        try
410        {
411          final Object methodValue = method.invoke(o);
412          if (methodValue == null)
413          {
414            return null;
415          }
416    
417          return encoder.encodeMethodValue(method, methodValue, attributeName);
418        }
419        catch (Exception e)
420        {
421          debugException(e);
422          throw new LDAPPersistException(ERR_GETTER_INFO_CANNOT_ENCODE.get(
423               method.getName(), containingClass.getName(), getExceptionMessage(e)),
424               e);
425        }
426      }
427    }