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 }