From bdc387f1ca53e930a87a4a2af93f36a1af47ef29 Mon Sep 17 00:00:00 2001 From: capernix Date: Thu, 17 Apr 2025 09:39:15 +0530 Subject: [PATCH 01/12] Implemented a method to find all properties of a given resource --- .../web/api/SchemaIntrospectionService.java | 46 ++++ .../impl/SchemaIntrospectionServiceImpl.java | 203 ++++++++++++++++++ .../SchemaIntrospectionController.java | 92 ++++++++ 3 files changed, 341 insertions(+) create mode 100644 omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/SchemaIntrospectionService.java create mode 100644 omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImpl.java create mode 100644 omod-common/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionController.java diff --git a/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/SchemaIntrospectionService.java b/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/SchemaIntrospectionService.java new file mode 100644 index 000000000..f88731320 --- /dev/null +++ b/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/SchemaIntrospectionService.java @@ -0,0 +1,46 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under + * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. + * + * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS + * graphic logo is a trademark of OpenMRS Inc. + */ +package org.openmrs.module.webservices.rest.web.api; + +import java.util.Map; + +import org.openmrs.module.webservices.rest.web.resource.api.Resource; + +/** + * Service for introspecting resource properties via reflection. + * This enables clients to discover all properties that may be available in custom representations. + */ +public interface SchemaIntrospectionService { + + /** + * Gets the delegate type (T) for a resource that extends DelegatingCrudResource. + * + * @param resource The resource whose delegate class we want to determine + * @return The class type of the delegate, or null if not determinable + */ + Class getDelegateType(Resource resource); + + /** + * Discovers all available properties on a delegate type using reflection. + * This includes public instance fields and JavaBean-style getter methods. + * + * @param delegateType The class to introspect for properties + * @return A map of property names to their Java type names + */ + Map discoverAvailableProperties(Class delegateType); + + /** + * Combines getDelegateType and discoverAvailableProperties for convenience. + * + * @param resource The resource to introspect + * @return A map of property names to their Java type names + */ + Map discoverResourceProperties(Resource resource); +} \ No newline at end of file diff --git a/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImpl.java b/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImpl.java new file mode 100644 index 000000000..312f36cf2 --- /dev/null +++ b/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImpl.java @@ -0,0 +1,203 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under + * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. + * + * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS + * graphic logo is a trademark of OpenMRS Inc. + */ +package org.openmrs.module.webservices.rest.web.api.impl; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.openmrs.api.impl.BaseOpenmrsService; +import org.openmrs.module.webservices.rest.web.api.SchemaIntrospectionService; +import org.openmrs.module.webservices.rest.web.resource.api.Resource; +import org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceHandler; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Component; + +/** + * Default implementation of {@link SchemaIntrospectionService} + */ +@Component +public class SchemaIntrospectionServiceImpl extends BaseOpenmrsService implements SchemaIntrospectionService { + + protected final Log log = LogFactory.getLog(getClass()); + + /** + * @see org.openmrs.module.webservices.rest.web.api.SchemaIntrospectionService#getDelegateType(Resource) + */ + @Override + public Class getDelegateType(Resource resource) { + if (resource == null) { + return null; + } + + // Handle non-delegating resources + if (!(resource instanceof DelegatingResourceHandler)) { + log.warn("Resource " + resource.getClass().getName() + " is not a DelegatingResourceHandler"); + return null; + } + + // Attempt to determine the delegate type from the generic parameter T in DelegatingCrudResource + // or DelegatingSubResource + Class resourceClass = resource.getClass(); + + // Search through the class hierarchy to find the class that implements/extends with the generic type + while (resourceClass != null) { + Type[] genericInterfaces = resourceClass.getGenericInterfaces(); + for (Type genericInterface : genericInterfaces) { + if (genericInterface instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) genericInterface; + Type rawType = parameterizedType.getRawType(); + + // Check if this interface is DelegatingResourceHandler or a subinterface of it + if (rawType instanceof Class + && DelegatingResourceHandler.class.isAssignableFrom((Class) rawType)) { + // First type parameter should be the delegate type + Type[] typeArgs = parameterizedType.getActualTypeArguments(); + if (typeArgs.length > 0 && typeArgs[0] instanceof Class) { + return (Class) typeArgs[0]; + } + } + } + } + + // Check the superclass's generic type + Type genericSuperclass = resourceClass.getGenericSuperclass(); + if (genericSuperclass instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass; + Type[] typeArgs = parameterizedType.getActualTypeArguments(); + + // First type argument should be the delegate type + if (typeArgs.length > 0 && typeArgs[0] instanceof Class) { + return (Class) typeArgs[0]; + } + } + + // Move up the hierarchy + resourceClass = resourceClass.getSuperclass(); + } + + log.warn("Could not determine delegate type for " + resource.getClass().getName()); + return null; + } + + /** + * @see org.openmrs.module.webservices.rest.web.api.SchemaIntrospectionService#discoverAvailableProperties(Class) + */ + @Override + public Map discoverAvailableProperties(Class delegateType) { + if (delegateType == null) { + return new HashMap(); + } + + Map properties = new HashMap(); + + // Process fields up through the class hierarchy + Class currentClass = delegateType; + while (currentClass != null && !currentClass.equals(Object.class)) { + processFields(currentClass, properties); + currentClass = currentClass.getSuperclass(); + } + + // Process getters - use Spring's BeanUtils to get all property descriptors + PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(delegateType); + for (PropertyDescriptor descriptor : propertyDescriptors) { + // Skip "class" and properties without a readable method (getter) + if ("class".equals(descriptor.getName()) || descriptor.getReadMethod() == null) { + continue; + } + + Method readMethod = descriptor.getReadMethod(); + + // Only include public methods + if (Modifier.isPublic(readMethod.getModifiers()) && !Modifier.isStatic(readMethod.getModifiers())) { + // Get return type, including generic type if available + String typeName = getTypeName(readMethod.getGenericReturnType()); + properties.put(descriptor.getName(), typeName); + } + } + + return properties; + } + + /** + * @see org.openmrs.module.webservices.rest.web.api.SchemaIntrospectionService#discoverResourceProperties(Resource) + */ + @Override + public Map discoverResourceProperties(Resource resource) { + Class delegateType = getDelegateType(resource); + return discoverAvailableProperties(delegateType); + } + + /** + * Helper method to process fields from a class and add them to the properties map + * + * @param clazz The class to process fields from + * @param properties The map to add properties to + */ + private void processFields(Class clazz, Map properties) { + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + // Only include public instance fields + if (Modifier.isPublic(field.getModifiers()) && !Modifier.isStatic(field.getModifiers())) { + String typeName = getTypeName(field.getGenericType()); + properties.put(field.getName(), typeName); + } + } + } + + /** + * Helper method to get a user-friendly type name from a Type object + * + * @param type The type to get a name for + * @return A user-friendly type name string + */ + private String getTypeName(Type type) { + if (type instanceof Class) { + return ((Class) type).getSimpleName(); + } else if (type instanceof ParameterizedType) { + ParameterizedType paramType = (ParameterizedType) type; + Type rawType = paramType.getRawType(); + Type[] typeArgs = paramType.getActualTypeArguments(); + + StringBuilder sb = new StringBuilder(); + if (rawType instanceof Class) { + sb.append(((Class) rawType).getSimpleName()); + } else { + sb.append(rawType.toString()); + } + + if (typeArgs.length > 0) { + sb.append("<"); + for (int i = 0; i < typeArgs.length; i++) { + if (i > 0) { + sb.append(", "); + } + if (typeArgs[i] instanceof Class) { + sb.append(((Class) typeArgs[i]).getSimpleName()); + } else { + sb.append(typeArgs[i].toString()); + } + } + sb.append(">"); + } + + return sb.toString(); + } else { + return type.toString(); + } + } +} \ No newline at end of file diff --git a/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionController.java b/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionController.java new file mode 100644 index 000000000..6f29139f3 --- /dev/null +++ b/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionController.java @@ -0,0 +1,92 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under + * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. + * + * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS + * graphic logo is a trademark of OpenMRS Inc. + */ +package org.openmrs.module.webservices.rest.web.v1_0.controller; + +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.openmrs.module.webservices.rest.SimpleObject; +import org.openmrs.module.webservices.rest.web.RestConstants; +import org.openmrs.module.webservices.rest.web.api.RestService; +import org.openmrs.module.webservices.rest.web.api.SchemaIntrospectionService; +import org.openmrs.module.webservices.rest.web.resource.api.Resource; +import org.openmrs.module.webservices.rest.web.response.ObjectNotFoundException; +import org.openmrs.module.webservices.rest.web.response.ResponseException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * Controller that provides schema introspection for REST resources. + * This allows clients to discover what properties are available for a given resource. + */ +@Controller +@RequestMapping(value = "/rest/" + RestConstants.VERSION_1 + "/schemaintrospector") +public class SchemaIntrospectionController extends BaseRestController { + + protected final Log log = LogFactory.getLog(getClass()); + + @Autowired + private RestService restService; + + @Autowired + private SchemaIntrospectionService schemaIntrospectionService; + + /** + * Gets all properties available on a resource's delegate domain object + * + * @param resourceName The name of the resource to introspect + * @return A JSON object containing the resource properties + * @throws ResponseException + */ + @RequestMapping(value = "/{resourceName}", method = RequestMethod.GET) + @ResponseBody + public SimpleObject getResourceProperties(@PathVariable("resourceName") String resourceName) throws ResponseException { + // Get the resource by name with proper prefix + Resource resource; + try { + // Try with the full path (which is how resources are registered) + String fullResourceName = RestConstants.VERSION_1 + "/" + resourceName; + log.debug("Looking for resource with name: " + fullResourceName); + resource = restService.getResourceByName(fullResourceName); + } + catch (Exception e) { + log.error("Failed to find resource with name: " + resourceName, e); + throw new ObjectNotFoundException("Resource '" + resourceName + "' was not found"); + } + + if (resource == null) { + log.error("Resource is null for name: " + resourceName); + throw new ObjectNotFoundException("Resource '" + resourceName + "' was not found"); + } + + // Get the delegate type for the resource + Class delegateType = schemaIntrospectionService.getDelegateType(resource); + if (delegateType == null) { + log.error("Could not determine delegate type for resource: " + resourceName); + throw new ObjectNotFoundException("Could not determine delegate type for resource '" + resourceName + "'"); + } + + // Get properties for the delegate type + Map properties = schemaIntrospectionService.discoverAvailableProperties(delegateType); + + // Build the response + SimpleObject result = new SimpleObject(); + result.add("resourceName", resourceName); + result.add("delegateType", delegateType.getName()); + result.add("discoverableProperties", properties); + + return result; + } +} \ No newline at end of file From 9dee07ac3337274e911acb523af97ab010622d16 Mon Sep 17 00:00:00 2001 From: capernix Date: Thu, 17 Apr 2025 20:48:11 +0530 Subject: [PATCH 02/12] Added in tests to validate Schema Introspection --- .../SchemaIntrospectionServiceImplTest.java | 277 ++++++++++++++++++ .../SchemaIntrospectionControllerTest.java | 131 +++++++++ .../controller/SchemaIntrospectionTest.java | 82 ++++++ 3 files changed, 490 insertions(+) create mode 100644 omod-common/src/test/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImplTest.java create mode 100644 omod-common/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionControllerTest.java create mode 100644 omod-common/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionTest.java diff --git a/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImplTest.java b/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImplTest.java new file mode 100644 index 000000000..958dd2e4e --- /dev/null +++ b/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImplTest.java @@ -0,0 +1,277 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under + * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. + * + * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS + * graphic logo is a trademark of OpenMRS Inc. + */ +package org.openmrs.module.webservices.rest.web.api.impl; + +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.hasSize; + +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.openmrs.Patient; +import org.openmrs.Person; +import org.openmrs.module.webservices.rest.web.RequestContext; +import org.openmrs.module.webservices.rest.web.resource.api.Resource; +import org.openmrs.module.webservices.rest.web.resource.impl.DelegatingCrudResource; +import org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription; +import org.openmrs.module.webservices.rest.web.resource.impl.DelegatingSubResource; +import org.openmrs.module.webservices.rest.web.representation.Representation; +import org.openmrs.module.webservices.rest.web.response.ResourceDoesNotSupportOperationException; + +/** + * Tests for {@link SchemaIntrospectionServiceImpl}. + */ +public class SchemaIntrospectionServiceImplTest { + + private SchemaIntrospectionServiceImpl service; + + @Before + public void setup() { + service = new SchemaIntrospectionServiceImpl(); + } + + /** + * @see SchemaIntrospectionServiceImpl#getDelegateType(Resource) + */ + @Test + public void getDelegateType_shouldReturnNullForNullResource() { + // Test with null resource + assertThat(service.getDelegateType(null), is(nullValue())); + } + + /** + * @see SchemaIntrospectionServiceImpl#getDelegateType(Resource) + */ + @Test + public void getDelegateType_shouldReturnCorrectTypeForDelegatingCrudResource() { + // Test with a mock DelegatingCrudResource + TestPatientResource resource = new TestPatientResource(); + Class delegateType = service.getDelegateType(resource); + + assertThat(delegateType, is(notNullValue())); + assertThat(delegateType.getName(), is(Patient.class.getName())); + } + + /** + * @see SchemaIntrospectionServiceImpl#getDelegateType(Resource) + */ + @Test + public void getDelegateType_shouldReturnCorrectTypeForDelegatingSubResource() { + // Test with a mock DelegatingSubResource + TestPatientSubResource resource = new TestPatientSubResource(); + Class delegateType = service.getDelegateType(resource); + + assertThat(delegateType, is(notNullValue())); + assertThat(delegateType.getName(), is(Patient.class.getName())); + } + + /** + * @see SchemaIntrospectionServiceImpl#discoverAvailableProperties(Class) + */ + @Test + public void discoverAvailableProperties_shouldReturnEmptyMapForNullType() { + Map properties = service.discoverAvailableProperties(null); + + assertThat(properties, is(notNullValue())); + assertThat(properties.size(), is(0)); + } + + /** + * @see SchemaIntrospectionServiceImpl#discoverAvailableProperties(Class) + */ + @Test + public void discoverAvailableProperties_shouldIncludePropertiesFromGetters() { + Map properties = service.discoverAvailableProperties(Patient.class); + + // Test a few essential properties that should be discovered via getters + assertThat(properties, hasKey("uuid")); + assertThat(properties, hasKey("patientId")); + assertThat(properties, hasKey("identifiers")); + assertThat(properties, hasKey("person")); + assertThat(properties, hasKey("voided")); + } + + /** + * @see SchemaIntrospectionServiceImpl#discoverAvailableProperties(Class) + */ + @Test + public void discoverAvailableProperties_shouldIncludePropertiesFromSuperclasses() { + Map properties = service.discoverAvailableProperties(Patient.class); + + // Patient extends Person, so should have Person properties + assertThat(properties, hasKey("gender")); + assertThat(properties, hasKey("age")); + assertThat(properties, hasKey("birthdate")); + assertThat(properties, hasKey("names")); + assertThat(properties, hasKey("addresses")); + } + + /** + * @see SchemaIntrospectionServiceImpl#discoverAvailableProperties(Class) + */ + @Test + public void discoverAvailableProperties_shouldIncludePropertiesWithCorrectTypes() { + Map properties = service.discoverAvailableProperties(Patient.class); + + // Verify some property types + assertThat(properties, hasEntry("uuid", "String")); + assertThat(properties, hasEntry("voided", "Boolean")); + // Set of PatientIdentifier + assertThat(properties.get("identifiers").contains("Set"), is(true)); + } + + /** + * @see SchemaIntrospectionServiceImpl#discoverResourceProperties(Resource) + */ + @Test + public void discoverResourceProperties_shouldCombineGetDelegateTypeAndDiscoverAvailableProperties() { + TestPatientResource resource = new TestPatientResource(); + Map properties = service.discoverResourceProperties(resource); + + // Verify it discovered Patient properties + assertThat(properties, hasKey("uuid")); + assertThat(properties, hasKey("patientId")); + assertThat(properties, hasKey("identifiers")); + + // And Person properties (from superclass) + assertThat(properties, hasKey("gender")); + assertThat(properties, hasKey("birthdate")); + } + + /** + * Mock DelegatingCrudResource for testing + */ + private class TestPatientResource extends DelegatingCrudResource { + + @Override + public Patient newDelegate() { + return new Patient(); + } + + @Override + public Patient save(Patient delegate) { + return delegate; + } + + @Override + public Patient getByUniqueId(String uniqueId) { + return new Patient(); + } + + @Override + public void purge(Patient delegate, RequestContext context) { + } + + @Override + public DelegatingResourceDescription getRepresentationDescription(Representation rep) { + return null; + } + + @Override + public void delete(Patient delegate, String reason, RequestContext context) throws ResourceDoesNotSupportOperationException { + throw new ResourceDoesNotSupportOperationException(); + } + } + + /** + * Mock DelegatingSubResource for testing + */ + private class TestPatientSubResource extends DelegatingSubResource { + + @Override + public Patient getByUniqueId(String uniqueId) { + return new Patient(); + } + + @Override + public Patient newDelegate() { + return new Patient(); + } + + @Override + public Patient save(Patient delegate) { + return delegate; + } + + @Override + public void purge(Patient delegate, RequestContext context) { + } + + @Override + public Person getParent(Patient instance) { + return instance.getPerson(); + } + + @Override + public void setParent(Patient instance, Person parent) { + // In actual Patient class, the setPerson method is generated by Hibernate + // For our test, we'll just ignore the call + } + + @Override + public DelegatingResourceDescription getRepresentationDescription(Representation rep) { + return null; + } + + @Override + public org.openmrs.module.webservices.rest.web.resource.api.PageableResult doGetAll( + Person parent, + RequestContext context) { + return null; + } + + @Override + public void delete(Patient delegate, String reason, RequestContext context) throws ResourceDoesNotSupportOperationException { + throw new ResourceDoesNotSupportOperationException(); + } + } + + /** + * Mock TestPersonResource for testing sub-resources + */ + private class TestPersonResource extends DelegatingCrudResource { + + @Override + public Person newDelegate() { + return new Person(); + } + + @Override + public Person save(Person delegate) { + return delegate; + } + + @Override + public Person getByUniqueId(String uniqueId) { + return new Person(); + } + + @Override + public void purge(Person delegate, RequestContext context) { + } + + @Override + public DelegatingResourceDescription getRepresentationDescription(Representation rep) { + return null; + } + + @Override + public void delete(Person delegate, String reason, RequestContext context) throws ResourceDoesNotSupportOperationException { + throw new ResourceDoesNotSupportOperationException(); + } + } +} \ No newline at end of file diff --git a/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionControllerTest.java b/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionControllerTest.java new file mode 100644 index 000000000..4fd48308e --- /dev/null +++ b/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionControllerTest.java @@ -0,0 +1,131 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under + * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. + * + * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS + * graphic logo is a trademark of OpenMRS Inc. + */ +package org.openmrs.module.webservices.rest.web.v1_0.controller; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.openmrs.Patient; +import org.openmrs.module.webservices.rest.SimpleObject; +import org.openmrs.module.webservices.rest.web.RestConstants; +import org.openmrs.module.webservices.rest.web.api.RestService; +import org.openmrs.module.webservices.rest.web.api.SchemaIntrospectionService; +import org.openmrs.module.webservices.rest.web.resource.api.Resource; +import org.openmrs.module.webservices.rest.web.response.ObjectNotFoundException; + +/** + * Tests for {@link SchemaIntrospectionController}. + */ +public class SchemaIntrospectionControllerTest { + + @Mock + private RestService restService; + + @Mock + private SchemaIntrospectionService schemaIntrospectionService; + + @InjectMocks + private SchemaIntrospectionController controller; + + private Resource mockResource; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mockResource = mock(Resource.class); + } + + /** + * @see SchemaIntrospectionController#getResourceProperties(String) + */ + @Test + public void getResourceProperties_shouldReturnPropertiesForValidResource() throws Exception { + // Setup mock behaviors + when(restService.getResourceByName(RestConstants.VERSION_1 + "/patient")).thenReturn(mockResource); + when(schemaIntrospectionService.getDelegateType(mockResource)).thenReturn((Class) Patient.class); + + // Setup mock properties + Map mockProperties = new HashMap(); + mockProperties.put("uuid", "String"); + mockProperties.put("patientId", "Integer"); + mockProperties.put("gender", "String"); + mockProperties.put("voided", "Boolean"); + + when(schemaIntrospectionService.discoverAvailableProperties(Patient.class)).thenReturn(mockProperties); + + // Call the controller method + SimpleObject result = controller.getResourceProperties("patient"); + + // Verify the result + assertThat(result, is(notNullValue())); + assertThat((String) result.get("resourceName"), is(equalTo("patient"))); + assertThat((String) result.get("delegateType"), is(equalTo(Patient.class.getName()))); + + // Verify the properties were included + @SuppressWarnings("unchecked") + Map resultProperties = (Map) result.get("discoverableProperties"); + assertThat(resultProperties, is(notNullValue())); + assertThat(resultProperties.size(), is(equalTo(4))); + assertThat(resultProperties.get("uuid"), is(equalTo("String"))); + assertThat(resultProperties.get("patientId"), is(equalTo("Integer"))); + assertThat(resultProperties.get("gender"), is(equalTo("String"))); + assertThat(resultProperties.get("voided"), is(equalTo("Boolean"))); + } + + /** + * @see SchemaIntrospectionController#getResourceProperties(String) + */ + @Test(expected = ObjectNotFoundException.class) + public void getResourceProperties_shouldThrowExceptionForNonExistentResource() throws Exception { + // Setup mock behavior for non-existent resource + when(restService.getResourceByName(anyString())).thenReturn(null); + + // This should throw ObjectNotFoundException + controller.getResourceProperties("nonexistent"); + } + + /** + * @see SchemaIntrospectionController#getResourceProperties(String) + */ + @Test(expected = ObjectNotFoundException.class) + public void getResourceProperties_shouldThrowExceptionWhenDelegateTypeCannotBeDetermined() throws Exception { + // Setup mock behaviors where resource is found but delegate type isn't + when(restService.getResourceByName(RestConstants.VERSION_1 + "/patient")).thenReturn(mockResource); + when(schemaIntrospectionService.getDelegateType(mockResource)).thenReturn(null); + + // This should throw ObjectNotFoundException + controller.getResourceProperties("patient"); + } + + /** + * @see SchemaIntrospectionController#getResourceProperties(String) + */ + @Test(expected = ObjectNotFoundException.class) + public void getResourceProperties_shouldThrowExceptionWhenResourceLookupFails() throws Exception { + // Setup mock behavior to throw an exception during resource lookup + when(restService.getResourceByName(anyString())).thenThrow(new RuntimeException("Resource lookup failed")); + + // This should throw ObjectNotFoundException + controller.getResourceProperties("patient"); + } +} \ No newline at end of file diff --git a/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionTest.java b/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionTest.java new file mode 100644 index 000000000..69f5e9384 --- /dev/null +++ b/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionTest.java @@ -0,0 +1,82 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under + * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. + * + * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS + * graphic logo is a trademark of OpenMRS Inc. + */ +package org.openmrs.module.webservices.rest.web.v1_0.controller; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.hasKey; + +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.openmrs.Patient; +import org.openmrs.module.webservices.rest.web.api.impl.SchemaIntrospectionServiceImpl; + +/** + * Tests for schema introspection functionality. + * This is a standard unit test rather than an integration test to avoid Spring context issues. + */ +public class SchemaIntrospectionTest { + + private SchemaIntrospectionServiceImpl service; + + @Before + public void setup() { + service = new SchemaIntrospectionServiceImpl(); + } + + /** + * Tests that reflection discovers expected properties on Patient + */ + @Test + public void shouldDiscoverPatientProperties() { + Map properties = service.discoverAvailableProperties(Patient.class); + + // Verify discovered properties + assertThat(properties, is(notNullValue())); + assertThat(properties.size(), is(greaterThan(10))); // Should have many properties + + // Check some essential Patient properties + assertThat(properties, hasKey("uuid")); + assertThat(properties, hasKey("patientId")); + assertThat(properties, hasKey("identifiers")); + assertThat(properties, hasKey("person")); + + // Check some properties inherited from Person + assertThat(properties, hasKey("gender")); + assertThat(properties, hasKey("birthdate")); + assertThat(properties, hasKey("names")); + assertThat(properties, hasKey("addresses")); + } + + /** + * Tests property discovery for another common class + */ + @Test + public void shouldDiscoverPropertiesFromGetters() { + // Use a simple class from the OpenMRS API + Map properties = service.discoverAvailableProperties(org.openmrs.Location.class); + + // Verify some expected properties + assertThat(properties, is(notNullValue())); + assertThat(properties.size(), is(greaterThan(5))); + assertThat(properties, hasKey("uuid")); + assertThat(properties, hasKey("name")); + assertThat(properties, hasKey("description")); + assertThat(properties, hasKey("address1")); // Fixed: Location uses address1, address2, etc. + assertThat(properties, hasKey("address2")); // Added another address field to verify + assertThat(properties, hasKey("parentLocation")); + assertThat(properties, hasKey("childLocations")); + assertThat(properties, hasKey("retired")); + } +} \ No newline at end of file From b2f519f2796792a1fb72dd0e36d50e62ba772a49 Mon Sep 17 00:00:00 2001 From: capernix Date: Wed, 30 Apr 2025 20:05:50 +0530 Subject: [PATCH 03/12] Added the feature of including properties from @PropertyGetter as well --- .../impl/SchemaIntrospectionServiceImpl.java | 49 +++++++++++- .../SchemaIntrospectionServiceImplTest.java | 80 +++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) diff --git a/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImpl.java b/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImpl.java index 312f36cf2..034549b88 100644 --- a/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImpl.java +++ b/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImpl.java @@ -21,6 +21,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openmrs.api.impl.BaseOpenmrsService; +import org.openmrs.module.webservices.rest.web.annotation.PropertyGetter; +import org.openmrs.module.webservices.rest.web.annotation.PropertySetter; import org.openmrs.module.webservices.rest.web.api.SchemaIntrospectionService; import org.openmrs.module.webservices.rest.web.resource.api.Resource; import org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceHandler; @@ -139,7 +141,52 @@ public Map discoverAvailableProperties(Class delegateType) { @Override public Map discoverResourceProperties(Resource resource) { Class delegateType = getDelegateType(resource); - return discoverAvailableProperties(delegateType); + Map properties = discoverAvailableProperties(delegateType); + + // Also discover properties defined by PropertyGetter and PropertySetter annotations + if (resource != null) { + discoverAnnotatedProperties(resource.getClass(), properties); + } + + return properties; + } + + /** + * Discovers properties defined by PropertyGetter and PropertySetter annotations in a resource class + * and its superclasses + * + * @param resourceClass The resource class to scan for annotations + * @param properties The map to add discovered properties to + */ + private void discoverAnnotatedProperties(Class resourceClass, Map properties) { + // Process the current class and all its superclasses + Class currentClass = resourceClass; + while (currentClass != null && !currentClass.equals(Object.class)) { + // Process all methods in the class + for (Method method : currentClass.getDeclaredMethods()) { + // Check for PropertyGetter annotation + PropertyGetter getter = method.getAnnotation(PropertyGetter.class); + if (getter != null) { + String propertyName = getter.value(); + Type returnType = method.getGenericReturnType(); + properties.put(propertyName, getTypeName(returnType)); + } + + // Check for PropertySetter annotation + PropertySetter setter = method.getAnnotation(PropertySetter.class); + if (setter != null && method.getParameterTypes().length > 1) { + String propertyName = setter.value(); + // For setters, use the type of the second parameter (after the delegate) + Type paramType = method.getGenericParameterTypes()[1]; + // Only add if not already discovered through a getter + if (!properties.containsKey(propertyName)) { + properties.put(propertyName, getTypeName(paramType)); + } + } + } + // Move up to the superclass + currentClass = currentClass.getSuperclass(); + } } /** diff --git a/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImplTest.java b/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImplTest.java index 958dd2e4e..56ead9837 100644 --- a/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImplTest.java +++ b/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImplTest.java @@ -18,13 +18,18 @@ import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.hasSize; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import org.junit.Before; import org.junit.Test; import org.openmrs.Patient; +import org.openmrs.PatientIdentifier; import org.openmrs.Person; import org.openmrs.module.webservices.rest.web.RequestContext; +import org.openmrs.module.webservices.rest.web.annotation.PropertyGetter; +import org.openmrs.module.webservices.rest.web.annotation.PropertySetter; import org.openmrs.module.webservices.rest.web.resource.api.Resource; import org.openmrs.module.webservices.rest.web.resource.impl.DelegatingCrudResource; import org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription; @@ -152,6 +157,29 @@ public void discoverResourceProperties_shouldCombineGetDelegateTypeAndDiscoverAv assertThat(properties, hasKey("birthdate")); } + /** + * @see SchemaIntrospectionServiceImpl#discoverResourceProperties(Resource) + */ + @Test + public void discoverResourceProperties_shouldIncludePropertiesFromAnnotations() { + TestPatientResourceWithAnnotations resource = new TestPatientResourceWithAnnotations(); + Map properties = service.discoverResourceProperties(resource); + + // Verify standard properties from the delegate type + assertThat(properties, hasKey("uuid")); + assertThat(properties, hasKey("patientId")); + + // Verify properties defined by @PropertyGetter annotations + assertThat(properties, hasKey("activeIdentifiers")); + assertThat(properties, hasKey("displayName")); + assertThat(properties, hasEntry("activeIdentifiers", "List")); + assertThat(properties, hasEntry("displayName", "String")); + + // Verify properties defined by @PropertySetter annotations + assertThat(properties, hasKey("preferredName")); + assertThat(properties, hasEntry("preferredName", "String")); + } + /** * Mock DelegatingCrudResource for testing */ @@ -187,6 +215,58 @@ public void delete(Patient delegate, String reason, RequestContext context) thro } } + /** + * Mock DelegatingCrudResource for testing PropertyGetter and PropertySetter annotations + */ + private class TestPatientResourceWithAnnotations extends DelegatingCrudResource { + + @PropertyGetter("activeIdentifiers") + public List getActiveIdentifiers(Patient patient) { + return new ArrayList(); + } + + @PropertyGetter("displayName") + public String getDisplayName(Patient patient) { + return patient.getPersonName().getFullName(); + } + + @PropertySetter("preferredName") + public void setPreferredName(Patient patient, String name) { + // Implementation not needed for test + } + + // Standard resource methods + + @Override + public Patient newDelegate() { + return new Patient(); + } + + @Override + public Patient save(Patient delegate) { + return delegate; + } + + @Override + public Patient getByUniqueId(String uniqueId) { + return new Patient(); + } + + @Override + public void purge(Patient delegate, RequestContext context) { + } + + @Override + public DelegatingResourceDescription getRepresentationDescription(Representation rep) { + return null; + } + + @Override + public void delete(Patient delegate, String reason, RequestContext context) throws ResourceDoesNotSupportOperationException { + throw new ResourceDoesNotSupportOperationException(); + } + } + /** * Mock DelegatingSubResource for testing */ From 509bed8e99c2aaf43ac64f6bbe9d61a49e4e4977 Mon Sep 17 00:00:00 2001 From: capernix Date: Mon, 19 May 2025 21:39:34 +0530 Subject: [PATCH 04/12] Added a working Mojo Plugin --- omod/pom.xml | 17 ++++- .../openapi-generator-maven-plugin/README.md | 68 +++++++++++++++++ tools/openapi-generator-maven-plugin/pom.xml | 74 +++++++++++++++++++ .../java/org/openmrs/plugin/HelloMojo.java | 25 +++++++ 4 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 tools/openapi-generator-maven-plugin/README.md create mode 100644 tools/openapi-generator-maven-plugin/pom.xml create mode 100644 tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/HelloMojo.java diff --git a/omod/pom.xml b/omod/pom.xml index aac118259..552ccd0d6 100644 --- a/omod/pom.xml +++ b/omod/pom.xml @@ -105,11 +105,26 @@
${project.parent.basedir}/license-header.txt
- + org.jacoco jacoco-maven-plugin + + + org.openmrs.plugin + openapi-generator-maven-plugin + 1.0-SNAPSHOT + + + say-hello + process-classes + + hello + + + + diff --git a/tools/openapi-generator-maven-plugin/README.md b/tools/openapi-generator-maven-plugin/README.md new file mode 100644 index 000000000..a20d0f55c --- /dev/null +++ b/tools/openapi-generator-maven-plugin/README.md @@ -0,0 +1,68 @@ +# OpenAPI Generator Maven Plugin + +## Overview + +The `openapi-generator-maven-plugin` is a custom Maven plugin designed for the OpenMRS ecosystem. Its primary function is to print a friendly message during the Maven build lifecycle, serving as a foundational tool for future enhancements related to OpenAPI specification generation. + +## Purpose + +This plugin aims to facilitate integration with OpenMRS modules, particularly the `webservices.rest` module, by providing a simple yet effective way to execute custom build actions. + +## Features + +- **Maven Goal**: `hello` +- **Output**: Logs "Hello from OpenMRS Maven Plugin! 🎉" during the build process. +- **Lifecycle Phase**: Can be executed during the `process-classes` or `compile` phase. +- **Integration**: Designed to be easily integrated into OpenMRS modules. + +## Installation + +To install the plugin locally, navigate to the project directory and run: + +```bash +mvn clean install +``` + +This command will compile the plugin and install it into your local Maven repository. + +## Usage + +To use the plugin in your OpenMRS module, add the following configuration to your `pom.xml`: + +```xml + + org.openmrs.plugin + openapi-generator-maven-plugin + 1.0-SNAPSHOT + + + say-hello + process-classes + + hello + + + + +``` + +After adding the plugin configuration, run the build: + +```bash +mvn clean install +``` + +You should see the output: `Hello from OpenMRS Maven Plugin! 🎉` + +## Future Enhancements + +The plugin is intended to evolve with additional features, including: + +- Support for loading resource classes. +- Scanning for annotations like `@Resource` and `@DocumentedResource`. +- Generating OpenAPI JSON specifications. +- Archiving documentation into module artifacts. + +## Contribution + +Contributions to enhance the functionality of this plugin are welcome. Please ensure to follow the project's coding standards and guidelines when submitting changes. \ No newline at end of file diff --git a/tools/openapi-generator-maven-plugin/pom.xml b/tools/openapi-generator-maven-plugin/pom.xml new file mode 100644 index 000000000..4db375907 --- /dev/null +++ b/tools/openapi-generator-maven-plugin/pom.xml @@ -0,0 +1,74 @@ + + 4.0.0 + + org.openmrs.plugin + openapi-generator-maven-plugin + 1.0-SNAPSHOT + maven-plugin + + OpenAPI Generator Maven Plugin + A Maven plugin for generating OpenAPI specifications for OpenMRS modules. + https://example.com/openapi-generator-maven-plugin + + + 1.8 + 1.8 + UTF-8 + + + + + + org.apache.maven + maven-plugin-api + 3.8.6 + provided + + + + + org.apache.maven.plugin-tools + maven-plugin-annotations + 3.6.4 + provided + + + + + org.apache.maven + maven-core + 3.8.6 + provided + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.6.4 + + + default-descriptor + process-classes + + descriptor + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + 1.8 + 1.8 + + + + + \ No newline at end of file diff --git a/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/HelloMojo.java b/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/HelloMojo.java new file mode 100644 index 000000000..616bad563 --- /dev/null +++ b/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/HelloMojo.java @@ -0,0 +1,25 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under + * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. + * + * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS + * graphic logo is a trademark of OpenMRS Inc. + */ + +package org.openmrs.plugin; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; + +@Mojo(name = "hello", defaultPhase = LifecyclePhase.PROCESS_CLASSES) +public class HelloMojo extends AbstractMojo { + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + getLog().info("Hello from OpenMRS Maven Plugin!"); + } +} \ No newline at end of file From 491937924b588084eef3e09a8537c67df516c4de Mon Sep 17 00:00:00 2001 From: capernix Date: Tue, 20 May 2025 22:28:30 +0530 Subject: [PATCH 05/12] Added javaparser PoC successfully --- omod/pom.xml | 5 +- tools/openapi-generator-maven-plugin/pom.xml | 7 ++ .../java/org/openmrs/plugin/HelloMojo.java | 67 +++++++++++++++++++ 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/omod/pom.xml b/omod/pom.xml index 552ccd0d6..7238aa767 100644 --- a/omod/pom.xml +++ b/omod/pom.xml @@ -82,13 +82,12 @@ org.apache.maven.plugins maven-dependency-plugin - - + Expand resources unpack-dependencies - generate-resources + prepare-package ${project.parent.groupId} ${project.parent.artifactId}-omod-common diff --git a/tools/openapi-generator-maven-plugin/pom.xml b/tools/openapi-generator-maven-plugin/pom.xml index 4db375907..c0ce9d3d9 100644 --- a/tools/openapi-generator-maven-plugin/pom.xml +++ b/tools/openapi-generator-maven-plugin/pom.xml @@ -42,6 +42,13 @@ 3.8.6 provided + + + + com.github.javaparser + javaparser-core + 3.25.5 + diff --git a/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/HelloMojo.java b/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/HelloMojo.java index 616bad563..4ae82a3eb 100644 --- a/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/HelloMojo.java +++ b/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/HelloMojo.java @@ -15,11 +15,78 @@ import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; + +import com.github.javaparser.StaticJavaParser; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.comments.JavadocComment; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.List; +import java.util.Optional; @Mojo(name = "hello", defaultPhase = LifecyclePhase.PROCESS_CLASSES) public class HelloMojo extends AbstractMojo { + + private static final String TARGET_CLASS_NAME = "org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8.PatientResource1_8"; + + @Parameter(defaultValue = "${project}", required = true, readonly = true) + private MavenProject project; + @Override public void execute() throws MojoExecutionException, MojoFailureException { getLog().info("Hello from OpenMRS Maven Plugin!"); + getLog().info("Starting Javadoc parsing for target class: " + TARGET_CLASS_NAME); + + // Convert fully qualified class name to relative path + String classRelativePath = TARGET_CLASS_NAME.replace('.', File.separatorChar) + ".java"; + + // Extract simple class name for later use + String targetClassSimpleName = TARGET_CLASS_NAME.substring(TARGET_CLASS_NAME.lastIndexOf('.') + 1); + + // Try to find the source file in compile source roots + File targetSourceFile = null; + List sourceRoots = project.getCompileSourceRoots(); + + for (String sourceRoot : sourceRoots) { + File possibleFile = new File(sourceRoot, classRelativePath); + if (possibleFile.exists()) { + targetSourceFile = possibleFile; + getLog().info("Found source file: " + possibleFile.getAbsolutePath()); + break; + } + } + + if (targetSourceFile == null) { + getLog().warn("Could not find source file for class: " + TARGET_CLASS_NAME); + return; + } + + // Parse with JavaParser + try { + CompilationUnit cu = StaticJavaParser.parse(targetSourceFile); + Optional classOptional = cu.getClassByName(targetClassSimpleName); + + if (classOptional.isPresent()) { + ClassOrInterfaceDeclaration classDecl = classOptional.get(); + Optional javadocOptional = classDecl.getJavadocComment(); + + if (javadocOptional.isPresent()) { + String javadocText = javadocOptional.get().getContent(); + getLog().info("Class Javadoc for " + TARGET_CLASS_NAME + ":\n" + javadocText); + } else { + getLog().info("No class-level Javadoc found for " + TARGET_CLASS_NAME); + } + } else { + getLog().warn("Could not find class " + targetClassSimpleName + " in parsed file " + targetSourceFile.getName()); + } + } catch (FileNotFoundException e) { + getLog().error("Could not find source file for Javadoc parsing: " + targetSourceFile.getAbsolutePath(), e); + } catch (Exception e) { + getLog().error("Error parsing Javadoc for " + TARGET_CLASS_NAME, e); + } } } \ No newline at end of file From 46662c42704feae7634ba3f35ed6c70e3c9338e8 Mon Sep 17 00:00:00 2001 From: capernix Date: Wed, 18 Jun 2025 12:17:39 +0530 Subject: [PATCH 06/12] Enhanced the Plugin to invoke the required function and parse representations # Conflicts: # omod-1.8/pom.xml --- omod/pom.xml | 15 - tools/openapi-generator-maven-plugin/pom.xml | 81 +++- .../java/org/openmrs/plugin/HelloMojo.java | 92 ----- .../java/org/openmrs/plugin/OpenAPIMojo.java | 355 ++++++++++++++++++ 4 files changed, 434 insertions(+), 109 deletions(-) delete mode 100644 tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/HelloMojo.java create mode 100644 tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java diff --git a/omod/pom.xml b/omod/pom.xml index 7238aa767..6b59254dd 100644 --- a/omod/pom.xml +++ b/omod/pom.xml @@ -109,21 +109,6 @@ org.jacoco jacoco-maven-plugin - - - org.openmrs.plugin - openapi-generator-maven-plugin - 1.0-SNAPSHOT - - - say-hello - process-classes - - hello - - - - diff --git a/tools/openapi-generator-maven-plugin/pom.xml b/tools/openapi-generator-maven-plugin/pom.xml index c0ce9d3d9..052bc345b 100644 --- a/tools/openapi-generator-maven-plugin/pom.xml +++ b/tools/openapi-generator-maven-plugin/pom.xml @@ -40,8 +40,7 @@ org.apache.maven maven-core 3.8.6 - provided - + provided @@ -49,6 +48,84 @@ javaparser-core 3.25.5 + + + + io.swagger + swagger-core + 1.6.2 + + + + io.swagger + swagger-models + 1.6.2 + + + + io.swagger + swagger-annotations + 1.6.2 + + + + + io.swagger.core.v3 + swagger-core + 2.2.8 + + + + io.swagger.core.v3 + swagger-models + 2.2.8 + + + + io.swagger.core.v3 + swagger-annotations + 2.2.8 + + + + + com.fasterxml.jackson.core + jackson-databind + 2.15.2 + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + 2.15.2 + + + + + org.openmrs.api + openmrs-api + 2.7.0 + + + + org.openmrs.module + webservices.rest-omod-common + 2.50.0-SNAPSHOT + + + + + org.springframework + spring-web + 5.3.30 + + + + + javax.servlet + servlet-api + 2.5 + diff --git a/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/HelloMojo.java b/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/HelloMojo.java deleted file mode 100644 index 4ae82a3eb..000000000 --- a/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/HelloMojo.java +++ /dev/null @@ -1,92 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under - * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. - * - * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS - * graphic logo is a trademark of OpenMRS Inc. - */ - -package org.openmrs.plugin; - -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugins.annotations.LifecyclePhase; -import org.apache.maven.plugins.annotations.Mojo; -import org.apache.maven.plugins.annotations.Parameter; -import org.apache.maven.project.MavenProject; - -import com.github.javaparser.StaticJavaParser; -import com.github.javaparser.ast.CompilationUnit; -import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; -import com.github.javaparser.ast.comments.JavadocComment; - -import java.io.File; -import java.io.FileNotFoundException; -import java.util.List; -import java.util.Optional; - -@Mojo(name = "hello", defaultPhase = LifecyclePhase.PROCESS_CLASSES) -public class HelloMojo extends AbstractMojo { - - private static final String TARGET_CLASS_NAME = "org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8.PatientResource1_8"; - - @Parameter(defaultValue = "${project}", required = true, readonly = true) - private MavenProject project; - - @Override - public void execute() throws MojoExecutionException, MojoFailureException { - getLog().info("Hello from OpenMRS Maven Plugin!"); - getLog().info("Starting Javadoc parsing for target class: " + TARGET_CLASS_NAME); - - // Convert fully qualified class name to relative path - String classRelativePath = TARGET_CLASS_NAME.replace('.', File.separatorChar) + ".java"; - - // Extract simple class name for later use - String targetClassSimpleName = TARGET_CLASS_NAME.substring(TARGET_CLASS_NAME.lastIndexOf('.') + 1); - - // Try to find the source file in compile source roots - File targetSourceFile = null; - List sourceRoots = project.getCompileSourceRoots(); - - for (String sourceRoot : sourceRoots) { - File possibleFile = new File(sourceRoot, classRelativePath); - if (possibleFile.exists()) { - targetSourceFile = possibleFile; - getLog().info("Found source file: " + possibleFile.getAbsolutePath()); - break; - } - } - - if (targetSourceFile == null) { - getLog().warn("Could not find source file for class: " + TARGET_CLASS_NAME); - return; - } - - // Parse with JavaParser - try { - CompilationUnit cu = StaticJavaParser.parse(targetSourceFile); - Optional classOptional = cu.getClassByName(targetClassSimpleName); - - if (classOptional.isPresent()) { - ClassOrInterfaceDeclaration classDecl = classOptional.get(); - Optional javadocOptional = classDecl.getJavadocComment(); - - if (javadocOptional.isPresent()) { - String javadocText = javadocOptional.get().getContent(); - getLog().info("Class Javadoc for " + TARGET_CLASS_NAME + ":\n" + javadocText); - } else { - getLog().info("No class-level Javadoc found for " + TARGET_CLASS_NAME); - } - } else { - getLog().warn("Could not find class " + targetClassSimpleName + " in parsed file " + targetSourceFile.getName()); - } - } catch (FileNotFoundException e) { - getLog().error("Could not find source file for Javadoc parsing: " + targetSourceFile.getAbsolutePath(), e); - } catch (Exception e) { - getLog().error("Error parsing Javadoc for " + TARGET_CLASS_NAME, e); - } - } -} \ No newline at end of file diff --git a/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java b/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java new file mode 100644 index 000000000..966383d9c --- /dev/null +++ b/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java @@ -0,0 +1,355 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under + * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. + * + * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS + * graphic logo is a trademark of OpenMRS Inc. + */ +package org.openmrs.plugin; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.project.MavenProject; +import org.apache.maven.artifact.Artifact; + +import java.io.File; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.Map; + +@Mojo(name = "openapi", defaultPhase = LifecyclePhase.PROCESS_CLASSES, requiresDependencyResolution = ResolutionScope.RUNTIME) +public class OpenAPIMojo extends AbstractMojo{ + + private static final String TARGET_CLASS_NAME = "org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8.ConceptResource1_8"; + + private URLClassLoader classLoader; + + @Parameter(defaultValue = "${project}", required = true, readonly = true) + private MavenProject project; @Override + public void execute() throws MojoExecutionException, MojoFailureException { + getLog().info("=== OPENAPI GENERATION STARTED ==="); + + // Setup ClassLoader + this.classLoader = setupProjectClassLoader(); + + // CRITICAL: Disable OpenMRS Context to prevent static initialization errors + disableOpenMRSContext(); + + // Test RestConstants accessibility + testRestConstantsAccessibility(); + + // Load Target Resource Class + Class resourceClazz = loadClass(TARGET_CLASS_NAME); + + // Test PatientResource instance creation + testPatientResourceInstantiation(resourceClazz); + + // Summary + getLog().info("=============="); + } + + private URLClassLoader setupProjectClassLoader() { + try { + List urls = new ArrayList<>(); + + // Add project classes + String outputDirectory = project.getBuild().getOutputDirectory(); + File outputDir = new File(outputDirectory); + if (outputDir.exists()) { + urls.add(outputDir.toURI().toURL()); + } + + // Add all artifacts + Set allArtifacts = project.getArtifacts(); + if (allArtifacts != null) { + for (Artifact artifact : allArtifacts) { + File file = artifact.getFile(); + if (file != null && file.exists()) { + urls.add(file.toURI().toURL()); + } + } + } + getLog().info("ClassLoader setup complete: " + urls.size() + " URLs"); + return new URLClassLoader(urls.toArray(new URL[0]), Thread.currentThread().getContextClassLoader()); + + } catch (Exception e) { + throw new RuntimeException("Failed to setup ClassLoader", e); + } + } + + private void disableOpenMRSContext() { + getLog().info("=== Disabling OpenMRS Context for Build-Time Use ==="); + + try { + // Load RestUtil class and call disableContext() + Class restUtilClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.RestUtil"); + Method disableContextMethod = restUtilClass.getMethod("disableContext"); + disableContextMethod.invoke(null); + + getLog().info(" SUCCESS: OpenMRS Context disabled successfully"); + getLog().info(" RestUtil.contextEnabled is now false"); + getLog().info(" Static initializers will not attempt Context access"); + + } catch (ClassNotFoundException e) { + getLog().error(" FAILED: RestUtil class not found: " + e.getMessage()); + throw new RuntimeException("Cannot disable OpenMRS context", e); + } catch (NoSuchMethodException e) { + getLog().error(" FAILED: disableContext method not found: " + e.getMessage()); + throw new RuntimeException("Cannot disable OpenMRS context", e); + } catch (Exception e) { + getLog().error(" FAILED: Error disabling context: " + e.getMessage()); + throw new RuntimeException("Cannot disable OpenMRS context", e); + } + } + + private void testRestConstantsAccessibility() { + getLog().info("=== Testing RestConstants Accessibility ==="); + + try { + // Test 1: Can we load RestConstants in our ClassLoader? + Class restConstants = classLoader.loadClass("org.openmrs.module.webservices.rest.web.RestConstants"); + getLog().info(" RestConstants class loaded successfully: " + restConstants.getName()); + + // Test 2: Can we access static fields? + Object version1 = restConstants.getField("VERSION_1").get(null); + Object uriPrefix = restConstants.getField("URI_PREFIX").get(null); + Object representationDefault = restConstants.getField("REPRESENTATION_DEFAULT").get(null); + Object representationFull = restConstants.getField("REPRESENTATION_FULL").get(null); + + // Test 3: Are these values what we expect? + getLog().info(" RestConstants.VERSION_1 = " + version1); + getLog().info(" RestConstants.URI_PREFIX = " + uriPrefix); + getLog().info(" RestConstants.REPRESENTATION_DEFAULT = " + representationDefault); + getLog().info(" RestConstants.REPRESENTATION_FULL = " + representationFull); + + // Test 4: Verify expected values + if ("v1".equals(version1)) { + getLog().info(" VERSION_1 has expected value"); + } else { + getLog().warn(" VERSION_1 unexpected value: " + version1); + } + + if (uriPrefix != null && uriPrefix.toString().contains("/ws/rest")) { + getLog().info(" URI_PREFIX contains expected path"); + } else { + getLog().warn(" URI_PREFIX unexpected value: " + uriPrefix); + } + + if ("default".equals(representationDefault)) { + getLog().info(" REPRESENTATION_DEFAULT has expected value"); + } else { + getLog().warn(" REPRESENTATION_DEFAULT unexpected value: " + representationDefault); + } + + if ("full".equals(representationFull)) { + getLog().info(" REPRESENTATION_FULL has expected value"); + } else { + getLog().warn(" REPRESENTATION_FULL unexpected value: " + representationFull); + } + + getLog().info(" SUCCESS: RestConstants is fully accessible at build time!"); + + } catch (ClassNotFoundException e) { + getLog().error(" FAILED: RestConstants class not found in ClassLoader"); + getLog().error("This means RestConstants is not in the classpath"); + } catch (NoSuchFieldException e) { + getLog().error(" FAILED: RestConstants field not found: " + e.getMessage()); + } catch (Exception e) { + getLog().error(" FAILED: RestConstants not accessible: " + e.getClass().getSimpleName() + ": " + e.getMessage()); + e.printStackTrace(); + } + } + + private Class loadClass(String className) { + try { + Class clazz = classLoader.loadClass(className); + getLog().info("Loaded class: " + clazz.getName()); + return clazz; + } catch (ClassNotFoundException e) { + throw new RuntimeException("Failed to load class: " + className, e); + } + } private void testPatientResourceInstantiation(Class resourceClazz) { + getLog().info("=== Testing PatientResource Instance Creation ==="); + + try { + getLog().info("Attempting to create PatientResource1_8 instance using normal constructor..."); + + // Try normal constructor + Object instance = resourceClazz.getDeclaredConstructor().newInstance(); + + getLog().info(" SUCCESS! PatientResource1_8 instance created successfully!"); + getLog().info("Instance class: " + instance.getClass().getName()); + getLog().info("Instance toString: " + instance.toString()); + + // Test if we can call a simple method + testBasicMethodCall(instance); + + testGetRepresentationDescription(instance); + + } catch (ExceptionInInitializerError e) { + getLog().error(" FAILED: Static initialization error"); + getLog().error("Root cause: " + e.getCause().getClass().getSimpleName() + ": " + e.getCause().getMessage()); + getLog().error("This means parent classes have problematic static blocks"); + + } catch (NoClassDefFoundError e) { + getLog().error(" FAILED: Missing class dependency"); + getLog().error("Missing class: " + e.getMessage()); + getLog().error("This means some required classes aren't in the classpath"); + + } catch (InstantiationException e) { + getLog().error(" FAILED: Cannot instantiate class"); + getLog().error("Reason: " + e.getMessage()); + + } catch (IllegalAccessException e) { + getLog().error(" FAILED: Constructor not accessible"); + getLog().error("Reason: " + e.getMessage()); + + } catch (Exception e) { + getLog().error(" FAILED: Unexpected error during instantiation"); + getLog().error("Error type: " + e.getClass().getSimpleName()); + getLog().error("Error message: " + e.getMessage()); + e.printStackTrace(); + } +} + +private void testBasicMethodCall(Object instance) { + try { + getLog().info("--- Testing Basic Method Calls ---"); + + // Test toString (should always work) + String toStringResult = instance.toString(); + getLog().info(" toString() works: " + toStringResult); + + // Test getClass (should always work) + Class instanceClass = instance.getClass(); + getLog().info(" getClass() works: " + instanceClass.getName()); + + // List all methods available + Method[] methods = instanceClass.getDeclaredMethods(); + getLog().info(" Found " + methods.length + " declared methods"); + + // Look for getRepresentationDescription method + for (Method method : methods) { + if (method.getName().equals("getRepresentationDescription")) { + getLog().info(" Found getRepresentationDescription method: " + method.toString()); + } + } + + } catch (Exception e) { + getLog().warn(" Basic method calls failed: " + e.getMessage()); + } + } private void testGetRepresentationDescription(Object instance) { + try { + getLog().info("=== Testing getRepresentationDescription Method ==="); + + // First, verify RestConstants is still accessible in this context + testRestConstantsInMethodContext(); + + // Load representation classes + Class representationClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.representation.Representation"); + Class defaultRepClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.representation.DefaultRepresentation"); + Class fullRepClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.representation.FullRepresentation"); + + // Create representation instances + Object defaultRep = defaultRepClass.getDeclaredConstructor().newInstance(); + Object fullRep = fullRepClass.getDeclaredConstructor().newInstance(); + + // Get the method + Method method = instance.getClass().getMethod("getRepresentationDescription", representationClass); + + // Test DEFAULT representation + Object defaultResult = method.invoke(instance, defaultRep); + getLog().info(" DEFAULT result: " + defaultResult); + + // Test FULL representation + Object fullResult = method.invoke(instance, fullRep); + getLog().info(" FULL result: " + fullResult); + + if (defaultResult != null) { + extractSchemaFromDescription(defaultResult, "DEFAULT"); + } + + if (fullResult != null) { + extractSchemaFromDescription(fullResult, "FULL"); + } + + } catch (Exception e) { + getLog().error("Method invocation failed: " + e.getMessage()); + } +} + + private void testRestConstantsInMethodContext() { + try { + getLog().info("--- Verifying RestConstants in method execution context ---"); + + // Test if RestConstants is accessible from the same thread/context where + // getRepresentationDescription will be executed + Class restConstants = classLoader.loadClass("org.openmrs.module.webservices.rest.web.RestConstants"); + + // Quick access test + Object version1 = restConstants.getField("VERSION_1").get(null); + getLog().info(" RestConstants accessible in method context: VERSION_1 = " + version1); + + // Test accessing representation constants that getRepresentationDescription might use + Object repDefault = restConstants.getField("REPRESENTATION_DEFAULT").get(null); + Object repFull = restConstants.getField("REPRESENTATION_FULL").get(null); + + getLog().info(" Representation constants accessible:"); + getLog().info(" - REPRESENTATION_DEFAULT = " + repDefault); + getLog().info(" - REPRESENTATION_FULL = " + repFull); + + getLog().info(" RestConstants should be accessible to getRepresentationDescription()"); + + } catch (Exception e) { + getLog().error(" RestConstants not accessible in method context: " + e.getMessage()); + } + } + + private void extractSchemaFromDescription(Object description, String representationType) { + try { + getLog().info("=== Extracting Schema from " + representationType + " ==="); + + // Get the properties from DelegatingResourceDescription + Method getPropertiesMethod = description.getClass().getMethod("getProperties"); + Object properties = getPropertiesMethod.invoke(description); + + if (properties instanceof Map) { + Map propertyMap = (Map) properties; + getLog().info("Found " + propertyMap.size() + " properties:"); + + for (Map.Entry entry : propertyMap.entrySet()) { + String propertyName = entry.getKey().toString(); + Object propertyValue = entry.getValue(); + getLog().info(" - " + propertyName + " -> " + propertyValue); + } + } + + // Get links if they exist + Method getLinksMethod = description.getClass().getMethod("getLinks"); + Object links = getLinksMethod.invoke(description); + + if (links instanceof Map) { + Map linkMap = (Map) links; + getLog().info("Found " + linkMap.size() + " links:"); + + for (Map.Entry entry : linkMap.entrySet()) { + getLog().info(" - LINK: " + entry.getKey() + " -> " + entry.getValue()); + } + } + + } catch (Exception e) { + getLog().error("Failed to extract schema: " + e.getMessage()); + } +} +} From 578ecaca3b9034e32488f774fc32ee776c4cc7f9 Mon Sep 17 00:00:00 2001 From: capernix Date: Wed, 18 Jun 2025 22:19:48 +0530 Subject: [PATCH 07/12] Adding logging with slf4j, improved indentation of Mojo, corrected versioning number, removed unnecessary comments --- tools/openapi-generator-maven-plugin/pom.xml | 6 +- .../java/org/openmrs/plugin/OpenAPIMojo.java | 370 ++++++++---------- 2 files changed, 176 insertions(+), 200 deletions(-) diff --git a/tools/openapi-generator-maven-plugin/pom.xml b/tools/openapi-generator-maven-plugin/pom.xml index 052bc345b..29d9749b7 100644 --- a/tools/openapi-generator-maven-plugin/pom.xml +++ b/tools/openapi-generator-maven-plugin/pom.xml @@ -1,11 +1,9 @@ - 4.0.0 - - org.openmrs.plugin + 4.0.0 org.openmrs.plugin openapi-generator-maven-plugin - 1.0-SNAPSHOT + 1.0.0-SNAPSHOT maven-plugin OpenAPI Generator Maven Plugin diff --git a/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java b/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java index 966383d9c..99d5719e2 100644 --- a/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java +++ b/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java @@ -18,6 +18,8 @@ import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.apache.maven.artifact.Artifact; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.lang.reflect.Method; @@ -29,58 +31,60 @@ import java.util.Map; @Mojo(name = "openapi", defaultPhase = LifecyclePhase.PROCESS_CLASSES, requiresDependencyResolution = ResolutionScope.RUNTIME) -public class OpenAPIMojo extends AbstractMojo{ +public class OpenAPIMojo extends AbstractMojo { - private static final String TARGET_CLASS_NAME = "org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8.ConceptResource1_8"; + private static final Logger log = LoggerFactory.getLogger(OpenAPIMojo.class); + + private static final String TARGET_CLASS_NAME = "org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8.CohortResource1_8"; private URLClassLoader classLoader; @Parameter(defaultValue = "${project}", required = true, readonly = true) - private MavenProject project; @Override + private MavenProject project; + + @Override public void execute() throws MojoExecutionException, MojoFailureException { - getLog().info("=== OPENAPI GENERATION STARTED ==="); + log.info("=== OPENAPI GENERATION STARTED ==="); - // Setup ClassLoader this.classLoader = setupProjectClassLoader(); - // CRITICAL: Disable OpenMRS Context to prevent static initialization errors disableOpenMRSContext(); - // Test RestConstants accessibility - testRestConstantsAccessibility(); + //testRestConstantsAccessibility(); - // Load Target Resource Class Class resourceClazz = loadClass(TARGET_CLASS_NAME); - // Test PatientResource instance creation testPatientResourceInstantiation(resourceClazz); - // Summary - getLog().info("=============="); - } - - private URLClassLoader setupProjectClassLoader() { + log.info("=============="); + } private URLClassLoader setupProjectClassLoader() { try { List urls = new ArrayList<>(); - // Add project classes String outputDirectory = project.getBuild().getOutputDirectory(); File outputDir = new File(outputDirectory); if (outputDir.exists()) { urls.add(outputDir.toURI().toURL()); + log.info("Added project output directory: " + outputDirectory); + } else { + log.warn("Project output directory does not exist: " + outputDirectory); } - // Add all artifacts Set allArtifacts = project.getArtifacts(); if (allArtifacts != null) { + log.debug("Found " + allArtifacts.size() + " project artifacts"); for (Artifact artifact : allArtifacts) { File file = artifact.getFile(); if (file != null && file.exists()) { urls.add(file.toURI().toURL()); + log.debug("Added artifact: " + artifact.getGroupId() + ":" + artifact.getArtifactId() + " -> " + file.getPath()); + } else { + log.warn("Artifact file not found: " + artifact.getGroupId() + ":" + artifact.getArtifactId()); } } - } - getLog().info("ClassLoader setup complete: " + urls.size() + " URLs"); + } + + log.info("ClassLoader setup complete: " + urls.size() + " URLs"); return new URLClassLoader(urls.toArray(new URL[0]), Thread.currentThread().getContextClassLoader()); } catch (Exception e) { @@ -89,267 +93,241 @@ private URLClassLoader setupProjectClassLoader() { } private void disableOpenMRSContext() { - getLog().info("=== Disabling OpenMRS Context for Build-Time Use ==="); + log.info("=== Disabling OpenMRS Context for Build-Time Use ==="); try { - // Load RestUtil class and call disableContext() Class restUtilClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.RestUtil"); Method disableContextMethod = restUtilClass.getMethod("disableContext"); disableContextMethod.invoke(null); - - getLog().info(" SUCCESS: OpenMRS Context disabled successfully"); - getLog().info(" RestUtil.contextEnabled is now false"); - getLog().info(" Static initializers will not attempt Context access"); + log.info(" SUCCESS: OpenMRS Context disabled successfully"); + log.debug(" RestUtil.contextEnabled is now false"); + log.debug(" Static initializers will not attempt Context access"); } catch (ClassNotFoundException e) { - getLog().error(" FAILED: RestUtil class not found: " + e.getMessage()); + log.error(" FAILED: RestUtil class not found: " + e.getMessage()); throw new RuntimeException("Cannot disable OpenMRS context", e); } catch (NoSuchMethodException e) { - getLog().error(" FAILED: disableContext method not found: " + e.getMessage()); + log.error(" FAILED: disableContext method not found: " + e.getMessage()); throw new RuntimeException("Cannot disable OpenMRS context", e); } catch (Exception e) { - getLog().error(" FAILED: Error disabling context: " + e.getMessage()); + log.error(" FAILED: Error disabling context: " + e.getMessage()); throw new RuntimeException("Cannot disable OpenMRS context", e); } } - private void testRestConstantsAccessibility() { - getLog().info("=== Testing RestConstants Accessibility ==="); + private void testRestConstantsAccessibility() { + log.info("=== Testing RestConstants Accessibility ==="); try { - // Test 1: Can we load RestConstants in our ClassLoader? Class restConstants = classLoader.loadClass("org.openmrs.module.webservices.rest.web.RestConstants"); - getLog().info(" RestConstants class loaded successfully: " + restConstants.getName()); + log.info(" RestConstants class loaded successfully: " + restConstants.getName()); - // Test 2: Can we access static fields? Object version1 = restConstants.getField("VERSION_1").get(null); Object uriPrefix = restConstants.getField("URI_PREFIX").get(null); Object representationDefault = restConstants.getField("REPRESENTATION_DEFAULT").get(null); Object representationFull = restConstants.getField("REPRESENTATION_FULL").get(null); - // Test 3: Are these values what we expect? - getLog().info(" RestConstants.VERSION_1 = " + version1); - getLog().info(" RestConstants.URI_PREFIX = " + uriPrefix); - getLog().info(" RestConstants.REPRESENTATION_DEFAULT = " + representationDefault); - getLog().info(" RestConstants.REPRESENTATION_FULL = " + representationFull); + log.info(" RestConstants.VERSION_1 = " + version1); + log.info(" RestConstants.URI_PREFIX = " + uriPrefix); + log.info(" RestConstants.REPRESENTATION_DEFAULT = " + representationDefault); + log.info(" RestConstants.REPRESENTATION_FULL = " + representationFull); - // Test 4: Verify expected values if ("v1".equals(version1)) { - getLog().info(" VERSION_1 has expected value"); + log.info(" VERSION_1 has expected value"); } else { - getLog().warn(" VERSION_1 unexpected value: " + version1); + log.warn(" VERSION_1 unexpected value: " + version1); } if (uriPrefix != null && uriPrefix.toString().contains("/ws/rest")) { - getLog().info(" URI_PREFIX contains expected path"); + log.info(" URI_PREFIX contains expected path"); } else { - getLog().warn(" URI_PREFIX unexpected value: " + uriPrefix); + log.warn(" URI_PREFIX unexpected value: " + uriPrefix); } if ("default".equals(representationDefault)) { - getLog().info(" REPRESENTATION_DEFAULT has expected value"); + log.info(" REPRESENTATION_DEFAULT has expected value"); } else { - getLog().warn(" REPRESENTATION_DEFAULT unexpected value: " + representationDefault); + log.warn(" REPRESENTATION_DEFAULT unexpected value: " + representationDefault); } if ("full".equals(representationFull)) { - getLog().info(" REPRESENTATION_FULL has expected value"); + log.info(" REPRESENTATION_FULL has expected value"); } else { - getLog().warn(" REPRESENTATION_FULL unexpected value: " + representationFull); + log.warn(" REPRESENTATION_FULL unexpected value: " + representationFull); } - getLog().info(" SUCCESS: RestConstants is fully accessible at build time!"); + log.info(" SUCCESS: RestConstants is fully accessible at build time!"); } catch (ClassNotFoundException e) { - getLog().error(" FAILED: RestConstants class not found in ClassLoader"); - getLog().error("This means RestConstants is not in the classpath"); - } catch (NoSuchFieldException e) { - getLog().error(" FAILED: RestConstants field not found: " + e.getMessage()); + log.error(" FAILED: RestConstants class not found in ClassLoader"); + log.error("This means RestConstants is not in the classpath"); } catch (NoSuchFieldException e) { + log.error(" FAILED: RestConstants field not found: " + e.getMessage()); } catch (Exception e) { - getLog().error(" FAILED: RestConstants not accessible: " + e.getClass().getSimpleName() + ": " + e.getMessage()); + log.error(" FAILED: RestConstants not accessible: " + e.getClass().getSimpleName() + ": " + e.getMessage()); e.printStackTrace(); - } + } } - private Class loadClass(String className) { + private Class loadClass(String className) { try { Class clazz = classLoader.loadClass(className); - getLog().info("Loaded class: " + clazz.getName()); + log.info("Loaded class: " + clazz.getName()); return clazz; } catch (ClassNotFoundException e) { throw new RuntimeException("Failed to load class: " + className, e); } - } private void testPatientResourceInstantiation(Class resourceClazz) { - getLog().info("=== Testing PatientResource Instance Creation ==="); + } - try { - getLog().info("Attempting to create PatientResource1_8 instance using normal constructor..."); + private void testPatientResourceInstantiation(Class resourceClazz) { + log.debug("=== Testing PatientResource Instance Creation ==="); - // Try normal constructor - Object instance = resourceClazz.getDeclaredConstructor().newInstance(); - - getLog().info(" SUCCESS! PatientResource1_8 instance created successfully!"); - getLog().info("Instance class: " + instance.getClass().getName()); - getLog().info("Instance toString: " + instance.toString()); - - // Test if we can call a simple method - testBasicMethodCall(instance); + try { + log.debug("Attempting to create PatientResource1_8 instance using normal constructor..."); + + Object instance = resourceClazz.getDeclaredConstructor().newInstance(); + + log.info(" SUCCESS! PatientResource1_8 instance created successfully!"); + log.info("Instance class: " + instance.getClass().getName()); + log.info("Instance toString: " + instance.toString()); + + testBasicMethodCall(instance); - testGetRepresentationDescription(instance); - - } catch (ExceptionInInitializerError e) { - getLog().error(" FAILED: Static initialization error"); - getLog().error("Root cause: " + e.getCause().getClass().getSimpleName() + ": " + e.getCause().getMessage()); - getLog().error("This means parent classes have problematic static blocks"); - - } catch (NoClassDefFoundError e) { - getLog().error(" FAILED: Missing class dependency"); - getLog().error("Missing class: " + e.getMessage()); - getLog().error("This means some required classes aren't in the classpath"); - - } catch (InstantiationException e) { - getLog().error(" FAILED: Cannot instantiate class"); - getLog().error("Reason: " + e.getMessage()); - - } catch (IllegalAccessException e) { - getLog().error(" FAILED: Constructor not accessible"); - getLog().error("Reason: " + e.getMessage()); - - } catch (Exception e) { - getLog().error(" FAILED: Unexpected error during instantiation"); - getLog().error("Error type: " + e.getClass().getSimpleName()); - getLog().error("Error message: " + e.getMessage()); - e.printStackTrace(); + testGetRepresentationDescription(instance); + + } catch (ExceptionInInitializerError e) { + log.error(" FAILED: Static initialization error"); + log.error("Root cause: " + e.getCause().getClass().getSimpleName() + ": " + e.getCause().getMessage()); + log.error("This means parent classes have problematic static blocks"); + + } catch (NoClassDefFoundError e) { + log.error(" FAILED: Missing class dependency"); + log.error("Missing class: " + e.getMessage()); + log.error("This means some required classes aren't in the classpath"); + + } catch (InstantiationException e) { + log.error(" FAILED: Cannot instantiate class"); + log.error("Reason: " + e.getMessage()); + + } catch (IllegalAccessException e) { + log.error(" FAILED: Constructor not accessible"); + log.error("Reason: " + e.getMessage()); + + } catch (Exception e) { + log.error(" FAILED: Unexpected error during instantiation"); + log.error("Error type: " + e.getClass().getSimpleName()); + log.error("Error message: " + e.getMessage()); + e.printStackTrace(); + } } -} -private void testBasicMethodCall(Object instance) { - try { - getLog().info("--- Testing Basic Method Calls ---"); - - // Test toString (should always work) - String toStringResult = instance.toString(); - getLog().info(" toString() works: " + toStringResult); - - // Test getClass (should always work) - Class instanceClass = instance.getClass(); - getLog().info(" getClass() works: " + instanceClass.getName()); - - // List all methods available - Method[] methods = instanceClass.getDeclaredMethods(); - getLog().info(" Found " + methods.length + " declared methods"); - - // Look for getRepresentationDescription method - for (Method method : methods) { - if (method.getName().equals("getRepresentationDescription")) { - getLog().info(" Found getRepresentationDescription method: " + method.toString()); + private void testBasicMethodCall(Object instance) { + try { + log.debug("--- Testing Basic Method Calls ---"); + + String toStringResult = instance.toString(); + log.debug(" toString() works: " + toStringResult); + + Class instanceClass = instance.getClass(); + log.debug(" getClass() works: " + instanceClass.getName()); + + Method[] methods = instanceClass.getDeclaredMethods(); + log.debug(" Found " + methods.length + " declared methods"); + + for (Method method : methods) { + if (method.getName().equals("getRepresentationDescription")) { + log.info(" Found getRepresentationDescription method: " + method.toString()); + } } + } catch (Exception e) { + log.warn(" Basic method calls failed: " + e.getMessage()); } - - } catch (Exception e) { - getLog().warn(" Basic method calls failed: " + e.getMessage()); } - } private void testGetRepresentationDescription(Object instance) { - try { - getLog().info("=== Testing getRepresentationDescription Method ==="); - - // First, verify RestConstants is still accessible in this context - testRestConstantsInMethodContext(); - - // Load representation classes - Class representationClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.representation.Representation"); - Class defaultRepClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.representation.DefaultRepresentation"); - Class fullRepClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.representation.FullRepresentation"); - - // Create representation instances - Object defaultRep = defaultRepClass.getDeclaredConstructor().newInstance(); - Object fullRep = fullRepClass.getDeclaredConstructor().newInstance(); - - // Get the method - Method method = instance.getClass().getMethod("getRepresentationDescription", representationClass); - - // Test DEFAULT representation - Object defaultResult = method.invoke(instance, defaultRep); - getLog().info(" DEFAULT result: " + defaultResult); - - // Test FULL representation - Object fullResult = method.invoke(instance, fullRep); - getLog().info(" FULL result: " + fullResult); - if (defaultResult != null) { - extractSchemaFromDescription(defaultResult, "DEFAULT"); - } - - if (fullResult != null) { - extractSchemaFromDescription(fullResult, "FULL"); + private void testGetRepresentationDescription(Object instance) { + try { + log.info("=== Testing getRepresentationDescription Method ==="); + + //testRestConstantsInMethodContext(); + + Class representationClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.representation.Representation"); + Class defaultRepClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.representation.DefaultRepresentation"); + Class fullRepClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.representation.FullRepresentation"); + + Object defaultRep = defaultRepClass.getDeclaredConstructor().newInstance(); + Object fullRep = fullRepClass.getDeclaredConstructor().newInstance(); + + Method method = instance.getClass().getMethod("getRepresentationDescription", representationClass); + + Object defaultResult = method.invoke(instance, defaultRep); + log.info(" DEFAULT result: " + defaultResult); + + Object fullResult = method.invoke(instance, fullRep); + log.info(" FULL result: " + fullResult); if (defaultResult != null) { + extractSchemaFromDescription(defaultResult, "DEFAULT"); + } + + if (fullResult != null) { + extractSchemaFromDescription(fullResult, "FULL"); + } + + } catch (Exception e) { + log.error("Method invocation failed: " + e.getMessage()); } - - } catch (Exception e) { - getLog().error("Method invocation failed: " + e.getMessage()); } -} private void testRestConstantsInMethodContext() { try { - getLog().info("--- Verifying RestConstants in method execution context ---"); - - // Test if RestConstants is accessible from the same thread/context where - // getRepresentationDescription will be executed + log.info("--- Verifying RestConstants in method execution context ---"); Class restConstants = classLoader.loadClass("org.openmrs.module.webservices.rest.web.RestConstants"); - // Quick access test Object version1 = restConstants.getField("VERSION_1").get(null); - getLog().info(" RestConstants accessible in method context: VERSION_1 = " + version1); + log.info(" RestConstants accessible in method context: VERSION_1 = " + version1); - // Test accessing representation constants that getRepresentationDescription might use Object repDefault = restConstants.getField("REPRESENTATION_DEFAULT").get(null); Object repFull = restConstants.getField("REPRESENTATION_FULL").get(null); - getLog().info(" Representation constants accessible:"); - getLog().info(" - REPRESENTATION_DEFAULT = " + repDefault); - getLog().info(" - REPRESENTATION_FULL = " + repFull); + log.info(" Representation constants accessible:"); + log.info(" - REPRESENTATION_DEFAULT = " + repDefault); + log.info(" - REPRESENTATION_FULL = " + repFull); - getLog().info(" RestConstants should be accessible to getRepresentationDescription()"); + log.info(" RestConstants should be accessible to getRepresentationDescription()"); } catch (Exception e) { - getLog().error(" RestConstants not accessible in method context: " + e.getMessage()); + log.error(" RestConstants not accessible in method context: " + e.getMessage()); } } private void extractSchemaFromDescription(Object description, String representationType) { - try { - getLog().info("=== Extracting Schema from " + representationType + " ==="); - - // Get the properties from DelegatingResourceDescription - Method getPropertiesMethod = description.getClass().getMethod("getProperties"); - Object properties = getPropertiesMethod.invoke(description); - - if (properties instanceof Map) { - Map propertyMap = (Map) properties; - getLog().info("Found " + propertyMap.size() + " properties:"); - - for (Map.Entry entry : propertyMap.entrySet()) { - String propertyName = entry.getKey().toString(); - Object propertyValue = entry.getValue(); - getLog().info(" - " + propertyName + " -> " + propertyValue); + try { + log.info("=== Extracting Schema from " + representationType + " ==="); + + Method getPropertiesMethod = description.getClass().getMethod("getProperties"); + Object properties = getPropertiesMethod.invoke(description); + + if (properties instanceof Map) { + Map propertyMap = (Map) properties; + log.info("Found " + propertyMap.size() + " properties:"); + + for (Map.Entry entry : propertyMap.entrySet()) { + String propertyName = entry.getKey().toString(); + Object propertyValue = entry.getValue(); + log.info(" - " + propertyName + " -> " + propertyValue); + } } - } - - // Get links if they exist - Method getLinksMethod = description.getClass().getMethod("getLinks"); - Object links = getLinksMethod.invoke(description); - - if (links instanceof Map) { - Map linkMap = (Map) links; - getLog().info("Found " + linkMap.size() + " links:"); - for (Map.Entry entry : linkMap.entrySet()) { - getLog().info(" - LINK: " + entry.getKey() + " -> " + entry.getValue()); + Method getLinksMethod = description.getClass().getMethod("getLinks"); + Object links = getLinksMethod.invoke(description); + if (links instanceof Map) { + Map linkMap = (Map) links; + log.info("Found " + linkMap.size() + " links:"); + + for (Map.Entry entry : linkMap.entrySet()) { + log.info(" - LINK: " + entry.getKey() + " -> " + entry.getValue()); + } } + + } catch (Exception e) { + log.error("Failed to extract schema: " + e.getMessage()); } - - } catch (Exception e) { - getLog().error("Failed to extract schema: " + e.getMessage()); } } -} From a9dee67e3d14200f335e1760ff62ffbd9bfc92e0 Mon Sep 17 00:00:00 2001 From: capernix Date: Tue, 24 Jun 2025 10:43:29 +0530 Subject: [PATCH 08/12] Removed the Schema Introspector Functionality and phased it inside the plugin --- .../web/api/SchemaIntrospectionService.java | 46 --- .../impl/SchemaIntrospectionServiceImpl.java | 250 ------------ .../SchemaIntrospectionController.java | 92 ----- .../SchemaIntrospectionServiceImplTest.java | 357 ------------------ .../SchemaIntrospectionControllerTest.java | 131 ------- .../controller/SchemaIntrospectionTest.java | 82 ---- 6 files changed, 958 deletions(-) delete mode 100644 omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/SchemaIntrospectionService.java delete mode 100644 omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImpl.java delete mode 100644 omod-common/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionController.java delete mode 100644 omod-common/src/test/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImplTest.java delete mode 100644 omod-common/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionControllerTest.java delete mode 100644 omod-common/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionTest.java diff --git a/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/SchemaIntrospectionService.java b/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/SchemaIntrospectionService.java deleted file mode 100644 index f88731320..000000000 --- a/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/SchemaIntrospectionService.java +++ /dev/null @@ -1,46 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under - * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. - * - * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS - * graphic logo is a trademark of OpenMRS Inc. - */ -package org.openmrs.module.webservices.rest.web.api; - -import java.util.Map; - -import org.openmrs.module.webservices.rest.web.resource.api.Resource; - -/** - * Service for introspecting resource properties via reflection. - * This enables clients to discover all properties that may be available in custom representations. - */ -public interface SchemaIntrospectionService { - - /** - * Gets the delegate type (T) for a resource that extends DelegatingCrudResource. - * - * @param resource The resource whose delegate class we want to determine - * @return The class type of the delegate, or null if not determinable - */ - Class getDelegateType(Resource resource); - - /** - * Discovers all available properties on a delegate type using reflection. - * This includes public instance fields and JavaBean-style getter methods. - * - * @param delegateType The class to introspect for properties - * @return A map of property names to their Java type names - */ - Map discoverAvailableProperties(Class delegateType); - - /** - * Combines getDelegateType and discoverAvailableProperties for convenience. - * - * @param resource The resource to introspect - * @return A map of property names to their Java type names - */ - Map discoverResourceProperties(Resource resource); -} \ No newline at end of file diff --git a/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImpl.java b/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImpl.java deleted file mode 100644 index 034549b88..000000000 --- a/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImpl.java +++ /dev/null @@ -1,250 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under - * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. - * - * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS - * graphic logo is a trademark of OpenMRS Inc. - */ -package org.openmrs.module.webservices.rest.web.api.impl; - -import java.beans.PropertyDescriptor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.HashMap; -import java.util.Map; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.openmrs.api.impl.BaseOpenmrsService; -import org.openmrs.module.webservices.rest.web.annotation.PropertyGetter; -import org.openmrs.module.webservices.rest.web.annotation.PropertySetter; -import org.openmrs.module.webservices.rest.web.api.SchemaIntrospectionService; -import org.openmrs.module.webservices.rest.web.resource.api.Resource; -import org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceHandler; -import org.springframework.beans.BeanUtils; -import org.springframework.stereotype.Component; - -/** - * Default implementation of {@link SchemaIntrospectionService} - */ -@Component -public class SchemaIntrospectionServiceImpl extends BaseOpenmrsService implements SchemaIntrospectionService { - - protected final Log log = LogFactory.getLog(getClass()); - - /** - * @see org.openmrs.module.webservices.rest.web.api.SchemaIntrospectionService#getDelegateType(Resource) - */ - @Override - public Class getDelegateType(Resource resource) { - if (resource == null) { - return null; - } - - // Handle non-delegating resources - if (!(resource instanceof DelegatingResourceHandler)) { - log.warn("Resource " + resource.getClass().getName() + " is not a DelegatingResourceHandler"); - return null; - } - - // Attempt to determine the delegate type from the generic parameter T in DelegatingCrudResource - // or DelegatingSubResource - Class resourceClass = resource.getClass(); - - // Search through the class hierarchy to find the class that implements/extends with the generic type - while (resourceClass != null) { - Type[] genericInterfaces = resourceClass.getGenericInterfaces(); - for (Type genericInterface : genericInterfaces) { - if (genericInterface instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) genericInterface; - Type rawType = parameterizedType.getRawType(); - - // Check if this interface is DelegatingResourceHandler or a subinterface of it - if (rawType instanceof Class - && DelegatingResourceHandler.class.isAssignableFrom((Class) rawType)) { - // First type parameter should be the delegate type - Type[] typeArgs = parameterizedType.getActualTypeArguments(); - if (typeArgs.length > 0 && typeArgs[0] instanceof Class) { - return (Class) typeArgs[0]; - } - } - } - } - - // Check the superclass's generic type - Type genericSuperclass = resourceClass.getGenericSuperclass(); - if (genericSuperclass instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass; - Type[] typeArgs = parameterizedType.getActualTypeArguments(); - - // First type argument should be the delegate type - if (typeArgs.length > 0 && typeArgs[0] instanceof Class) { - return (Class) typeArgs[0]; - } - } - - // Move up the hierarchy - resourceClass = resourceClass.getSuperclass(); - } - - log.warn("Could not determine delegate type for " + resource.getClass().getName()); - return null; - } - - /** - * @see org.openmrs.module.webservices.rest.web.api.SchemaIntrospectionService#discoverAvailableProperties(Class) - */ - @Override - public Map discoverAvailableProperties(Class delegateType) { - if (delegateType == null) { - return new HashMap(); - } - - Map properties = new HashMap(); - - // Process fields up through the class hierarchy - Class currentClass = delegateType; - while (currentClass != null && !currentClass.equals(Object.class)) { - processFields(currentClass, properties); - currentClass = currentClass.getSuperclass(); - } - - // Process getters - use Spring's BeanUtils to get all property descriptors - PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(delegateType); - for (PropertyDescriptor descriptor : propertyDescriptors) { - // Skip "class" and properties without a readable method (getter) - if ("class".equals(descriptor.getName()) || descriptor.getReadMethod() == null) { - continue; - } - - Method readMethod = descriptor.getReadMethod(); - - // Only include public methods - if (Modifier.isPublic(readMethod.getModifiers()) && !Modifier.isStatic(readMethod.getModifiers())) { - // Get return type, including generic type if available - String typeName = getTypeName(readMethod.getGenericReturnType()); - properties.put(descriptor.getName(), typeName); - } - } - - return properties; - } - - /** - * @see org.openmrs.module.webservices.rest.web.api.SchemaIntrospectionService#discoverResourceProperties(Resource) - */ - @Override - public Map discoverResourceProperties(Resource resource) { - Class delegateType = getDelegateType(resource); - Map properties = discoverAvailableProperties(delegateType); - - // Also discover properties defined by PropertyGetter and PropertySetter annotations - if (resource != null) { - discoverAnnotatedProperties(resource.getClass(), properties); - } - - return properties; - } - - /** - * Discovers properties defined by PropertyGetter and PropertySetter annotations in a resource class - * and its superclasses - * - * @param resourceClass The resource class to scan for annotations - * @param properties The map to add discovered properties to - */ - private void discoverAnnotatedProperties(Class resourceClass, Map properties) { - // Process the current class and all its superclasses - Class currentClass = resourceClass; - while (currentClass != null && !currentClass.equals(Object.class)) { - // Process all methods in the class - for (Method method : currentClass.getDeclaredMethods()) { - // Check for PropertyGetter annotation - PropertyGetter getter = method.getAnnotation(PropertyGetter.class); - if (getter != null) { - String propertyName = getter.value(); - Type returnType = method.getGenericReturnType(); - properties.put(propertyName, getTypeName(returnType)); - } - - // Check for PropertySetter annotation - PropertySetter setter = method.getAnnotation(PropertySetter.class); - if (setter != null && method.getParameterTypes().length > 1) { - String propertyName = setter.value(); - // For setters, use the type of the second parameter (after the delegate) - Type paramType = method.getGenericParameterTypes()[1]; - // Only add if not already discovered through a getter - if (!properties.containsKey(propertyName)) { - properties.put(propertyName, getTypeName(paramType)); - } - } - } - // Move up to the superclass - currentClass = currentClass.getSuperclass(); - } - } - - /** - * Helper method to process fields from a class and add them to the properties map - * - * @param clazz The class to process fields from - * @param properties The map to add properties to - */ - private void processFields(Class clazz, Map properties) { - Field[] fields = clazz.getDeclaredFields(); - for (Field field : fields) { - // Only include public instance fields - if (Modifier.isPublic(field.getModifiers()) && !Modifier.isStatic(field.getModifiers())) { - String typeName = getTypeName(field.getGenericType()); - properties.put(field.getName(), typeName); - } - } - } - - /** - * Helper method to get a user-friendly type name from a Type object - * - * @param type The type to get a name for - * @return A user-friendly type name string - */ - private String getTypeName(Type type) { - if (type instanceof Class) { - return ((Class) type).getSimpleName(); - } else if (type instanceof ParameterizedType) { - ParameterizedType paramType = (ParameterizedType) type; - Type rawType = paramType.getRawType(); - Type[] typeArgs = paramType.getActualTypeArguments(); - - StringBuilder sb = new StringBuilder(); - if (rawType instanceof Class) { - sb.append(((Class) rawType).getSimpleName()); - } else { - sb.append(rawType.toString()); - } - - if (typeArgs.length > 0) { - sb.append("<"); - for (int i = 0; i < typeArgs.length; i++) { - if (i > 0) { - sb.append(", "); - } - if (typeArgs[i] instanceof Class) { - sb.append(((Class) typeArgs[i]).getSimpleName()); - } else { - sb.append(typeArgs[i].toString()); - } - } - sb.append(">"); - } - - return sb.toString(); - } else { - return type.toString(); - } - } -} \ No newline at end of file diff --git a/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionController.java b/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionController.java deleted file mode 100644 index 6f29139f3..000000000 --- a/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionController.java +++ /dev/null @@ -1,92 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under - * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. - * - * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS - * graphic logo is a trademark of OpenMRS Inc. - */ -package org.openmrs.module.webservices.rest.web.v1_0.controller; - -import java.util.Map; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.openmrs.module.webservices.rest.SimpleObject; -import org.openmrs.module.webservices.rest.web.RestConstants; -import org.openmrs.module.webservices.rest.web.api.RestService; -import org.openmrs.module.webservices.rest.web.api.SchemaIntrospectionService; -import org.openmrs.module.webservices.rest.web.resource.api.Resource; -import org.openmrs.module.webservices.rest.web.response.ObjectNotFoundException; -import org.openmrs.module.webservices.rest.web.response.ResponseException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; - -/** - * Controller that provides schema introspection for REST resources. - * This allows clients to discover what properties are available for a given resource. - */ -@Controller -@RequestMapping(value = "/rest/" + RestConstants.VERSION_1 + "/schemaintrospector") -public class SchemaIntrospectionController extends BaseRestController { - - protected final Log log = LogFactory.getLog(getClass()); - - @Autowired - private RestService restService; - - @Autowired - private SchemaIntrospectionService schemaIntrospectionService; - - /** - * Gets all properties available on a resource's delegate domain object - * - * @param resourceName The name of the resource to introspect - * @return A JSON object containing the resource properties - * @throws ResponseException - */ - @RequestMapping(value = "/{resourceName}", method = RequestMethod.GET) - @ResponseBody - public SimpleObject getResourceProperties(@PathVariable("resourceName") String resourceName) throws ResponseException { - // Get the resource by name with proper prefix - Resource resource; - try { - // Try with the full path (which is how resources are registered) - String fullResourceName = RestConstants.VERSION_1 + "/" + resourceName; - log.debug("Looking for resource with name: " + fullResourceName); - resource = restService.getResourceByName(fullResourceName); - } - catch (Exception e) { - log.error("Failed to find resource with name: " + resourceName, e); - throw new ObjectNotFoundException("Resource '" + resourceName + "' was not found"); - } - - if (resource == null) { - log.error("Resource is null for name: " + resourceName); - throw new ObjectNotFoundException("Resource '" + resourceName + "' was not found"); - } - - // Get the delegate type for the resource - Class delegateType = schemaIntrospectionService.getDelegateType(resource); - if (delegateType == null) { - log.error("Could not determine delegate type for resource: " + resourceName); - throw new ObjectNotFoundException("Could not determine delegate type for resource '" + resourceName + "'"); - } - - // Get properties for the delegate type - Map properties = schemaIntrospectionService.discoverAvailableProperties(delegateType); - - // Build the response - SimpleObject result = new SimpleObject(); - result.add("resourceName", resourceName); - result.add("delegateType", delegateType.getName()); - result.add("discoverableProperties", properties); - - return result; - } -} \ No newline at end of file diff --git a/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImplTest.java b/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImplTest.java deleted file mode 100644 index 56ead9837..000000000 --- a/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/api/impl/SchemaIntrospectionServiceImplTest.java +++ /dev/null @@ -1,357 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under - * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. - * - * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS - * graphic logo is a trademark of OpenMRS Inc. - */ -package org.openmrs.module.webservices.rest.web.api.impl; - -import static org.hamcrest.CoreMatchers.hasItem; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.hasKey; -import static org.hamcrest.Matchers.hasSize; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.junit.Before; -import org.junit.Test; -import org.openmrs.Patient; -import org.openmrs.PatientIdentifier; -import org.openmrs.Person; -import org.openmrs.module.webservices.rest.web.RequestContext; -import org.openmrs.module.webservices.rest.web.annotation.PropertyGetter; -import org.openmrs.module.webservices.rest.web.annotation.PropertySetter; -import org.openmrs.module.webservices.rest.web.resource.api.Resource; -import org.openmrs.module.webservices.rest.web.resource.impl.DelegatingCrudResource; -import org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription; -import org.openmrs.module.webservices.rest.web.resource.impl.DelegatingSubResource; -import org.openmrs.module.webservices.rest.web.representation.Representation; -import org.openmrs.module.webservices.rest.web.response.ResourceDoesNotSupportOperationException; - -/** - * Tests for {@link SchemaIntrospectionServiceImpl}. - */ -public class SchemaIntrospectionServiceImplTest { - - private SchemaIntrospectionServiceImpl service; - - @Before - public void setup() { - service = new SchemaIntrospectionServiceImpl(); - } - - /** - * @see SchemaIntrospectionServiceImpl#getDelegateType(Resource) - */ - @Test - public void getDelegateType_shouldReturnNullForNullResource() { - // Test with null resource - assertThat(service.getDelegateType(null), is(nullValue())); - } - - /** - * @see SchemaIntrospectionServiceImpl#getDelegateType(Resource) - */ - @Test - public void getDelegateType_shouldReturnCorrectTypeForDelegatingCrudResource() { - // Test with a mock DelegatingCrudResource - TestPatientResource resource = new TestPatientResource(); - Class delegateType = service.getDelegateType(resource); - - assertThat(delegateType, is(notNullValue())); - assertThat(delegateType.getName(), is(Patient.class.getName())); - } - - /** - * @see SchemaIntrospectionServiceImpl#getDelegateType(Resource) - */ - @Test - public void getDelegateType_shouldReturnCorrectTypeForDelegatingSubResource() { - // Test with a mock DelegatingSubResource - TestPatientSubResource resource = new TestPatientSubResource(); - Class delegateType = service.getDelegateType(resource); - - assertThat(delegateType, is(notNullValue())); - assertThat(delegateType.getName(), is(Patient.class.getName())); - } - - /** - * @see SchemaIntrospectionServiceImpl#discoverAvailableProperties(Class) - */ - @Test - public void discoverAvailableProperties_shouldReturnEmptyMapForNullType() { - Map properties = service.discoverAvailableProperties(null); - - assertThat(properties, is(notNullValue())); - assertThat(properties.size(), is(0)); - } - - /** - * @see SchemaIntrospectionServiceImpl#discoverAvailableProperties(Class) - */ - @Test - public void discoverAvailableProperties_shouldIncludePropertiesFromGetters() { - Map properties = service.discoverAvailableProperties(Patient.class); - - // Test a few essential properties that should be discovered via getters - assertThat(properties, hasKey("uuid")); - assertThat(properties, hasKey("patientId")); - assertThat(properties, hasKey("identifiers")); - assertThat(properties, hasKey("person")); - assertThat(properties, hasKey("voided")); - } - - /** - * @see SchemaIntrospectionServiceImpl#discoverAvailableProperties(Class) - */ - @Test - public void discoverAvailableProperties_shouldIncludePropertiesFromSuperclasses() { - Map properties = service.discoverAvailableProperties(Patient.class); - - // Patient extends Person, so should have Person properties - assertThat(properties, hasKey("gender")); - assertThat(properties, hasKey("age")); - assertThat(properties, hasKey("birthdate")); - assertThat(properties, hasKey("names")); - assertThat(properties, hasKey("addresses")); - } - - /** - * @see SchemaIntrospectionServiceImpl#discoverAvailableProperties(Class) - */ - @Test - public void discoverAvailableProperties_shouldIncludePropertiesWithCorrectTypes() { - Map properties = service.discoverAvailableProperties(Patient.class); - - // Verify some property types - assertThat(properties, hasEntry("uuid", "String")); - assertThat(properties, hasEntry("voided", "Boolean")); - // Set of PatientIdentifier - assertThat(properties.get("identifiers").contains("Set"), is(true)); - } - - /** - * @see SchemaIntrospectionServiceImpl#discoverResourceProperties(Resource) - */ - @Test - public void discoverResourceProperties_shouldCombineGetDelegateTypeAndDiscoverAvailableProperties() { - TestPatientResource resource = new TestPatientResource(); - Map properties = service.discoverResourceProperties(resource); - - // Verify it discovered Patient properties - assertThat(properties, hasKey("uuid")); - assertThat(properties, hasKey("patientId")); - assertThat(properties, hasKey("identifiers")); - - // And Person properties (from superclass) - assertThat(properties, hasKey("gender")); - assertThat(properties, hasKey("birthdate")); - } - - /** - * @see SchemaIntrospectionServiceImpl#discoverResourceProperties(Resource) - */ - @Test - public void discoverResourceProperties_shouldIncludePropertiesFromAnnotations() { - TestPatientResourceWithAnnotations resource = new TestPatientResourceWithAnnotations(); - Map properties = service.discoverResourceProperties(resource); - - // Verify standard properties from the delegate type - assertThat(properties, hasKey("uuid")); - assertThat(properties, hasKey("patientId")); - - // Verify properties defined by @PropertyGetter annotations - assertThat(properties, hasKey("activeIdentifiers")); - assertThat(properties, hasKey("displayName")); - assertThat(properties, hasEntry("activeIdentifiers", "List")); - assertThat(properties, hasEntry("displayName", "String")); - - // Verify properties defined by @PropertySetter annotations - assertThat(properties, hasKey("preferredName")); - assertThat(properties, hasEntry("preferredName", "String")); - } - - /** - * Mock DelegatingCrudResource for testing - */ - private class TestPatientResource extends DelegatingCrudResource { - - @Override - public Patient newDelegate() { - return new Patient(); - } - - @Override - public Patient save(Patient delegate) { - return delegate; - } - - @Override - public Patient getByUniqueId(String uniqueId) { - return new Patient(); - } - - @Override - public void purge(Patient delegate, RequestContext context) { - } - - @Override - public DelegatingResourceDescription getRepresentationDescription(Representation rep) { - return null; - } - - @Override - public void delete(Patient delegate, String reason, RequestContext context) throws ResourceDoesNotSupportOperationException { - throw new ResourceDoesNotSupportOperationException(); - } - } - - /** - * Mock DelegatingCrudResource for testing PropertyGetter and PropertySetter annotations - */ - private class TestPatientResourceWithAnnotations extends DelegatingCrudResource { - - @PropertyGetter("activeIdentifiers") - public List getActiveIdentifiers(Patient patient) { - return new ArrayList(); - } - - @PropertyGetter("displayName") - public String getDisplayName(Patient patient) { - return patient.getPersonName().getFullName(); - } - - @PropertySetter("preferredName") - public void setPreferredName(Patient patient, String name) { - // Implementation not needed for test - } - - // Standard resource methods - - @Override - public Patient newDelegate() { - return new Patient(); - } - - @Override - public Patient save(Patient delegate) { - return delegate; - } - - @Override - public Patient getByUniqueId(String uniqueId) { - return new Patient(); - } - - @Override - public void purge(Patient delegate, RequestContext context) { - } - - @Override - public DelegatingResourceDescription getRepresentationDescription(Representation rep) { - return null; - } - - @Override - public void delete(Patient delegate, String reason, RequestContext context) throws ResourceDoesNotSupportOperationException { - throw new ResourceDoesNotSupportOperationException(); - } - } - - /** - * Mock DelegatingSubResource for testing - */ - private class TestPatientSubResource extends DelegatingSubResource { - - @Override - public Patient getByUniqueId(String uniqueId) { - return new Patient(); - } - - @Override - public Patient newDelegate() { - return new Patient(); - } - - @Override - public Patient save(Patient delegate) { - return delegate; - } - - @Override - public void purge(Patient delegate, RequestContext context) { - } - - @Override - public Person getParent(Patient instance) { - return instance.getPerson(); - } - - @Override - public void setParent(Patient instance, Person parent) { - // In actual Patient class, the setPerson method is generated by Hibernate - // For our test, we'll just ignore the call - } - - @Override - public DelegatingResourceDescription getRepresentationDescription(Representation rep) { - return null; - } - - @Override - public org.openmrs.module.webservices.rest.web.resource.api.PageableResult doGetAll( - Person parent, - RequestContext context) { - return null; - } - - @Override - public void delete(Patient delegate, String reason, RequestContext context) throws ResourceDoesNotSupportOperationException { - throw new ResourceDoesNotSupportOperationException(); - } - } - - /** - * Mock TestPersonResource for testing sub-resources - */ - private class TestPersonResource extends DelegatingCrudResource { - - @Override - public Person newDelegate() { - return new Person(); - } - - @Override - public Person save(Person delegate) { - return delegate; - } - - @Override - public Person getByUniqueId(String uniqueId) { - return new Person(); - } - - @Override - public void purge(Person delegate, RequestContext context) { - } - - @Override - public DelegatingResourceDescription getRepresentationDescription(Representation rep) { - return null; - } - - @Override - public void delete(Person delegate, String reason, RequestContext context) throws ResourceDoesNotSupportOperationException { - throw new ResourceDoesNotSupportOperationException(); - } - } -} \ No newline at end of file diff --git a/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionControllerTest.java b/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionControllerTest.java deleted file mode 100644 index 4fd48308e..000000000 --- a/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionControllerTest.java +++ /dev/null @@ -1,131 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under - * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. - * - * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS - * graphic logo is a trademark of OpenMRS Inc. - */ -package org.openmrs.module.webservices.rest.web.v1_0.controller; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.openmrs.Patient; -import org.openmrs.module.webservices.rest.SimpleObject; -import org.openmrs.module.webservices.rest.web.RestConstants; -import org.openmrs.module.webservices.rest.web.api.RestService; -import org.openmrs.module.webservices.rest.web.api.SchemaIntrospectionService; -import org.openmrs.module.webservices.rest.web.resource.api.Resource; -import org.openmrs.module.webservices.rest.web.response.ObjectNotFoundException; - -/** - * Tests for {@link SchemaIntrospectionController}. - */ -public class SchemaIntrospectionControllerTest { - - @Mock - private RestService restService; - - @Mock - private SchemaIntrospectionService schemaIntrospectionService; - - @InjectMocks - private SchemaIntrospectionController controller; - - private Resource mockResource; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - mockResource = mock(Resource.class); - } - - /** - * @see SchemaIntrospectionController#getResourceProperties(String) - */ - @Test - public void getResourceProperties_shouldReturnPropertiesForValidResource() throws Exception { - // Setup mock behaviors - when(restService.getResourceByName(RestConstants.VERSION_1 + "/patient")).thenReturn(mockResource); - when(schemaIntrospectionService.getDelegateType(mockResource)).thenReturn((Class) Patient.class); - - // Setup mock properties - Map mockProperties = new HashMap(); - mockProperties.put("uuid", "String"); - mockProperties.put("patientId", "Integer"); - mockProperties.put("gender", "String"); - mockProperties.put("voided", "Boolean"); - - when(schemaIntrospectionService.discoverAvailableProperties(Patient.class)).thenReturn(mockProperties); - - // Call the controller method - SimpleObject result = controller.getResourceProperties("patient"); - - // Verify the result - assertThat(result, is(notNullValue())); - assertThat((String) result.get("resourceName"), is(equalTo("patient"))); - assertThat((String) result.get("delegateType"), is(equalTo(Patient.class.getName()))); - - // Verify the properties were included - @SuppressWarnings("unchecked") - Map resultProperties = (Map) result.get("discoverableProperties"); - assertThat(resultProperties, is(notNullValue())); - assertThat(resultProperties.size(), is(equalTo(4))); - assertThat(resultProperties.get("uuid"), is(equalTo("String"))); - assertThat(resultProperties.get("patientId"), is(equalTo("Integer"))); - assertThat(resultProperties.get("gender"), is(equalTo("String"))); - assertThat(resultProperties.get("voided"), is(equalTo("Boolean"))); - } - - /** - * @see SchemaIntrospectionController#getResourceProperties(String) - */ - @Test(expected = ObjectNotFoundException.class) - public void getResourceProperties_shouldThrowExceptionForNonExistentResource() throws Exception { - // Setup mock behavior for non-existent resource - when(restService.getResourceByName(anyString())).thenReturn(null); - - // This should throw ObjectNotFoundException - controller.getResourceProperties("nonexistent"); - } - - /** - * @see SchemaIntrospectionController#getResourceProperties(String) - */ - @Test(expected = ObjectNotFoundException.class) - public void getResourceProperties_shouldThrowExceptionWhenDelegateTypeCannotBeDetermined() throws Exception { - // Setup mock behaviors where resource is found but delegate type isn't - when(restService.getResourceByName(RestConstants.VERSION_1 + "/patient")).thenReturn(mockResource); - when(schemaIntrospectionService.getDelegateType(mockResource)).thenReturn(null); - - // This should throw ObjectNotFoundException - controller.getResourceProperties("patient"); - } - - /** - * @see SchemaIntrospectionController#getResourceProperties(String) - */ - @Test(expected = ObjectNotFoundException.class) - public void getResourceProperties_shouldThrowExceptionWhenResourceLookupFails() throws Exception { - // Setup mock behavior to throw an exception during resource lookup - when(restService.getResourceByName(anyString())).thenThrow(new RuntimeException("Resource lookup failed")); - - // This should throw ObjectNotFoundException - controller.getResourceProperties("patient"); - } -} \ No newline at end of file diff --git a/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionTest.java b/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionTest.java deleted file mode 100644 index 69f5e9384..000000000 --- a/omod-common/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/SchemaIntrospectionTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under - * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. - * - * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS - * graphic logo is a trademark of OpenMRS Inc. - */ -package org.openmrs.module.webservices.rest.web.v1_0.controller; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.hasKey; - -import java.util.Map; - -import org.junit.Before; -import org.junit.Test; -import org.openmrs.Patient; -import org.openmrs.module.webservices.rest.web.api.impl.SchemaIntrospectionServiceImpl; - -/** - * Tests for schema introspection functionality. - * This is a standard unit test rather than an integration test to avoid Spring context issues. - */ -public class SchemaIntrospectionTest { - - private SchemaIntrospectionServiceImpl service; - - @Before - public void setup() { - service = new SchemaIntrospectionServiceImpl(); - } - - /** - * Tests that reflection discovers expected properties on Patient - */ - @Test - public void shouldDiscoverPatientProperties() { - Map properties = service.discoverAvailableProperties(Patient.class); - - // Verify discovered properties - assertThat(properties, is(notNullValue())); - assertThat(properties.size(), is(greaterThan(10))); // Should have many properties - - // Check some essential Patient properties - assertThat(properties, hasKey("uuid")); - assertThat(properties, hasKey("patientId")); - assertThat(properties, hasKey("identifiers")); - assertThat(properties, hasKey("person")); - - // Check some properties inherited from Person - assertThat(properties, hasKey("gender")); - assertThat(properties, hasKey("birthdate")); - assertThat(properties, hasKey("names")); - assertThat(properties, hasKey("addresses")); - } - - /** - * Tests property discovery for another common class - */ - @Test - public void shouldDiscoverPropertiesFromGetters() { - // Use a simple class from the OpenMRS API - Map properties = service.discoverAvailableProperties(org.openmrs.Location.class); - - // Verify some expected properties - assertThat(properties, is(notNullValue())); - assertThat(properties.size(), is(greaterThan(5))); - assertThat(properties, hasKey("uuid")); - assertThat(properties, hasKey("name")); - assertThat(properties, hasKey("description")); - assertThat(properties, hasKey("address1")); // Fixed: Location uses address1, address2, etc. - assertThat(properties, hasKey("address2")); // Added another address field to verify - assertThat(properties, hasKey("parentLocation")); - assertThat(properties, hasKey("childLocations")); - assertThat(properties, hasKey("retired")); - } -} \ No newline at end of file From 8cbf3f1a8533546529e5d1984f2abc90a5a591d2 Mon Sep 17 00:00:00 2001 From: capernix Date: Tue, 24 Jun 2025 11:31:25 +0530 Subject: [PATCH 09/12] Modified the error handling practices to be more descriptive and removed all general Exception handling --- .../java/org/openmrs/plugin/OpenAPIMojo.java | 259 +++++++++++------- 1 file changed, 161 insertions(+), 98 deletions(-) diff --git a/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java b/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java index 99d5719e2..15897fa75 100644 --- a/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java +++ b/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java @@ -81,14 +81,13 @@ public void execute() throws MojoExecutionException, MojoFailureException { } else { log.warn("Artifact file not found: " + artifact.getGroupId() + ":" + artifact.getArtifactId()); } - } - } + } } log.info("ClassLoader setup complete: " + urls.size() + " URLs"); return new URLClassLoader(urls.toArray(new URL[0]), Thread.currentThread().getContextClassLoader()); - } catch (Exception e) { - throw new RuntimeException("Failed to setup ClassLoader", e); + } catch (java.net.MalformedURLException e) { + throw new RuntimeException("Invalid URL in classpath elements: " + e.getMessage(), e); } } @@ -102,15 +101,17 @@ private void disableOpenMRSContext() { log.info(" SUCCESS: OpenMRS Context disabled successfully"); log.debug(" RestUtil.contextEnabled is now false"); log.debug(" Static initializers will not attempt Context access"); - - } catch (ClassNotFoundException e) { + } catch (ClassNotFoundException e) { log.error(" FAILED: RestUtil class not found: " + e.getMessage()); throw new RuntimeException("Cannot disable OpenMRS context", e); } catch (NoSuchMethodException e) { log.error(" FAILED: disableContext method not found: " + e.getMessage()); throw new RuntimeException("Cannot disable OpenMRS context", e); - } catch (Exception e) { - log.error(" FAILED: Error disabling context: " + e.getMessage()); + } catch (IllegalAccessException e) { + log.error(" FAILED: Cannot access disableContext method: " + e.getMessage()); + throw new RuntimeException("Cannot disable OpenMRS context", e); + } catch (java.lang.reflect.InvocationTargetException e) { + log.error(" FAILED: disableContext method invocation failed: " + e.getCause().getMessage()); throw new RuntimeException("Cannot disable OpenMRS context", e); } } @@ -155,16 +156,15 @@ private void testRestConstantsAccessibility() { } else { log.warn(" REPRESENTATION_FULL unexpected value: " + representationFull); } - - log.info(" SUCCESS: RestConstants is fully accessible at build time!"); + log.info(" SUCCESS: RestConstants is fully accessible at build time!"); } catch (ClassNotFoundException e) { log.error(" FAILED: RestConstants class not found in ClassLoader"); - log.error("This means RestConstants is not in the classpath"); } catch (NoSuchFieldException e) { + log.error("This means RestConstants is not in the classpath"); + } catch (NoSuchFieldException e) { log.error(" FAILED: RestConstants field not found: " + e.getMessage()); - } catch (Exception e) { - log.error(" FAILED: RestConstants not accessible: " + e.getClass().getSimpleName() + ": " + e.getMessage()); - e.printStackTrace(); + } catch (IllegalAccessException e) { + log.error(" FAILED: Cannot access RestConstants fields: " + e.getMessage()); } } @@ -176,9 +176,8 @@ private Class loadClass(String className) { } catch (ClassNotFoundException e) { throw new RuntimeException("Failed to load class: " + className, e); } - } - - private void testPatientResourceInstantiation(Class resourceClazz) { + } + private void testPatientResourceInstantiation(Class resourceClazz) throws MojoExecutionException { log.debug("=== Testing PatientResource Instance Creation ==="); try { @@ -206,128 +205,192 @@ private void testPatientResourceInstantiation(Class resourceClazz) { } catch (InstantiationException e) { log.error(" FAILED: Cannot instantiate class"); - log.error("Reason: " + e.getMessage()); - + log.error("Reason: " + e.getMessage()); } catch (IllegalAccessException e) { log.error(" FAILED: Constructor not accessible"); log.error("Reason: " + e.getMessage()); - } catch (Exception e) { - log.error(" FAILED: Unexpected error during instantiation"); - log.error("Error type: " + e.getClass().getSimpleName()); - log.error("Error message: " + e.getMessage()); - e.printStackTrace(); - } - } - - private void testBasicMethodCall(Object instance) { - try { - log.debug("--- Testing Basic Method Calls ---"); - - String toStringResult = instance.toString(); - log.debug(" toString() works: " + toStringResult); - - Class instanceClass = instance.getClass(); - log.debug(" getClass() works: " + instanceClass.getName()); - - Method[] methods = instanceClass.getDeclaredMethods(); - log.debug(" Found " + methods.length + " declared methods"); + } catch (NoSuchMethodException e) { + log.error(" FAILED: Default constructor not found"); + log.error("Reason: " + e.getMessage()); - for (Method method : methods) { - if (method.getName().equals("getRepresentationDescription")) { - log.info(" Found getRepresentationDescription method: " + method.toString()); - } + } catch (java.lang.reflect.InvocationTargetException e) { + log.error(" FAILED: Constructor execution failed"); + log.error("Cause: " + e.getCause().getClass().getSimpleName() + ": " + e.getCause().getMessage()); + } + } private void testBasicMethodCall(Object instance) { + log.debug("--- Testing Basic Method Calls ---"); + + String toStringResult = instance.toString(); + log.debug(" toString() works: " + toStringResult); + + Class instanceClass = instance.getClass(); + log.debug(" getClass() works: " + instanceClass.getName()); + + Method[] methods = instanceClass.getDeclaredMethods(); + log.debug(" Found " + methods.length + " declared methods"); + + for (Method method : methods) { + if (method.getName().equals("getRepresentationDescription")) { + log.info(" Found getRepresentationDescription method: " + method.toString()); } - } catch (Exception e) { - log.warn(" Basic method calls failed: " + e.getMessage()); } - } - - private void testGetRepresentationDescription(Object instance) { + }private void testGetRepresentationDescription(Object instance) throws MojoExecutionException { + log.info("=== Testing getRepresentationDescription Method ==="); + try { - log.info("=== Testing getRepresentationDescription Method ==="); + Class representationClass = loadRequiredClass("org.openmrs.module.webservices.rest.web.representation.Representation"); + Class defaultRepClass = loadRequiredClass("org.openmrs.module.webservices.rest.web.representation.DefaultRepresentation"); + Class fullRepClass = loadRequiredClass("org.openmrs.module.webservices.rest.web.representation.FullRepresentation"); - //testRestConstantsInMethodContext(); - - Class representationClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.representation.Representation"); - Class defaultRepClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.representation.DefaultRepresentation"); - Class fullRepClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.representation.FullRepresentation"); - - Object defaultRep = defaultRepClass.getDeclaredConstructor().newInstance(); - Object fullRep = fullRepClass.getDeclaredConstructor().newInstance(); + Object defaultRep = createRequiredInstance(defaultRepClass); + Object fullRep = createRequiredInstance(fullRepClass); Method method = instance.getClass().getMethod("getRepresentationDescription", representationClass); - Object defaultResult = method.invoke(instance, defaultRep); - log.info(" DEFAULT result: " + defaultResult); + Object defaultResult = invokeRepresentationMethod(method, instance, defaultRep, "DEFAULT"); + Object fullResult = invokeRepresentationMethod(method, instance, fullRep, "FULL"); - Object fullResult = method.invoke(instance, fullRep); - log.info(" FULL result: " + fullResult); if (defaultResult != null) { - extractSchemaFromDescription(defaultResult, "DEFAULT"); - } - - if (fullResult != null) { - extractSchemaFromDescription(fullResult, "FULL"); - } + tryExtractSchema(defaultResult, "DEFAULT"); + tryExtractSchema(fullResult, "FULL"); - } catch (Exception e) { - log.error("Method invocation failed: " + e.getMessage()); + } catch (ClassNotFoundException e) { + throw new MojoExecutionException("Required OpenMRS representation classes not found in classpath. " + + "Ensure OpenMRS webservices.rest module is properly included in dependencies.", e); + } catch (NoSuchMethodException e) { + throw new MojoExecutionException("getRepresentationDescription method not found on resource class. " + + "This OpenMRS version may not be supported.", e); + } catch (InstantiationException | IllegalAccessException e) { + throw new MojoExecutionException("Cannot create or access OpenMRS representation instances. " + + "Check OpenMRS version compatibility.", e); } } - private void testRestConstantsInMethodContext() { + private Class loadRequiredClass(String className) throws ClassNotFoundException { + return classLoader.loadClass(className); + } + + private Object createRequiredInstance(Class clazz) throws InstantiationException, IllegalAccessException { try { - log.info("--- Verifying RestConstants in method execution context ---"); - Class restConstants = classLoader.loadClass("org.openmrs.module.webservices.rest.web.RestConstants"); - - Object version1 = restConstants.getField("VERSION_1").get(null); - log.info(" RestConstants accessible in method context: VERSION_1 = " + version1); - - Object repDefault = restConstants.getField("REPRESENTATION_DEFAULT").get(null); - Object repFull = restConstants.getField("REPRESENTATION_FULL").get(null); - - log.info(" Representation constants accessible:"); - log.info(" - REPRESENTATION_DEFAULT = " + repDefault); - log.info(" - REPRESENTATION_FULL = " + repFull); - - log.info(" RestConstants should be accessible to getRepresentationDescription()"); - - } catch (Exception e) { - log.error(" RestConstants not accessible in method context: " + e.getMessage()); + return clazz.getDeclaredConstructor().newInstance(); + } catch (NoSuchMethodException e) { + throw new InstantiationException("No default constructor found for " + clazz.getSimpleName()); + } catch (java.lang.reflect.InvocationTargetException e) { + throw new InstantiationException("Constructor failed: " + e.getCause().getMessage()); } } - - private void extractSchemaFromDescription(Object description, String representationType) { + + private Object invokeRepresentationMethod(Method method, Object instance, Object representation, String type) { + try { + Object result = method.invoke(instance, representation); + log.info("{} result: {}", type, result); + return result; + } catch (java.lang.reflect.InvocationTargetException e) { + log.warn("Failed to invoke getRepresentationDescription for {}: {}", type, e.getCause().getMessage()); + if (log.isDebugEnabled()) { + log.debug("Method invocation error details", e.getCause()); + } + return null; + } catch (IllegalAccessException e) { + log.warn("Cannot access getRepresentationDescription method for {}: {}", type, e.getMessage()); + return null; + } + } + + private boolean tryExtractSchema(Object description, String representationType) { + if (description == null) { + log.info("No {} representation to extract schema from", representationType); + return false; + } + + log.info("=== Extracting Schema from {} ===", representationType); + + boolean propertiesSuccess = tryExtractProperties(description, representationType); + if (!propertiesSuccess) { + log.warn("Could not extract properties for {}", representationType); + } + + boolean linksSuccess = tryExtractLinks(description, representationType); + if (!linksSuccess) { + log.warn("Could not extract links for {}", representationType); + } + + return propertiesSuccess || linksSuccess; + + } + + private boolean tryExtractProperties(Object description, String type) { try { - log.info("=== Extracting Schema from " + representationType + " ==="); - Method getPropertiesMethod = description.getClass().getMethod("getProperties"); Object properties = getPropertiesMethod.invoke(description); if (properties instanceof Map) { Map propertyMap = (Map) properties; - log.info("Found " + propertyMap.size() + " properties:"); + log.info("Found {} properties:", propertyMap.size()); for (Map.Entry entry : propertyMap.entrySet()) { - String propertyName = entry.getKey().toString(); + String propertyName = String.valueOf(entry.getKey()); Object propertyValue = entry.getValue(); - log.info(" - " + propertyName + " -> " + propertyValue); + log.info(" - {} -> {}", propertyName, propertyValue); } + return true; + } else { + log.warn("getProperties() returned non-Map type for {}: {}", type, + properties != null ? properties.getClass().getSimpleName() : "null"); + return false; } + } catch (NoSuchMethodException e) { + log.warn("getProperties method not available for {} (OpenMRS version compatibility)", type); + return false; + } catch (IllegalAccessException e) { + log.warn("Cannot access getProperties method for {}", type); + return false; + } catch (java.lang.reflect.InvocationTargetException e) { + log.warn("getProperties method failed for {}: {}", type, e.getCause().getMessage()); + if (log.isDebugEnabled()) { + log.debug("getProperties error details", e.getCause()); + } + return false; + } catch (ClassCastException e) { + log.warn("getProperties returned unexpected type for {}: {}", type, e.getMessage()); + return false; + } + } + + private boolean tryExtractLinks(Object description, String type) { + try { Method getLinksMethod = description.getClass().getMethod("getLinks"); Object links = getLinksMethod.invoke(description); + if (links instanceof Map) { Map linkMap = (Map) links; - log.info("Found " + linkMap.size() + " links:"); + log.info("Found {} links:", linkMap.size()); for (Map.Entry entry : linkMap.entrySet()) { - log.info(" - LINK: " + entry.getKey() + " -> " + entry.getValue()); + log.info(" - LINK: {} -> {}", entry.getKey(), entry.getValue()); } + return true; + } else { + log.warn("getLinks() returned non-Map type for {}: {}", type, + links != null ? links.getClass().getSimpleName() : "null"); + return false; } - } catch (Exception e) { - log.error("Failed to extract schema: " + e.getMessage()); + } catch (NoSuchMethodException e) { + log.warn("getLinks method not available for {} (OpenMRS version compatibility)", type); + return false; + } catch (IllegalAccessException e) { + log.warn("Cannot access getLinks method for {}", type); + return false; + } catch (java.lang.reflect.InvocationTargetException e) { + log.warn("getLinks method failed for {}: {}", type, e.getCause().getMessage()); + if (log.isDebugEnabled()) { + log.debug("getLinks error details", e.getCause()); + } + return false; } catch (ClassCastException e) { + log.warn("getLinks returned unexpected type for {}: {}", type, e.getMessage()); + return false; } } } From 966ed506a08099665700f06d1bd5c7e6d510b6c2 Mon Sep 17 00:00:00 2001 From: capernix Date: Tue, 1 Jul 2025 22:07:59 +0530 Subject: [PATCH 10/12] Fixes for RESTWS-980 (Plugin, ConditionResource, etc.) --- omod-2.4/pom.xml | 18 + .../openmrs2_2/ConditionResource2_2.java | 24 +- omod/pom.xml | 3 +- .../openapi-generator-maven-plugin/README.md | 68 --- .../java/org/openmrs/plugin/OpenAPIMojo.java | 425 +++++++++--------- 5 files changed, 255 insertions(+), 283 deletions(-) delete mode 100644 tools/openapi-generator-maven-plugin/README.md diff --git a/omod-2.4/pom.xml b/omod-2.4/pom.xml index 236bc8b6e..07f178603 100644 --- a/omod-2.4/pom.xml +++ b/omod-2.4/pom.xml @@ -97,6 +97,24 @@
${project.parent.basedir}/license-header.txt
+ + org.openmrs.plugin + openapi-generator-maven-plugin + 1.0.0-SNAPSHOT + + + org.openmrs.module.webservices.rest.web.v1_0.resource + + + + + process-classes + + openapi + + + +
diff --git a/omod-2.4/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/resource/openmrs2_2/ConditionResource2_2.java b/omod-2.4/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/resource/openmrs2_2/ConditionResource2_2.java index 9bba1b5a2..e8b88b103 100644 --- a/omod-2.4/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/resource/openmrs2_2/ConditionResource2_2.java +++ b/omod-2.4/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/resource/openmrs2_2/ConditionResource2_2.java @@ -42,7 +42,14 @@ "2.2.* - 9.*" }) public class ConditionResource2_2 extends DataDelegatingCrudResource { - private ConditionService conditionService = Context.getConditionService(); + private ConditionService conditionService; + + private ConditionService getConditionService() { + if (conditionService == null) { + conditionService = Context.getConditionService(); + } + return conditionService; + } /** * @see org.openmrs.module.webservices.rest.web.resource.impl.DelegatingCrudResource#getRepresentationDescription(Representation) @@ -180,7 +187,7 @@ public DelegatingResourceDescription getUpdatableProperties() { */ @Override public Condition getByUniqueId(String uuid) { - return conditionService.getConditionByUuid(uuid); + return getConditionService().getConditionByUuid(uuid); } /** @@ -189,7 +196,7 @@ public Condition getByUniqueId(String uuid) { */ @Override protected void delete(Condition condition, String reason, RequestContext requestContext) throws ResponseException { - conditionService.voidCondition(condition, reason); + getConditionService().voidCondition(condition, reason); } /** @@ -205,7 +212,7 @@ public Condition newDelegate() { */ @Override public Condition save(Condition condition) { - return conditionService.saveCondition(condition); + return getConditionService().saveCondition(condition); } /** @@ -214,7 +221,7 @@ public Condition save(Condition condition) { */ @Override public void purge(Condition condition, RequestContext requestContext) throws ResponseException { - conditionService.purgeCondition(condition); + getConditionService().purgeCondition(condition); } /** @@ -237,7 +244,6 @@ public String getDisplayString(Condition condition) { protected PageableResult doSearch(RequestContext context) { String patientUuid = context.getRequest().getParameter("patientUuid"); String includeInactive = context.getRequest().getParameter("includeInactive"); - ConditionService conditionService = Context.getConditionService(); if (StringUtils.isBlank(patientUuid)) { return new EmptySearchResult(); } @@ -249,13 +255,13 @@ protected PageableResult doSearch(RequestContext context) { if (StringUtils.isNotBlank(includeInactive)) { boolean isIncludeInactive = BooleanUtils.toBoolean(includeInactive); if (isIncludeInactive) { - return new NeedsPaging(conditionService.getAllConditions(patient), context); + return new NeedsPaging(getConditionService().getAllConditions(patient), context); } else { - return new NeedsPaging(conditionService.getActiveConditions(patient), context); + return new NeedsPaging(getConditionService().getActiveConditions(patient), context); } } else { - return new NeedsPaging(conditionService.getActiveConditions(patient), context); + return new NeedsPaging(getConditionService().getActiveConditions(patient), context); } } } diff --git a/omod/pom.xml b/omod/pom.xml index 6b59254dd..098c35fb6 100644 --- a/omod/pom.xml +++ b/omod/pom.xml @@ -82,7 +82,8 @@ org.apache.maven.plugins maven-dependency-plugin - + + Expand resources unpack-dependencies diff --git a/tools/openapi-generator-maven-plugin/README.md b/tools/openapi-generator-maven-plugin/README.md deleted file mode 100644 index a20d0f55c..000000000 --- a/tools/openapi-generator-maven-plugin/README.md +++ /dev/null @@ -1,68 +0,0 @@ -# OpenAPI Generator Maven Plugin - -## Overview - -The `openapi-generator-maven-plugin` is a custom Maven plugin designed for the OpenMRS ecosystem. Its primary function is to print a friendly message during the Maven build lifecycle, serving as a foundational tool for future enhancements related to OpenAPI specification generation. - -## Purpose - -This plugin aims to facilitate integration with OpenMRS modules, particularly the `webservices.rest` module, by providing a simple yet effective way to execute custom build actions. - -## Features - -- **Maven Goal**: `hello` -- **Output**: Logs "Hello from OpenMRS Maven Plugin! 🎉" during the build process. -- **Lifecycle Phase**: Can be executed during the `process-classes` or `compile` phase. -- **Integration**: Designed to be easily integrated into OpenMRS modules. - -## Installation - -To install the plugin locally, navigate to the project directory and run: - -```bash -mvn clean install -``` - -This command will compile the plugin and install it into your local Maven repository. - -## Usage - -To use the plugin in your OpenMRS module, add the following configuration to your `pom.xml`: - -```xml - - org.openmrs.plugin - openapi-generator-maven-plugin - 1.0-SNAPSHOT - - - say-hello - process-classes - - hello - - - - -``` - -After adding the plugin configuration, run the build: - -```bash -mvn clean install -``` - -You should see the output: `Hello from OpenMRS Maven Plugin! 🎉` - -## Future Enhancements - -The plugin is intended to evolve with additional features, including: - -- Support for loading resource classes. -- Scanning for annotations like `@Resource` and `@DocumentedResource`. -- Generating OpenAPI JSON specifications. -- Archiving documentation into module artifacts. - -## Contribution - -Contributions to enhance the functionality of this plugin are welcome. Please ensure to follow the project's coding standards and guidelines when submitting changes. \ No newline at end of file diff --git a/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java b/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java index 15897fa75..d5717606f 100644 --- a/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java +++ b/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java @@ -18,6 +18,8 @@ import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.apache.maven.artifact.Artifact; +import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,29 +37,50 @@ public class OpenAPIMojo extends AbstractMojo { private static final Logger log = LoggerFactory.getLogger(OpenAPIMojo.class); - private static final String TARGET_CLASS_NAME = "org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8.CohortResource1_8"; - private URLClassLoader classLoader; + + private Class representationClass; + private Class defaultRepresentationClass; + private Class fullRepresentationClass; + private Class requestMappingClass; + private Class restUtilClass; + private Class restConstantsClass; @Parameter(defaultValue = "${project}", required = true, readonly = true) private MavenProject project; + @Parameter + private List scanPackages; + + @Parameter(defaultValue = "false") + private boolean skipEmptyModules; + @Override public void execute() throws MojoExecutionException, MojoFailureException { log.info("=== OPENAPI GENERATION STARTED ==="); + log.info("Processing module: {}", project.getArtifactId()); this.classLoader = setupProjectClassLoader(); - + loadCommonClasses(); disableOpenMRSContext(); - //testRestConstantsAccessibility(); + List> resourceClasses = discoverResourceClasses(); - Class resourceClazz = loadClass(TARGET_CLASS_NAME); - - testPatientResourceInstantiation(resourceClazz); + if (resourceClasses.isEmpty()) { + log.info("No resources found in this module - skipping OpenAPI generation"); + return; + } + + log.info("Found {} resource classes to process", resourceClasses.size()); + + for (Class resourceClass : resourceClasses) { + processResourceClass(resourceClass); + } - log.info("=============="); - } private URLClassLoader setupProjectClassLoader() { + log.info("=== OPENAPI GENERATION COMPLETED ==="); + } + + private URLClassLoader setupProjectClassLoader() { try { List urls = new ArrayList<>(); @@ -65,7 +88,6 @@ public void execute() throws MojoExecutionException, MojoFailureException { File outputDir = new File(outputDirectory); if (outputDir.exists()) { urls.add(outputDir.toURI().toURL()); - log.info("Added project output directory: " + outputDirectory); } else { log.warn("Project output directory does not exist: " + outputDirectory); } @@ -83,89 +105,49 @@ public void execute() throws MojoExecutionException, MojoFailureException { } } } - log.info("ClassLoader setup complete: " + urls.size() + " URLs"); return new URLClassLoader(urls.toArray(new URL[0]), Thread.currentThread().getContextClassLoader()); } catch (java.net.MalformedURLException e) { throw new RuntimeException("Invalid URL in classpath elements: " + e.getMessage(), e); } } - - private void disableOpenMRSContext() { - log.info("=== Disabling OpenMRS Context for Build-Time Use ==="); + + private void loadCommonClasses() { + log.debug("=== Loading Common Classes (One-time Cache) ==="); try { - Class restUtilClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.RestUtil"); - Method disableContextMethod = restUtilClass.getMethod("disableContext"); - disableContextMethod.invoke(null); - log.info(" SUCCESS: OpenMRS Context disabled successfully"); - log.debug(" RestUtil.contextEnabled is now false"); - log.debug(" Static initializers will not attempt Context access"); - } catch (ClassNotFoundException e) { - log.error(" FAILED: RestUtil class not found: " + e.getMessage()); - throw new RuntimeException("Cannot disable OpenMRS context", e); - } catch (NoSuchMethodException e) { - log.error(" FAILED: disableContext method not found: " + e.getMessage()); - throw new RuntimeException("Cannot disable OpenMRS context", e); - } catch (IllegalAccessException e) { - log.error(" FAILED: Cannot access disableContext method: " + e.getMessage()); - throw new RuntimeException("Cannot disable OpenMRS context", e); - } catch (java.lang.reflect.InvocationTargetException e) { - log.error(" FAILED: disableContext method invocation failed: " + e.getCause().getMessage()); - throw new RuntimeException("Cannot disable OpenMRS context", e); + representationClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.representation.Representation"); + defaultRepresentationClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.representation.DefaultRepresentation"); + fullRepresentationClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.representation.FullRepresentation"); + log.debug("Cached representation classes"); + + restUtilClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.RestUtil"); + restConstantsClass = classLoader.loadClass("org.openmrs.module.webservices.rest.web.RestConstants"); + log.debug("Cached REST utility classes"); + + try { + requestMappingClass = classLoader.loadClass("org.springframework.web.bind.annotation.RequestMapping"); + log.debug("Cached RequestMapping annotation class"); + } catch (ClassNotFoundException e) { + log.debug("RequestMapping annotation not available - will skip annotation checks"); + requestMappingClass = null; + } + } catch (ClassNotFoundException e) { + throw new RuntimeException("Failed to load required OpenMRS classes. Ensure webservices.rest module is in dependencies.", e); } } - private void testRestConstantsAccessibility() { - log.info("=== Testing RestConstants Accessibility ==="); - + private void disableOpenMRSContext() { try { - Class restConstants = classLoader.loadClass("org.openmrs.module.webservices.rest.web.RestConstants"); - log.info(" RestConstants class loaded successfully: " + restConstants.getName()); - - Object version1 = restConstants.getField("VERSION_1").get(null); - Object uriPrefix = restConstants.getField("URI_PREFIX").get(null); - Object representationDefault = restConstants.getField("REPRESENTATION_DEFAULT").get(null); - Object representationFull = restConstants.getField("REPRESENTATION_FULL").get(null); - - log.info(" RestConstants.VERSION_1 = " + version1); - log.info(" RestConstants.URI_PREFIX = " + uriPrefix); - log.info(" RestConstants.REPRESENTATION_DEFAULT = " + representationDefault); - log.info(" RestConstants.REPRESENTATION_FULL = " + representationFull); - - if ("v1".equals(version1)) { - log.info(" VERSION_1 has expected value"); - } else { - log.warn(" VERSION_1 unexpected value: " + version1); - } - - if (uriPrefix != null && uriPrefix.toString().contains("/ws/rest")) { - log.info(" URI_PREFIX contains expected path"); - } else { - log.warn(" URI_PREFIX unexpected value: " + uriPrefix); - } - - if ("default".equals(representationDefault)) { - log.info(" REPRESENTATION_DEFAULT has expected value"); - } else { - log.warn(" REPRESENTATION_DEFAULT unexpected value: " + representationDefault); - } - - if ("full".equals(representationFull)) { - log.info(" REPRESENTATION_FULL has expected value"); - } else { - log.warn(" REPRESENTATION_FULL unexpected value: " + representationFull); + Method disableContextMethod = restUtilClass.getMethod("disableContext"); + disableContextMethod.invoke(null); + log.info("SUCCESS: OpenMRS Context disabled successfully"); + log.debug(" RestUtil.contextEnabled is now false"); + log.debug(" Static initializers will not attempt Context access"); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + log.error("FAILED: Cannot disable OpenMRS context", e); + throw new RuntimeException("Cannot disable OpenMRS context", e); } - log.info(" SUCCESS: RestConstants is fully accessible at build time!"); - - } catch (ClassNotFoundException e) { - log.error(" FAILED: RestConstants class not found in ClassLoader"); - log.error("This means RestConstants is not in the classpath"); - } catch (NoSuchFieldException e) { - log.error(" FAILED: RestConstants field not found: " + e.getMessage()); - } catch (IllegalAccessException e) { - log.error(" FAILED: Cannot access RestConstants fields: " + e.getMessage()); - } } private Class loadClass(String className) { @@ -176,75 +158,170 @@ private Class loadClass(String className) { } catch (ClassNotFoundException e) { throw new RuntimeException("Failed to load class: " + className, e); } - } - private void testPatientResourceInstantiation(Class resourceClazz) throws MojoExecutionException { - log.debug("=== Testing PatientResource Instance Creation ==="); + } + + private List> discoverResourceClasses() throws MojoExecutionException { + List> discoveredClasses = new ArrayList<>(); - try { - log.debug("Attempting to create PatientResource1_8 instance using normal constructor..."); + if (scanPackages != null && !scanPackages.isEmpty()) { + log.info("Scanning packages: {}", scanPackages); + discoveredClasses = scanPackagesForResources(scanPackages); + } + + else { + throw new MojoExecutionException( + "No resource discovery configuration provided. " + + "Please specify either 'targetClasses' or 'scanPackages' in plugin configuration." + ); + } + + if (discoveredClasses.isEmpty() && !skipEmptyModules) { + throw new MojoExecutionException("No REST resource classes found in this module"); + } + + log.info("Discovered {} resource classes", discoveredClasses.size()); + return discoveredClasses; + } + + private List> scanPackagesForResources(List packages) { + List> resourceClasses = new ArrayList<>(); + + for (String packageName : packages) { + log.debug("Scanning package: {}", packageName); - Object instance = resourceClazz.getDeclaredConstructor().newInstance(); + String packagePath = packageName.replace('.', '/'); - log.info(" SUCCESS! PatientResource1_8 instance created successfully!"); - log.info("Instance class: " + instance.getClass().getName()); - log.info("Instance toString: " + instance.toString()); + String outputDirectory = project.getBuild().getOutputDirectory(); + File packageDir = new File(outputDirectory, packagePath); - testBasicMethodCall(instance); - - testGetRepresentationDescription(instance); + if (!packageDir.exists()) { + log.warn("Package directory does not exist: {}", packageDir.getAbsolutePath()); + continue; + } - } catch (ExceptionInInitializerError e) { - log.error(" FAILED: Static initialization error"); - log.error("Root cause: " + e.getCause().getClass().getSimpleName() + ": " + e.getCause().getMessage()); - log.error("This means parent classes have problematic static blocks"); + List classFiles = findClassFiles(packageDir); - } catch (NoClassDefFoundError e) { - log.error(" FAILED: Missing class dependency"); - log.error("Missing class: " + e.getMessage()); - log.error("This means some required classes aren't in the classpath"); + for (File classFile : classFiles) { + String className = getClassNameFromFile(classFile, outputDirectory); + + try { + Class clazz = classLoader.loadClass(className); + + if (isRestResourceClass(clazz)) { + resourceClasses.add(clazz); + log.info("Found REST resource: {}", className); + } else { + log.debug("Skipped non-resource class: {}", className); + } + + } catch (ClassNotFoundException | NoClassDefFoundError e) { + log.debug("Could not load class {}: {}", className, e.getMessage()); + } + } + } + + return resourceClasses; + } + + private boolean isRestResourceClass(Class clazz) { + if (clazz.isInterface() || + java.lang.reflect.Modifier.isAbstract(clazz.getModifiers()) || + clazz.getName().contains("Test")) { + return false; + } + + if (!clazz.getName().contains(".resource.")) { + return false; + } + + try { + Method getRepDescMethod = clazz.getMethod("getRepresentationDescription", representationClass); - } catch (InstantiationException e) { - log.error(" FAILED: Cannot instantiate class"); - log.error("Reason: " + e.getMessage()); - } catch (IllegalAccessException e) { - log.error(" FAILED: Constructor not accessible"); - log.error("Reason: " + e.getMessage()); + if (getRepDescMethod != null) { + log.debug("Class {} has getRepresentationDescription method", clazz.getSimpleName()); + return true; + } } catch (NoSuchMethodException e) { - log.error(" FAILED: Default constructor not found"); - log.error("Reason: " + e.getMessage()); - - } catch (java.lang.reflect.InvocationTargetException e) { - log.error(" FAILED: Constructor execution failed"); - log.error("Cause: " + e.getCause().getClass().getSimpleName() + ": " + e.getCause().getMessage()); + Class current = clazz; + while (current != null) { + String baseClassName = current.getName(); + if (baseClassName.contains("DelegatingResourceHandler") || + baseClassName.contains("DelegatingCrudResource") || + baseClassName.contains("BaseRestController")) { + log.debug("Class {} extends REST resource base class", clazz.getSimpleName()); + return true; + } + current = current.getSuperclass(); + } } - } private void testBasicMethodCall(Object instance) { - log.debug("--- Testing Basic Method Calls ---"); - String toStringResult = instance.toString(); - log.debug(" toString() works: " + toStringResult); + if (requestMappingClass != null) { + try { + if (clazz.isAnnotationPresent(requestMappingClass.asSubclass(java.lang.annotation.Annotation.class))) { + log.debug("Class {} has REST annotations", clazz.getSimpleName()); + return true; + } + } catch (ClassCastException e) { + log.debug("RequestMapping class is not an annotation, skipping annotation check"); + } + } - Class instanceClass = instance.getClass(); - log.debug(" getClass() works: " + instanceClass.getName()); + return false; + } + + private List findClassFiles(File directory) { + List classFiles = new ArrayList<>(); - Method[] methods = instanceClass.getDeclaredMethods(); - log.debug(" Found " + methods.length + " declared methods"); + if (!directory.exists() || !directory.isDirectory()) { + return classFiles; + } - for (Method method : methods) { - if (method.getName().equals("getRepresentationDescription")) { - log.info(" Found getRepresentationDescription method: " + method.toString()); + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + classFiles.addAll(findClassFiles(file)); + } else if (file.getName().endsWith(".class")) { + classFiles.add(file); + } } } - }private void testGetRepresentationDescription(Object instance) throws MojoExecutionException { - log.info("=== Testing getRepresentationDescription Method ==="); + + return classFiles; + } + + private String getClassNameFromFile(File classFile, String outputDirectory) { + String filePath = classFile.getAbsolutePath(); + String outputPath = new File(outputDirectory).getAbsolutePath(); + + String relativePath = filePath.substring(outputPath.length() + 1); + String className = relativePath.substring(0, relativePath.length() - 6); // Remove ".class" + + return className.replace(File.separatorChar, '.'); + } + + private void processResourceClass(Class resourceClass) { + log.info("=== Resource: {} ===", resourceClass.getSimpleName()); try { - Class representationClass = loadRequiredClass("org.openmrs.module.webservices.rest.web.representation.Representation"); - Class defaultRepClass = loadRequiredClass("org.openmrs.module.webservices.rest.web.representation.DefaultRepresentation"); - Class fullRepClass = loadRequiredClass("org.openmrs.module.webservices.rest.web.representation.FullRepresentation"); + Object instance = resourceClass.getDeclaredConstructor().newInstance(); - Object defaultRep = createRequiredInstance(defaultRepClass); - Object fullRep = createRequiredInstance(fullRepClass); + processGetRepresentationDescription(instance); + + } catch (Exception e) { + log.warn("Failed to process resource {}: {}", resourceClass.getSimpleName(), e.getMessage()); + if (log.isDebugEnabled()) { + log.debug("Resource processing error details", e); + } + } + } + + private void processGetRepresentationDescription(Object instance) throws MojoExecutionException { + + try { + Object defaultRep = createRequiredInstance(defaultRepresentationClass); + Object fullRep = createRequiredInstance(fullRepresentationClass); Method method = instance.getClass().getMethod("getRepresentationDescription", representationClass); @@ -254,9 +331,6 @@ private void testPatientResourceInstantiation(Class resourceClazz) throws Moj tryExtractSchema(defaultResult, "DEFAULT"); tryExtractSchema(fullResult, "FULL"); - } catch (ClassNotFoundException e) { - throw new MojoExecutionException("Required OpenMRS representation classes not found in classpath. " + - "Ensure OpenMRS webservices.rest module is properly included in dependencies.", e); } catch (NoSuchMethodException e) { throw new MojoExecutionException("getRepresentationDescription method not found on resource class. " + "This OpenMRS version may not be supported.", e); @@ -266,24 +340,20 @@ private void testPatientResourceInstantiation(Class resourceClazz) throws Moj } } - private Class loadRequiredClass(String className) throws ClassNotFoundException { - return classLoader.loadClass(className); - } - private Object createRequiredInstance(Class clazz) throws InstantiationException, IllegalAccessException { try { return clazz.getDeclaredConstructor().newInstance(); - } catch (NoSuchMethodException e) { - throw new InstantiationException("No default constructor found for " + clazz.getSimpleName()); - } catch (java.lang.reflect.InvocationTargetException e) { - throw new InstantiationException("Constructor failed: " + e.getCause().getMessage()); + } catch (NoSuchMethodException | InvocationTargetException e) { + String message = e instanceof NoSuchMethodException ? + "No default constructor found for " + clazz.getSimpleName() : + "Constructor failed: " + ((InvocationTargetException)e).getCause().getMessage(); + throw new InstantiationException(message); } } private Object invokeRepresentationMethod(Method method, Object instance, Object representation, String type) { try { Object result = method.invoke(instance, representation); - log.info("{} result: {}", type, result); return result; } catch (java.lang.reflect.InvocationTargetException e) { log.warn("Failed to invoke getRepresentationDescription for {}: {}", type, e.getCause().getMessage()); @@ -302,20 +372,13 @@ private boolean tryExtractSchema(Object description, String representationType) log.info("No {} representation to extract schema from", representationType); return false; } - - log.info("=== Extracting Schema from {} ===", representationType); - + boolean propertiesSuccess = tryExtractProperties(description, representationType); if (!propertiesSuccess) { log.warn("Could not extract properties for {}", representationType); } - boolean linksSuccess = tryExtractLinks(description, representationType); - if (!linksSuccess) { - log.warn("Could not extract links for {}", representationType); - } - - return propertiesSuccess || linksSuccess; + return propertiesSuccess; } @@ -326,13 +389,7 @@ private boolean tryExtractProperties(Object description, String type) { if (properties instanceof Map) { Map propertyMap = (Map) properties; - log.info("Found {} properties:", propertyMap.size()); - - for (Map.Entry entry : propertyMap.entrySet()) { - String propertyName = String.valueOf(entry.getKey()); - Object propertyValue = entry.getValue(); - log.info(" - {} -> {}", propertyName, propertyValue); - } + log.info("Found {} properties in {}", propertyMap.size(), type); return true; } else { log.warn("getProperties() returned non-Map type for {}: {}", type, @@ -340,57 +397,15 @@ private boolean tryExtractProperties(Object description, String type) { return false; } - } catch (NoSuchMethodException e) { - log.warn("getProperties method not available for {} (OpenMRS version compatibility)", type); + } catch (NoSuchMethodException | IllegalAccessException | ClassCastException e) { + log.warn("Cannot extract properties for {}: {}", type, e.getMessage()); return false; - } catch (IllegalAccessException e) { - log.warn("Cannot access getProperties method for {}", type); - return false; - } catch (java.lang.reflect.InvocationTargetException e) { + } catch (InvocationTargetException e) { log.warn("getProperties method failed for {}: {}", type, e.getCause().getMessage()); if (log.isDebugEnabled()) { - log.debug("getProperties error details", e.getCause()); - } - return false; - } catch (ClassCastException e) { - log.warn("getProperties returned unexpected type for {}: {}", type, e.getMessage()); - return false; + log.debug("getProperties error details", e.getCause()); } - } - - private boolean tryExtractLinks(Object description, String type) { - try { - Method getLinksMethod = description.getClass().getMethod("getLinks"); - Object links = getLinksMethod.invoke(description); - - if (links instanceof Map) { - Map linkMap = (Map) links; - log.info("Found {} links:", linkMap.size()); - - for (Map.Entry entry : linkMap.entrySet()) { - log.info(" - LINK: {} -> {}", entry.getKey(), entry.getValue()); - } - return true; - } else { - log.warn("getLinks() returned non-Map type for {}: {}", type, - links != null ? links.getClass().getSimpleName() : "null"); - return false; - } - - } catch (NoSuchMethodException e) { - log.warn("getLinks method not available for {} (OpenMRS version compatibility)", type); - return false; - } catch (IllegalAccessException e) { - log.warn("Cannot access getLinks method for {}", type); - return false; - } catch (java.lang.reflect.InvocationTargetException e) { - log.warn("getLinks method failed for {}: {}", type, e.getCause().getMessage()); - if (log.isDebugEnabled()) { - log.debug("getLinks error details", e.getCause()); - } - return false; } catch (ClassCastException e) { - log.warn("getLinks returned unexpected type for {}: {}", type, e.getMessage()); - return false; + return false; } } } From cc95ed56d526c60e84157f00127f135acb9e8939 Mon Sep 17 00:00:00 2001 From: capernix Date: Wed, 2 Jul 2025 11:05:16 +0530 Subject: [PATCH 11/12] Moving the OpenAPI Plugin to the parent directory and adding it to the build cycle --- .../pom.xml | 22 ++++++------------- .../plugin/OpenmrsOpenApiSpecMojo.java | 4 ++-- pom.xml | 3 +++ 3 files changed, 12 insertions(+), 17 deletions(-) rename {tools/openapi-generator-maven-plugin => openapi-generator-maven-plugin}/pom.xml (85%) rename tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java => openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenmrsOpenApiSpecMojo.java (99%) diff --git a/tools/openapi-generator-maven-plugin/pom.xml b/openapi-generator-maven-plugin/pom.xml similarity index 85% rename from tools/openapi-generator-maven-plugin/pom.xml rename to openapi-generator-maven-plugin/pom.xml index 29d9749b7..c86fe6cfc 100644 --- a/tools/openapi-generator-maven-plugin/pom.xml +++ b/openapi-generator-maven-plugin/pom.xml @@ -1,14 +1,15 @@ - 4.0.0 org.openmrs.plugin + 4.0.0 + + org.openmrs.plugin openapi-generator-maven-plugin 1.0.0-SNAPSHOT maven-plugin - - OpenAPI Generator Maven Plugin + + Rest Web Services OpenAPI Plugin A Maven plugin for generating OpenAPI specifications for OpenMRS modules. - https://example.com/openapi-generator-maven-plugin 1.8 @@ -17,7 +18,6 @@ - org.apache.maven maven-plugin-api @@ -25,7 +25,6 @@ provided - org.apache.maven.plugin-tools maven-plugin-annotations @@ -33,21 +32,19 @@ provided - org.apache.maven maven-core 3.8.6 - provided + provided + - com.github.javaparser javaparser-core 3.25.5 - io.swagger swagger-core @@ -66,7 +63,6 @@ 1.6.2 - io.swagger.core.v3 swagger-core @@ -85,7 +81,6 @@ 2.2.8 - com.fasterxml.jackson.core jackson-databind @@ -98,7 +93,6 @@ 2.15.2 - org.openmrs.api openmrs-api @@ -111,14 +105,12 @@ 2.50.0-SNAPSHOT - org.springframework spring-web 5.3.30 - javax.servlet servlet-api diff --git a/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java b/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenmrsOpenApiSpecMojo.java similarity index 99% rename from tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java rename to openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenmrsOpenApiSpecMojo.java index d5717606f..2e9233afb 100644 --- a/tools/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenAPIMojo.java +++ b/openapi-generator-maven-plugin/src/main/java/org/openmrs/plugin/OpenmrsOpenApiSpecMojo.java @@ -33,9 +33,9 @@ import java.util.Map; @Mojo(name = "openapi", defaultPhase = LifecyclePhase.PROCESS_CLASSES, requiresDependencyResolution = ResolutionScope.RUNTIME) -public class OpenAPIMojo extends AbstractMojo { +public class OpenmrsOpenApiSpecMojo extends AbstractMojo { - private static final Logger log = LoggerFactory.getLogger(OpenAPIMojo.class); + private static final Logger log = LoggerFactory.getLogger(OpenmrsOpenApiSpecMojo.class); private URLClassLoader classLoader; diff --git a/pom.xml b/pom.xml index cad09349a..cf5cbc106 100644 --- a/pom.xml +++ b/pom.xml @@ -466,6 +466,7 @@ + openapi-generator-maven-plugin omod-common omod-2.4 omod-2.5 @@ -484,6 +485,7 @@ + openapi-generator-maven-plugin omod-common omod-2.4 omod @@ -528,6 +530,7 @@ release + openapi-generator-maven-plugin omod-common omod-2.4 omod-2.5 From 08a928785d59984622a63249468e129f9ba65074 Mon Sep 17 00:00:00 2001 From: capernix Date: Wed, 2 Jul 2025 12:07:25 +0530 Subject: [PATCH 12/12] Added OpenMRS repo information in the pom file --- openapi-generator-maven-plugin/pom.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openapi-generator-maven-plugin/pom.xml b/openapi-generator-maven-plugin/pom.xml index c86fe6cfc..b5ee2cb4b 100644 --- a/openapi-generator-maven-plugin/pom.xml +++ b/openapi-generator-maven-plugin/pom.xml @@ -17,6 +17,14 @@ UTF-8 + + + openmrs-repo + OpenMRS Nexus Repository + https://mavenrepo.openmrs.org/public + + + org.apache.maven