1
+ /**
2
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
3
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
4
+ * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5
+ * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6
+ *
7
+ * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8
+ * graphic logo is a trademark of OpenMRS Inc.
9
+ */
10
+ package org .openmrs .module .webservices .rest .web .api .impl ;
11
+
12
+ import java .beans .PropertyDescriptor ;
13
+ import java .lang .reflect .Field ;
14
+ import java .lang .reflect .Method ;
15
+ import java .lang .reflect .Modifier ;
16
+ import java .lang .reflect .ParameterizedType ;
17
+ import java .lang .reflect .Type ;
18
+ import java .util .HashMap ;
19
+ import java .util .Map ;
20
+
21
+ import org .apache .commons .logging .Log ;
22
+ import org .apache .commons .logging .LogFactory ;
23
+ import org .openmrs .api .impl .BaseOpenmrsService ;
24
+ import org .openmrs .module .webservices .rest .web .api .SchemaIntrospectionService ;
25
+ import org .openmrs .module .webservices .rest .web .resource .api .Resource ;
26
+ import org .openmrs .module .webservices .rest .web .resource .impl .DelegatingResourceHandler ;
27
+ import org .springframework .beans .BeanUtils ;
28
+ import org .springframework .stereotype .Component ;
29
+
30
+ /**
31
+ * Default implementation of {@link SchemaIntrospectionService}
32
+ */
33
+ @ Component
34
+ public class SchemaIntrospectionServiceImpl extends BaseOpenmrsService implements SchemaIntrospectionService {
35
+
36
+ protected final Log log = LogFactory .getLog (getClass ());
37
+
38
+ /**
39
+ * @see org.openmrs.module.webservices.rest.web.api.SchemaIntrospectionService#getDelegateType(Resource)
40
+ */
41
+ @ Override
42
+ public Class <?> getDelegateType (Resource resource ) {
43
+ if (resource == null ) {
44
+ return null ;
45
+ }
46
+
47
+ // Handle non-delegating resources
48
+ if (!(resource instanceof DelegatingResourceHandler )) {
49
+ log .warn ("Resource " + resource .getClass ().getName () + " is not a DelegatingResourceHandler" );
50
+ return null ;
51
+ }
52
+
53
+ // Attempt to determine the delegate type from the generic parameter T in DelegatingCrudResource<T>
54
+ // or DelegatingSubResource<T, P, PR>
55
+ Class <?> resourceClass = resource .getClass ();
56
+
57
+ // Search through the class hierarchy to find the class that implements/extends with the generic type
58
+ while (resourceClass != null ) {
59
+ Type [] genericInterfaces = resourceClass .getGenericInterfaces ();
60
+ for (Type genericInterface : genericInterfaces ) {
61
+ if (genericInterface instanceof ParameterizedType ) {
62
+ ParameterizedType parameterizedType = (ParameterizedType ) genericInterface ;
63
+ Type rawType = parameterizedType .getRawType ();
64
+
65
+ // Check if this interface is DelegatingResourceHandler or a subinterface of it
66
+ if (rawType instanceof Class
67
+ && DelegatingResourceHandler .class .isAssignableFrom ((Class <?>) rawType )) {
68
+ // First type parameter should be the delegate type
69
+ Type [] typeArgs = parameterizedType .getActualTypeArguments ();
70
+ if (typeArgs .length > 0 && typeArgs [0 ] instanceof Class ) {
71
+ return (Class <?>) typeArgs [0 ];
72
+ }
73
+ }
74
+ }
75
+ }
76
+
77
+ // Check the superclass's generic type
78
+ Type genericSuperclass = resourceClass .getGenericSuperclass ();
79
+ if (genericSuperclass instanceof ParameterizedType ) {
80
+ ParameterizedType parameterizedType = (ParameterizedType ) genericSuperclass ;
81
+ Type [] typeArgs = parameterizedType .getActualTypeArguments ();
82
+
83
+ // First type argument should be the delegate type
84
+ if (typeArgs .length > 0 && typeArgs [0 ] instanceof Class ) {
85
+ return (Class <?>) typeArgs [0 ];
86
+ }
87
+ }
88
+
89
+ // Move up the hierarchy
90
+ resourceClass = resourceClass .getSuperclass ();
91
+ }
92
+
93
+ log .warn ("Could not determine delegate type for " + resource .getClass ().getName ());
94
+ return null ;
95
+ }
96
+
97
+ /**
98
+ * @see org.openmrs.module.webservices.rest.web.api.SchemaIntrospectionService#discoverAvailableProperties(Class)
99
+ */
100
+ @ Override
101
+ public Map <String , String > discoverAvailableProperties (Class <?> delegateType ) {
102
+ if (delegateType == null ) {
103
+ return new HashMap <String , String >();
104
+ }
105
+
106
+ Map <String , String > properties = new HashMap <String , String >();
107
+
108
+ // Process fields up through the class hierarchy
109
+ Class <?> currentClass = delegateType ;
110
+ while (currentClass != null && !currentClass .equals (Object .class )) {
111
+ processFields (currentClass , properties );
112
+ currentClass = currentClass .getSuperclass ();
113
+ }
114
+
115
+ // Process getters - use Spring's BeanUtils to get all property descriptors
116
+ PropertyDescriptor [] propertyDescriptors = BeanUtils .getPropertyDescriptors (delegateType );
117
+ for (PropertyDescriptor descriptor : propertyDescriptors ) {
118
+ // Skip "class" and properties without a readable method (getter)
119
+ if ("class" .equals (descriptor .getName ()) || descriptor .getReadMethod () == null ) {
120
+ continue ;
121
+ }
122
+
123
+ Method readMethod = descriptor .getReadMethod ();
124
+
125
+ // Only include public methods
126
+ if (Modifier .isPublic (readMethod .getModifiers ()) && !Modifier .isStatic (readMethod .getModifiers ())) {
127
+ // Get return type, including generic type if available
128
+ String typeName = getTypeName (readMethod .getGenericReturnType ());
129
+ properties .put (descriptor .getName (), typeName );
130
+ }
131
+ }
132
+
133
+ return properties ;
134
+ }
135
+
136
+ /**
137
+ * @see org.openmrs.module.webservices.rest.web.api.SchemaIntrospectionService#discoverResourceProperties(Resource)
138
+ */
139
+ @ Override
140
+ public Map <String , String > discoverResourceProperties (Resource resource ) {
141
+ Class <?> delegateType = getDelegateType (resource );
142
+ return discoverAvailableProperties (delegateType );
143
+ }
144
+
145
+ /**
146
+ * Helper method to process fields from a class and add them to the properties map
147
+ *
148
+ * @param clazz The class to process fields from
149
+ * @param properties The map to add properties to
150
+ */
151
+ private void processFields (Class <?> clazz , Map <String , String > properties ) {
152
+ Field [] fields = clazz .getDeclaredFields ();
153
+ for (Field field : fields ) {
154
+ // Only include public instance fields
155
+ if (Modifier .isPublic (field .getModifiers ()) && !Modifier .isStatic (field .getModifiers ())) {
156
+ String typeName = getTypeName (field .getGenericType ());
157
+ properties .put (field .getName (), typeName );
158
+ }
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Helper method to get a user-friendly type name from a Type object
164
+ *
165
+ * @param type The type to get a name for
166
+ * @return A user-friendly type name string
167
+ */
168
+ private String getTypeName (Type type ) {
169
+ if (type instanceof Class ) {
170
+ return ((Class <?>) type ).getSimpleName ();
171
+ } else if (type instanceof ParameterizedType ) {
172
+ ParameterizedType paramType = (ParameterizedType ) type ;
173
+ Type rawType = paramType .getRawType ();
174
+ Type [] typeArgs = paramType .getActualTypeArguments ();
175
+
176
+ StringBuilder sb = new StringBuilder ();
177
+ if (rawType instanceof Class ) {
178
+ sb .append (((Class <?>) rawType ).getSimpleName ());
179
+ } else {
180
+ sb .append (rawType .toString ());
181
+ }
182
+
183
+ if (typeArgs .length > 0 ) {
184
+ sb .append ("<" );
185
+ for (int i = 0 ; i < typeArgs .length ; i ++) {
186
+ if (i > 0 ) {
187
+ sb .append (", " );
188
+ }
189
+ if (typeArgs [i ] instanceof Class ) {
190
+ sb .append (((Class <?>) typeArgs [i ]).getSimpleName ());
191
+ } else {
192
+ sb .append (typeArgs [i ].toString ());
193
+ }
194
+ }
195
+ sb .append (">" );
196
+ }
197
+
198
+ return sb .toString ();
199
+ } else {
200
+ return type .toString ();
201
+ }
202
+ }
203
+ }
0 commit comments