View Javadoc
1   package org.synchronoss.cpo.core.meta.domain;
2   
3   /*-
4    * [[
5    * core
6    * ==
7    * Copyright (C) 2003 - 2026 Exaxis LLC, Synchronoss Technologies Inc
8    * ==
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation, either version 3 of the
12   * License, or (at your option) any later version.
13   *
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Lesser Public License for more details.
18   *
19   * You should have received a copy of the GNU General Lesser Public
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
22   * ]]
23   */
24  
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.TreeMap;
29  import java.util.concurrent.locks.ReentrantLock;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  import org.synchronoss.cpo.core.CpoException;
33  import org.synchronoss.cpo.core.MetaDFVisitable;
34  import org.synchronoss.cpo.core.MetaVisitor;
35  import org.synchronoss.cpo.core.enums.Crud;
36  import org.synchronoss.cpo.core.helper.CpoClassLoader;
37  import org.synchronoss.cpo.core.helper.ExceptionHelper;
38  import org.synchronoss.cpo.core.meta.CpoMetaDescriptor;
39  import org.synchronoss.cpo.core.meta.bean.CpoClassBean;
40  
41  /**
42   * Runtime metadata for a JavaBean class mapped by CPO: its {@link CpoAttribute}s, keyed both by
43   * Java property name and by datastore column name, and its {@link CpoFunctionGroup}s (one per named
44   * CRUD operation group). Subclasses ({@link CpoClassCaseSensitive}, {@link
45   * CpoClassCaseInsensitive}) determine how the datastore-name lookup treats case.
46   *
47   * @author dberry
48   */
49  public abstract class CpoClass extends CpoClassBean
50      implements Comparable<CpoClass>, MetaDFVisitable {
51    /** Version Id for this class. */
52    private static final long serialVersionUID = 1L;
53  
54    private static final Logger logger = LoggerFactory.getLogger(CpoClass.class);
55  
56    /** Guards lazy initialization of {@link #javaMap}/{@link #dataMap}. */
57    private final ReentrantLock lock = new ReentrantLock();
58  
59    /** The JavaBean class this metadata describes. */
60    private Class<?> metaClass = null;
61  
62    /** javaMap contains a Map of CpoAttribute Objects the key is the javaName of the attribute */
63    private Map<String, CpoAttribute> javaMap = new HashMap<>();
64  
65    /** dataMap contains a Map of CpoAttribute Objects the key is the dataName of the attribute */
66    private Map<String, CpoAttribute> dataMap = new HashMap<>();
67  
68    /**
69     * functionGroups is a hashMap that contains a hashMap of CpoFunctionGroup Lists that are used by
70     * this bean to create and retrieve it into a datasource.
71     */
72    private Map<String, CpoFunctionGroup> functionGroups = new HashMap<>();
73  
74    /** Creates an empty instance. */
75    public CpoClass() {}
76  
77    /**
78     * Gets the reflectively-resolved Java class this metadata describes, once {@link
79     * #loadRunTimeInfo(CpoMetaDescriptor)} has been called.
80     *
81     * @return the resolved Java class, or {@code null} if runtime info has not been loaded
82     */
83    public Class<?> getMetaClass() {
84      return metaClass;
85    }
86  
87    /**
88     * Adds an attribute to the datastore-name lookup map, using whatever case-sensitivity policy the
89     * concrete subclass implements.
90     *
91     * @param dataName the datastore-side name to key on
92     * @param cpoAttribute the attribute to add
93     */
94    public abstract void addDataNameToMap(String dataName, CpoAttribute cpoAttribute);
95  
96    /**
97     * Removes an attribute from the datastore-name lookup map.
98     *
99     * @param dataName the datastore-side name to remove
100    */
101   public abstract void removeDataNameFromMap(String dataName);
102 
103   /**
104    * Gets the attribute bound to the given datastore-side name.
105    *
106    * @param dataName the datastore-side name to look up
107    * @return the matching attribute, or {@code null} if none is bound to {@code dataName}
108    */
109   public abstract CpoAttribute getAttributeData(String dataName);
110 
111   /**
112    * Gets the underlying datastore-name-to-attribute map, for use by subclasses implementing the
113    * abstract lookup methods.
114    *
115    * @return the datastore-name-keyed attribute map
116    */
117   protected Map<String, CpoAttribute> getDataMap() {
118     return dataMap;
119   }
120 
121   /**
122    * Adds an attribute to this class, indexing it by both its Java name and its datastore name. A
123    * no-op if {@code cpoAttribute} is {@code null}.
124    *
125    * @param cpoAttribute the attribute to add
126    */
127   public void addAttribute(CpoAttribute cpoAttribute) {
128     if (cpoAttribute != null) {
129       logger.debug(
130           "Adding Attribute: " + cpoAttribute.getJavaName() + ":" + cpoAttribute.getDataName());
131       javaMap.put(cpoAttribute.getJavaName(), cpoAttribute);
132       addDataNameToMap(cpoAttribute.getDataName(), cpoAttribute);
133     }
134   }
135 
136   /**
137    * Removes an attribute from this class's Java-name and datastore-name indexes. A no-op if {@code
138    * cpoAttribute} is {@code null}.
139    *
140    * @param cpoAttribute the attribute to remove
141    */
142   public void removeAttribute(CpoAttribute cpoAttribute) {
143     if (cpoAttribute != null) {
144       logger.debug(
145           "Removing Attribute: " + cpoAttribute.getJavaName() + ":" + cpoAttribute.getDataName());
146       javaMap.remove(cpoAttribute.getJavaName());
147       removeDataNameFromMap(cpoAttribute.getDataName());
148     }
149   }
150 
151   /**
152    * Gets all function groups defined on this class, keyed internally by CRUD type and group name.
153    *
154    * @return the function groups map
155    */
156   public Map<String, CpoFunctionGroup> getFunctionGroups() {
157     return this.functionGroups;
158   }
159 
160   /**
161    * Gets the function group for the given CRUD type and group name.
162    *
163    * @param crud the CRUD operation type
164    * @param groupName the function group name
165    * @return the matching function group
166    * @throws CpoException if no function group matches {@code crud} and {@code groupName}
167    */
168   public CpoFunctionGroup getFunctionGroup(Crud crud, String groupName) throws CpoException {
169     String key = buildFunctionGroupKey(crud.operation, groupName);
170     CpoFunctionGroup group = functionGroups.get(key);
171     if (group == null) {
172       throw new CpoException("Function Group Not Found: " + crud.operation + ":" + groupName);
173     }
174     return group;
175   }
176 
177   /**
178    * Gets whether a function group exists for the given CRUD type and group name.
179    *
180    * @param crud the CRUD operation type
181    * @param groupName the function group name
182    * @return {@code true} if a matching function group exists, {@code false} otherwise
183    * @throws CpoException if the lookup fails
184    */
185   public boolean existsFunctionGroup(Crud crud, String groupName) throws CpoException {
186     String key = buildFunctionGroupKey(crud.operation, groupName);
187     CpoFunctionGroup group = functionGroups.get(key);
188     return group != null;
189   }
190 
191   /**
192    * Adds a function group to this class, keyed by its CRUD type and name. A no-op (returning {@code
193    * null}) if {@code group} is {@code null}.
194    *
195    * @param group the function group to add
196    * @return the function group previously registered under the same key, or {@code null} if none
197    */
198   public CpoFunctionGroup addFunctionGroup(CpoFunctionGroup group) {
199     if (group == null) {
200       return null;
201     }
202 
203     String key = buildFunctionGroupKey(group.getType(), group.getName());
204     logger.debug("Adding function group: " + key);
205     return this.functionGroups.put(key, group);
206   }
207 
208   /**
209    * Removes a function group from this class. A no-op if {@code group} is {@code null}.
210    *
211    * @param group the function group to remove
212    */
213   public void removeFunctionGroup(CpoFunctionGroup group) {
214     if (group != null) {
215       String key = buildFunctionGroupKey(group.getType(), group.getName());
216       functionGroups.remove(key);
217     }
218   }
219 
220   private String buildFunctionGroupKey(String groupType, String groupName) {
221     StringBuilder builder = new StringBuilder();
222     if (groupType != null) {
223       builder.append(groupType);
224     }
225     builder.append("@");
226     if (groupName != null) {
227       builder.append(groupName);
228     }
229     return builder.toString();
230   }
231 
232   /**
233    * {@inheritDoc}
234    *
235    * <p>Orders classes by {@link #getName()}.
236    */
237   @Override
238   public int compareTo(CpoClass anotherCpoClass) {
239     return getName().compareTo(anotherCpoClass.getName());
240   }
241 
242   /**
243    * {@inheritDoc}
244    *
245    * <p>Visits this class, then its attributes (sorted by Java name), then each function group
246    * (sorted by name) together with its functions and each function's arguments, all in their
247    * natural/declared order.
248    */
249   @Override
250   public void acceptMetaDFVisitor(MetaVisitor visitor) {
251     visitor.visit(this);
252 
253     // visit attributes -- need these sorted
254     TreeMap<String, CpoAttribute> attributeMap = new TreeMap<>(javaMap);
255     for (CpoAttribute cpoAttribute : attributeMap.values()) {
256       visitor.visit(cpoAttribute);
257     }
258 
259     // visit function groups -- need these sorted
260     TreeMap<String, CpoFunctionGroup> functionGroupMap = new TreeMap<>(functionGroups);
261     for (CpoFunctionGroup cpoFunctionGroup : functionGroupMap.values()) {
262       visitor.visit(cpoFunctionGroup);
263 
264       // visit the functions
265       List<CpoFunction> functions = cpoFunctionGroup.getFunctions();
266       if (functions != null) {
267         for (CpoFunction cpoFunction : functions) {
268           visitor.visit(cpoFunction);
269 
270           // visit the arguments
271           List<CpoArgument> arguments = cpoFunction.getArguments();
272           if (arguments != null) {
273             for (CpoArgument cpoArgument : arguments) {
274               visitor.visit(cpoArgument);
275             }
276           }
277         }
278       }
279     }
280   }
281 
282   /**
283    * Resolves this class's reflective metadata (the concrete Java class, and each attribute's
284    * getter/setter/transform) against the classpath, if not already resolved. Idempotent and
285    * thread-safe; subsequent calls after the first successful resolution are no-ops.
286    *
287    * @param metaDescriptor the owning descriptor, passed through to each attribute's resolution
288    * @throws CpoException if the class cannot be found, or an attribute cannot be resolved
289    */
290   public void loadRunTimeInfo(CpoMetaDescriptor metaDescriptor) throws CpoException {
291     lock.lock();
292     try {
293       if (metaClass == null) {
294         Class<?> tmpMetaClass = null;
295 
296         try {
297           logger.debug("Loading runtimeinfo for " + getName());
298           tmpMetaClass = CpoClassLoader.forName(getName());
299         } catch (ClassNotFoundException cnfe) {
300           throw new CpoException(
301               "Class not found: " + getName() + ": " + ExceptionHelper.getLocalizedMessage(cnfe));
302         }
303 
304         for (CpoAttribute attribute : javaMap.values()) {
305           attribute.loadRunTimeInfo(metaDescriptor, tmpMetaClass);
306         }
307         logger.debug("Loaded runtimeinfo for " + getName());
308 
309         metaClass = tmpMetaClass;
310       }
311     } finally {
312       lock.unlock();
313     }
314   }
315 
316   /**
317    * Gets the attribute bound to the given JavaBean property name.
318    *
319    * @param javaName the JavaBean property name to look up
320    * @return the matching attribute, or {@code null} if {@code javaName} is {@code null} or no
321    *     attribute is bound to it
322    */
323   public CpoAttribute getAttributeJava(String javaName) {
324     if (javaName == null) {
325       return null;
326     }
327     return javaMap.get(javaName);
328   }
329 
330   /**
331    * {@inheritDoc}
332    *
333    * <p>Returns this class's {@link #getName() name}.
334    */
335   @Override
336   public String toString() {
337     return this.getName();
338   }
339 
340   /**
341    * Gets the full field-by-field string representation of this class, as produced by {@link
342    * CpoClassBean#toString()}.
343    *
344    * @return the full string representation
345    */
346   public String toStringFull() {
347     return super.toString();
348   }
349 
350   /** Clears this class's attribute and function group maps, discarding all metadata. */
351   public void emptyMaps() {
352     javaMap.clear();
353     dataMap.clear();
354     functionGroups.clear();
355   }
356 }