From 4dd8f5cf012bb11b24c1fafead7c409b654e2edb Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Wed, 3 Sep 2025 10:52:56 +0100 Subject: [PATCH 01/65] Add MapStruct dependency --- pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pom.xml b/pom.xml index 66458685f4..9a6b98e377 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,7 @@ 3.2.0 2.2.34 6.10.1.202505221210-r + 1.6.3 @@ -480,6 +481,12 @@ 4.3.1 + + org.mapstruct + mapstruct + ${mapstruct.version} + + From 3625a2c04468b2968c03f7f1062fd4e2a997607f Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Wed, 3 Sep 2025 11:05:29 +0100 Subject: [PATCH 02/65] Add MapStruct annotation processor --- pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pom.xml b/pom.xml index 9a6b98e377..763ca490ac 100644 --- a/pom.xml +++ b/pom.xml @@ -543,6 +543,13 @@ 3.10.1 11 + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + From 678c4c82a1d90e847d1e7ead4852620ab8f38d60 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Wed, 10 Sep 2025 16:49:11 +0100 Subject: [PATCH 03/65] Use MapStruct mapping for EventsFacade --- .../ac/cam/cl/dtg/isaac/api/EventsFacade.java | 22 +++++------ .../cl/dtg/util/mappers/ContentMapperMS.java | 12 ++++++ .../cam/cl/dtg/util/mappers/EventMapper.java | 37 +++++++++++++++++++ .../cam/cl/dtg/util/mappers/MainMapper.java | 9 +++++ .../cam/cl/dtg/util/mappers/UserMapper.java | 25 +++++++++++++ .../api/AbstractIsaacIntegrationTest.java | 3 ++ .../cam/cl/dtg/isaac/api/EventsFacadeIT.java | 2 +- 7 files changed, 98 insertions(+), 12 deletions(-) create mode 100644 src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java create mode 100644 src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java create mode 100644 src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java create mode 100644 src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java index 2a39bb4123..11c726f1d9 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java @@ -23,7 +23,6 @@ import com.opencsv.CSVWriter; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import ma.glasnost.orika.MapperFacade; import org.jboss.resteasy.annotations.GZIP; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,6 +65,7 @@ import uk.ac.cam.cl.dtg.segue.search.AbstractFilterInstruction; import uk.ac.cam.cl.dtg.segue.search.DateRangeFilterInstruction; import uk.ac.cam.cl.dtg.util.AbstractConfigLoader; +import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.DELETE; @@ -117,7 +117,7 @@ public class EventsFacade extends AbstractIsaacFacade { private final UserAccountManager userAccountManager; private final SchoolListReader schoolListReader; - private final MapperFacade mapper; + private final MainMapper mapper; /** * EventsFacade. @@ -136,7 +136,7 @@ public EventsFacade(final AbstractConfigLoader properties, final ILogManager log final UserAssociationManager userAssociationManager, final GroupManager groupManager, final UserAccountManager userAccountManager, final SchoolListReader schoolListReader, - final MapperFacade mapper) { + final MainMapper mapper) { super(properties, logManager); this.bookingManager = bookingManager; this.userManager = userManager; @@ -327,7 +327,7 @@ private ResultsWrapper getEventsReservedByUser(final HttpServletRequ throws SegueDatabaseException, ContentManagerException { List filteredResults = Lists.newArrayList(); - List userReservationList = this.mapper.mapAsList(bookingManager.getAllEventReservationsForUser(currentUser.getId()), EventBookingDTO.class); + List userReservationList = this.mapper.mapAsList(bookingManager.getAllEventReservationsForUser(currentUser.getId()), DetailedEventBookingDTO.class, EventBookingDTO.class); for (EventBookingDTO booking : userReservationList) { @@ -565,7 +565,7 @@ public final Response getEventBookingForGivenGroup(@Context final HttpServletReq eventBookings = userAssociationManager.filterUnassociatedRecords(currentUser, eventBookings, booking -> booking.getUserBooked().getId()); - return Response.ok(this.mapper.mapAsList(eventBookings, EventBookingDTO.class)).build(); + return Response.ok(this.mapper.mapAsList(eventBookings, EventBookingDTO.class, EventBookingDTO.class)).build(); } catch (SegueDatabaseException e) { String errorMsg = String.format( "Database error occurred while trying retrieve bookings for group (%s) on event (%s).", @@ -598,7 +598,7 @@ public final Response getEventBookingForAllGroups(@Context final HttpServletRequ return new SegueErrorResponse(Status.FORBIDDEN, "You do not have permission to use this endpoint.").toResponse(); } - List eventBookings = this.mapper.mapAsList(bookingManager.getBookingsByEventId(eventId), EventBookingDTO.class); + List eventBookings = this.mapper.mapAsList(bookingManager.getBookingsByEventId(eventId), DetailedEventBookingDTO.class, EventBookingDTO.class); // Only allowed to see the bookings of connected users eventBookings = userAssociationManager.filterUnassociatedRecords( @@ -778,7 +778,7 @@ public final Response createBookingForGivenUser(@Context final HttpServletReques ADMIN_BOOKING_REASON_FIELDNAME, additionalInformation.get("authorisation") == null ? "NOT_PROVIDED" : additionalInformation.get("authorisation") )); - return Response.ok(this.mapper.map(booking, EventBookingDTO.class)).build(); + return Response.ok(this.mapper.copy(booking)).build(); } catch (NoUserLoggedInException e) { return SegueErrorResponse.getNotLoggedInResponse(); } catch (SegueDatabaseException e) { @@ -859,7 +859,7 @@ public final Response createReservationsForGivenUsers(@Context final HttpServlet USER_ID_LIST_FKEY_FIELDNAME, userIds.toArray(), BOOKING_STATUS_FIELDNAME, BookingStatus.RESERVED.toString() )); - return Response.ok(this.mapper.mapAsList(bookings, EventBookingDTO.class)).build(); + return Response.ok(this.mapper.mapAsList(bookings, EventBookingDTO.class, EventBookingDTO.class)).build(); } catch (NoUserLoggedInException e) { return SegueErrorResponse.getNotLoggedInResponse(); @@ -1017,7 +1017,7 @@ public final Response createBookingForMe(@Context final HttpServletRequest reque this.getLogManager().logEvent(userManager.getCurrentUser(request), request, SegueServerLogType.EVENT_BOOKING, ImmutableMap.of(EVENT_ID_FKEY_FIELDNAME, event.getId())); - return Response.ok(this.mapper.map(eventBookingDTO, EventBookingDTO.class)).build(); + return Response.ok(this.mapper.copy(eventBookingDTO)).build(); } catch (NoUserLoggedInException e) { return SegueErrorResponse.getNotLoggedInResponse(); } catch (SegueDatabaseException e) { @@ -1072,7 +1072,7 @@ public final Response addMeToWaitingList(@Context final HttpServletRequest reque this.getLogManager().logEvent(userManager.getCurrentUser(request), request, SegueServerLogType.EVENT_WAITING_LIST_BOOKING, ImmutableMap.of(EVENT_ID_FKEY_FIELDNAME, event.getId())); - return Response.ok(this.mapper.map(eventBookingDTO, EventBookingDTO.class)).build(); + return Response.ok(this.mapper.copy(eventBookingDTO)).build(); } catch (NoUserLoggedInException e) { return SegueErrorResponse.getNotLoggedInResponse(); } catch (SegueDatabaseException e) { @@ -1608,7 +1608,7 @@ private IsaacEventPageDTO getRawEventDTOById(final String eventId) // The Events Facade *mutates* the EventDTO returned by this method; we must return a copy of // the original object else we will poison the contentManager's cache! // TODO: might it be better to get the DO from the cache and map it to DTO here to reduce overhead? - return mapper.map(possibleEvent, IsaacEventPageDTO.class); + return (IsaacEventPageDTO) mapper.copy(possibleEvent); } return null; } diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java new file mode 100644 index 0000000000..95ea54c9a2 --- /dev/null +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java @@ -0,0 +1,12 @@ +package uk.ac.cam.cl.dtg.util.mappers; + +import org.mapstruct.Mapper; +import org.mapstruct.SubclassMapping; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacEventPageDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.ContentDTO; + +@Mapper +public interface ContentMapperMS { + @SubclassMapping(source = IsaacEventPageDTO.class, target = IsaacEventPageDTO.class) + ContentDTO copy(ContentDTO source); +} diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java new file mode 100644 index 0000000000..19349d4a39 --- /dev/null +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java @@ -0,0 +1,37 @@ +package uk.ac.cam.cl.dtg.util.mappers; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import uk.ac.cam.cl.dtg.isaac.dto.eventbookings.DetailedEventBookingDTO; +import uk.ac.cam.cl.dtg.isaac.dto.eventbookings.EventBookingDTO; + +@Mapper(uses = UserMapper.class) +public interface EventMapper { + default T map(DetailedEventBookingDTO source, Class targetClass) { + if (targetClass.equals(EventBookingDTO.class)) { + return (T) mapDetailedEventBookingDTOtoEventBookingDTO(source); + } else { + // TODO throw better exception + throw new RuntimeException(); + } + } + + default List mapAsList(List source, Class sourceClass, Class targetClass) { + if (sourceClass.equals(EventBookingDTO.class) && targetClass.equals(EventBookingDTO.class)) { + return (List) copyListOfEventBookingDTO((List) source); + } else if (sourceClass.equals(DetailedEventBookingDTO.class) && targetClass.equals(EventBookingDTO.class)) { + return (List) mapListOfDetailedEventBookingDTOtoEventBookingDTO((List) source); + } else { + throw new RuntimeException(); + } + } + + @Mapping(source = "userBooked", target = "userBooked", qualifiedByName = "copyUserSummaryDTO") + EventBookingDTO copy(EventBookingDTO source); + List copyListOfEventBookingDTO(List source); + EventBookingDTO mapDetailedEventBookingDTOtoEventBookingDTO(DetailedEventBookingDTO source); + List mapListOfDetailedEventBookingDTOtoEventBookingDTO(List source); +} \ No newline at end of file diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java new file mode 100644 index 0000000000..330c1884e5 --- /dev/null +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java @@ -0,0 +1,9 @@ +package uk.ac.cam.cl.dtg.util.mappers; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface MainMapper extends ContentMapperMS, UserMapper, EventMapper { + MainMapper INSTANCE = Mappers.getMapper(MainMapper.class); +} \ No newline at end of file diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java new file mode 100644 index 0000000000..7b2ebb6eb5 --- /dev/null +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java @@ -0,0 +1,25 @@ +package uk.ac.cam.cl.dtg.util.mappers; + +import org.mapstruct.Mapper; +import org.mapstruct.Named; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +import uk.ac.cam.cl.dtg.isaac.dto.users.UserSummaryDTO; +import uk.ac.cam.cl.dtg.isaac.dto.users.UserSummaryForAdminUsersDTO; +import uk.ac.cam.cl.dtg.isaac.dto.users.UserSummaryWithEmailAddressDTO; +import uk.ac.cam.cl.dtg.isaac.dto.users.UserSummaryWithGroupMembershipDTO; + +@Mapper +public interface UserMapper { + UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); + + @Named("copyUserSummaryDTO") + @SubclassMapping(source = UserSummaryForAdminUsersDTO.class, target = UserSummaryForAdminUsersDTO.class) + @SubclassMapping(source = UserSummaryWithEmailAddressDTO.class, target = UserSummaryWithEmailAddressDTO.class) + @SubclassMapping(source = UserSummaryWithGroupMembershipDTO.class, target = UserSummaryWithGroupMembershipDTO.class) + UserSummaryDTO copy(UserSummaryDTO source); + UserSummaryForAdminUsersDTO copy(UserSummaryForAdminUsersDTO source); + UserSummaryWithEmailAddressDTO copy(UserSummaryWithEmailAddressDTO source); + UserSummaryWithGroupMembershipDTO copy(UserSummaryWithGroupMembershipDTO source); +} \ No newline at end of file diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java index ef618bcd32..6f9323aa96 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java @@ -84,6 +84,7 @@ import uk.ac.cam.cl.dtg.segue.search.ElasticSearchProvider; import uk.ac.cam.cl.dtg.util.AbstractConfigLoader; import uk.ac.cam.cl.dtg.util.YamlLoader; +import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import jakarta.servlet.http.HttpSession; import java.io.IOException; @@ -121,6 +122,7 @@ public class AbstractIsaacIntegrationTest { protected static ElasticSearchProvider elasticSearchProvider; protected static SchoolListReader schoolListReader; protected static MapperFacade mapperFacade; + protected static MainMapper mainMapper; protected static ContentSummarizerService contentSummarizerService; protected static IMisuseMonitor misuseMonitor; @@ -245,6 +247,7 @@ public static void setUpClass() throws Exception { questionManager = new QuestionManager(contentMapper, pgQuestionAttempts); mapperFacade = contentMapper.getAutoMapper(); + mainMapper = MainMapper.INSTANCE; providersToRegister = new HashMap<>(); providersToRegister.put(AuthenticationProvider.RASPBERRYPI, new RaspberryPiOidcAuthenticator( diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacadeIT.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacadeIT.java index b53a6722b3..3b46de5b12 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacadeIT.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacadeIT.java @@ -35,7 +35,7 @@ public class EventsFacadeIT extends IsaacIntegrationTest { @BeforeEach public void setUp() { // Get an instance of the facade to test - eventsFacade = new EventsFacade(properties, logManager, eventBookingManager, userAccountManager, contentManager, userAssociationManager, groupManager, userAccountManager, schoolListReader, mapperFacade); + eventsFacade = new EventsFacade(properties, logManager, eventBookingManager, userAccountManager, contentManager, userAssociationManager, groupManager, userAccountManager, schoolListReader, mainMapper); } @Test From 5176ffc764bd31f5fcb85a9bff89dec8422522a8 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Wed, 10 Sep 2025 16:54:32 +0100 Subject: [PATCH 04/65] Use MapStruct mapping for PagesFacade --- .../uk/ac/cam/cl/dtg/isaac/api/PagesFacade.java | 6 +++--- .../cl/dtg/util/mappers/ContentMapperMS.java | 17 +++++++++++++++++ .../ac/cam/cl/dtg/isaac/api/PagesFacadeIT.java | 2 +- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/PagesFacade.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/PagesFacade.java index c26f900d6c..cdcafad745 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/PagesFacade.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/PagesFacade.java @@ -20,7 +20,6 @@ import com.google.inject.Inject; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import ma.glasnost.orika.MapperFacade; import org.jboss.resteasy.annotations.GZIP; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,6 +59,7 @@ import uk.ac.cam.cl.dtg.segue.dao.content.ContentManagerException; import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager; import uk.ac.cam.cl.dtg.util.AbstractConfigLoader; +import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import jakarta.annotation.Nullable; import jakarta.servlet.http.HttpServletRequest; @@ -102,7 +102,7 @@ public class PagesFacade extends AbstractIsaacFacade { private static final Logger log = LoggerFactory.getLogger(PagesFacade.class); private final ContentService api; - private final MapperFacade mapper; + private final MainMapper mapper; private final UserAccountManager userManager; private final URIManager uriManager; private final QuestionManager questionManager; @@ -134,7 +134,7 @@ public class PagesFacade extends AbstractIsaacFacade { */ @Inject public PagesFacade(final ContentService api, final AbstractConfigLoader propertiesLoader, - final ILogManager logManager, final MapperFacade mapper, final GitContentManager contentManager, + final ILogManager logManager, final MainMapper mapper, final GitContentManager contentManager, final UserAccountManager userManager, final URIManager uriManager, final QuestionManager questionManager, final GameManager gameManager, final UserAttemptManager userAttemptManager) { diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java index 95ea54c9a2..a72c530e1c 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java @@ -1,12 +1,29 @@ package uk.ac.cam.cl.dtg.util.mappers; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.SubclassMapping; import uk.ac.cam.cl.dtg.isaac.dto.IsaacEventPageDTO; import uk.ac.cam.cl.dtg.isaac.dto.content.ContentDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.ContentSummaryDTO; @Mapper public interface ContentMapperMS { @SubclassMapping(source = IsaacEventPageDTO.class, target = IsaacEventPageDTO.class) ContentDTO copy(ContentDTO source); + + default T map(ContentDTO source, Class targetClass) { + if (targetClass.equals(ContentSummaryDTO.class)) { + return (T) mapContentDTOtoContentSummaryDTO(source); + } else { + throw new RuntimeException(); + } + } + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + ContentSummaryDTO mapContentDTOtoContentSummaryDTO(ContentDTO source); } diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/PagesFacadeIT.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/PagesFacadeIT.java index 2ec8ef97dd..c2578297b0 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/PagesFacadeIT.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/PagesFacadeIT.java @@ -42,7 +42,7 @@ public class PagesFacadeIT extends IsaacIntegrationTest{ @BeforeEach public void setUp() { this.pagesFacade = new PagesFacade(new ContentService(contentManager), properties, logManager, - mapperFacade, contentManager, userAccountManager, new URIManager(properties), questionManager, gameManager, userAttemptManager); + mainMapper, contentManager, userAccountManager, new URIManager(properties), questionManager, gameManager, userAttemptManager); } @Test From 8d5ba6a17cc724c3b6c91995bf8ec6bc206133ff Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Mon, 15 Sep 2025 14:02:54 +0100 Subject: [PATCH 05/65] Remove email addresses from UserSummaryDTOs in EventBookingDTOs --- .../ac/cam/cl/dtg/isaac/api/EventsFacade.java | 2 ++ .../cam/cl/dtg/util/mappers/UserMapper.java | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java index 11c726f1d9..b38b8e6752 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java @@ -604,6 +604,8 @@ public final Response getEventBookingForAllGroups(@Context final HttpServletRequ eventBookings = userAssociationManager.filterUnassociatedRecords( currentUser, eventBookings, booking -> booking.getUserBooked().getId()); + eventBookings.forEach(booking -> booking.setUserBooked(mapper.map(booking.getUserBooked(), UserSummaryDTO.class))); + return Response.ok(eventBookings).build(); } catch (NoUserLoggedInException e) { return SegueErrorResponse.getNotLoggedInResponse(); diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java index 7b2ebb6eb5..8988977d30 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java @@ -1,6 +1,8 @@ package uk.ac.cam.cl.dtg.util.mappers; +import org.mapstruct.BeanMapping; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.Named; import org.mapstruct.SubclassMapping; import org.mapstruct.factory.Mappers; @@ -14,6 +16,25 @@ public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); + default T map(UserSummaryDTO source, Class targetClass) { + if (targetClass.equals(UserSummaryWithGroupMembershipDTO.class)) { + return (T) mapUserSummaryDTOtoUserSummaryWithGroupMembershipDTO(source); + } else if (targetClass.equals(UserSummaryDTO.class)) { + return (T) mapExtendedUserSummaryDTOtoBaseUserSummaryDTO(source); + } else { + throw new RuntimeException(); + } + } + + @Mapping(target = "groupMembershipInformation", ignore = true) + @SubclassMapping(source = UserSummaryForAdminUsersDTO.class, target = UserSummaryWithGroupMembershipDTO.class) + @SubclassMapping(source = UserSummaryWithEmailAddressDTO.class, target = UserSummaryWithGroupMembershipDTO.class) + @SubclassMapping(source = UserSummaryWithGroupMembershipDTO.class, target = UserSummaryWithGroupMembershipDTO.class) + UserSummaryWithGroupMembershipDTO mapUserSummaryDTOtoUserSummaryWithGroupMembershipDTO(UserSummaryDTO source); + + @BeanMapping(resultType = UserSummaryDTO.class) + UserSummaryDTO mapExtendedUserSummaryDTOtoBaseUserSummaryDTO(UserSummaryDTO source); + @Named("copyUserSummaryDTO") @SubclassMapping(source = UserSummaryForAdminUsersDTO.class, target = UserSummaryForAdminUsersDTO.class) @SubclassMapping(source = UserSummaryWithEmailAddressDTO.class, target = UserSummaryWithEmailAddressDTO.class) From c9359d5f4a65ef5b9412a74ecf1a4db8873cdddd Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Mon, 15 Sep 2025 15:49:41 +0100 Subject: [PATCH 06/65] Use MapStruct mapping for UserAccountManager --- .../api/managers/UserAccountManager.java | 34 ++++---- .../SegueGuiceConfigurationModule.java | 5 +- .../cam/cl/dtg/util/mappers/UserMapper.java | 77 +++++++++++++++++++ .../api/AbstractIsaacIntegrationTest.java | 2 +- .../ac/cam/cl/dtg/isaac/api/SignupFlowIT.java | 2 +- .../cam/cl/dtg/isaac/api/UsersFacadeIT.java | 4 +- .../segue/api/managers/UserManagerTest.java | 11 +-- 7 files changed, 105 insertions(+), 30 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/UserAccountManager.java b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/UserAccountManager.java index ee94a6a13b..b9a4b18f3a 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/UserAccountManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/UserAccountManager.java @@ -18,8 +18,6 @@ import com.google.api.client.util.Lists; import com.google.common.collect.ImmutableMap; import com.google.inject.Inject; -import ma.glasnost.orika.MapperFacade; -import ma.glasnost.orika.impl.DefaultMapperFactory; import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.validator.routines.EmailValidator; @@ -82,6 +80,7 @@ import uk.ac.cam.cl.dtg.segue.dao.users.IAnonymousUserDataManager; import uk.ac.cam.cl.dtg.segue.dao.users.IUserDataManager; import uk.ac.cam.cl.dtg.util.AbstractConfigLoader; +import uk.ac.cam.cl.dtg.util.mappers.UserMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -117,7 +116,7 @@ public class UserAccountManager implements IUserAccountManager { private final IUserDataManager database; private final QuestionManager questionAttemptDb; private final ILogManager logManager; - private final MapperFacade dtoMapper; + private final UserMapper dtoMapper; private final EmailManager emailManager; private final IAnonymousUserDataManager temporaryUserCache; @@ -151,7 +150,7 @@ public class UserAccountManager implements IUserAccountManager { */ @Inject public UserAccountManager(final IUserDataManager database, final QuestionManager questionDb, final AbstractConfigLoader properties, - final Map providersToRegister, final MapperFacade dtoMapper, + final Map providersToRegister, final UserMapper dtoMapper, final EmailManager emailQueue, final IAnonymousUserDataManager temporaryUserCache, final ILogManager logManager, final UserAuthenticationManager userAuthenticationManager, final ISecondFactorAuthenticator secondFactorManager, @@ -825,7 +824,7 @@ public final UserAuthenticationSettingsDTO getUsersAuthenticationSettings(final UserAuthenticationSettings userAuthenticationSettings = this.database.getUserAuthenticationSettings(user.getId()); if (userAuthenticationSettings != null) { - return this.dtoMapper.map(userAuthenticationSettings, UserAuthenticationSettingsDTO.class); + return this.dtoMapper.map(userAuthenticationSettings); } else { return new UserAuthenticationSettingsDTO(); } @@ -844,8 +843,7 @@ public void upgradeUsersPasswordHashAlgorithm(final Long userId, final String ch * @throws SegueDatabaseException - if there is a database error. */ public List findUsers(final RegisteredUserDTO prototype) throws SegueDatabaseException { - List registeredUsersDOs = this.database.findUsers(this.dtoMapper.map(prototype, - RegisteredUser.class)); + List registeredUsersDOs = this.database.findUsers(this.dtoMapper.map(prototype)); return this.convertUserDOListToUserDTOList(registeredUsersDOs); } @@ -981,12 +979,12 @@ public RegisteredUserDTO createUserObjectAndSession(final HttpServletRequest req Validate.isTrue(user.getId() == null, "When creating a new user the user id must not be set."); - MapperFacade mapper = this.dtoMapper; + UserMapper mapper = this.dtoMapper; IPasswordAuthenticator authenticator = (IPasswordAuthenticator) this.registeredAuthProviders.get(AuthenticationProvider.SEGUE); // We want to map via a DTO first to make sure that the user cannot set fields that aren't exposed to them: - RegisteredUserDTO userDtoForNewUser = mapper.map(user, RegisteredUserDTO.class); - RegisteredUser userToSave = mapper.map(userDtoForNewUser, RegisteredUser.class); + RegisteredUserDTO userDtoForNewUser = mapper.map(user); + RegisteredUser userToSave = mapper.map(userDtoForNewUser); // ----- Validate the user object and details: ----- @@ -1096,7 +1094,7 @@ public RegisteredUserDTO updateUserObject(final RegisteredUser updatedUser, fina // We want to map to DTO first to make sure that the user cannot // change fields that aren't exposed to them - RegisteredUserDTO userDTOContainingUpdates = this.dtoMapper.map(updatedUser, RegisteredUserDTO.class); + RegisteredUserDTO userDTOContainingUpdates = this.dtoMapper.map(updatedUser); if (updatedUser.getId() == null) { throw new IllegalArgumentException( "The user object specified does not have an id. Users cannot be updated without a specific id set."); @@ -1133,11 +1131,9 @@ public RegisteredUserDTO updateUserObject(final RegisteredUser updatedUser, fina authenticator.ensureValidPassword(newPassword); } - MapperFacade mergeMapper = new DefaultMapperFactory.Builder().mapNulls(false).build().getMapperFacade(); + RegisteredUser userToSave = dtoMapper.copy(existingUser); + dtoMapper.merge(userDTOContainingUpdates, userToSave); - RegisteredUser userToSave = new RegisteredUser(); - mergeMapper.map(existingUser, userToSave); - mergeMapper.map(userDTOContainingUpdates, userToSave); // Don't modify email verification status, registration date, role, or teacher account pending status userToSave.setEmailVerificationStatus(existingUser.getEmailVerificationStatus()); userToSave.setRegistrationDate(existingUser.getRegistrationDate()); @@ -1680,7 +1676,7 @@ private void sendVerificationEmailsForEmailChange(final RegisteredUserDTO userDT emailManager.sendTemplatedEmailToUser(userDTO, emailChangeTemplate, emailTokens, EmailType.SYSTEM); // Defensive copy to ensure old email address is preserved (shouldn't change until new email is verified) - RegisteredUserDTO temporaryUser = this.dtoMapper.map(userDTO, RegisteredUserDTO.class); + RegisteredUserDTO temporaryUser = this.dtoMapper.copy(userDTO); temporaryUser.setEmail(newEmail); temporaryUser.setEmailVerificationStatus(EmailVerificationStatus.NOT_VERIFIED); this.sendVerificationEmailForCurrentEmail(temporaryUser, newEmailToken); @@ -1748,7 +1744,7 @@ private void mergeAnonymousUserWithRegisteredUser(final AnonymousUser anonymousU final RegisteredUserDTO userDTO = this.convertUserDOToUserDTO(user); this.questionAttemptDb.mergeAnonymousQuestionAttemptsIntoRegisteredUser( - this.dtoMapper.map(anonymousUser, AnonymousUserDTO.class), userDTO); + this.dtoMapper.map(anonymousUser), userDTO); // may as well spawn a new thread to do the log migration stuff asynchronously // work now. @@ -1901,7 +1897,7 @@ private List convertUserDOListToUserDTOList(final List(); } - return users.parallelStream().map(user -> this.dtoMapper.map(user, RegisteredUserDTO.class)).collect(Collectors.toList()); + return users.parallelStream().map(this.dtoMapper::map).collect(Collectors.toList()); } /** @@ -1926,7 +1922,7 @@ private RegisteredUser getCurrentRegisteredUserDO(final HttpServletRequest reque * @return An anonymous user containing any anonymous question attempts (which could be none) */ private AnonymousUserDTO getAnonymousUserDTO(final HttpServletRequest request) throws SegueDatabaseException { - return this.dtoMapper.map(this.getAnonymousUserDO(request), AnonymousUserDTO.class); + return this.dtoMapper.map(this.getAnonymousUserDO(request)); } /** diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java index e1a05e78ea..478e2cc57e 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java @@ -148,6 +148,7 @@ import uk.ac.cam.cl.dtg.util.locations.MaxMindIPLocationResolver; import uk.ac.cam.cl.dtg.util.locations.PostCodeIOLocationResolver; import uk.ac.cam.cl.dtg.util.locations.PostCodeLocationResolver; +import uk.ac.cam.cl.dtg.util.mappers.UserMapper; import jakarta.annotation.Nullable; import jakarta.servlet.ServletContextEvent; @@ -804,13 +805,13 @@ private UserAuthenticationManager getUserAuthenticationManager(final IUserDataMa private IUserAccountManager getUserManager(final IUserDataManager database, final QuestionManager questionManager, final AbstractConfigLoader properties, final Map providersToRegister, final EmailManager emailQueue, final IAnonymousUserDataManager temporaryUserCache, - final ILogManager logManager, final MapperFacade mapperFacade, + final ILogManager logManager, final UserMapper userMapper, final UserAuthenticationManager userAuthenticationManager, final ISecondFactorAuthenticator secondFactorManager, final AbstractUserPreferenceManager userPreferenceManager) { if (null == userManager) { userManager = new UserAccountManager(database, questionManager, properties, providersToRegister, - mapperFacade, emailQueue, temporaryUserCache, logManager, userAuthenticationManager, + userMapper, emailQueue, temporaryUserCache, logManager, userAuthenticationManager, secondFactorManager, userPreferenceManager); log.info("Creating singleton of UserManager"); } diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java index 8988977d30..d12b28e444 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java @@ -3,10 +3,20 @@ import org.mapstruct.BeanMapping; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; import org.mapstruct.Named; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.NullValuePropertyMappingStrategy; import org.mapstruct.SubclassMapping; import org.mapstruct.factory.Mappers; +import uk.ac.cam.cl.dtg.isaac.dos.users.AnonymousUser; +import uk.ac.cam.cl.dtg.isaac.dos.users.RegisteredUser; +import uk.ac.cam.cl.dtg.isaac.dos.users.UserAuthenticationSettings; +import uk.ac.cam.cl.dtg.isaac.dos.users.UserFromAuthProvider; +import uk.ac.cam.cl.dtg.isaac.dto.users.AnonymousUserDTO; +import uk.ac.cam.cl.dtg.isaac.dto.users.RegisteredUserDTO; +import uk.ac.cam.cl.dtg.isaac.dto.users.UserAuthenticationSettingsDTO; import uk.ac.cam.cl.dtg.isaac.dto.users.UserSummaryDTO; import uk.ac.cam.cl.dtg.isaac.dto.users.UserSummaryForAdminUsersDTO; import uk.ac.cam.cl.dtg.isaac.dto.users.UserSummaryWithEmailAddressDTO; @@ -16,6 +26,37 @@ public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); + @Mapping(target = "emailVerificationToken", ignore = true) + @Mapping(target = "emailToVerify", ignore = true) + RegisteredUser map(RegisteredUserDTO source); + + @Mapping(target = "firstLogin", ignore = true) + RegisteredUserDTO map(RegisteredUser source); + UserAuthenticationSettingsDTO map(UserAuthenticationSettings source); + AnonymousUserDTO map(AnonymousUser source); + + default T map(RegisteredUserDTO source, Class targetClass) { + if (targetClass.equals(UserSummaryDTO.class)) { + return (T) mapUserToSummary(source); + } else if (targetClass.equals(UserSummaryForAdminUsersDTO.class)) { + return (T) mapUserToAdminSummaryDTO(source); + } else if (targetClass.equals(UserSummaryWithEmailAddressDTO.class)) { + return (T) mapUserToSummaryWithEmailDTO(source); + } else if (targetClass.equals(UserSummaryWithGroupMembershipDTO.class)) { + return (T) mapUserToSummaryWithGroupMembershipDTO(source); + } else { + throw new RuntimeException(); + } + } + + default T map(UserFromAuthProvider source, Class targetClass) { + if (targetClass.equals(RegisteredUser.class)) { + return (T) mapUserFromAuthProviderToRegisteredUser(source); + } else { + throw new RuntimeException(); + } + } + default T map(UserSummaryDTO source, Class targetClass) { if (targetClass.equals(UserSummaryWithGroupMembershipDTO.class)) { return (T) mapUserSummaryDTOtoUserSummaryWithGroupMembershipDTO(source); @@ -26,6 +67,42 @@ default T map(UserSummaryDTO source, Class targetClass) { } } + @Mapping(target = "authorisedFullAccess", ignore = true) + UserSummaryDTO mapUserToSummary(RegisteredUserDTO source); + + @Mapping(target = "authorisedFullAccess", ignore = true) + UserSummaryForAdminUsersDTO mapUserToAdminSummaryDTO(RegisteredUserDTO source); + + @Mapping(target = "authorisedFullAccess", ignore = true) + UserSummaryWithEmailAddressDTO mapUserToSummaryWithEmailDTO(RegisteredUserDTO source); + + @Mapping(target = "groupMembershipInformation", ignore = true) + @Mapping(target = "authorisedFullAccess", ignore = true) + UserSummaryWithGroupMembershipDTO mapUserToSummaryWithGroupMembershipDTO(RegisteredUserDTO source); + + RegisteredUser copy(RegisteredUser source); + RegisteredUserDTO copy(RegisteredUserDTO source); + + @Mapping(target = "emailVerificationToken", ignore = true) + @Mapping(target = "emailToVerify", ignore = true) + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE, + nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) + void merge(RegisteredUserDTO source, @MappingTarget RegisteredUser target); + + @Mapping(target = "teacherAccountPending", ignore = true) + @Mapping(target = "schoolOther", ignore = true) + @Mapping(target = "schoolId", ignore = true) + @Mapping(target = "role", ignore = true) + @Mapping(target = "registrationDate", ignore = true) + @Mapping(target = "registeredContextsLastConfirmed", ignore = true) + @Mapping(target = "registeredContexts", ignore = true) + @Mapping(target = "lastUpdated", ignore = true) + @Mapping(target = "lastSeen", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "emailVerificationToken", ignore = true) + @Mapping(target = "emailToVerify", ignore = true) + RegisteredUser mapUserFromAuthProviderToRegisteredUser(UserFromAuthProvider source); + @Mapping(target = "groupMembershipInformation", ignore = true) @SubclassMapping(source = UserSummaryForAdminUsersDTO.class, target = UserSummaryWithGroupMembershipDTO.class) @SubclassMapping(source = UserSummaryWithEmailAddressDTO.class, target = UserSummaryWithGroupMembershipDTO.class) diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java index 6f9323aa96..5770d64d33 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java @@ -283,7 +283,7 @@ public static void setUpClass() throws Exception { } replay(secondFactorManager); - userAccountManager = new UserAccountManager(pgUsers, questionManager, properties, providersToRegister, mapperFacade, emailManager, pgAnonymousUsers, logManager, userAuthenticationManager, secondFactorManager, userPreferenceManager); + userAccountManager = new UserAccountManager(pgUsers, questionManager, properties, providersToRegister, mainMapper, emailManager, pgAnonymousUsers, logManager, userAuthenticationManager, secondFactorManager, userPreferenceManager); ObjectMapper objectMapper = new ObjectMapper(); mailGunEmailManager = new MailGunEmailManager(globalTokens, properties, userPreferenceManager); diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/SignupFlowIT.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/SignupFlowIT.java index 9ed8ffeb70..37ce42e333 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/SignupFlowIT.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/SignupFlowIT.java @@ -37,7 +37,7 @@ public void signUpFlow_emailVerificationRequiredCaveatSet_removesCaveatAfterVeri // set up email facade UserAccountManager userAccountManagerForTest = new UserAccountManager(pgUsers, questionManager, - propertiesForTest, providersToRegister, mapperFacade, emailManager, pgAnonymousUsers, logManager, + propertiesForTest, providersToRegister, mainMapper, emailManager, pgAnonymousUsers, logManager, userAuthenticationManager, secondFactorManager, userPreferenceManager); EmailFacade emailFacade = new EmailFacade(propertiesForTest, logManager, emailManager, diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/UsersFacadeIT.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/UsersFacadeIT.java index a64dcf027d..5aae72adc8 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/UsersFacadeIT.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/UsersFacadeIT.java @@ -122,7 +122,7 @@ public void createOrUpdateEndpoint_registerAsTeacherWithSignUpFlagEnabled_accept ); UserAccountManager userAccountManagerForTest = new UserAccountManager(pgUsers, questionManager, - propertiesForTest, providersToRegister, mapperFacade, emailManager, pgAnonymousUsers, logManager, + propertiesForTest, providersToRegister, mainMapper, emailManager, pgAnonymousUsers, logManager, userAuthenticationManager, secondFactorManager, userPreferenceManager); UsersFacade usersFacadeForTest = new UsersFacade(propertiesForTest, userAccountManagerForTest, logManager, @@ -832,7 +832,7 @@ public void generatePasswordResetTokenForOtherUser_teacherResetsStudentPassword_ UserAuthenticationManager userAuthenticationManager = new UserAuthenticationManager( pgUsers, dummyDeletionTokenManager, properties, providersToRegister, dummyEmailManager); UserAccountManager userAccountManager = new UserAccountManager( - pgUsers, questionManager, properties, providersToRegister, mapperFacade, emailManager, pgAnonymousUsers, + pgUsers, questionManager, properties, providersToRegister, mainMapper, emailManager, pgAnonymousUsers, logManager, userAuthenticationManager, secondFactorManager, userPreferenceManager); UsersFacade usersFacadeForTest = new UsersFacade(properties, userAccountManager, logManager, userAssociationManager, misuseMonitor, userPreferenceManager, schoolListReader); diff --git a/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java b/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java index 2089afa4bc..40bbbcfc7f 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java @@ -54,6 +54,7 @@ import uk.ac.cam.cl.dtg.segue.dao.users.IDeletionTokenPersistenceManager; import uk.ac.cam.cl.dtg.segue.dao.users.IUserDataManager; import uk.ac.cam.cl.dtg.util.AbstractConfigLoader; +import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; @@ -91,7 +92,7 @@ public class UserManagerTest { private AbstractConfigLoader dummyPropertiesLoader; private static final String CSRF_TEST_VALUE = "CSRFTESTVALUE"; - private MapperFacade dummyMapper; + private MainMapper dummyMapper; private EmailManager dummyQueue; private SimpleDateFormat sdf; @@ -121,7 +122,7 @@ public final void setUp() throws Exception { this.dummyProvidersMap.put(AuthenticationProvider.SEGUE, dummyLocalAuth); this.dummyHostName = "bob"; - this.dummyMapper = createMock(MapperFacade.class); + this.dummyMapper = createMock(MainMapper.class); this.dummyQueue = createMock(EmailManager.class); this.dummyPropertiesLoader = createMock(AbstractConfigLoader.class); this.sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy"); @@ -211,7 +212,7 @@ public final void getCurrentUser_IsAuthenticatedWithValidHMAC_userIsReturned() t .andReturn(Lists.newArrayList(AuthenticationProvider.GOOGLE)).once(); replay(dummyQuestionDatabase); - expect(dummyMapper.map(returnUser, RegisteredUserDTO.class)).andReturn(new RegisteredUserDTO()).atLeastOnce(); + expect(dummyMapper.map(returnUser)).andReturn(new RegisteredUserDTO()).atLeastOnce(); replay(dummyMapper, dummyDatabase, dummyLocalAuth); // Act @@ -391,8 +392,8 @@ public final void authenticateCallback_checkNewUserIsAuthenticated_createInterna RegisteredUserDTO mappedUserDTO = new RegisteredUserDTO(); expect(dummyMapper.map(providerUser, RegisteredUser.class)).andReturn(mappedUser).atLeastOnce(); - expect(dummyMapper.map(mappedUser, RegisteredUserDTO.class)).andReturn(mappedUserDTO).atLeastOnce(); - expect(dummyMapper.map(au, AnonymousUserDTO.class)).andReturn(someAnonymousUserDTO).anyTimes(); + expect(dummyMapper.map(mappedUser)).andReturn(mappedUserDTO).atLeastOnce(); + expect(dummyMapper.map(au)).andReturn(someAnonymousUserDTO).anyTimes(); // handle duplicate account check. expect(dummyDatabase.getByEmail(providerUser.getEmail())).andReturn(null).once(); From 2b9c5c8ddc1ff2a8b0d11b7e2fc6dfa11b24ae7f Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Wed, 17 Sep 2025 16:48:38 +0100 Subject: [PATCH 07/65] Map `teacherAccountPending` from auth provider to local user --- src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java index d12b28e444..7b0dd7899d 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java @@ -89,7 +89,6 @@ default T map(UserSummaryDTO source, Class targetClass) { nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) void merge(RegisteredUserDTO source, @MappingTarget RegisteredUser target); - @Mapping(target = "teacherAccountPending", ignore = true) @Mapping(target = "schoolOther", ignore = true) @Mapping(target = "schoolId", ignore = true) @Mapping(target = "role", ignore = true) From c5cc65944f0b4e7564edab19fd37dd6cbf2b5ef6 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Wed, 17 Sep 2025 17:06:28 +0100 Subject: [PATCH 08/65] Add unimplemented mapping exception --- .../uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java | 2 +- .../java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java | 5 ++--- .../cl/dtg/util/mappers/UnimplementedMappingException.java | 7 +++++++ .../java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java | 6 +++--- 4 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 src/main/java/uk/ac/cam/cl/dtg/util/mappers/UnimplementedMappingException.java diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java index a72c530e1c..8c4360c846 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java @@ -16,7 +16,7 @@ default T map(ContentDTO source, Class targetClass) { if (targetClass.equals(ContentSummaryDTO.class)) { return (T) mapContentDTOtoContentSummaryDTO(source); } else { - throw new RuntimeException(); + throw new UnimplementedMappingException(ContentDTO.class, targetClass); } } diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java index 19349d4a39..0dd4e8f266 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java @@ -14,8 +14,7 @@ default T map(DetailedEventBookingDTO source, Class< if (targetClass.equals(EventBookingDTO.class)) { return (T) mapDetailedEventBookingDTOtoEventBookingDTO(source); } else { - // TODO throw better exception - throw new RuntimeException(); + throw new UnimplementedMappingException(DetailedEventBookingDTO.class, targetClass); } } @@ -25,7 +24,7 @@ default List mapAsList } else if (sourceClass.equals(DetailedEventBookingDTO.class) && targetClass.equals(EventBookingDTO.class)) { return (List) mapListOfDetailedEventBookingDTOtoEventBookingDTO((List) source); } else { - throw new RuntimeException(); + throw new UnimplementedMappingException(sourceClass, targetClass); } } diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UnimplementedMappingException.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UnimplementedMappingException.java new file mode 100644 index 0000000000..a1a8161f4e --- /dev/null +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UnimplementedMappingException.java @@ -0,0 +1,7 @@ +package uk.ac.cam.cl.dtg.util.mappers; + +public class UnimplementedMappingException extends UnsupportedOperationException { + public UnimplementedMappingException(Class sourceClass, Class targetClass) { + super(String.format("Mapping from %s to %s is not implemented", sourceClass.getSimpleName(), targetClass.getSimpleName())); + } +} diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java index 7b0dd7899d..4ab5e7db0e 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java @@ -45,7 +45,7 @@ default T map(RegisteredUserDTO source, Class targ } else if (targetClass.equals(UserSummaryWithGroupMembershipDTO.class)) { return (T) mapUserToSummaryWithGroupMembershipDTO(source); } else { - throw new RuntimeException(); + throw new UnimplementedMappingException(RegisteredUserDTO.class, targetClass); } } @@ -53,7 +53,7 @@ default T map(UserFromAuthProvider source, Class targetClass) { if (targetClass.equals(RegisteredUser.class)) { return (T) mapUserFromAuthProviderToRegisteredUser(source); } else { - throw new RuntimeException(); + throw new UnimplementedMappingException(UserFromAuthProvider.class, targetClass); } } @@ -63,7 +63,7 @@ default T map(UserSummaryDTO source, Class targetClass) { } else if (targetClass.equals(UserSummaryDTO.class)) { return (T) mapExtendedUserSummaryDTOtoBaseUserSummaryDTO(source); } else { - throw new RuntimeException(); + throw new UnimplementedMappingException(UserSummaryDTO.class, targetClass); } } From efb74920a5ce52abbd85640664441d3c0424246b Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Thu, 18 Sep 2025 10:04:51 +0100 Subject: [PATCH 09/65] Use MapStruct mapping for GameManager --- .../uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java | 8 ++++---- .../uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java | 4 ++++ .../cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java | 2 +- .../ac/cam/cl/dtg/isaac/api/managers/GameManagerTest.java | 7 +++---- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java index 990b33a317..8b0e179d69 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java @@ -19,7 +19,6 @@ import com.google.api.client.util.Maps; import com.google.common.collect.Sets; import com.google.inject.Inject; -import ma.glasnost.orika.MapperFacade; import org.apache.commons.collections4.comparators.ComparatorChain; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.ImmutablePair; @@ -57,6 +56,7 @@ import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException; import uk.ac.cam.cl.dtg.segue.dao.content.ContentManagerException; import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager; +import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotNull; @@ -90,7 +90,7 @@ public class GameManager { private final GameboardPersistenceManager gameboardPersistenceManager; private final Random randomGenerator; - private final MapperFacade mapper; + private final MainMapper mapper; private final GitContentManager contentManager; private final QuestionManager questionManager; @@ -108,7 +108,7 @@ public class GameManager { */ @Inject public GameManager(final GitContentManager contentManager, - final GameboardPersistenceManager gameboardPersistenceManager, final MapperFacade mapper, + final GameboardPersistenceManager gameboardPersistenceManager, final MainMapper mapper, final QuestionManager questionManager) { this.contentManager = contentManager; this.gameboardPersistenceManager = gameboardPersistenceManager; @@ -1202,7 +1202,7 @@ private IsaacWildcard getWildCardById(final String id) throws ContentManagerExce Content wildcardResults = this.contentManager.getContentDOById(id); - return mapper.map(wildcardResults, IsaacWildcard.class); + return mapper.map(wildcardResults); } /** diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java index 8c4360c846..abf93fb3d4 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java @@ -3,6 +3,8 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.SubclassMapping; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacWildcard; +import uk.ac.cam.cl.dtg.isaac.dos.content.Content; import uk.ac.cam.cl.dtg.isaac.dto.IsaacEventPageDTO; import uk.ac.cam.cl.dtg.isaac.dto.content.ContentDTO; import uk.ac.cam.cl.dtg.isaac.dto.content.ContentSummaryDTO; @@ -26,4 +28,6 @@ default T map(ContentDTO source, Class targetClass) { @Mapping(target = "questionPartIds", ignore = true) @Mapping(target = "difficulty", ignore = true) ContentSummaryDTO mapContentDTOtoContentSummaryDTO(ContentDTO source); + + IsaacWildcard map(Content source); } diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java index 5770d64d33..a38a91c8da 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java @@ -293,7 +293,7 @@ public static void setUpClass() throws Exception { IAssignmentPersistenceManager assignmentPersistenceManager = new PgAssignmentPersistenceManager(postgresSqlDb, mapperFacade); GameboardPersistenceManager gameboardPersistenceManager = new GameboardPersistenceManager(postgresSqlDb, contentManager, mapperFacade, contentMapper); - gameManager = new GameManager(contentManager, gameboardPersistenceManager, mapperFacade, questionManager); + gameManager = new GameManager(contentManager, gameboardPersistenceManager, mainMapper, questionManager); groupManager = new GroupManager(pgUserGroupPersistenceManager, userAccountManager, gameManager, mapperFacade); userAssociationManager = new UserAssociationManager(pgAssociationDataManager, userAccountManager, groupManager); PgTransactionManager pgTransactionManager = new PgTransactionManager(postgresSqlDb); diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManagerTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManagerTest.java index 33d1c89744..894806deed 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManagerTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManagerTest.java @@ -16,11 +16,9 @@ package uk.ac.cam.cl.dtg.isaac.api.managers; -import ma.glasnost.orika.MapperFacade; import org.easymock.Capture; import org.easymock.EasyMock; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.easymock.PowerMock; @@ -35,6 +33,7 @@ import uk.ac.cam.cl.dtg.segue.dao.content.ContentManagerException; import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager; import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager.BooleanSearchClause; +import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import java.util.Collections; import java.util.List; @@ -53,14 +52,14 @@ public class GameManagerTest { private GitContentManager dummyContentManager; private GameboardPersistenceManager dummyGameboardPersistenceManager; - private MapperFacade dummyMapper; + private MainMapper dummyMapper; private QuestionManager dummyQuestionManager; @Before public void setUp() { this.dummyContentManager = PowerMock.createMock(GitContentManager.class); this.dummyGameboardPersistenceManager = PowerMock.createMock(GameboardPersistenceManager.class); - this.dummyMapper = PowerMock.createMock(MapperFacade.class); + this.dummyMapper = PowerMock.createMock(MainMapper.class); this.dummyQuestionManager = PowerMock.createMock(QuestionManager.class); } From 407e9afa33e2e2e702e6367ea53be28d7171a113 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Thu, 18 Sep 2025 12:15:08 +0100 Subject: [PATCH 10/65] Use MapStruct mapping for GameboardPersistenceManager --- .../isaac/dao/GameboardPersistenceManager.java | 11 ++++++----- .../SegueGuiceConfigurationModule.java | 3 ++- .../cl/dtg/util/mappers/ContentMapperMS.java | 18 ++++++++++++++++++ .../cl/dtg/util/mappers/GameboardMapper.java | 12 ++++++++++++ .../ac/cam/cl/dtg/util/mappers/MainMapper.java | 2 +- .../api/AbstractIsaacIntegrationTest.java | 2 +- 6 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 src/main/java/uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/GameboardPersistenceManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/GameboardPersistenceManager.java index 14fdd74680..40cc97dfc5 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/GameboardPersistenceManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/GameboardPersistenceManager.java @@ -43,6 +43,7 @@ import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapper; import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager; import uk.ac.cam.cl.dtg.segue.database.PostgresSqlDb; +import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import jakarta.annotation.Nullable; import java.io.IOException; @@ -81,7 +82,7 @@ public class GameboardPersistenceManager { private final PostgresSqlDb database; private final Cache gameboardNonPersistentStorage; - private final MapperFacade mapper; // used for content object mapping. + private final MainMapper mapper; // used for content object mapping. private final ObjectMapper objectMapper; // used for json serialisation private final GitContentManager contentManager; @@ -100,7 +101,7 @@ public class GameboardPersistenceManager { */ @Inject public GameboardPersistenceManager(final PostgresSqlDb database, final GitContentManager contentManager, - final MapperFacade mapper, final ContentMapper objectMapper) { + final MainMapper mapper, final ContentMapper objectMapper) { this.database = database; this.mapper = mapper; this.contentManager = contentManager; @@ -189,7 +190,7 @@ public String temporarilyStoreGameboard(final GameboardDTO gameboard) { */ public String saveGameboardToPermanentStorage(final GameboardDTO gameboard) throws SegueDatabaseException { - GameboardDO gameboardToSave = mapper.map(gameboard, GameboardDO.class); + GameboardDO gameboardToSave = mapper.map(gameboard); // the mapping operation won't work for the list so we should just // create a new one. gameboardToSave.setContents(Lists.newArrayList()); @@ -601,7 +602,7 @@ private List convertToGameboardDTOs(final List gamebo List gameboardDTOs = Lists.newArrayList(); for (GameboardDO gameboardDO : gameboardDOs) { - GameboardDTO gameboardDTO = mapper.map(gameboardDO, GameboardDTO.class); + GameboardDTO gameboardDTO = mapper.map(gameboardDO); List sparseGameboardItems = gameboardDO.getContents().stream() .map(GameboardItem::buildLightweightItemFromContentDescriptor) .collect(Collectors.toList()); @@ -640,7 +641,7 @@ private GameboardDTO convertToGameboardDTO(final GameboardDO gameboardDO, final * @return GameboardDO. */ private GameboardDO convertToGameboardDO(final GameboardDTO gameboardDTO) { - GameboardDO gameboardDO = mapper.map(gameboardDTO, GameboardDO.class); + GameboardDO gameboardDO = mapper.map(gameboardDTO); // the mapping operation won't work for the list so we should just // create a new one. gameboardDO.setContents(Lists.newArrayList()); diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java index 478e2cc57e..5a50431316 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java @@ -148,6 +148,7 @@ import uk.ac.cam.cl.dtg.util.locations.MaxMindIPLocationResolver; import uk.ac.cam.cl.dtg.util.locations.PostCodeIOLocationResolver; import uk.ac.cam.cl.dtg.util.locations.PostCodeLocationResolver; +import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import uk.ac.cam.cl.dtg.util.mappers.UserMapper; import jakarta.annotation.Nullable; @@ -1259,7 +1260,7 @@ private IPLocationResolver getIPLocator(final AbstractConfigLoader properties) t @Provides @Singleton private static GameboardPersistenceManager getGameboardPersistenceManager(final PostgresSqlDb database, final GitContentManager contentManager, - final MapperFacade mapper, final ContentMapper objectMapper) { + final MainMapper mapper, final ContentMapper objectMapper) { if (null == gameboardPersistenceManager) { gameboardPersistenceManager = new GameboardPersistenceManager(database, contentManager, mapper, objectMapper); log.info("Creating Singleton of GameboardPersistenceManager"); diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java index abf93fb3d4..99070abbfd 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java @@ -5,6 +5,7 @@ import org.mapstruct.SubclassMapping; import uk.ac.cam.cl.dtg.isaac.dos.IsaacWildcard; import uk.ac.cam.cl.dtg.isaac.dos.content.Content; +import uk.ac.cam.cl.dtg.isaac.dto.GameboardItem; import uk.ac.cam.cl.dtg.isaac.dto.IsaacEventPageDTO; import uk.ac.cam.cl.dtg.isaac.dto.content.ContentDTO; import uk.ac.cam.cl.dtg.isaac.dto.content.ContentSummaryDTO; @@ -17,6 +18,8 @@ public interface ContentMapperMS { default T map(ContentDTO source, Class targetClass) { if (targetClass.equals(ContentSummaryDTO.class)) { return (T) mapContentDTOtoContentSummaryDTO(source); + } else if (targetClass.equals(GameboardItem.class)) { + return (T) mapContentDTOtoGameboardItem(source); } else { throw new UnimplementedMappingException(ContentDTO.class, targetClass); } @@ -30,4 +33,19 @@ default T map(ContentDTO source, Class targetClass) { ContentSummaryDTO mapContentDTOtoContentSummaryDTO(ContentDTO source); IsaacWildcard map(Content source); + + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "state", ignore = true) + @Mapping(target = "questionPartsTotal", ignore = true) + @Mapping(target = "questionPartsNotAttempted", ignore = true) + @Mapping(target = "questionPartsIncorrect", ignore = true) + @Mapping(target = "questionPartsCorrect", ignore = true) + @Mapping(target = "questionPartStates", ignore = true) + @Mapping(target = "passMark", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @Mapping(target = "description", ignore = true) + @Mapping(target = "creationContext", ignore = true) + @Mapping(target = "contentType", ignore = true) + @Mapping(target = "boardId", ignore = true) + GameboardItem mapContentDTOtoGameboardItem(ContentDTO source); } diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java new file mode 100644 index 0000000000..85e779d276 --- /dev/null +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java @@ -0,0 +1,12 @@ +package uk.ac.cam.cl.dtg.util.mappers; + + +import org.mapstruct.Mapper; +import uk.ac.cam.cl.dtg.isaac.dos.GameboardDO; +import uk.ac.cam.cl.dtg.isaac.dto.GameboardDTO; + +@Mapper +public interface GameboardMapper { + GameboardDTO map(GameboardDO source); + GameboardDO map(GameboardDTO source); +} diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java index 330c1884e5..7511e67514 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java @@ -4,6 +4,6 @@ import org.mapstruct.factory.Mappers; @Mapper -public interface MainMapper extends ContentMapperMS, UserMapper, EventMapper { +public interface MainMapper extends ContentMapperMS, UserMapper, EventMapper, GameboardMapper { MainMapper INSTANCE = Mappers.getMapper(MainMapper.class); } \ No newline at end of file diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java index a38a91c8da..0a1e086b0b 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java @@ -292,7 +292,7 @@ public static void setUpClass() throws Exception { PgUserGroupPersistenceManager pgUserGroupPersistenceManager = new PgUserGroupPersistenceManager(postgresSqlDb); IAssignmentPersistenceManager assignmentPersistenceManager = new PgAssignmentPersistenceManager(postgresSqlDb, mapperFacade); - GameboardPersistenceManager gameboardPersistenceManager = new GameboardPersistenceManager(postgresSqlDb, contentManager, mapperFacade, contentMapper); + GameboardPersistenceManager gameboardPersistenceManager = new GameboardPersistenceManager(postgresSqlDb, contentManager, mainMapper, contentMapper); gameManager = new GameManager(contentManager, gameboardPersistenceManager, mainMapper, questionManager); groupManager = new GroupManager(pgUserGroupPersistenceManager, userAccountManager, gameManager, mapperFacade); userAssociationManager = new UserAssociationManager(pgAssociationDataManager, userAccountManager, groupManager); From d0ddaf12c02960bb1ee9873cdfca89a33b40da29 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Thu, 18 Sep 2025 12:38:05 +0100 Subject: [PATCH 11/65] Fix gameManager initialisation --- .../ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java index b51ad366fc..cb80c1ffb0 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java @@ -294,8 +294,8 @@ public static void setUpClass() throws Exception { PgUserGroupPersistenceManager pgUserGroupPersistenceManager = new PgUserGroupPersistenceManager(postgresSqlDb); IAssignmentPersistenceManager assignmentPersistenceManager = new PgAssignmentPersistenceManager(postgresSqlDb, mapperFacade); - GameboardPersistenceManager gameboardPersistenceManager = new GameboardPersistenceManager(postgresSqlDb, contentManager, mapperFacade, contentMapper); - gameManager = new GameManager(contentManager, gameboardPersistenceManager, mapperFacade, questionManager); + GameboardPersistenceManager gameboardPersistenceManager = new GameboardPersistenceManager(postgresSqlDb, contentManager, mainMapper, contentMapper); + gameManager = new GameManager(contentManager, gameboardPersistenceManager, mainMapper, questionManager, properties); groupManager = new GroupManager(pgUserGroupPersistenceManager, userAccountManager, gameManager, mapperFacade); userAssociationManager = new UserAssociationManager(pgAssociationDataManager, userAccountManager, groupManager); PgTransactionManager pgTransactionManager = new PgTransactionManager(postgresSqlDb); From 308e4192d6c6970669641a2c120e2014cfa848fb Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Fri, 19 Sep 2025 14:40:18 +0100 Subject: [PATCH 12/65] Use MapStruct mapping for GroupManager --- .../dao/GameboardPersistenceManager.java | 1 - .../dtg/segue/api/managers/GroupManager.java | 16 +++++++-------- .../SegueGuiceConfigurationModule.java | 2 +- .../cam/cl/dtg/util/mappers/UserMapper.java | 20 +++++++++++++++++++ .../uk/ac/cam/cl/dtg/isaac/IsaacTest.java | 3 ++- .../api/AbstractIsaacIntegrationTest.java | 2 +- .../segue/api/managers/GroupManagerTest.java | 8 ++++---- 7 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/GameboardPersistenceManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/GameboardPersistenceManager.java index 40cc97dfc5..6b6ef61501 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/GameboardPersistenceManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/GameboardPersistenceManager.java @@ -24,7 +24,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.inject.Inject; -import ma.glasnost.orika.MapperFacade; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.ac.cam.cl.dtg.isaac.dos.GameboardContentDescriptor; diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/GroupManager.java b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/GroupManager.java index 57948c1a51..6401eade76 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/GroupManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/GroupManager.java @@ -19,7 +19,6 @@ import com.google.api.client.util.Sets; import com.google.common.collect.Maps; import com.google.inject.Inject; -import ma.glasnost.orika.MapperFacade; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.ImmutablePair; import org.slf4j.Logger; @@ -47,6 +46,7 @@ import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException; import uk.ac.cam.cl.dtg.segue.dao.content.ContentManagerException; import uk.ac.cam.cl.dtg.segue.dao.users.IUserGroupPersistenceManager; +import uk.ac.cam.cl.dtg.util.mappers.UserMapper; import java.util.ArrayList; import java.util.Collection; @@ -72,7 +72,7 @@ public class GroupManager { private final IUserGroupPersistenceManager groupDatabase; private final UserAccountManager userManager; private final GameManager gameManager; - private final MapperFacade dtoMapper; + private final UserMapper dtoMapper; private List groupsObservers; /** @@ -87,7 +87,7 @@ public class GroupManager { */ @Inject public GroupManager(final IUserGroupPersistenceManager groupDatabase, final UserAccountManager userManager, - final GameManager gameManager, final MapperFacade dtoMapper) { + final GameManager gameManager, final UserMapper dtoMapper) { Objects.requireNonNull(groupDatabase); Objects.requireNonNull(userManager); Objects.requireNonNull(gameManager); @@ -133,7 +133,7 @@ public UserGroupDTO createUserGroup(final String groupName, final RegisteredUser */ public UserGroupDTO editUserGroup(final UserGroupDTO groupToEdit) throws SegueDatabaseException { Objects.requireNonNull(groupToEdit); - UserGroup userGroup = dtoMapper.map(groupToEdit, UserGroup.class); + UserGroup userGroup = dtoMapper.map(groupToEdit); userGroup.setLastUpdated(new Date()); UserGroup existingGroup = groupDatabase.findGroupById(groupToEdit.getId()); @@ -195,7 +195,7 @@ public List getUsersInGroup(final UserGroupDTO group) throws public Map getUserMembershipMapForGroup(Long groupId) throws SegueDatabaseException { Map result = Maps.newHashMap(); for(Map.Entry entry : this.groupDatabase.getGroupMembershipMap(groupId).entrySet()) { - result.put(entry.getKey(), dtoMapper.map(entry.getValue(), GroupMembershipDTO.class)); + result.put(entry.getKey(), dtoMapper.map(entry.getValue())); } return result; } @@ -547,7 +547,7 @@ public static boolean hasAdditionalManagerPrivileges(final UserGroupDTO group, f * - if there is a database problem. */ private UserGroupDTO convertGroupToDTO(final UserGroup group) throws SegueDatabaseException { - UserGroupDTO dtoToReturn = dtoMapper.map(group, UserGroupDTO.class); + UserGroupDTO dtoToReturn = dtoMapper.map(group); try { dtoToReturn.setOwnerSummary(userManager.convertToDetailedUserSummaryObject(userManager.getUserDTOById(group.getOwnerId()), UserSummaryWithEmailAddressDTO.class)); @@ -583,7 +583,7 @@ private List convertGroupsToDTOs(final Collection group // go through each group and get the related user information in the correct format for (UserGroup group : groups) { - UserGroupDTO dtoToReturn = dtoMapper.map(group, UserGroupDTO.class); + UserGroupDTO dtoToReturn = dtoMapper.map(group); result.add(dtoToReturn); } @@ -652,7 +652,7 @@ public void convertToUserSummaryGroupMembership(UserGroupDTO group, List T map(UserSummaryDTO source, Class targetClass) { @Mapping(target = "authorisedFullAccess", ignore = true) UserSummaryDTO mapUserToSummary(RegisteredUserDTO source); + @Mapping(target = "token", ignore = true) + @Mapping(target = "ownerSummary", ignore = true) + @Mapping(target = "mongoId", ignore = true) + @Mapping(target = "additionalManagers", ignore = true) + @Mapping(target = "additionalManagersUserIds", ignore = true) + UserGroupDTO map(UserGroup source); + @Mapping(target = "authorisedFullAccess", ignore = true) UserSummaryForAdminUsersDTO mapUserToAdminSummaryDTO(RegisteredUserDTO source); @Mapping(target = "authorisedFullAccess", ignore = true) UserSummaryWithEmailAddressDTO mapUserToSummaryWithEmailDTO(RegisteredUserDTO source); + GroupMembership map(GroupMembershipDTO source); + + GroupMembershipDTO map(GroupMembership source); + + @Mapping(target = "status", ignore = true) + UserGroup map(UserGroupDTO source); + @Mapping(target = "groupMembershipInformation", ignore = true) @Mapping(target = "authorisedFullAccess", ignore = true) UserSummaryWithGroupMembershipDTO mapUserToSummaryWithGroupMembershipDTO(RegisteredUserDTO source); @@ -83,6 +101,8 @@ default T map(UserSummaryDTO source, Class targetClass) { RegisteredUser copy(RegisteredUser source); RegisteredUserDTO copy(RegisteredUserDTO source); + GroupMembershipDTO copy(GroupMembershipDTO source); + @Mapping(target = "emailVerificationToken", ignore = true) @Mapping(target = "emailToVerify", ignore = true) @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE, diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/IsaacTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/IsaacTest.java index 6b9e01f64b..13fca5debd 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/IsaacTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/IsaacTest.java @@ -46,6 +46,7 @@ import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException; import uk.ac.cam.cl.dtg.segue.dao.content.ContentManagerException; import uk.ac.cam.cl.dtg.segue.dao.users.IUserGroupPersistenceManager; +import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import java.util.Arrays; import java.util.Collections; @@ -274,7 +275,7 @@ protected void initializeMocks() throws ContentManagerException, SegueDatabaseEx groupDatabase = createMock(IUserGroupPersistenceManager.class); UserAccountManager userAccountManager = createMock(UserAccountManager.class); GameManager gameManager = createMock(GameManager.class); - MapperFacade mapperFacade = createMock(MapperFacade.class); + MainMapper mapperFacade = createMock(MainMapper.class); groupManager = partialMockBuilder(GroupManager.class) .withConstructor(groupDatabase, userAccountManager, gameManager, mapperFacade) .addMockedMethod("getGroupById").addMockedMethod("isUserInGroup").addMockedMethod("getGroupMembershipList", RegisteredUserDTO.class, boolean.class) diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java index cb80c1ffb0..1c6085f122 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java @@ -296,7 +296,7 @@ public static void setUpClass() throws Exception { GameboardPersistenceManager gameboardPersistenceManager = new GameboardPersistenceManager(postgresSqlDb, contentManager, mainMapper, contentMapper); gameManager = new GameManager(contentManager, gameboardPersistenceManager, mainMapper, questionManager, properties); - groupManager = new GroupManager(pgUserGroupPersistenceManager, userAccountManager, gameManager, mapperFacade); + groupManager = new GroupManager(pgUserGroupPersistenceManager, userAccountManager, gameManager, mainMapper); userAssociationManager = new UserAssociationManager(pgAssociationDataManager, userAccountManager, groupManager); PgTransactionManager pgTransactionManager = new PgTransactionManager(postgresSqlDb); eventBookingManager = new EventBookingManager(bookingPersistanceManager, emailManager, userAssociationManager, properties, groupManager, userAccountManager, pgTransactionManager); diff --git a/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/GroupManagerTest.java b/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/GroupManagerTest.java index 3375a6e5c9..c6d2537fca 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/GroupManagerTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/GroupManagerTest.java @@ -17,7 +17,6 @@ import com.google.api.client.util.Lists; import com.google.api.client.util.Sets; -import ma.glasnost.orika.MapperFacade; import org.easymock.Capture; import org.junit.Before; import org.junit.Test; @@ -33,6 +32,7 @@ import uk.ac.cam.cl.dtg.isaac.dto.users.RegisteredUserDTO; import uk.ac.cam.cl.dtg.isaac.dto.users.UserSummaryWithEmailAddressDTO; import uk.ac.cam.cl.dtg.util.AbstractConfigLoader; +import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import java.text.SimpleDateFormat; import java.util.Date; @@ -52,7 +52,7 @@ public class GroupManagerTest { private AbstractConfigLoader dummyPropertiesLoader; - private MapperFacade dummyMapper; + private MainMapper dummyMapper; private ICommunicator dummyCommunicator; private SimpleDateFormat sdf; @@ -68,7 +68,7 @@ public class GroupManagerTest { */ @Before public final void setUp() throws Exception { - this.dummyMapper = createMock(MapperFacade.class); + this.dummyMapper = createMock(MainMapper.class); this.dummyCommunicator = createMock(ICommunicator.class); this.dummyPropertiesLoader = createMock(AbstractConfigLoader.class); this.sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy"); @@ -112,7 +112,7 @@ public final void groupManager_createValidGroup_aGroupShouldBeCreated() { .andReturn(someSetOfManagers).atLeastOnce(); expect(this.userManager.findUsers(someSetOfManagers)).andReturn(someListOfUsers); expect(this.userManager.convertToDetailedUserSummaryObjectList(someListOfUsers, UserSummaryWithEmailAddressDTO.class)).andReturn(someListOfUsersDTOs); - expect(this.dummyMapper.map(resultFromDB, UserGroupDTO.class)).andReturn(mappedGroup).atLeastOnce(); + expect(this.dummyMapper.map(resultFromDB)).andReturn(mappedGroup).atLeastOnce(); replay(this.userManager, this.groupDataManager, this.dummyMapper); From 3e46318c2730bc4c3f2bb11a2a52696316ef699b Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Fri, 19 Sep 2025 16:07:13 +0100 Subject: [PATCH 13/65] Use MapStruct mapping for PgQuizAttemptPersistenceManager --- .../isaac/dao/PgQuizAttemptPersistenceManager.java | 11 +++++------ .../ac/cam/cl/dtg/util/mappers/GameboardMapper.java | 11 +++++++++++ .../dtg/isaac/api/AbstractIsaacIntegrationTest.java | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAttemptPersistenceManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAttemptPersistenceManager.java index a377e4f3c3..ede6d855b0 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAttemptPersistenceManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAttemptPersistenceManager.java @@ -19,13 +19,13 @@ import com.google.api.client.util.Maps; import com.google.api.client.util.Sets; import com.google.inject.Inject; -import ma.glasnost.orika.MapperFacade; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.ac.cam.cl.dtg.isaac.dos.QuizAttemptDO; import uk.ac.cam.cl.dtg.isaac.dto.QuizAttemptDTO; import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException; import uk.ac.cam.cl.dtg.segue.database.PostgresSqlDb; +import uk.ac.cam.cl.dtg.util.mappers.GameboardMapper; import jakarta.annotation.Nullable; import java.sql.Array; @@ -46,7 +46,7 @@ public class PgQuizAttemptPersistenceManager implements IQuizAttemptPersistenceManager { private static final Logger log = LoggerFactory.getLogger(PgQuizAttemptPersistenceManager.class); - private final MapperFacade mapper; + private final GameboardMapper mapper; private final PostgresSqlDb database; /** @@ -58,8 +58,7 @@ public class PgQuizAttemptPersistenceManager implements IQuizAttemptPersistenceM * - An instance of an automapper that can be used for mapping to and from AssignmentDOs and DTOs. */ @Inject - public PgQuizAttemptPersistenceManager(final PostgresSqlDb database, - final MapperFacade mapper) { + public PgQuizAttemptPersistenceManager(final PostgresSqlDb database, final GameboardMapper mapper) { this.database = database; this.mapper = mapper; } @@ -87,7 +86,7 @@ public QuizAttemptDTO getByQuizAssignmentIdAndUserId(Long quizAssignmentId, Long @Override public Long saveAttempt(QuizAttemptDTO attempt) throws SegueDatabaseException { - QuizAttemptDO attemptToSave = mapper.map(attempt, QuizAttemptDO.class); + QuizAttemptDO attemptToSave = mapper.map(attempt); String query = "INSERT INTO quiz_attempts(user_id, quiz_id, quiz_assignment_id, start_date) VALUES (?, ?, ?, ?);"; try (Connection conn = database.getDatabaseConnection(); @@ -286,7 +285,7 @@ public void deleteAttempt(Long quizAttemptId) throws SegueDatabaseException { * @return Assignment DTO */ private QuizAttemptDTO convertToQuizAttemptDTO(final QuizAttemptDO attemptDO) { - return mapper.map(attemptDO, QuizAttemptDTO.class); + return mapper.map(attemptDO); } /** diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java index 85e779d276..b474ede930 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java @@ -2,11 +2,22 @@ import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import uk.ac.cam.cl.dtg.isaac.dos.GameboardDO; +import uk.ac.cam.cl.dtg.isaac.dos.QuizAttemptDO; import uk.ac.cam.cl.dtg.isaac.dto.GameboardDTO; +import uk.ac.cam.cl.dtg.isaac.dto.QuizAttemptDTO; @Mapper public interface GameboardMapper { GameboardDTO map(GameboardDO source); GameboardDO map(GameboardDTO source); + + @Mapping(target = "quizSummary", ignore = true) + @Mapping(target = "quizAssignment", ignore = true) + @Mapping(target = "quiz", ignore = true) + @Mapping(target = "feedbackMode", ignore = true) + QuizAttemptDTO map(QuizAttemptDO source); + + QuizAttemptDO map(QuizAttemptDTO source); } diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java index 1c6085f122..15eeee5696 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java @@ -307,7 +307,7 @@ public static void setUpClass() throws Exception { quizAssignmentPersistenceManager = new PgQuizAssignmentPersistenceManager(postgresSqlDb, mapperFacade); quizAssignmentManager = new QuizAssignmentManager(quizAssignmentPersistenceManager, new EmailService(properties, emailManager, groupManager, userAccountManager, mailGunEmailManager), quizManager, groupManager, properties); assignmentService = new AssignmentService(userAccountManager); - quizAttemptPersistenceManager = new PgQuizAttemptPersistenceManager(postgresSqlDb, mapperFacade); + quizAttemptPersistenceManager = new PgQuizAttemptPersistenceManager(postgresSqlDb, mainMapper); quizAttemptManager = new QuizAttemptManager(quizAttemptPersistenceManager); quizQuestionAttemptPersistenceManager = new PgQuizQuestionAttemptPersistenceManager(postgresSqlDb, contentMapper); quizQuestionManager = new QuizQuestionManager(questionManager, contentMapper, quizQuestionAttemptPersistenceManager, quizManager, quizAttemptManager); From 798fc1e0c2f06480952e726ae4280fb71bde5d46 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Fri, 19 Sep 2025 16:12:51 +0100 Subject: [PATCH 14/65] Use MapStruct mapping for PgAssignmentPersistenceManager --- .../dtg/isaac/dao/PgAssignmentPersistenceManager.java | 10 +++++----- .../uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java | 10 +++++++++- .../cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgAssignmentPersistenceManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgAssignmentPersistenceManager.java index 797033c683..ad8f7e8382 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgAssignmentPersistenceManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgAssignmentPersistenceManager.java @@ -24,6 +24,7 @@ import uk.ac.cam.cl.dtg.isaac.dto.AssignmentDTO; import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException; import uk.ac.cam.cl.dtg.segue.database.PostgresSqlDb; +import uk.ac.cam.cl.dtg.util.mappers.GameboardMapper; import java.sql.Array; import java.sql.Connection; @@ -43,7 +44,7 @@ public class PgAssignmentPersistenceManager implements IAssignmentPersistenceManager { private static final Logger log = LoggerFactory.getLogger(PgAssignmentPersistenceManager.class); - private final MapperFacade mapper; + private final GameboardMapper mapper; private final PostgresSqlDb database; /** @@ -55,15 +56,14 @@ public class PgAssignmentPersistenceManager implements IAssignmentPersistenceMan * - An instance of an automapper that can be used for mapping to and from AssignmentDOs and DTOs. */ @Inject - public PgAssignmentPersistenceManager(final PostgresSqlDb database, - final MapperFacade mapper) { + public PgAssignmentPersistenceManager(final PostgresSqlDb database, final GameboardMapper mapper) { this.database = database; this.mapper = mapper; } @Override public Long saveAssignment(final AssignmentDTO assignment) throws SegueDatabaseException { - AssignmentDO assignmentToSave = mapper.map(assignment, AssignmentDO.class); + AssignmentDO assignmentToSave = mapper.map(assignment); String query = "INSERT INTO assignments(gameboard_id, group_id, owner_user_id, creation_date, due_date, notes, scheduled_start_date)" + " VALUES (?, ?, ?, ?, ?, ?, ?);"; @@ -262,7 +262,7 @@ public void deleteAssignment(final Long id) throws SegueDatabaseException { * @return Assignment DTO */ private AssignmentDTO convertToAssignmentDTO(final AssignmentDO assignmentDO) { - return mapper.map(assignmentDO, AssignmentDTO.class); + return mapper.map(assignmentDO); } /** diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java index b474ede930..c8b9194e0e 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java @@ -3,8 +3,10 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import uk.ac.cam.cl.dtg.isaac.dos.AssignmentDO; import uk.ac.cam.cl.dtg.isaac.dos.GameboardDO; import uk.ac.cam.cl.dtg.isaac.dos.QuizAttemptDO; +import uk.ac.cam.cl.dtg.isaac.dto.AssignmentDTO; import uk.ac.cam.cl.dtg.isaac.dto.GameboardDTO; import uk.ac.cam.cl.dtg.isaac.dto.QuizAttemptDTO; @@ -13,11 +15,17 @@ public interface GameboardMapper { GameboardDTO map(GameboardDO source); GameboardDO map(GameboardDTO source); + @Mapping(target = "legacyId", ignore = true) + @Mapping(target = "groupName", ignore = true) + @Mapping(target = "gameboard", ignore = true) + @Mapping(target = "assignerSummary", ignore = true) + AssignmentDTO map(AssignmentDO source); + AssignmentDO map(AssignmentDTO source); + @Mapping(target = "quizSummary", ignore = true) @Mapping(target = "quizAssignment", ignore = true) @Mapping(target = "quiz", ignore = true) @Mapping(target = "feedbackMode", ignore = true) QuizAttemptDTO map(QuizAttemptDO source); - QuizAttemptDO map(QuizAttemptDTO source); } diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java index 15eeee5696..a28e711b1c 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java @@ -292,7 +292,7 @@ public static void setUpClass() throws Exception { EventBookingPersistenceManager bookingPersistanceManager = new EventBookingPersistenceManager(postgresSqlDb, userAccountManager, contentManager, objectMapper); PgAssociationDataManager pgAssociationDataManager = new PgAssociationDataManager(postgresSqlDb); PgUserGroupPersistenceManager pgUserGroupPersistenceManager = new PgUserGroupPersistenceManager(postgresSqlDb); - IAssignmentPersistenceManager assignmentPersistenceManager = new PgAssignmentPersistenceManager(postgresSqlDb, mapperFacade); + IAssignmentPersistenceManager assignmentPersistenceManager = new PgAssignmentPersistenceManager(postgresSqlDb, mainMapper); GameboardPersistenceManager gameboardPersistenceManager = new GameboardPersistenceManager(postgresSqlDb, contentManager, mainMapper, contentMapper); gameManager = new GameManager(contentManager, gameboardPersistenceManager, mainMapper, questionManager, properties); From d0c7e9329a112ca043b1423e246b031879402d12 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Fri, 19 Sep 2025 16:22:59 +0100 Subject: [PATCH 15/65] Use MapStruct mapping for PgQuizAssignmentPersistenceManager --- .../isaac/dao/PgQuizAssignmentPersistenceManager.java | 10 +++++----- .../uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java | 10 ++++++++++ .../cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAssignmentPersistenceManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAssignmentPersistenceManager.java index bc0f416ea5..0b14aea5af 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAssignmentPersistenceManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAssignmentPersistenceManager.java @@ -26,6 +26,7 @@ import uk.ac.cam.cl.dtg.isaac.dto.QuizAssignmentDTO; import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException; import uk.ac.cam.cl.dtg.segue.database.PostgresSqlDb; +import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import java.sql.Array; import java.sql.Connection; @@ -43,7 +44,7 @@ public class PgQuizAssignmentPersistenceManager implements IQuizAssignmentPersistenceManager { private static final Logger log = LoggerFactory.getLogger(PgQuizAssignmentPersistenceManager.class); - private final MapperFacade mapper; + private final MainMapper mapper; private final PostgresSqlDb database; /** @@ -55,15 +56,14 @@ public class PgQuizAssignmentPersistenceManager implements IQuizAssignmentPersis * - An instance of an automapper that can be used for mapping to and from AssignmentDOs and DTOs. */ @Inject - public PgQuizAssignmentPersistenceManager(final PostgresSqlDb database, - final MapperFacade mapper) { + public PgQuizAssignmentPersistenceManager(final PostgresSqlDb database, final MainMapper mapper) { this.database = database; this.mapper = mapper; } @Override public Long saveAssignment(final QuizAssignmentDTO assignment) throws SegueDatabaseException { - QuizAssignmentDO assignmentToSave = mapper.map(assignment, QuizAssignmentDO.class); + QuizAssignmentDO assignmentToSave = mapper.map(assignment); String query = "INSERT INTO quiz_assignments(quiz_id, group_id, owner_user_id, creation_date, due_date, scheduled_start_date, quiz_feedback_mode)" + " VALUES (?, ?, ?, ?, ?, ?, ?);"; @@ -267,7 +267,7 @@ public List getAssignmentsScheduledForHour(final Date timesta * @return QuizAssignmentDTO */ private QuizAssignmentDTO convertToQuizAssignmentDTO(final QuizAssignmentDO quizAssignmentDO) { - return mapper.map(quizAssignmentDO, QuizAssignmentDTO.class); + return mapper.map(quizAssignmentDO); } /** diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java index c8b9194e0e..871b4b2bbc 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java @@ -5,9 +5,11 @@ import org.mapstruct.Mapping; import uk.ac.cam.cl.dtg.isaac.dos.AssignmentDO; import uk.ac.cam.cl.dtg.isaac.dos.GameboardDO; +import uk.ac.cam.cl.dtg.isaac.dos.QuizAssignmentDO; import uk.ac.cam.cl.dtg.isaac.dos.QuizAttemptDO; import uk.ac.cam.cl.dtg.isaac.dto.AssignmentDTO; import uk.ac.cam.cl.dtg.isaac.dto.GameboardDTO; +import uk.ac.cam.cl.dtg.isaac.dto.QuizAssignmentDTO; import uk.ac.cam.cl.dtg.isaac.dto.QuizAttemptDTO; @Mapper @@ -22,6 +24,14 @@ public interface GameboardMapper { AssignmentDTO map(AssignmentDO source); AssignmentDO map(AssignmentDTO source); + @Mapping(target = "userFeedback", ignore = true) + @Mapping(target = "quizSummary", ignore = true) + @Mapping(target = "quiz", ignore = true) + @Mapping(target = "attempt", ignore = true) + @Mapping(target = "assignerSummary", ignore = true) + QuizAssignmentDTO map(QuizAssignmentDO source); + QuizAssignmentDO map(QuizAssignmentDTO source); + @Mapping(target = "quizSummary", ignore = true) @Mapping(target = "quizAssignment", ignore = true) @Mapping(target = "quiz", ignore = true) diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java index a28e711b1c..3af85097b0 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java @@ -304,7 +304,7 @@ public static void setUpClass() throws Exception { schoolListReader = createNiceMock(SchoolListReader.class); quizManager = new QuizManager(properties, new ContentService(contentManager), contentManager, new ContentSummarizerService(mapperFacade, new URIManager(properties))); - quizAssignmentPersistenceManager = new PgQuizAssignmentPersistenceManager(postgresSqlDb, mapperFacade); + quizAssignmentPersistenceManager = new PgQuizAssignmentPersistenceManager(postgresSqlDb, mainMapper); quizAssignmentManager = new QuizAssignmentManager(quizAssignmentPersistenceManager, new EmailService(properties, emailManager, groupManager, userAccountManager, mailGunEmailManager), quizManager, groupManager, properties); assignmentService = new AssignmentService(userAccountManager); quizAttemptPersistenceManager = new PgQuizAttemptPersistenceManager(postgresSqlDb, mainMapper); From 296a4d6ade9a9eb6cebb6bc7a5b312a54afae4c0 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Fri, 19 Sep 2025 16:29:14 +0100 Subject: [PATCH 16/65] Remove unused orika imports --- .../ac/cam/cl/dtg/isaac/dao/PgAssignmentPersistenceManager.java | 1 - .../cam/cl/dtg/isaac/dao/PgQuizAssignmentPersistenceManager.java | 1 - src/test/java/uk/ac/cam/cl/dtg/isaac/IsaacTest.java | 1 - .../uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java | 1 - 4 files changed, 4 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgAssignmentPersistenceManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgAssignmentPersistenceManager.java index ad8f7e8382..f73d35e067 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgAssignmentPersistenceManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgAssignmentPersistenceManager.java @@ -17,7 +17,6 @@ import com.google.api.client.util.Lists; import com.google.inject.Inject; -import ma.glasnost.orika.MapperFacade; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.ac.cam.cl.dtg.isaac.dos.AssignmentDO; diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAssignmentPersistenceManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAssignmentPersistenceManager.java index 0b14aea5af..bbe7132c52 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAssignmentPersistenceManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAssignmentPersistenceManager.java @@ -17,7 +17,6 @@ import com.google.api.client.util.Lists; import com.google.inject.Inject; -import ma.glasnost.orika.MapperFacade; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.ac.cam.cl.dtg.isaac.api.managers.AssignmentCancelledException; diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/IsaacTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/IsaacTest.java index 13fca5debd..399543a93e 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/IsaacTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/IsaacTest.java @@ -17,7 +17,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import ma.glasnost.orika.MapperFacade; import org.junit.Before; import uk.ac.cam.cl.dtg.isaac.api.Constants; import uk.ac.cam.cl.dtg.isaac.api.managers.GameManager; diff --git a/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java b/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java index 40bbbcfc7f..86044bb98d 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java @@ -19,7 +19,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; -import ma.glasnost.orika.MapperFacade; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; import org.easymock.EasyMock; From 28e3ef9a367d40d0d05f3854d8f50af071fa5093 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Wed, 24 Sep 2025 10:57:13 +0100 Subject: [PATCH 17/65] Use MapStruct mapping for ContentSummarizerService --- .../api/services/ContentSummarizerService.java | 6 +++--- .../cam/cl/dtg/util/mappers/ContentMapperMS.java | 15 +++++++++++++++ .../isaac/api/AbstractIsaacIntegrationTest.java | 4 ++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/services/ContentSummarizerService.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/services/ContentSummarizerService.java index 3700715cd2..4b51106ba7 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/services/ContentSummarizerService.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/services/ContentSummarizerService.java @@ -16,20 +16,20 @@ package uk.ac.cam.cl.dtg.isaac.api.services; import com.google.inject.Inject; -import ma.glasnost.orika.MapperFacade; import uk.ac.cam.cl.dtg.isaac.api.managers.URIManager; import uk.ac.cam.cl.dtg.isaac.dto.ResultsWrapper; import uk.ac.cam.cl.dtg.isaac.dto.content.ContentDTO; import uk.ac.cam.cl.dtg.isaac.dto.content.ContentSummaryDTO; +import uk.ac.cam.cl.dtg.util.mappers.ContentMapperMS; import java.util.ArrayList; public class ContentSummarizerService { - protected final MapperFacade mapper; + protected final ContentMapperMS mapper; private final URIManager uriManager; @Inject - public ContentSummarizerService(final MapperFacade mapper, final URIManager uriManager) { + public ContentSummarizerService(final ContentMapperMS mapper, final URIManager uriManager) { this.mapper = mapper; this.uriManager = uriManager; } diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java index 99070abbfd..7457312550 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java @@ -7,8 +7,11 @@ import uk.ac.cam.cl.dtg.isaac.dos.content.Content; import uk.ac.cam.cl.dtg.isaac.dto.GameboardItem; import uk.ac.cam.cl.dtg.isaac.dto.IsaacEventPageDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuizDTO; import uk.ac.cam.cl.dtg.isaac.dto.content.ContentDTO; import uk.ac.cam.cl.dtg.isaac.dto.content.ContentSummaryDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.DetailedQuizSummaryDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.QuizSummaryDTO; @Mapper public interface ContentMapperMS { @@ -20,6 +23,10 @@ default T map(ContentDTO source, Class targetClass) { return (T) mapContentDTOtoContentSummaryDTO(source); } else if (targetClass.equals(GameboardItem.class)) { return (T) mapContentDTOtoGameboardItem(source); + } else if (targetClass.equals(QuizSummaryDTO.class)) { + return (T) mapContentDTOtoQuizSummaryDTO(source); + } else if (targetClass.equals(DetailedQuizSummaryDTO.class)) { + return (T) mapContentDTOtoDetailedQuizSummaryDTO(source); } else { throw new UnimplementedMappingException(ContentDTO.class, targetClass); } @@ -48,4 +55,12 @@ default T map(ContentDTO source, Class targetClass) { @Mapping(target = "contentType", ignore = true) @Mapping(target = "boardId", ignore = true) GameboardItem mapContentDTOtoGameboardItem(ContentDTO source); + + QuizSummaryDTO mapContentDTOtoQuizSummaryDTO(ContentDTO source); + + @SubclassMapping(source = IsaacQuizDTO.class, target = DetailedQuizSummaryDTO.class) + DetailedQuizSummaryDTO mapContentDTOtoDetailedQuizSummaryDTO(ContentDTO source); + + @Mapping(target = "rubric", ignore = true) + DetailedQuizSummaryDTO map(IsaacQuizDTO source); } diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java index 3af85097b0..eca8a7c52a 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java @@ -303,7 +303,7 @@ public static void setUpClass() throws Exception { assignmentManager = new AssignmentManager(assignmentPersistenceManager, groupManager, new EmailService(properties, emailManager, groupManager, userAccountManager, mailGunEmailManager), gameManager, properties); schoolListReader = createNiceMock(SchoolListReader.class); - quizManager = new QuizManager(properties, new ContentService(contentManager), contentManager, new ContentSummarizerService(mapperFacade, new URIManager(properties))); + quizManager = new QuizManager(properties, new ContentService(contentManager), contentManager, new ContentSummarizerService(mainMapper, new URIManager(properties))); quizAssignmentPersistenceManager = new PgQuizAssignmentPersistenceManager(postgresSqlDb, mainMapper); quizAssignmentManager = new QuizAssignmentManager(quizAssignmentPersistenceManager, new EmailService(properties, emailManager, groupManager, userAccountManager, mailGunEmailManager), quizManager, groupManager, properties); assignmentService = new AssignmentService(userAccountManager); @@ -328,7 +328,7 @@ public static void setUpClass() throws Exception { expect(httpSession.getId()).andReturn(someSegueAnonymousUserId).anyTimes(); replay(httpSession); - contentSummarizerService = new ContentSummarizerService(mapperFacade, new URIManager(properties)); + contentSummarizerService = new ContentSummarizerService(mainMapper, new URIManager(properties)); // NOTE: The next part is commented out until we figure out a way of actually using Guice to do the heavy lifting for us.. /* From 5247293b267f3fcb6918a0169dba038159afd8bb Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Wed, 24 Sep 2025 15:57:22 +0100 Subject: [PATCH 18/65] Use MapStruct mappings for QuestionManager --- .../segue/api/managers/QuestionManager.java | 33 ++--- .../cl/dtg/util/mappers/ContentMapperMS.java | 135 +++++++++++++++++- .../cam/cl/dtg/util/mappers/MainMapper.java | 2 +- .../mappers/QuestionValidationMapper.java | 24 ++++ .../api/AbstractIsaacIntegrationTest.java | 2 +- 5 files changed, 170 insertions(+), 26 deletions(-) create mode 100644 src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapper.java diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java index b6ebf70409..ab83acb1f5 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java @@ -62,6 +62,7 @@ import uk.ac.cam.cl.dtg.segue.configuration.SegueGuiceConfigurationModule; import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException; import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapper; +import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.core.Response; @@ -87,7 +88,8 @@ public class QuestionManager { private static final Logger log = LoggerFactory.getLogger(QuestionManager.class); - private final ContentMapper mapper; + private final ContentMapper mapperUtils; + private final MainMapper mapper; private final IQuestionAttemptManager questionAttemptPersistenceManager; /** * Create a default Question manager object. @@ -97,7 +99,8 @@ public class QuestionManager { * @param questionPersistenceManager - for question attempt persistence. */ @Inject - public QuestionManager(final ContentMapper mapper, final IQuestionAttemptManager questionPersistenceManager) { + public QuestionManager(final ContentMapper mapperUtils, final MainMapper mapper, final IQuestionAttemptManager questionPersistenceManager) { + this.mapperUtils = mapperUtils; this.mapper = mapper; this.questionAttemptPersistenceManager = questionPersistenceManager; } @@ -121,7 +124,7 @@ public final Response validateAnswer(final Question question, final ChoiceDTO su .build(); } - Choice answerFromUser = mapper.getAutoMapper().map(submittedAnswer, Choice.class); + Choice answerFromUser = mapper.map(submittedAnswer, Choice.class); QuestionValidationResponse validateQuestionResponse; Histogram.Timer validatorTimer = VALIDATOR_LATENCY_HISTOGRAM.labels(validator.getClass().getSimpleName()).startTimer(); @@ -135,7 +138,7 @@ public final Response validateAnswer(final Question question, final ChoiceDTO su } return Response.ok( - mapper.getAutoMapper().map(validateQuestionResponse, QuestionValidationResponseDTO.class)).build(); + mapper.map(validateQuestionResponse)).build(); } @@ -295,8 +298,7 @@ public QuestionValidationResponseDTO convertQuestionValidationResponseToDTO( // Determine what kind of ValidationResponse to turn it in to. DTOMapping dtoMapping = questionValidationResponse.getClass().getAnnotation(DTOMapping.class); if (QuestionValidationResponseDTO.class.isAssignableFrom(dtoMapping.value())) { - return mapper.getAutoMapper().map(questionValidationResponse, - (Class) dtoMapping.value()); + return mapper.map(questionValidationResponse); } else { log.error("Unable to set best attempt as we cannot match the answer to a DTO type."); throw new ClassCastException("Unable to cast " + questionValidationResponse.getClass() @@ -311,8 +313,7 @@ public QuestionValidationResponseDTO convertQuestionValidationResponseToDTO( */ public void recordQuestionAttempt(final AbstractSegueUserDTO user, final QuestionValidationResponseDTO questionResponse) throws SegueDatabaseException { - QuestionValidationResponse questionResponseDO = this.mapper.getAutoMapper().map(questionResponse, - QuestionValidationResponse.class); + QuestionValidationResponse questionResponseDO = this.mapper.map(questionResponse); String questionPageId = extractPageIdFromQuestionId(questionResponse.getQuestionId()); if (user instanceof RegisteredUserDTO) { @@ -336,10 +337,8 @@ public void recordQuestionAttempt(final AbstractSegueUserDTO user, public List testQuestion(final String questionType, final TestQuestion testDefinition) throws BadRequestException, ValidatorUnavailableException { try { - MapperFacade autoMapper = mapper.getAutoMapper(); - // Create a fake question - Class questionClass = mapper.getClassByType(questionType); + Class questionClass = mapperUtils.getClassByType(questionType); if (null == questionClass || !ChoiceQuestion.class.isAssignableFrom(questionClass)) { throw new BadRequestException(String.format("Not a valid questionType (%s)", questionType)); } @@ -353,8 +352,7 @@ public List testQuestion(final String questionType, final TestQuestion // For each test, check its actual results against the response of the validator on the fake question List results = Lists.newArrayList(); for (TestCase testCase : testDefinition.getTestCases()) { - Choice inferredChoiceSubclass = - autoMapper.map(autoMapper.map(testCase.getAnswer(), ChoiceDTO.class), Choice.class); + Choice inferredChoiceSubclass = mapper.mapChoice(mapper.mapChoice(testCase.getAnswer())); QuestionValidationResponse questionValidationResponse = questionValidator .validateQuestionResponse(testQuestion, inferredChoiceSubclass); testCase.setCorrect(questionValidationResponse.isCorrect()); @@ -625,7 +623,7 @@ public final Response generateSpecification(final ChoiceDTO answer) { .build(); } - Choice answerFromUser = mapper.getAutoMapper().map(answer, Choice.class); + Choice answerFromUser = mapper.map(answer, Choice.class); String specification; try { specification = specifier.createSpecification(answerFromUser); @@ -636,8 +634,7 @@ public final Response generateSpecification(final ChoiceDTO answer) { ResultsWrapper results = new ResultsWrapper<>(Collections.singletonList(specification), 1L); - return Response.ok( - mapper.getAutoMapper().map(results, ResultsWrapper.class)).build(); + return Response.ok(mapper.copy(results)).build(); } public static String extractPageIdFromQuestionId(String questionId) { @@ -648,9 +645,9 @@ public ChoiceDTO convertJsonAnswerToChoice(String jsonAnswer) throws ErrorRespon ChoiceDTO answerFromClientDTO; try { // convert submitted JSON into a Choice: - Choice answerFromClient = mapper.getSharedContentObjectMapper().readValue(jsonAnswer, Choice.class); + Choice answerFromClient = mapperUtils.getSharedContentObjectMapper().readValue(jsonAnswer, Choice.class); // convert to a DTO so that it strips out any untrusted data. - answerFromClientDTO = mapper.getAutoMapper().map(answerFromClient, ChoiceDTO.class); + answerFromClientDTO = mapper.mapChoice(answerFromClient); } catch (JsonMappingException | JsonParseException e) { log.info("Failed to map to any expected input...", e); SegueErrorResponse error = new SegueErrorResponse(Response.Status.NOT_FOUND, "Unable to map response to a " diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java index 7457312550..cb0d10f7c1 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java @@ -1,17 +1,32 @@ package uk.ac.cam.cl.dtg.util.mappers; +import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.SubclassMapping; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacCard; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacCardDeck; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacEventPage; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacFeaturedProfile; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacPageFragment; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacPod; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacQuizSection; import uk.ac.cam.cl.dtg.isaac.dos.IsaacWildcard; -import uk.ac.cam.cl.dtg.isaac.dos.content.Content; +import uk.ac.cam.cl.dtg.isaac.dos.content.*; import uk.ac.cam.cl.dtg.isaac.dto.GameboardItem; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacCardDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacCardDeckDTO; import uk.ac.cam.cl.dtg.isaac.dto.IsaacEventPageDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacFeaturedProfileDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacPageFragmentDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacPodDTO; import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuizDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.ContentDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.ContentSummaryDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.DetailedQuizSummaryDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.QuizSummaryDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuizSectionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacWildcardDTO; +import uk.ac.cam.cl.dtg.isaac.dto.ResultsWrapper; +import uk.ac.cam.cl.dtg.isaac.dto.content.*; + +import java.util.List; @Mapper public interface ContentMapperMS { @@ -63,4 +78,112 @@ default T map(ContentDTO source, Class targetClass) { @Mapping(target = "rubric", ignore = true) DetailedQuizSummaryDTO map(IsaacQuizDTO source); -} + + SidebarDTO map(String source); + + @Mapping(target = "searchableContent", ignore = true) + @Mapping(target = "correct", ignore = true) + @Mapping(target = "explanation", ignore = true) + @InheritInverseConfiguration(name = "mapChoice") + Choice mapChoice(ChoiceDTO source); + + @SubclassMapping(source = Formula.class, target = FormulaDTO.class) + @SubclassMapping(source = FreeTextRule.class, target = FreeTextRuleDTO.class) + @SubclassMapping(source = ItemChoice.class, target = ItemChoiceDTO.class) + @SubclassMapping(source = LogicFormula.class, target = LogicFormulaDTO.class) + @SubclassMapping(source = Quantity.class, target = QuantityDTO.class) + @SubclassMapping(source = RegexPattern.class, target = RegexPatternDTO.class) + @SubclassMapping(source = StringChoice.class, target = StringChoiceDTO.class) + ChoiceDTO mapChoice(Choice source); + + List copyListOfString(List source); + default ResultsWrapper copy(ResultsWrapper source) { + return new ResultsWrapper<>(copyListOfString(source.getResults()), source.getTotalResults()); + } + + @Mapping(target = "searchableContent", ignore = true) + @InheritInverseConfiguration(name = "mapContent") + Content mapContent(ContentDTO source); + + @SubclassMapping(source = AnvilApp.class, target = AnvilAppDTO.class) + @SubclassMapping(source = Choice.class, target = ChoiceDTO.class) + @SubclassMapping(source = CodeSnippet.class, target = CodeSnippetDTO.class) + @SubclassMapping(source = CodeTabs.class, target = CodeTabsDTO.class) + @SubclassMapping(source = EmailTemplate.class, target = EmailTemplateDTO.class) + @SubclassMapping(source = GlossaryTerm.class, target = GlossaryTermDTO.class) + @SubclassMapping(source = IsaacCard.class, target = IsaacCardDTO.class) + @SubclassMapping(source = IsaacCardDeck.class, target = IsaacCardDeckDTO.class) + @SubclassMapping(source = IsaacEventPage.class, target = IsaacEventPageDTO.class) + @SubclassMapping(source = IsaacFeaturedProfile.class, target = IsaacFeaturedProfileDTO.class) + @SubclassMapping(source = IsaacPageFragment.class, target = IsaacPageFragmentDTO.class) + @SubclassMapping(source = IsaacPod.class, target = IsaacPodDTO.class) + @SubclassMapping(source = IsaacQuizSection.class, target = IsaacQuizSectionDTO.class) + @SubclassMapping(source = IsaacWildcard.class, target = IsaacWildcardDTO.class) + @SubclassMapping(source = Item.class, target = ItemDTO.class) + @SubclassMapping(source = Media.class, target = MediaDTO.class) + @SubclassMapping(source = Notification.class, target = NotificationDTO.class) + @SubclassMapping(source = Question.class, target = QuestionDTO.class) + @SubclassMapping(source = SeguePage.class, target = SeguePageDTO.class) + ContentDTO mapContent(Content source); + + List mapListOfContentSummaryDtoToListOfString(List source); + + List mapListOfStringToListOfContentSummaryDTO(List source); + + default ContentSummaryDTO mapStringToContentSummaryDTO(String source) { + if (source == null) { + return null; + } + ContentSummaryDTO contentSummaryDTO = new ContentSummaryDTO(); + contentSummaryDTO.setId(source); + return contentSummaryDTO; + } + + default String mapContentSummaryDTOtoString(ContentSummaryDTO source) { + if (source == null) { + return null; + } + return source.getId(); + } + + // Needed to avoid abstract interface errors + default ContentBase map(ContentBaseDTO source) { + if (source == null) { + return null; + } else if (source instanceof ContentDTO) { + return mapContent((ContentDTO) source); + } else { + throw new UnimplementedMappingException(source.getClass(), ContentBase.class); + } + } + + default ContentBaseDTO map(ContentBase source) { + if (source == null) { + return null; + } else if (source instanceof Content) { + return mapContent((Content) source); + } else { + throw new UnimplementedMappingException(source.getClass(), ContentBaseDTO.class); + } + } + + default Media map(MediaDTO source) { + if (source == null) { + return null; + } else if (source instanceof ImageDTO || source instanceof VideoDTO) { + return map(source); + } else { + throw new UnimplementedMappingException(source.getClass(), Media.class); + } + } + + default MediaDTO map(Media source) { + if (source == null) { + return null; + } else if (source instanceof Image || source instanceof Video) { + return map(source); + } else { + throw new UnimplementedMappingException(source.getClass(), MediaDTO.class); + } + } +} \ No newline at end of file diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java index 7511e67514..288593e6aa 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java @@ -4,6 +4,6 @@ import org.mapstruct.factory.Mappers; @Mapper -public interface MainMapper extends ContentMapperMS, UserMapper, EventMapper, GameboardMapper { +public interface MainMapper extends ContentMapperMS, UserMapper, EventMapper, GameboardMapper, QuestionValidationMapper { MainMapper INSTANCE = Mappers.getMapper(MainMapper.class); } \ No newline at end of file diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapper.java new file mode 100644 index 0000000000..7b7db07e6c --- /dev/null +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapper.java @@ -0,0 +1,24 @@ +package uk.ac.cam.cl.dtg.util.mappers; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.SubclassMapping; +import uk.ac.cam.cl.dtg.isaac.dos.FormulaValidationResponse; +import uk.ac.cam.cl.dtg.isaac.dos.ItemValidationResponse; +import uk.ac.cam.cl.dtg.isaac.dos.QuantityValidationResponse; +import uk.ac.cam.cl.dtg.isaac.dos.QuestionValidationResponse; +import uk.ac.cam.cl.dtg.isaac.dto.FormulaValidationResponseDTO; +import uk.ac.cam.cl.dtg.isaac.dto.ItemValidationResponseDTO; +import uk.ac.cam.cl.dtg.isaac.dto.QuantityValidationResponseDTO; +import uk.ac.cam.cl.dtg.isaac.dto.QuestionValidationResponseDTO; + +@Mapper(uses = ContentMapperMS.class) +public interface QuestionValidationMapper { + @SubclassMapping(source = FormulaValidationResponseDTO.class, target = FormulaValidationResponse.class) + @SubclassMapping(source = ItemValidationResponseDTO.class, target = ItemValidationResponse.class) + @SubclassMapping(source = QuantityValidationResponseDTO.class, target = QuantityValidationResponse.class) + QuestionValidationResponse map(QuestionValidationResponseDTO source); + + @InheritInverseConfiguration + QuestionValidationResponseDTO map(QuestionValidationResponse source); +} diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java index eca8a7c52a..080c3fd09a 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java @@ -246,7 +246,7 @@ public static void setUpClass() throws Exception { ContentMapper contentMapper = new ContentMapper(new Reflections("uk.ac.cam.cl.dtg")); PgQuestionAttempts pgQuestionAttempts = new PgQuestionAttempts(postgresSqlDb, contentMapper); - questionManager = new QuestionManager(contentMapper, pgQuestionAttempts); + questionManager = new QuestionManager(contentMapper, mainMapper, pgQuestionAttempts); mapperFacade = contentMapper.getAutoMapper(); mainMapper = MainMapper.INSTANCE; From 269af919e14833c6447b50d6c02e795f5afc8836 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Wed, 24 Sep 2025 16:54:58 +0100 Subject: [PATCH 19/65] Use MapStruct mapping for QuizQuestionManager --- .../isaac/api/managers/QuizQuestionManager.java | 15 +++++---------- .../isaac/api/AbstractIsaacIntegrationTest.java | 2 +- .../api/managers/QuizQuestionManagerTest.java | 9 +++++++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManager.java index 82de59dfbe..1b209c4055 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManager.java @@ -32,16 +32,14 @@ import uk.ac.cam.cl.dtg.segue.api.managers.QuestionManager; import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException; import uk.ac.cam.cl.dtg.segue.dao.content.ContentManagerException; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapper; import uk.ac.cam.cl.dtg.isaac.dos.QuestionValidationResponse; -import uk.ac.cam.cl.dtg.isaac.dos.content.Choice; -import uk.ac.cam.cl.dtg.isaac.dos.content.DTOMapping; import uk.ac.cam.cl.dtg.isaac.dos.content.Question; import uk.ac.cam.cl.dtg.isaac.dto.QuestionValidationResponseDTO; import uk.ac.cam.cl.dtg.isaac.dto.SegueErrorResponse; import uk.ac.cam.cl.dtg.isaac.dto.content.ChoiceDTO; import uk.ac.cam.cl.dtg.isaac.dto.content.QuestionDTO; import uk.ac.cam.cl.dtg.isaac.dto.users.RegisteredUserDTO; +import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import jakarta.annotation.Nullable; import jakarta.ws.rs.core.Response; @@ -58,7 +56,7 @@ public class QuizQuestionManager { private final QuestionManager questionManager; - private final ContentMapper mapper; + private final MainMapper mapper; private final IQuizQuestionAttemptPersistenceManager quizQuestionAttemptManager; private final QuizManager quizManager; private final QuizAttemptManager quizAttemptManager; @@ -83,7 +81,7 @@ public class QuizQuestionManager { * - for attempts, particularly checking attempts are completed before revealing feedback. */ @Inject - public QuizQuestionManager(final QuestionManager questionManager, final ContentMapper mapper, + public QuizQuestionManager(final QuestionManager questionManager, final MainMapper mapper, final IQuizQuestionAttemptPersistenceManager quizQuestionAttemptManager, final QuizManager quizManager, final QuizAttemptManager quizAttemptManager) { this.questionManager = questionManager; @@ -109,7 +107,7 @@ public QuestionValidationResponseDTO validateAnswer(Question question, ChoiceDTO } public void recordQuestionAttempt(QuizAttemptDTO quizAttempt, QuestionValidationResponseDTO questionResponse) throws SegueDatabaseException { - QuestionValidationResponse questionResponseDO = this.mapper.getAutoMapper().map(questionResponse, QuestionValidationResponse.class); + QuestionValidationResponse questionResponseDO = this.mapper.map(questionResponse); this.quizQuestionAttemptManager.registerQuestionAttempt(quizAttempt.getId(), questionResponseDO); } @@ -301,11 +299,8 @@ void augmentQuestionObjectWithAttemptInformation(Map) dtoMapping.value())); + lastAttempt.setAnswer(mapper.mapChoice(lastResponse.getAnswer())); lastAttempt.setQuestionId(lastResponse.getQuestionId()); } lastAttempt.setDateAttempted(null); // Strip timestamps, since quiz responses may be seen by other users. diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java index 080c3fd09a..e0e0235aa0 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java @@ -310,7 +310,7 @@ public static void setUpClass() throws Exception { quizAttemptPersistenceManager = new PgQuizAttemptPersistenceManager(postgresSqlDb, mainMapper); quizAttemptManager = new QuizAttemptManager(quizAttemptPersistenceManager); quizQuestionAttemptPersistenceManager = new PgQuizQuestionAttemptPersistenceManager(postgresSqlDb, contentMapper); - quizQuestionManager = new QuizQuestionManager(questionManager, contentMapper, quizQuestionAttemptPersistenceManager, quizManager, quizAttemptManager); + quizQuestionManager = new QuizQuestionManager(questionManager, mainMapper, quizQuestionAttemptPersistenceManager, quizManager, quizAttemptManager); userAttemptManager = new UserAttemptManager(questionManager); fastTrackManger = new FastTrackManger(properties, contentManager, gameManager); diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManagerTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManagerTest.java index c524a7c421..4e6bfded59 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManagerTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManagerTest.java @@ -37,6 +37,7 @@ import uk.ac.cam.cl.dtg.isaac.dto.content.ContentDTO; import uk.ac.cam.cl.dtg.isaac.dto.content.QuestionDTO; import uk.ac.cam.cl.dtg.isaac.dto.users.RegisteredUserDTO; +import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import java.util.Arrays; import java.util.Collections; @@ -80,16 +81,20 @@ public class QuizQuestionManagerTest extends AbstractManagerTest { public void setUp() { quizQuestionAttemptPersistenceManager = createMock(IQuizQuestionAttemptPersistenceManager.class); questionManager = createMock(QuestionManager.class); - ContentMapper contentMapper = createMock(ContentMapper.class); + MainMapper contentMapper = createMock(MainMapper.class); MapperFacade mapperFacade = createMock(MapperFacade.class); quizAttemptManager = createMock(QuizAttemptManager.class); quizQuestionManager = new QuizQuestionManager(questionManager, contentMapper, quizQuestionAttemptPersistenceManager, quizManager, quizAttemptManager); - expect(contentMapper.getAutoMapper()).andStubReturn(mapperFacade); expect(mapperFacade.map(correctAnswer, ChoiceDTO.class)).andStubReturn(correctAnswerDTO); expect(mapperFacade.map(wrongAnswer, ChoiceDTO.class)).andStubReturn(wrongAnswerDTO); + expect(contentMapper.mapChoice(correctAnswer)).andStubReturn(correctAnswerDTO); + expect(contentMapper.mapChoice(wrongAnswer)).andStubReturn(wrongAnswerDTO); + expect(contentMapper.mapChoice((ChoiceDTO) null)).andStubReturn(null); + + registerDefaultsFor(questionManager, m -> { expect(m.convertQuestionValidationResponseToDTO(wrongResponse)).andStubReturn(wrongResponseDTO); expect(m.convertQuestionValidationResponseToDTO(correctResponse)).andStubReturn(correctResponseDTO); From fa93f2050a7965cf779dd7383067b2da580b22dc Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Wed, 24 Sep 2025 17:06:47 +0100 Subject: [PATCH 20/65] Use MapStruct mapping for GitContentManager --- .../SegueGuiceConfigurationModule.java | 4 +- .../segue/dao/content/GitContentManager.java | 43 +++++++++++-------- .../api/AbstractIsaacIntegrationTest.java | 2 +- .../dtg/segue/dao/GitContentManagerTest.java | 11 +++-- .../cl/dtg/segue/etl/ContentIndexerTest.java | 19 ++++---- 5 files changed, 44 insertions(+), 35 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java index 41befc1a21..0dd67b89ad 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java @@ -552,10 +552,10 @@ private static RestHighLevelClient getSearchConnectionInformation( @Inject @Provides @Singleton - private static GitContentManager getContentManager(final GitDb database, final ISearchProvider searchProvider, + private static GitContentManager getContentManager(final GitDb database, final ISearchProvider searchProvider, final MainMapper mainMapper, final ContentMapper contentMapper, final AbstractConfigLoader globalProperties) { if (null == contentManager) { - contentManager = new GitContentManager(database, searchProvider, contentMapper, globalProperties); + contentManager = new GitContentManager(database, searchProvider, mainMapper, contentMapper, globalProperties); log.info("Creating singleton of ContentManager"); } diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java index 7b5907dce0..24c79f6406 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java @@ -52,6 +52,8 @@ import uk.ac.cam.cl.dtg.segue.search.SimpleFilterInstruction; import uk.ac.cam.cl.dtg.segue.search.TermsFilterInstruction; import uk.ac.cam.cl.dtg.util.AbstractConfigLoader; +import uk.ac.cam.cl.dtg.util.mappers.ContentMapperMS; +import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import jakarta.annotation.Nullable; import java.io.ByteArrayOutputStream; @@ -83,7 +85,8 @@ public class GitContentManager { private static final String CONTENT_TYPE = "content"; private final GitDb database; - private final ContentMapper mapper; + private final ContentMapperMS mapper; + private final ContentMapper mapperUtils; private final ISearchProvider searchProvider; private final AbstractConfigLoader globalProperties; private final boolean showOnlyPublishedContent; @@ -109,10 +112,11 @@ public class GitContentManager { * - global properties. */ @Inject - public GitContentManager(final GitDb database, final ISearchProvider searchProvider, - final ContentMapper contentMapper, final AbstractConfigLoader globalProperties) { + public GitContentManager(final GitDb database, final ISearchProvider searchProvider, final ContentMapperMS mapper, + final ContentMapper mapperUtils, final AbstractConfigLoader globalProperties) { this.database = database; - this.mapper = contentMapper; + this.mapper = mapper; + this.mapperUtils = mapperUtils; this.searchProvider = searchProvider; this.globalProperties = globalProperties; @@ -149,10 +153,11 @@ public GitContentManager(final GitDb database, final ISearchProvider searchProvi * - The utility class for mapping content objects. */ public GitContentManager(final GitDb database, final ISearchProvider searchProvider, - final ContentMapper contentMapper) { + final ContentMapperMS contentMapper, ContentMapper mapperUtils) { this.database = database; this.mapper = contentMapper; this.searchProvider = searchProvider; + this.mapperUtils = mapperUtils; this.globalProperties = null; this.showOnlyPublishedContent = false; this.hideRegressionTestContent = false; @@ -190,7 +195,7 @@ public final ContentDTO getContentById(final String id) throws ContentManagerExc * @throws ContentManagerException on failure to return the object or null. */ public final ContentDTO getContentById(final String id, final boolean failQuietly) throws ContentManagerException { - return this.mapper.getDTOByDO(this.getContentDOById(id, failQuietly)); + return this.mapperUtils.getDTOByDO(this.getContentDOById(id, failQuietly)); } /** @@ -204,7 +209,7 @@ public final ContentDTO getContentById(final String id, final boolean failQuietl * @return the DTO form of the object. */ public final ContentDTO getContentDTOByDO(final Content content) { - return this.mapper.getDTOByDO(content); + return this.mapperUtils.getDTOByDO(content); } /** @@ -249,7 +254,7 @@ public final Content getContentDOById(final String id, final boolean failQuietly CONTENT_TYPE, id, Constants.ID_FIELDNAME + "." + Constants.UNPROCESSED_SEARCH_FIELD_SUFFIX, 0, 1, getBaseFilters()); - List searchResults = mapper.mapFromStringListToContentList(rawResults.getResults()); + List searchResults = mapperUtils.mapFromStringListToContentList(rawResults.getResults()); return new ResultsWrapper<>(searchResults, rawResults.getTotalResults()); }); @@ -310,8 +315,8 @@ public ResultsWrapper getUnsafeCachedContentDTOsMatchingIds(final Co finalFilter ); - List searchResults = mapper.mapFromStringListToContentList(searchHits.getResults()); - return new ResultsWrapper<>(mapper.getDTOByDOList(searchResults), searchHits.getTotalResults()); + List searchResults = mapperUtils.mapFromStringListToContentList(searchHits.getResults()); + return new ResultsWrapper<>(mapperUtils.getDTOByDOList(searchResults), searchHits.getTotalResults()); }); } catch (final ExecutionException e) { throw new ContentManagerException(e.getCause().getMessage()); @@ -389,9 +394,9 @@ public final ResultsWrapper siteWideSearch( sortOrder ); - List searchResults = mapper.mapFromStringListToContentList(searchHits.getResults()); + List searchResults = mapperUtils.mapFromStringListToContentList(searchHits.getResults()); - return new ResultsWrapper<>(mapper.getDTOByDOList(searchResults), searchHits.getTotalResults()); + return new ResultsWrapper<>(mapperUtils.getDTOByDOList(searchResults), searchHits.getTotalResults()); } /** Search the content for questions (and fasttrack questions) that match a given user provided search string and @@ -495,9 +500,9 @@ public final ResultsWrapper questionSearch( sortOrder ); - List searchResults = mapper.mapFromStringListToContentList(searchHits.getResults()); + List searchResults = mapperUtils.mapFromStringListToContentList(searchHits.getResults()); - return new ResultsWrapper<>(mapper.getDTOByDOList(searchResults), searchHits.getTotalResults()); + return new ResultsWrapper<>(mapperUtils.getDTOByDOList(searchResults), searchHits.getTotalResults()); } public final ResultsWrapper findByFieldNames( @@ -543,9 +548,9 @@ public final ResultsWrapper findByFieldNames( // setup object mapper to use pre-configured deserializer module. // Required to deal with type polymorphism - List result = mapper.mapFromStringListToContentList(searchHits.getResults()); + List result = mapperUtils.mapFromStringListToContentList(searchHits.getResults()); - List contentDTOResults = mapper.getDTOByDOList(result); + List contentDTOResults = mapperUtils.getDTOByDOList(result); finalResults = new ResultsWrapper<>(contentDTOResults, searchHits.getTotalResults()); @@ -573,9 +578,9 @@ public final ResultsWrapper findByFieldNamesRandomOrder( // setup object mapper to use pre-configured deserializer module. // Required to deal with type polymorphism - List result = mapper.mapFromStringListToContentList(searchHits.getResults()); + List result = mapperUtils.mapFromStringListToContentList(searchHits.getResults()); - List contentDTOResults = mapper.getDTOByDOList(result); + List contentDTOResults = mapperUtils.getDTOByDOList(result); finalResults = new ResultsWrapper<>(contentDTOResults, searchHits.getTotalResults()); @@ -707,7 +712,7 @@ public ContentDTO populateRelatedContent(final ContentDTO contentDTO) for (String contentId : relatedContentIds) { ContentDTO relatedContent = resultsMappedById.get(contentId); if (relatedContent != null) { - ContentSummaryDTO summary = this.mapper.getAutoMapper().map(relatedContent, ContentSummaryDTO.class); + ContentSummaryDTO summary = this.mapper.map(relatedContent, ContentSummaryDTO.class); GitContentManager.generateDerivedSummaryValues(relatedContent, summary); relatedContentDTOs.add(summary); } else { diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java index e0e0235aa0..e39c9c4aca 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java @@ -269,7 +269,7 @@ public static void setUpClass() throws Exception { Git git = createNiceMock(Git.class); GitDb gitDb = new GitDb(git); - contentManager = new GitContentManager(gitDb, elasticSearchProvider, contentMapper, properties); + contentManager = new GitContentManager(gitDb, elasticSearchProvider, mainMapper, contentMapper, properties); logManager = createNiceMock(ILogManager.class); IDeletionTokenPersistenceManager deletionTokenPersistenceManager = new PgDeletionTokenPersistenceManager(postgresSqlDb); diff --git a/src/test/java/uk/ac/cam/cl/dtg/segue/dao/GitContentManagerTest.java b/src/test/java/uk/ac/cam/cl/dtg/segue/dao/GitContentManagerTest.java index 900bb36695..edda29ea05 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/segue/dao/GitContentManagerTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/segue/dao/GitContentManagerTest.java @@ -25,6 +25,7 @@ import uk.ac.cam.cl.dtg.isaac.dos.content.Content; import uk.ac.cam.cl.dtg.isaac.dos.content.ContentBase; import uk.ac.cam.cl.dtg.segue.search.ISearchProvider; +import uk.ac.cam.cl.dtg.util.mappers.ContentMapperMS; import java.util.*; @@ -40,7 +41,8 @@ public class GitContentManagerTest { private GitDb database; private ISearchProvider searchProvider; - private ContentMapper contentMapper; + private ContentMapperMS contentMapper; + private ContentMapper contentMapperUtils; private GitContentManager defaultGCM; @@ -56,9 +58,10 @@ public class GitContentManagerTest { public final void setUp() throws Exception { this.database = createMock(GitDb.class); this.searchProvider = createMock(ISearchProvider.class); - this.contentMapper = createMock(ContentMapper.class); + this.contentMapper = createMock(ContentMapperMS.class); + this.contentMapperUtils = createMock(ContentMapper.class); - this.defaultGCM = new GitContentManager(database, searchProvider, contentMapper); + this.defaultGCM = new GitContentManager(database, searchProvider, contentMapper, contentMapperUtils); } /** * Test that the getById method returns null if it is passed a null id. @@ -109,6 +112,6 @@ private GitContentManager validateReferentialIntegrity_setUpTest( Map contents = new TreeMap(); contents.put(INITIAL_VERSION, content); - return new GitContentManager(database, searchProvider, contentMapper); + return new GitContentManager(database, searchProvider, contentMapper, contentMapperUtils); } } diff --git a/src/test/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexerTest.java b/src/test/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexerTest.java index 73d8a924a4..6bea960741 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexerTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexerTest.java @@ -38,6 +38,7 @@ import uk.ac.cam.cl.dtg.segue.database.GitDb; import uk.ac.cam.cl.dtg.isaac.dos.content.Content; import uk.ac.cam.cl.dtg.isaac.dos.content.ContentBase; +import uk.ac.cam.cl.dtg.util.mappers.ContentMapperMS; /** * Test class for the GitContentManager class. @@ -47,7 +48,8 @@ public class ContentIndexerTest { private GitDb database; private ElasticSearchIndexer searchProvider; - private ContentMapper contentMapper; + private ContentMapperMS contentMapper; + private ContentMapper contentMapperUtils; private ContentIndexer defaultContentIndexer; @@ -63,9 +65,9 @@ public class ContentIndexerTest { public final void setUp() throws Exception { this.database = createMock(GitDb.class); this.searchProvider = createMock(ElasticSearchIndexer.class); - this.contentMapper = createMock(ContentMapper.class); - this.defaultContentIndexer = new ContentIndexer(database, searchProvider, - contentMapper); + this.contentMapper = createMock(ContentMapperMS.class); + this.contentMapperUtils = createMock(ContentMapper.class); + this.defaultContentIndexer = new ContentIndexer(database, searchProvider, contentMapperUtils); } /** @@ -108,7 +110,7 @@ public void buildSearchIndexes_sendContentToSearchProvider_checkSearchProviderIs // prepare pre-canned responses for the object mapper ObjectMapper objectMapper = createMock(ObjectMapper.class); - expect(contentMapper.generateNewPreconfiguredContentMapper()).andReturn(objectMapper) + expect(contentMapperUtils.generateNewPreconfiguredContentMapper()).andReturn(objectMapper) .once(); expect(objectMapper.writeValueAsString(content)).andReturn( uniqueObjectHash).once(); @@ -151,10 +153,9 @@ public void buildSearchIndexes_sendContentToSearchProvider_checkSearchProviderIs searchProvider.bulkIndexWithIDs(eq(INITIAL_VERSION), eq(Constants.CONTENT_INDEX_TYPE.CONTENT.toString()), anyObject()); expectLastCall().once(); - replay(searchProvider, contentMapper, objectMapper); + replay(searchProvider, contentMapper, contentMapperUtils, objectMapper); - ContentIndexer contentIndexer = new ContentIndexer(database, - searchProvider, contentMapper); + ContentIndexer contentIndexer = new ContentIndexer(database, searchProvider, contentMapperUtils); // Method under test Whitebox.invokeMethod(contentIndexer, @@ -476,6 +477,6 @@ private GitContentManager validateReferentialIntegrity_setUpTest( Map contents = new TreeMap(); contents.put(INITIAL_VERSION, content); - return new GitContentManager(database, searchProvider, contentMapper); + return new GitContentManager(database, searchProvider, contentMapper, contentMapperUtils); } } From 0e054159085ee3dd52f73af3196bb14abaea6325 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Thu, 25 Sep 2025 14:37:27 +0100 Subject: [PATCH 21/65] Replace remaining Orika usage in ContentMapper --- .../SegueGuiceConfigurationModule.java | 14 ------ .../dtg/segue/dao/content/ContentMapper.java | 45 ++----------------- .../cl/dtg/util/mappers/ContentMapperMS.java | 28 +++--------- .../api/AbstractIsaacIntegrationTest.java | 3 -- 4 files changed, 9 insertions(+), 81 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java index 0dd67b89ad..0dda7e14b2 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java @@ -990,20 +990,6 @@ private CountryLookupManager getCountryLookupManager( return countryLookupManager; } - - - /** - * Gets the instance of the dozer mapper object. - * - * @return a preconfigured instance of an Auto Mapper. This is specialised for mapping SegueObjects. - */ - @Provides - @Singleton - @Inject - public static MapperFacade getDOtoDTOMapper() { - return SegueGuiceConfigurationModule.getContentMapper().getAutoMapper(); - } - /** * @return segue version currently running. */ diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentMapper.java index 4871ead8c9..c8ab79b0fe 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentMapper.java @@ -21,10 +21,6 @@ import com.google.api.client.util.Lists; import com.google.common.collect.Maps; import com.google.inject.Inject; -import ma.glasnost.orika.MapperFacade; -import ma.glasnost.orika.MapperFactory; -import ma.glasnost.orika.converter.ConverterFactory; -import ma.glasnost.orika.impl.DefaultMapperFactory; import org.reflections.Reflections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,9 +41,8 @@ import uk.ac.cam.cl.dtg.isaac.dto.content.SeguePageDTO; import uk.ac.cam.cl.dtg.isaac.dto.content.SidebarDTO; import uk.ac.cam.cl.dtg.segue.dao.JsonLoader; -import uk.ac.cam.cl.dtg.segue.dao.users.AnonymousUserQuestionAttemptsOrikaConverter; import uk.ac.cam.cl.dtg.segue.dao.users.QuestionValidationResponseDeserializer; -import uk.ac.cam.cl.dtg.segue.dao.users.QuestionValidationResponseOrikaConverter; +import uk.ac.cam.cl.dtg.util.mappers.ContentMapperMS; import java.io.IOException; import java.util.ArrayList; @@ -67,9 +62,6 @@ public class ContentMapper { // field. private final Map> jsonTypes; private final Map, Class> mapOfDOsToDTOs; - - // this autoMapper is initialised lazily in the getAutoMapper method - private MapperFacade autoMapper = null; private static ObjectMapper preconfiguredObjectMapper; @@ -264,8 +256,7 @@ public ContentDTO getDTOByDO(final Content content) { if (null == content) { return null; } - - ContentDTO result = getAutoMapper().map(content, this.mapOfDOsToDTOs.get(content.getClass())); + ContentDTO result = ContentMapperMS.INSTANCE.mapContent(content); populateRelatedContentWithIDs(content, result); populateSidebarWithIDs(content, result); return result; @@ -296,7 +287,7 @@ public List getDTOByDOList(final List contentDOList) { /** * Provides a pre-configured module that can be added to an object mapper so that contentBase objects can be - * deseerialized using the custom deserializer. + * deserialized using the custom deserializer. * * This object Mapper is shared and should be treated as immutable. * @@ -337,34 +328,6 @@ public List mapFromStringListToContentList(final List stringLis } return contentList; } - - /** - * Get an instance of the automapper which has been configured to cope with recursive content objects. This - * automapper is more efficient than the jackson one as there is no intermediate representation. - * - * @return autoMapper - */ - public MapperFacade getAutoMapper() { - if (null == this.autoMapper) { - log.info("Creating instance of content auto mapper."); - MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); - ContentBaseOrikaConverter contentBaseOrikaConverter = new ContentBaseOrikaConverter(this); - - ConverterFactory converterFactory = mapperFactory.getConverterFactory(); - converterFactory.registerConverter(contentBaseOrikaConverter); - converterFactory.registerConverter(new QuestionBaseOrikaConverter(contentBaseOrikaConverter)); - converterFactory.registerConverter(new ChoiceOrikaConverter()); - converterFactory.registerConverter(new ItemOrikaConverter()); - converterFactory.registerConverter(new QuestionValidationResponseOrikaConverter()); - converterFactory.registerConverter(new AnonymousUserQuestionAttemptsOrikaConverter()); - converterFactory.registerConverter(new AudienceOrikaConverter()); - converterFactory.registerConverter(new SidebarEntryOrikaConverter()); - - this.autoMapper = mapperFactory.getMapperFacade(); - } - - return this.autoMapper; - } /** * Creates a brand new object mapper. @@ -376,7 +339,7 @@ public ObjectMapper generateNewPreconfiguredContentMapper() { ContentBaseDeserializer contentDeserializer = new ContentBaseDeserializer(); contentDeserializer.registerTypeMap(jsonTypes); - /* When deserialising from Git, the top-level content mapper needs to have an ItemDeseriaizer, + /* When deserialising from Git, the top-level content mapper needs to have an ItemDeserializer, and when deserialising a Choice object directly then the ChoiceDeserializer needs to have an ItemDeserializer inside it too. The perils of a recursive content model! */ ItemDeserializer itemDeserializer = new ItemDeserializer(contentDeserializer); diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java index cb0d10f7c1..66a34d062d 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java @@ -4,12 +4,14 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; import uk.ac.cam.cl.dtg.isaac.dos.IsaacCard; import uk.ac.cam.cl.dtg.isaac.dos.IsaacCardDeck; import uk.ac.cam.cl.dtg.isaac.dos.IsaacEventPage; import uk.ac.cam.cl.dtg.isaac.dos.IsaacFeaturedProfile; import uk.ac.cam.cl.dtg.isaac.dos.IsaacPageFragment; import uk.ac.cam.cl.dtg.isaac.dos.IsaacPod; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacQuiz; import uk.ac.cam.cl.dtg.isaac.dos.IsaacQuizSection; import uk.ac.cam.cl.dtg.isaac.dos.IsaacWildcard; import uk.ac.cam.cl.dtg.isaac.dos.content.*; @@ -30,6 +32,8 @@ @Mapper public interface ContentMapperMS { + ContentMapperMS INSTANCE = Mappers.getMapper(ContentMapperMS.class); + @SubclassMapping(source = IsaacEventPageDTO.class, target = IsaacEventPageDTO.class) ContentDTO copy(ContentDTO source); @@ -76,7 +80,6 @@ default T map(ContentDTO source, Class targetClass) { @SubclassMapping(source = IsaacQuizDTO.class, target = DetailedQuizSummaryDTO.class) DetailedQuizSummaryDTO mapContentDTOtoDetailedQuizSummaryDTO(ContentDTO source); - @Mapping(target = "rubric", ignore = true) DetailedQuizSummaryDTO map(IsaacQuizDTO source); SidebarDTO map(String source); @@ -120,10 +123,9 @@ default ResultsWrapper copy(ResultsWrapper source) { @SubclassMapping(source = IsaacQuizSection.class, target = IsaacQuizSectionDTO.class) @SubclassMapping(source = IsaacWildcard.class, target = IsaacWildcardDTO.class) @SubclassMapping(source = Item.class, target = ItemDTO.class) - @SubclassMapping(source = Media.class, target = MediaDTO.class) @SubclassMapping(source = Notification.class, target = NotificationDTO.class) @SubclassMapping(source = Question.class, target = QuestionDTO.class) - @SubclassMapping(source = SeguePage.class, target = SeguePageDTO.class) + @SubclassMapping(source = IsaacQuiz.class, target = IsaacQuizDTO.class) ContentDTO mapContent(Content source); List mapListOfContentSummaryDtoToListOfString(List source); @@ -166,24 +168,4 @@ default ContentBaseDTO map(ContentBase source) { throw new UnimplementedMappingException(source.getClass(), ContentBaseDTO.class); } } - - default Media map(MediaDTO source) { - if (source == null) { - return null; - } else if (source instanceof ImageDTO || source instanceof VideoDTO) { - return map(source); - } else { - throw new UnimplementedMappingException(source.getClass(), Media.class); - } - } - - default MediaDTO map(Media source) { - if (source == null) { - return null; - } else if (source instanceof Image || source instanceof Video) { - return map(source); - } else { - throw new UnimplementedMappingException(source.getClass(), MediaDTO.class); - } - } } \ No newline at end of file diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java index e39c9c4aca..e1e0413a03 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import com.google.api.client.util.Maps; -import ma.glasnost.orika.MapperFacade; import org.apache.commons.lang3.SystemUtils; import org.eclipse.jgit.api.Git; import org.junit.jupiter.api.AfterAll; @@ -122,7 +121,6 @@ public class AbstractIsaacIntegrationTest { protected static PostgresSqlDb postgresSqlDb; protected static ElasticSearchProvider elasticSearchProvider; protected static SchoolListReader schoolListReader; - protected static MapperFacade mapperFacade; protected static MainMapper mainMapper; protected static ContentSummarizerService contentSummarizerService; protected static IMisuseMonitor misuseMonitor; @@ -248,7 +246,6 @@ public static void setUpClass() throws Exception { PgQuestionAttempts pgQuestionAttempts = new PgQuestionAttempts(postgresSqlDb, contentMapper); questionManager = new QuestionManager(contentMapper, mainMapper, pgQuestionAttempts); - mapperFacade = contentMapper.getAutoMapper(); mainMapper = MainMapper.INSTANCE; providersToRegister = new HashMap<>(); From e9576134b74d8d4bf72476019fe6499369d47ae9 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Thu, 25 Sep 2025 14:50:13 +0100 Subject: [PATCH 22/65] Remove remaining Orika usage --- .../segue/api/managers/QuestionManager.java | 1 - .../SegueGuiceConfigurationModule.java | 1 - ...ractPolymorphicBidirectionalConverter.java | 34 ----- .../content/AbstractPolymorphicConverter.java | 28 ---- .../dao/content/AudienceOrikaConverter.java | 33 ---- .../dao/content/ChoiceOrikaConverter.java | 142 ------------------ .../content/ContentBaseOrikaConverter.java | 73 --------- .../segue/dao/content/ItemOrikaConverter.java | 76 ---------- .../content/QuestionBaseOrikaConverter.java | 46 ------ .../content/SidebarEntryOrikaConverter.java | 68 --------- ...ousUserQuestionAttemptsOrikaConverter.java | 73 --------- ...stionValidationResponseOrikaConverter.java | 92 ------------ .../api/managers/QuizQuestionManagerTest.java | 8 +- 13 files changed, 1 insertion(+), 674 deletions(-) delete mode 100644 src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/AbstractPolymorphicBidirectionalConverter.java delete mode 100644 src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/AbstractPolymorphicConverter.java delete mode 100644 src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/AudienceOrikaConverter.java delete mode 100644 src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ChoiceOrikaConverter.java delete mode 100644 src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentBaseOrikaConverter.java delete mode 100644 src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ItemOrikaConverter.java delete mode 100644 src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/QuestionBaseOrikaConverter.java delete mode 100644 src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/SidebarEntryOrikaConverter.java delete mode 100644 src/main/java/uk/ac/cam/cl/dtg/segue/dao/users/AnonymousUserQuestionAttemptsOrikaConverter.java delete mode 100644 src/main/java/uk/ac/cam/cl/dtg/segue/dao/users/QuestionValidationResponseOrikaConverter.java diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java index ab83acb1f5..743a2c6121 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java @@ -22,7 +22,6 @@ import com.google.inject.Inject; import com.google.inject.Injector; import io.prometheus.client.Histogram; -import ma.glasnost.orika.MapperFacade; import org.joda.time.LocalDate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java index 0dda7e14b2..d6ee953008 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java @@ -33,7 +33,6 @@ import com.google.inject.name.Named; import com.google.inject.name.Names; import com.google.inject.util.Providers; -import ma.glasnost.orika.MapperFacade; import org.apache.commons.lang3.SystemUtils; import org.elasticsearch.client.RestHighLevelClient; import org.reflections.Reflections; diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/AbstractPolymorphicBidirectionalConverter.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/AbstractPolymorphicBidirectionalConverter.java deleted file mode 100644 index f473898c7e..0000000000 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/AbstractPolymorphicBidirectionalConverter.java +++ /dev/null @@ -1,34 +0,0 @@ -package uk.ac.cam.cl.dtg.segue.dao.content; - - -import ma.glasnost.orika.converter.BidirectionalConverter; -import ma.glasnost.orika.metadata.Type; - -/** - * An Orika BidirectionalConverter that implements the old, polymorphic, canConvert check. - * - * @param - source type - * @param - destination type - */ -public abstract class AbstractPolymorphicBidirectionalConverter extends BidirectionalConverter { - - @Override - public boolean canConvert(Type sourceType, Type destinationType) { - /* The behaviour of canConvert changed in Orika v1.5.0 to only convert exact class matches, - to fix an issue with converters acting too loosely on pairs of classes they were not - meant to convert. - The crux of the change was swapping from sourceType.isAssignableFrom() to sourceType.equals(), - which for our use case prevents the clever polymorphism we do with subtypes working in converters. - - Since this is a bidirectional converter, we must check whether the reverse conversion is - possible too. It is not immediately obvious what the correct reverse check must be, and so this - check has been copied from the old Orika code. - - See the commit that changed this behaviour here: - https://github.com/orika-mapper/orika/commit/554396579c96b3356c3c31ceb2e236cba0ffbaba - */ - boolean forwardConvertable = this.sourceType.isAssignableFrom(sourceType) && this.destinationType.equals(destinationType); - boolean reverseConvertable = this.destinationType.isAssignableFrom(sourceType) && this.sourceType.equals(destinationType); - return forwardConvertable || reverseConvertable; - } -} diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/AbstractPolymorphicConverter.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/AbstractPolymorphicConverter.java deleted file mode 100644 index 250a52c4c6..0000000000 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/AbstractPolymorphicConverter.java +++ /dev/null @@ -1,28 +0,0 @@ -package uk.ac.cam.cl.dtg.segue.dao.content; - - -import ma.glasnost.orika.CustomConverter; -import ma.glasnost.orika.metadata.Type; - -/** - * An Orika CustomConverter that implements the old, polymorphic, canConvert check. - * - * @param - source type - * @param - destination type - */ -public abstract class AbstractPolymorphicConverter extends CustomConverter { - - @Override - public boolean canConvert(Type sourceType, Type destinationType) { - /* The behaviour of canConvert changed in Orika v1.5.0 to only convert exact class matches, - to fix an issue with converters acting too loosely on pairs of classes they were not - meant to convert. - The crux of the change was swapping from sourceType.isAssignableFrom() to sourceType.equals(), - which for our use case prevents the clever polymorphism we do with subtypes working in converters. - - See the commit that changed this behaviour here: - https://github.com/orika-mapper/orika/commit/554396579c96b3356c3c31ceb2e236cba0ffbaba - */ - return this.sourceType.isAssignableFrom(sourceType) && this.destinationType.equals(destinationType); - } -} diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/AudienceOrikaConverter.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/AudienceOrikaConverter.java deleted file mode 100644 index 8737fb8305..0000000000 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/AudienceOrikaConverter.java +++ /dev/null @@ -1,33 +0,0 @@ -package uk.ac.cam.cl.dtg.segue.dao.content; - -import ma.glasnost.orika.CustomConverter; -import ma.glasnost.orika.MappingContext; -import ma.glasnost.orika.metadata.Type; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -/** - * AudienceOrikaConverter A specialist converter class to work with the Orika automapper library. - * - * Responsible for converting the intended audience data structure from DO to DTO. - * This converter will be adopted by Orika whenever it introspects a conversion between these specific types (not only - * for the audience field). Its implementation is generic so that is fine. - * It seems ORIKA is not good at converting between highly nested data structures. - */ -public class AudienceOrikaConverter - extends CustomConverter>>, List>>> { - - @Override - public List>> convert( - List>> maps, Type>>> type, - MappingContext _context) { - if (maps == null) {return null;} - - // This is horrible to read but it is a deep copy of the data structure - better safe than sorry. - return maps.stream().map(m -> m.entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream().collect(Collectors.toList())))) - .collect(Collectors.toList()); - } -} diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ChoiceOrikaConverter.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ChoiceOrikaConverter.java deleted file mode 100644 index cfd1ab3b7b..0000000000 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ChoiceOrikaConverter.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2014 Stephen Cummins - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package uk.ac.cam.cl.dtg.segue.dao.content; - -import ma.glasnost.orika.MappingContext; -import ma.glasnost.orika.metadata.Type; -import uk.ac.cam.cl.dtg.isaac.dos.content.ChemicalFormula; -import uk.ac.cam.cl.dtg.isaac.dos.content.Choice; -import uk.ac.cam.cl.dtg.isaac.dos.content.CoordinateChoice; -import uk.ac.cam.cl.dtg.isaac.dos.content.Formula; -import uk.ac.cam.cl.dtg.isaac.dos.content.LLMFreeTextChoice; -import uk.ac.cam.cl.dtg.isaac.dos.content.FreeTextRule; -import uk.ac.cam.cl.dtg.isaac.dos.content.GraphChoice; -import uk.ac.cam.cl.dtg.isaac.dos.content.ItemChoice; -import uk.ac.cam.cl.dtg.isaac.dos.content.LogicFormula; -import uk.ac.cam.cl.dtg.isaac.dos.content.ParsonsChoice; -import uk.ac.cam.cl.dtg.isaac.dos.content.Quantity; -import uk.ac.cam.cl.dtg.isaac.dos.content.StringChoice; -import uk.ac.cam.cl.dtg.isaac.dos.content.RegexPattern; -import uk.ac.cam.cl.dtg.isaac.dto.LLMFreeTextChoiceDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.ChemicalFormulaDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.ChoiceDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.CoordinateChoiceDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.FormulaDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.FreeTextRuleDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.GraphChoiceDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.ItemChoiceDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.LogicFormulaDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.ParsonsChoiceDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.QuantityDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.StringChoiceDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.RegexPatternDTO; - -/** - * ContentBaseOrikaConverter A specialist converter class to work with the Orika automapper library. - * - * Responsible for converting Choice objects to their correct subtype. - * - */ -public class ChoiceOrikaConverter extends AbstractPolymorphicBidirectionalConverter { - - /** - * Constructs an Orika Converter specialises in selecting the correct subclass for choice objects. - * - */ - public ChoiceOrikaConverter() { - - } - - @Override - public ChoiceDTO convertTo(final Choice source, final Type destinationType, - MappingContext _context) { - if (null == source) { - return null; - } - - if (source instanceof Quantity) { - return super.mapperFacade.map(source, QuantityDTO.class); - } else if (source instanceof Formula) { - return super.mapperFacade.map(source, FormulaDTO.class); - } else if (source instanceof ChemicalFormula) { - return super.mapperFacade.map(source, ChemicalFormulaDTO.class); - } else if (source instanceof LogicFormula) { - return super.mapperFacade.map(source, LogicFormulaDTO.class); - } else if (source instanceof GraphChoice) { - return super.mapperFacade.map(source, GraphChoiceDTO.class); - } else if (source instanceof StringChoice) { - return super.mapperFacade.map(source, StringChoiceDTO.class); - } else if (source instanceof RegexPattern) { - return super.mapperFacade.map(source, RegexPatternDTO.class); - } else if (source instanceof LLMFreeTextChoice) { - return super.mapperFacade.map(source, LLMFreeTextChoiceDTO.class); - } else if (source instanceof FreeTextRule) { - return super.mapperFacade.map(source, FreeTextRuleDTO.class); - } else if (source instanceof ParsonsChoice) { - return super.mapperFacade.map(source, ParsonsChoiceDTO.class); - } else if (source instanceof CoordinateChoice) { - return super.mapperFacade.map(source, CoordinateChoiceDTO.class); - } else if (source instanceof ItemChoice) { - return super.mapperFacade.map(source, ItemChoiceDTO.class); - } else { - // I would have expected this to cause an infinite loop / stack - // overflow but apparently it doesn't. - ChoiceDTO choiceDTO = new ChoiceDTO(); - super.mapperFacade.map(source, choiceDTO); - return choiceDTO; - } - } - - @Override - public Choice convertFrom(final ChoiceDTO source, final Type destinationType, - MappingContext _context) { - if (null == source) { - return null; - } - - if (source instanceof QuantityDTO) { - return super.mapperFacade.map(source, Quantity.class); - } else if (source instanceof FormulaDTO) { - return super.mapperFacade.map(source, Formula.class); - } else if (source instanceof ChemicalFormulaDTO) { - return super.mapperFacade.map(source, ChemicalFormula.class); - } else if (source instanceof LogicFormulaDTO) { - return super.mapperFacade.map(source, LogicFormula.class); - } else if (source instanceof GraphChoiceDTO) { - return super.mapperFacade.map(source, GraphChoice.class); - } else if (source instanceof StringChoiceDTO) { - return super.mapperFacade.map(source, StringChoice.class); - } else if (source instanceof RegexPatternDTO) { - return super.mapperFacade.map(source, RegexPattern.class); - } else if (source instanceof LLMFreeTextChoiceDTO) { - return super.mapperFacade.map(source, LLMFreeTextChoice.class); - } else if (source instanceof FreeTextRuleDTO) { - return super.mapperFacade.map(source, FreeTextRule.class); - } else if (source instanceof ParsonsChoiceDTO) { - return super.mapperFacade.map(source, ParsonsChoice.class); - } else if (source instanceof CoordinateChoiceDTO) { - return super.mapperFacade.map(source, CoordinateChoice.class); - } else if (source instanceof ItemChoiceDTO) { - return super.mapperFacade.map(source, ItemChoice.class); - } else { - // I would have expected this to cause an infinite loop / stack - // overflow but apparently it doesn't. - Choice choice = new Choice(); - super.mapperFacade.map(source, choice); - return choice; - } - } -} diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentBaseOrikaConverter.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentBaseOrikaConverter.java deleted file mode 100644 index 3af838d7a4..0000000000 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentBaseOrikaConverter.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2014 Stephen Cummins - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package uk.ac.cam.cl.dtg.segue.dao.content; - -import ma.glasnost.orika.MappingContext; -import ma.glasnost.orika.metadata.Type; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import uk.ac.cam.cl.dtg.isaac.dos.content.Content; -import uk.ac.cam.cl.dtg.isaac.dos.content.ContentBase; -import uk.ac.cam.cl.dtg.isaac.dto.content.ContentBaseDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.ContentDTO; - -/** - * ContentBaseOrikaConverter A specialist converter class to work with the Orika automapper library. - * - * Responsible for converting Content objects to their correct subtype. - * - */ -public class ContentBaseOrikaConverter extends AbstractPolymorphicConverter { - private static final Logger log = LoggerFactory.getLogger(ContentBaseOrikaConverter.class); - - private final ContentMapper contentMapper; - - /** - * Constructs an Orika Converter specialises in selecting the correct subclass for content objects. - * - * @param contentMapper - * - An instance of a preconfigured content mapper that knows about the content inheritance hierarchy. - */ - public ContentBaseOrikaConverter(final ContentMapper contentMapper) { - this.contentMapper = contentMapper; - } - - @Override - public ContentBaseDTO convert(final ContentBase source, final Type destinationType, - MappingContext _context) { - - if (null == source) { - return null; - } - - Class contentClass = contentMapper.getClassByType(source.getType()); - - if (contentClass == null) { - // if we cannot figure out what content object default to content. - contentClass = Content.class; - } - - Class destinationClass = contentMapper.getDTOClassByDOClass(contentClass); - - if (destinationClass == null) { - log.error("Error - unable to locate DTO class from DO class "); - return null; - } - - return super.mapperFacade.map(source, destinationClass); - } - -} diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ItemOrikaConverter.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ItemOrikaConverter.java deleted file mode 100644 index 2c3809789f..0000000000 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ItemOrikaConverter.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2019 James Sharkey - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package uk.ac.cam.cl.dtg.segue.dao.content; - -import ma.glasnost.orika.MappingContext; -import ma.glasnost.orika.metadata.Type; -import uk.ac.cam.cl.dtg.isaac.dos.content.CoordinateItem; -import uk.ac.cam.cl.dtg.isaac.dos.content.Item; -import uk.ac.cam.cl.dtg.isaac.dos.content.ParsonsItem; -import uk.ac.cam.cl.dtg.isaac.dto.content.CoordinateItemDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.ItemDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.ParsonsItemDTO; - -/** - * Converts Item objects to and from their DTO equivalents. - * - */ -public class ItemOrikaConverter extends AbstractPolymorphicBidirectionalConverter { - - /** - * Constructs an Orika Converter specialises in selecting the correct subclass for choice objects. - * - */ - public ItemOrikaConverter() { - - } - - @Override - public ItemDTO convertTo(final Item source, final Type destinationType, MappingContext _context) { - if (null == source) { - return null; - } - - if (source instanceof ParsonsItem) { - return super.mapperFacade.map(source, ParsonsItemDTO.class); - } else if (source instanceof CoordinateItem) { - return super.mapperFacade.map(source, CoordinateItemDTO.class); - } else { - // This looks like it should cause an infinite loop / stack overflow but apparently it does not. - ItemDTO itemDTO = new ItemDTO(); - super.mapperFacade.map(source, itemDTO); - return itemDTO; - } - } - - @Override - public Item convertFrom(final ItemDTO source, final Type destinationType, MappingContext _context) { - if (null == source) { - return null; - } - - if (source instanceof ParsonsItemDTO) { - return super.mapperFacade.map(source, ParsonsItem.class); - } else if (source instanceof CoordinateItemDTO) { - return super.mapperFacade.map(source, CoordinateItem.class); - } else { - // This looks like it should cause an infinite loop / stack overflow but apparently it does not. - Item item = new Item(); - super.mapperFacade.map(source, item); - return item; - } - } -} diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/QuestionBaseOrikaConverter.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/QuestionBaseOrikaConverter.java deleted file mode 100644 index f53ba52351..0000000000 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/QuestionBaseOrikaConverter.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2024 James Sharkey - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package uk.ac.cam.cl.dtg.segue.dao.content; - - -import ma.glasnost.orika.MappingContext; -import ma.glasnost.orika.metadata.Type; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacQuestionBase; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuestionBaseDTO; - -/** - * Class to force Orika to map IsaacQuestionBase to DTO correctly in a generic manner. - * - * It is necessary since InlineRegions have a "List inlineQuestions", which are not - * of type ContentBase and so do not correctly use the recursive conversion. This converter matches the - * type for those objects and so Orika will choose to use it when it needs to convert them. All this class - * then has to do is use the base converter as normal to actually convert the questions from DO to DTO. - * - */ -public class QuestionBaseOrikaConverter extends AbstractPolymorphicConverter { - - private final ContentBaseOrikaConverter baseOrikaConverter; - - public QuestionBaseOrikaConverter(final ContentBaseOrikaConverter baseOrikaConverter) { - this.baseOrikaConverter = baseOrikaConverter; - } - - @Override - public IsaacQuestionBaseDTO convert(IsaacQuestionBase isaacQuestionBase, Type type, MappingContext mappingContext) { - // We don't do anything clever here; the ContentBase converter is clever, we just defer to it: - return (IsaacQuestionBaseDTO) baseOrikaConverter.convert(isaacQuestionBase, type, mappingContext); - } -} diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/SidebarEntryOrikaConverter.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/SidebarEntryOrikaConverter.java deleted file mode 100644 index 72583d321a..0000000000 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/SidebarEntryOrikaConverter.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2025 James Sharkey - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package uk.ac.cam.cl.dtg.segue.dao.content; - -import ma.glasnost.orika.MappingContext; -import ma.glasnost.orika.metadata.Type; -import uk.ac.cam.cl.dtg.isaac.dos.content.SidebarEntry; -import uk.ac.cam.cl.dtg.isaac.dos.content.SidebarGroup; -import uk.ac.cam.cl.dtg.isaac.dto.content.SidebarEntryDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.SidebarGroupDTO; - -/** - * Converts SidebarEntry objects to and from their DTO equivalents. - * - */ -public class SidebarEntryOrikaConverter extends AbstractPolymorphicBidirectionalConverter { - - /** - * Constructs an Orika Converter to select the correct subclass for SidebarEntry objects. - * - */ - public SidebarEntryOrikaConverter() { - - } - - @Override - public SidebarEntryDTO convertTo(final SidebarEntry source, final Type destinationType, MappingContext _context) { - if (null == source) { - return null; - } - - if (source instanceof SidebarGroup) { - return super.mapperFacade.map(source, SidebarGroupDTO.class); - } else { - SidebarEntryDTO sidebarEntryDTO = new SidebarEntryDTO(); - super.mapperFacade.map(source, sidebarEntryDTO); - return sidebarEntryDTO; - } - } - - @Override - public SidebarEntry convertFrom(final SidebarEntryDTO source, final Type destinationType, MappingContext _context) { - if (null == source) { - return null; - } - - if (source instanceof SidebarGroupDTO) { - return super.mapperFacade.map(source, SidebarGroup.class); - } else { - SidebarEntry sidebarEntry = new SidebarEntry(); - super.mapperFacade.map(source, sidebarEntry); - return sidebarEntry; - } - } -} diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/users/AnonymousUserQuestionAttemptsOrikaConverter.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/users/AnonymousUserQuestionAttemptsOrikaConverter.java deleted file mode 100644 index 3663358d7a..0000000000 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/users/AnonymousUserQuestionAttemptsOrikaConverter.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2014 Stephen Cummins - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package uk.ac.cam.cl.dtg.segue.dao.users; - -import com.google.api.client.util.Lists; -import com.google.api.client.util.Maps; -import ma.glasnost.orika.MappingContext; -import ma.glasnost.orika.metadata.Type; -import uk.ac.cam.cl.dtg.segue.dao.content.AbstractPolymorphicConverter; -import uk.ac.cam.cl.dtg.isaac.dos.QuestionValidationResponse; -import uk.ac.cam.cl.dtg.isaac.dto.QuestionValidationResponseDTO; - -import java.util.List; -import java.util.Map; - -/** - * AnonymousQuestionAttemptsOrikaConverter A specialist converter class to work with the Orika automapper library. - * - * Responsible for converting question attempt maps from their DO state to their DTO state. It seems ORIKA is not good - * at converting between highly nested data structures. - */ -public class AnonymousUserQuestionAttemptsOrikaConverter - extends - AbstractPolymorphicConverter>>, - Map>>> { - - /** - * Constructs an Orika Converter specialises in selecting the correct subclass for choice objects. - * - */ - public AnonymousUserQuestionAttemptsOrikaConverter() { - - } - - @Override - public Map>> convert( - final Map>> source, - final Type>>> destinationType, - MappingContext _context) { - // convert in one direction - if (null == source) { - return null; - } - - // now map the hard question attempts stuff. - Map>> newMap = Maps.newHashMap(); - for (Map.Entry>> page : source.entrySet()) { - Map> attemptsMap = Maps.newHashMap(); - for (Map.Entry> questionEntry : page.getValue().entrySet()) { - List dtoList = Lists.newArrayList(); - for (QuestionValidationResponse dto : questionEntry.getValue()) { - dtoList.add(this.mapperFacade.map(dto, QuestionValidationResponseDTO.class)); - } - attemptsMap.put(questionEntry.getKey(), dtoList); - } - newMap.put(page.getKey(), attemptsMap); - } - return newMap; - } -} \ No newline at end of file diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/users/QuestionValidationResponseOrikaConverter.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/users/QuestionValidationResponseOrikaConverter.java deleted file mode 100644 index fec4ef2443..0000000000 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/users/QuestionValidationResponseOrikaConverter.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2014 Stephen Cummins - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package uk.ac.cam.cl.dtg.segue.dao.users; - -import ma.glasnost.orika.MappingContext; -import ma.glasnost.orika.metadata.Type; -import uk.ac.cam.cl.dtg.isaac.dos.ItemValidationResponse; -import uk.ac.cam.cl.dtg.isaac.dos.LLMFreeTextQuestionValidationResponse; -import uk.ac.cam.cl.dtg.isaac.dto.ItemValidationResponseDTO; -import uk.ac.cam.cl.dtg.isaac.dto.LLMFreeTextQuestionValidationResponseDTO; -import uk.ac.cam.cl.dtg.segue.dao.content.AbstractPolymorphicBidirectionalConverter; -import uk.ac.cam.cl.dtg.isaac.dos.QuantityValidationResponse; -import uk.ac.cam.cl.dtg.isaac.dos.QuestionValidationResponse; -import uk.ac.cam.cl.dtg.isaac.dto.QuantityValidationResponseDTO; -import uk.ac.cam.cl.dtg.isaac.dto.QuestionValidationResponseDTO; - -/** - * QuestionValidationResponseOrikaConverter A specialist converter class to work with the Orika automapper library. - * - * Responsible for converting QuestionValidationResponse objects to their correct subtype. - * - */ -public class QuestionValidationResponseOrikaConverter extends - AbstractPolymorphicBidirectionalConverter { - - /** - * Constructs an Orika Converter specialises in selecting the correct subclass for choice objects. - * - */ - public QuestionValidationResponseOrikaConverter() { - - } - - @Override - public QuestionValidationResponseDTO convertTo(final QuestionValidationResponse source, - final Type destinationType, - MappingContext _context) { - if (null == source) { - return null; - } - - if (source instanceof QuantityValidationResponse) { - return super.mapperFacade.map(source, QuantityValidationResponseDTO.class); - } else if (source instanceof ItemValidationResponse) { - return super.mapperFacade.map(source, ItemValidationResponseDTO.class); - } else if (source instanceof LLMFreeTextQuestionValidationResponse) { - return super.mapperFacade.map(source, LLMFreeTextQuestionValidationResponseDTO.class); - } else { - // I would have expected this to cause an infinite loop / stack - // overflow but apparently it doesn't. - QuestionValidationResponseDTO questionValidationResponseDTO = new QuestionValidationResponseDTO(); - super.mapperFacade.map(source, questionValidationResponseDTO); - return questionValidationResponseDTO; - } - } - - @Override - public QuestionValidationResponse convertFrom(final QuestionValidationResponseDTO source, - final Type destinationType, - MappingContext _context) { - if (null == source) { - return null; - } - - if (source instanceof QuantityValidationResponseDTO) { - return super.mapperFacade.map(source, QuantityValidationResponse.class); - } else if (source instanceof ItemValidationResponseDTO) { - return super.mapperFacade.map(source, ItemValidationResponse.class); - } else if (source instanceof LLMFreeTextQuestionValidationResponseDTO) { - return super.mapperFacade.map(source, LLMFreeTextQuestionValidationResponse.class); - } else { - // I would have expected this to cause an infinite loop / stack - // overflow but apparently it doesn't. - QuestionValidationResponse questionValidationResponse = new QuestionValidationResponse(); - super.mapperFacade.map(source, questionValidationResponse); - return questionValidationResponse; - } - } -} diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManagerTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManagerTest.java index 4e6bfded59..151d5ee4cb 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManagerTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManagerTest.java @@ -17,7 +17,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import ma.glasnost.orika.MapperFacade; import org.junit.Before; import org.junit.Test; import uk.ac.cam.cl.dtg.isaac.dao.IQuizQuestionAttemptPersistenceManager; @@ -27,7 +26,6 @@ import uk.ac.cam.cl.dtg.segue.api.managers.QuestionManager; import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException; import uk.ac.cam.cl.dtg.segue.dao.content.ContentManagerException; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapper; import uk.ac.cam.cl.dtg.isaac.dos.QuantityValidationResponse; import uk.ac.cam.cl.dtg.isaac.dos.QuestionValidationResponse; import uk.ac.cam.cl.dtg.isaac.dos.content.Choice; @@ -82,14 +80,10 @@ public void setUp() { quizQuestionAttemptPersistenceManager = createMock(IQuizQuestionAttemptPersistenceManager.class); questionManager = createMock(QuestionManager.class); MainMapper contentMapper = createMock(MainMapper.class); - MapperFacade mapperFacade = createMock(MapperFacade.class); quizAttemptManager = createMock(QuizAttemptManager.class); quizQuestionManager = new QuizQuestionManager(questionManager, contentMapper, quizQuestionAttemptPersistenceManager, quizManager, quizAttemptManager); - expect(mapperFacade.map(correctAnswer, ChoiceDTO.class)).andStubReturn(correctAnswerDTO); - expect(mapperFacade.map(wrongAnswer, ChoiceDTO.class)).andStubReturn(wrongAnswerDTO); - expect(contentMapper.mapChoice(correctAnswer)).andStubReturn(correctAnswerDTO); expect(contentMapper.mapChoice(wrongAnswer)).andStubReturn(wrongAnswerDTO); expect(contentMapper.mapChoice((ChoiceDTO) null)).andStubReturn(null); @@ -100,7 +94,7 @@ public void setUp() { expect(m.convertQuestionValidationResponseToDTO(correctResponse)).andStubReturn(correctResponseDTO); }); - replay(quizQuestionAttemptPersistenceManager, questionManager, contentMapper, mapperFacade, quizAttemptManager); + replay(quizQuestionAttemptPersistenceManager, questionManager, contentMapper, quizAttemptManager); } @Before From 7cf0f72a66e2983a799325ee5f5124f1f4e1f148 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Thu, 25 Sep 2025 14:50:42 +0100 Subject: [PATCH 23/65] Remove Orika dependency --- pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pom.xml b/pom.xml index 82ed5ed57a..58e11c9db3 100644 --- a/pom.xml +++ b/pom.xml @@ -140,12 +140,6 @@ ${jgit.version} - - ma.glasnost.orika - orika-core - 1.5.4 - - com.google.guava guava From 8073c1a854f804d583beb282fa51d1aa97cf3dcf Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Thu, 25 Sep 2025 15:47:38 +0100 Subject: [PATCH 24/65] Rename content mappers --- .../api/services/ContentSummarizerService.java | 6 +++--- .../isaac/dao/GameboardPersistenceManager.java | 4 ++-- ...gQuizQuestionAttemptPersistenceManager.java | 4 ++-- .../cl/dtg/isaac/quiz/PgQuestionAttempts.java | 4 ++-- .../cam/cl/dtg/segue/api/QuestionFacade.java | 6 +++--- .../segue/api/managers/QuestionManager.java | 6 +++--- .../SegueGuiceConfigurationModule.java | 18 +++++++++--------- ...tentMapper.java => ContentMapperUtils.java} | 14 +++++++------- .../segue/dao/content/GitContentManager.java | 15 +++++++-------- .../cam/cl/dtg/segue/etl/ContentIndexer.java | 6 +++--- .../dtg/segue/etl/ETLConfigurationModule.java | 10 +++++----- .../ac/cam/cl/dtg/segue/etl/SchoolIndexer.java | 6 +++--- ...ContentMapperMS.java => ContentMapper.java} | 4 ++-- .../ac/cam/cl/dtg/util/mappers/MainMapper.java | 2 +- .../util/mappers/QuestionValidationMapper.java | 2 +- .../api/AbstractIsaacIntegrationTest.java | 4 ++-- .../cl/dtg/isaac/api/QuestionFacadeTest.java | 6 +++--- .../cl/dtg/segue/dao/ContentMapperTest.java | 10 +++++----- .../dtg/segue/dao/GitContentManagerTest.java | 12 ++++++------ .../cl/dtg/segue/etl/ContentIndexerTest.java | 13 ++++++------- 20 files changed, 75 insertions(+), 77 deletions(-) rename src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/{ContentMapper.java => ContentMapperUtils.java} (97%) rename src/main/java/uk/ac/cam/cl/dtg/util/mappers/{ContentMapperMS.java => ContentMapper.java} (98%) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/services/ContentSummarizerService.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/services/ContentSummarizerService.java index 4b51106ba7..e1cfd8fc88 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/services/ContentSummarizerService.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/services/ContentSummarizerService.java @@ -20,16 +20,16 @@ import uk.ac.cam.cl.dtg.isaac.dto.ResultsWrapper; import uk.ac.cam.cl.dtg.isaac.dto.content.ContentDTO; import uk.ac.cam.cl.dtg.isaac.dto.content.ContentSummaryDTO; -import uk.ac.cam.cl.dtg.util.mappers.ContentMapperMS; +import uk.ac.cam.cl.dtg.util.mappers.ContentMapper; import java.util.ArrayList; public class ContentSummarizerService { - protected final ContentMapperMS mapper; + protected final ContentMapper mapper; private final URIManager uriManager; @Inject - public ContentSummarizerService(final ContentMapperMS mapper, final URIManager uriManager) { + public ContentSummarizerService(final ContentMapper mapper, final URIManager uriManager) { this.mapper = mapper; this.uriManager = uriManager; } diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/GameboardPersistenceManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/GameboardPersistenceManager.java index 6b6ef61501..1670d4302d 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/GameboardPersistenceManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/GameboardPersistenceManager.java @@ -39,7 +39,7 @@ import uk.ac.cam.cl.dtg.segue.api.Constants; import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException; import uk.ac.cam.cl.dtg.segue.dao.content.ContentManagerException; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapper; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager; import uk.ac.cam.cl.dtg.segue.database.PostgresSqlDb; import uk.ac.cam.cl.dtg.util.mappers.MainMapper; @@ -100,7 +100,7 @@ public class GameboardPersistenceManager { */ @Inject public GameboardPersistenceManager(final PostgresSqlDb database, final GitContentManager contentManager, - final MainMapper mapper, final ContentMapper objectMapper) { + final MainMapper mapper, final ContentMapperUtils objectMapper) { this.database = database; this.mapper = mapper; this.contentManager = contentManager; diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizQuestionAttemptPersistenceManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizQuestionAttemptPersistenceManager.java index af593668d2..326ed6a4f7 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizQuestionAttemptPersistenceManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizQuestionAttemptPersistenceManager.java @@ -23,7 +23,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapper; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; import uk.ac.cam.cl.dtg.segue.database.PostgresSqlDb; import uk.ac.cam.cl.dtg.isaac.dos.QuestionValidationResponse; @@ -51,7 +51,7 @@ public class PgQuizQuestionAttemptPersistenceManager implements IQuizQuestionAtt * - the ContentMapper to get a Jackson ObjectMapper for persisting question answers. */ @Inject - public PgQuizQuestionAttemptPersistenceManager(final PostgresSqlDb database, final ContentMapper objectMapper) { + public PgQuizQuestionAttemptPersistenceManager(final PostgresSqlDb database, final ContentMapperUtils objectMapper) { this.database = database; this.objectMapper = objectMapper.getSharedContentObjectMapper(); } diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/quiz/PgQuestionAttempts.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/quiz/PgQuestionAttempts.java index f48ad5ef73..37efd1612d 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/quiz/PgQuestionAttempts.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/quiz/PgQuestionAttempts.java @@ -27,7 +27,7 @@ import uk.ac.cam.cl.dtg.isaac.dos.users.Role; import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException; import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseLockTimoutException; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapper; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; import uk.ac.cam.cl.dtg.segue.database.PostgresSqlDb; import java.io.IOException; @@ -70,7 +70,7 @@ public class PgQuestionAttempts implements IQuestionAttemptManager { * - for mapping between DO and DTO */ @Inject - public PgQuestionAttempts(final PostgresSqlDb ds, final ContentMapper objectMapper) { + public PgQuestionAttempts(final PostgresSqlDb ds, final ContentMapperUtils objectMapper) { this.database = ds; this.objectMapper = objectMapper.getSharedContentObjectMapper(); } diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/api/QuestionFacade.java b/src/main/java/uk/ac/cam/cl/dtg/segue/api/QuestionFacade.java index 318a1031ff..a32b972919 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/api/QuestionFacade.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/api/QuestionFacade.java @@ -53,7 +53,7 @@ import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException; import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseLockTimoutException; import uk.ac.cam.cl.dtg.segue.dao.content.ContentManagerException; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapper; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager; import uk.ac.cam.cl.dtg.util.AbstractConfigLoader; import uk.ac.cam.cl.dtg.util.RequestIPExtractor; @@ -96,7 +96,7 @@ public static final class NoUserConsentGrantedException extends Exception { } } - private final ContentMapper mapper; + private final ContentMapperUtils mapper; private final GitContentManager contentManager; private final UserAccountManager userManager; private final AbstractUserPreferenceManager userPreferenceManager; @@ -172,7 +172,7 @@ public RegisteredUserDTO assertUserCanAnswerLLMQuestions(final AbstractSegueUser */ @Inject - public QuestionFacade(final AbstractConfigLoader properties, final ContentMapper mapper, + public QuestionFacade(final AbstractConfigLoader properties, final ContentMapperUtils mapper, final GitContentManager contentManager, final UserAccountManager userManager, final AbstractUserPreferenceManager userPreferenceManager, final QuestionManager questionManager, final ILogManager logManager, final IMisuseMonitor misuseMonitor, diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java index 743a2c6121..359e01e0c9 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java @@ -60,7 +60,7 @@ import uk.ac.cam.cl.dtg.segue.api.ErrorResponseWrapper; import uk.ac.cam.cl.dtg.segue.configuration.SegueGuiceConfigurationModule; import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapper; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import jakarta.ws.rs.BadRequestException; @@ -87,7 +87,7 @@ public class QuestionManager { private static final Logger log = LoggerFactory.getLogger(QuestionManager.class); - private final ContentMapper mapperUtils; + private final ContentMapperUtils mapperUtils; private final MainMapper mapper; private final IQuestionAttemptManager questionAttemptPersistenceManager; /** @@ -98,7 +98,7 @@ public class QuestionManager { * @param questionPersistenceManager - for question attempt persistence. */ @Inject - public QuestionManager(final ContentMapper mapperUtils, final MainMapper mapper, final IQuestionAttemptManager questionPersistenceManager) { + public QuestionManager(final ContentMapperUtils mapperUtils, final MainMapper mapper, final IQuestionAttemptManager questionPersistenceManager) { this.mapperUtils = mapperUtils; this.mapper = mapper; this.questionAttemptPersistenceManager = questionPersistenceManager; diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java index d6ee953008..5215a61439 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java @@ -109,7 +109,7 @@ import uk.ac.cam.cl.dtg.segue.dao.PgLogManager; import uk.ac.cam.cl.dtg.segue.dao.associations.IAssociationDataManager; import uk.ac.cam.cl.dtg.segue.dao.associations.PgAssociationDataManager; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapper; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager; import uk.ac.cam.cl.dtg.segue.dao.schools.SchoolListReader; import uk.ac.cam.cl.dtg.segue.dao.users.IAnonymousUserDataManager; @@ -182,7 +182,7 @@ public class SegueGuiceConfigurationModule extends AbstractModule implements Ser // Singletons - we only ever want there to be one instance of each of these. private static PostgresSqlDb postgresDB; - private static ContentMapper mapper = null; + private static ContentMapperUtils mapper = null; private static GitContentManager contentManager = null; private static RestHighLevelClient elasticSearchClient = null; private static UserAccountManager userManager = null; @@ -544,7 +544,7 @@ private static RestHighLevelClient getSearchConnectionInformation( * - database reference * @param searchProvider * - search provider to use - * @param contentMapper + * @param contentMapperUtils * - content mapper to use. * @return a fully configured content Manager. */ @@ -552,9 +552,9 @@ private static RestHighLevelClient getSearchConnectionInformation( @Provides @Singleton private static GitContentManager getContentManager(final GitDb database, final ISearchProvider searchProvider, final MainMapper mainMapper, - final ContentMapper contentMapper, final AbstractConfigLoader globalProperties) { + final ContentMapperUtils contentMapperUtils, final AbstractConfigLoader globalProperties) { if (null == contentManager) { - contentManager = new GitContentManager(database, searchProvider, mainMapper, contentMapper, globalProperties); + contentManager = new GitContentManager(database, searchProvider, mainMapper, contentMapperUtils, globalProperties); log.info("Creating singleton of ContentManager"); } @@ -607,9 +607,9 @@ private static ILogManager getLogManager(final PostgresSqlDb database, @Inject @Provides @Singleton - private static ContentMapper getContentMapper() { + private static ContentMapperUtils getContentMapper() { if (null == mapper) { - mapper = new ContentMapper(getReflectionsClass("uk.ac.cam.cl.dtg")); + mapper = new ContentMapperUtils(getReflectionsClass("uk.ac.cam.cl.dtg")); log.info("Creating Singleton of the Content Mapper"); } @@ -830,7 +830,7 @@ private IUserAccountManager getUserManager(final IUserDataManager database, fina @Inject @Provides @Singleton - private IQuestionAttemptManager getQuestionManager(final PostgresSqlDb ds, final ContentMapper objectMapper) { + private IQuestionAttemptManager getQuestionManager(final PostgresSqlDb ds, final ContentMapperUtils objectMapper) { // this needs to be a singleton as it provides a temporary cache for anonymous question attempts. if (null == questionPersistenceManager) { questionPersistenceManager = new PgQuestionAttempts(ds, objectMapper); @@ -1245,7 +1245,7 @@ private IPLocationResolver getIPLocator(final AbstractConfigLoader properties) t @Provides @Singleton private static GameboardPersistenceManager getGameboardPersistenceManager(final PostgresSqlDb database, final GitContentManager contentManager, - final MainMapper mapper, final ContentMapper objectMapper) { + final MainMapper mapper, final ContentMapperUtils objectMapper) { if (null == gameboardPersistenceManager) { gameboardPersistenceManager = new GameboardPersistenceManager(database, contentManager, mapper, objectMapper); log.info("Creating Singleton of GameboardPersistenceManager"); diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentMapperUtils.java similarity index 97% rename from src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentMapper.java rename to src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentMapperUtils.java index c8ab79b0fe..32eac4fc53 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentMapperUtils.java @@ -42,7 +42,7 @@ import uk.ac.cam.cl.dtg.isaac.dto.content.SidebarDTO; import uk.ac.cam.cl.dtg.segue.dao.JsonLoader; import uk.ac.cam.cl.dtg.segue.dao.users.QuestionValidationResponseDeserializer; -import uk.ac.cam.cl.dtg.util.mappers.ContentMapperMS; +import uk.ac.cam.cl.dtg.util.mappers.ContentMapper; import java.io.IOException; import java.util.ArrayList; @@ -54,8 +54,8 @@ /** * Class responsible for mapping Content objects (or contentBase objects) to their respective subclass. */ -public class ContentMapper { - private static final Logger log = LoggerFactory.getLogger(ContentMapper.class); +public class ContentMapperUtils { + private static final Logger log = LoggerFactory.getLogger(ContentMapperUtils.class); // Used for serialization into the correct POJO as well as deserialization. // Currently depends on the string key being the same text value as the type @@ -72,7 +72,7 @@ public class ContentMapper { * */ @Inject - public ContentMapper() { + public ContentMapperUtils() { jsonTypes = Maps.newConcurrentMap(); mapOfDOsToDTOs = Maps.newConcurrentMap(); } @@ -84,7 +84,7 @@ public ContentMapper() { * - string representing the parent package to search for content classes. e.g. uk.ac.cam.cl.dtg.segue */ @SuppressWarnings("unchecked") - public ContentMapper(final Reflections configuredReflectionClass) { + public ContentMapperUtils(final Reflections configuredReflectionClass) { this(); Objects.requireNonNull(configuredReflectionClass); @@ -256,7 +256,7 @@ public ContentDTO getDTOByDO(final Content content) { if (null == content) { return null; } - ContentDTO result = ContentMapperMS.INSTANCE.mapContent(content); + ContentDTO result = ContentMapper.INSTANCE.mapContent(content); populateRelatedContentWithIDs(content, result); populateSidebarWithIDs(content, result); return result; @@ -294,7 +294,7 @@ public List getDTOByDOList(final List contentDOList) { * @return a jackson object mapper. */ public ObjectMapper getSharedContentObjectMapper() { - if (ContentMapper.preconfiguredObjectMapper != null) { + if (ContentMapperUtils.preconfiguredObjectMapper != null) { return preconfiguredObjectMapper; } diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java index 24c79f6406..35c7e56580 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java @@ -52,8 +52,7 @@ import uk.ac.cam.cl.dtg.segue.search.SimpleFilterInstruction; import uk.ac.cam.cl.dtg.segue.search.TermsFilterInstruction; import uk.ac.cam.cl.dtg.util.AbstractConfigLoader; -import uk.ac.cam.cl.dtg.util.mappers.ContentMapperMS; -import uk.ac.cam.cl.dtg.util.mappers.MainMapper; +import uk.ac.cam.cl.dtg.util.mappers.ContentMapper; import jakarta.annotation.Nullable; import java.io.ByteArrayOutputStream; @@ -85,8 +84,8 @@ public class GitContentManager { private static final String CONTENT_TYPE = "content"; private final GitDb database; - private final ContentMapperMS mapper; - private final ContentMapper mapperUtils; + private final ContentMapper mapper; + private final ContentMapperUtils mapperUtils; private final ISearchProvider searchProvider; private final AbstractConfigLoader globalProperties; private final boolean showOnlyPublishedContent; @@ -112,8 +111,8 @@ public class GitContentManager { * - global properties. */ @Inject - public GitContentManager(final GitDb database, final ISearchProvider searchProvider, final ContentMapperMS mapper, - final ContentMapper mapperUtils, final AbstractConfigLoader globalProperties) { + public GitContentManager(final GitDb database, final ISearchProvider searchProvider, final ContentMapper mapper, + final ContentMapperUtils mapperUtils, final AbstractConfigLoader globalProperties) { this.database = database; this.mapper = mapper; this.mapperUtils = mapperUtils; @@ -153,7 +152,7 @@ public GitContentManager(final GitDb database, final ISearchProvider searchProvi * - The utility class for mapping content objects. */ public GitContentManager(final GitDb database, final ISearchProvider searchProvider, - final ContentMapperMS contentMapper, ContentMapper mapperUtils) { + final ContentMapper contentMapper, ContentMapperUtils mapperUtils) { this.database = database; this.mapper = contentMapper; this.searchProvider = searchProvider; @@ -201,7 +200,7 @@ public final ContentDTO getContentById(final String id, final boolean failQuietl /** * Get a DTO object from a DO object. * - * This method merely wraps {@link ContentMapper#getDTOByDO(Content)}, and will trust the content of the DO. + * This method merely wraps {@link ContentMapperUtils#getDTOByDO(Content)}, and will trust the content of the DO. * Only use for DO objects obtained from {@link #getContentDOById(String)} when the DTO is also required, * to avoid the potential cache-miss and ElasticSearch round-trip of {@link #getContentById(String)}. * diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexer.java b/src/main/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexer.java index 4b0fdaa2c5..494daf426a 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexer.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexer.java @@ -47,7 +47,7 @@ import uk.ac.cam.cl.dtg.isaac.dos.content.Video; import uk.ac.cam.cl.dtg.segue.api.Constants; import uk.ac.cam.cl.dtg.segue.dao.content.ContentManagerException; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapper; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; import uk.ac.cam.cl.dtg.segue.database.GitDb; import uk.ac.cam.cl.dtg.segue.search.SegueSearchException; @@ -84,13 +84,13 @@ public class ContentIndexer { private ElasticSearchIndexer es; private GitDb database; - private ContentMapper mapper; + private ContentMapperUtils mapper; private static final int MEDIA_FILE_SIZE_LIMIT = 300 * 1024; // Bytes private static final int NANOSECONDS_IN_A_MILLISECOND = 1000000; @Inject - public ContentIndexer(GitDb database, ElasticSearchIndexer es, ContentMapper mapper) { + public ContentIndexer(GitDb database, ElasticSearchIndexer es, ContentMapperUtils mapper) { this.database = database; this.es = es; this.mapper = mapper; diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/etl/ETLConfigurationModule.java b/src/main/java/uk/ac/cam/cl/dtg/segue/etl/ETLConfigurationModule.java index e72cd82c50..83a33a9176 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/etl/ETLConfigurationModule.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/etl/ETLConfigurationModule.java @@ -14,7 +14,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.ac.cam.cl.dtg.segue.api.Constants; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapper; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; import uk.ac.cam.cl.dtg.segue.database.GitDb; import uk.ac.cam.cl.dtg.util.AbstractConfigLoader; import uk.ac.cam.cl.dtg.util.WriteablePropertiesLoader; @@ -33,7 +33,7 @@ class ETLConfigurationModule extends AbstractModule { private static final Logger log = LoggerFactory.getLogger(ETLConfigurationModule.class); private static Injector injector = null; private static AbstractConfigLoader globalProperties = null; - private static ContentMapper mapper = null; + private static ContentMapperUtils mapper = null; private static RestHighLevelClient elasticSearchClient = null; private static SchoolIndexer schoolIndexer = null; private static ETLManager etlManager = null; @@ -114,10 +114,10 @@ protected void configure() { @Inject @Provides @Singleton - private static ContentMapper getContentMapper() { + private static ContentMapperUtils getContentMapper() { if (null == mapper) { Reflections r = new Reflections("uk.ac.cam.cl.dtg"); - mapper = new ContentMapper(r); + mapper = new ContentMapperUtils(r); } return mapper; } @@ -178,7 +178,7 @@ private static RestHighLevelClient getSearchConnectionInformation( @Singleton private SchoolIndexer getSchoolListIndexer(@Named(Constants.SCHOOL_CSV_LIST_PATH) final String schoolListPath, final ElasticSearchIndexer es, - final ContentMapper mapper) { + final ContentMapperUtils mapper) { if (null == schoolIndexer) { schoolIndexer = new SchoolIndexer(es, mapper, schoolListPath); log.info("Creating singleton of SchoolListReader"); diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/etl/SchoolIndexer.java b/src/main/java/uk/ac/cam/cl/dtg/segue/etl/SchoolIndexer.java index 5f06552a58..4837ab97ee 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/etl/SchoolIndexer.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/etl/SchoolIndexer.java @@ -9,7 +9,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.ac.cam.cl.dtg.segue.api.Constants; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapper; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; import uk.ac.cam.cl.dtg.segue.dao.schools.UnableToIndexSchoolsException; import uk.ac.cam.cl.dtg.isaac.dos.users.School; import uk.ac.cam.cl.dtg.segue.search.SegueSearchException; @@ -33,10 +33,10 @@ class SchoolIndexer { private static final Logger log = LoggerFactory.getLogger(SchoolIndexer.class); private ElasticSearchIndexer es; - private ContentMapper mapper; + private ContentMapperUtils mapper; private String schoolsListPath; - SchoolIndexer(ElasticSearchIndexer es, ContentMapper mapper, String schoolsListPath) { + SchoolIndexer(ElasticSearchIndexer es, ContentMapperUtils mapper, String schoolsListPath) { this.es = es; this.mapper = mapper; this.schoolsListPath = schoolsListPath; diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java similarity index 98% rename from src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java rename to src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index 66a34d062d..a5312525b2 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperMS.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -31,8 +31,8 @@ import java.util.List; @Mapper -public interface ContentMapperMS { - ContentMapperMS INSTANCE = Mappers.getMapper(ContentMapperMS.class); +public interface ContentMapper { + ContentMapper INSTANCE = Mappers.getMapper(ContentMapper.class); @SubclassMapping(source = IsaacEventPageDTO.class, target = IsaacEventPageDTO.class) ContentDTO copy(ContentDTO source); diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java index 288593e6aa..04d0c0d6ea 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java @@ -4,6 +4,6 @@ import org.mapstruct.factory.Mappers; @Mapper -public interface MainMapper extends ContentMapperMS, UserMapper, EventMapper, GameboardMapper, QuestionValidationMapper { +public interface MainMapper extends ContentMapper, UserMapper, EventMapper, GameboardMapper, QuestionValidationMapper { MainMapper INSTANCE = Mappers.getMapper(MainMapper.class); } \ No newline at end of file diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapper.java index 7b7db07e6c..fc22c0d7a1 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapper.java @@ -12,7 +12,7 @@ import uk.ac.cam.cl.dtg.isaac.dto.QuantityValidationResponseDTO; import uk.ac.cam.cl.dtg.isaac.dto.QuestionValidationResponseDTO; -@Mapper(uses = ContentMapperMS.class) +@Mapper(uses = ContentMapper.class) public interface QuestionValidationMapper { @SubclassMapping(source = FormulaValidationResponseDTO.class, target = FormulaValidationResponse.class) @SubclassMapping(source = ItemValidationResponseDTO.class, target = ItemValidationResponse.class) diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java index e1e0413a03..27afa2a324 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java @@ -70,7 +70,7 @@ import uk.ac.cam.cl.dtg.segue.dao.ILogManager; import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException; import uk.ac.cam.cl.dtg.segue.dao.associations.PgAssociationDataManager; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapper; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager; import uk.ac.cam.cl.dtg.segue.dao.schools.SchoolListReader; import uk.ac.cam.cl.dtg.segue.dao.users.IDeletionTokenPersistenceManager; @@ -242,7 +242,7 @@ public static void setUpClass() throws Exception { pgAnonymousUsers = new PgAnonymousUsers(postgresSqlDb); passwordDataManager = new PgPasswordDataManager(postgresSqlDb); - ContentMapper contentMapper = new ContentMapper(new Reflections("uk.ac.cam.cl.dtg")); + ContentMapperUtils contentMapper = new ContentMapperUtils(new Reflections("uk.ac.cam.cl.dtg")); PgQuestionAttempts pgQuestionAttempts = new PgQuestionAttempts(postgresSqlDb, contentMapper); questionManager = new QuestionManager(contentMapper, mainMapper, pgQuestionAttempts); diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/QuestionFacadeTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/QuestionFacadeTest.java index 4c82ad98ac..eaf31421f8 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/QuestionFacadeTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/QuestionFacadeTest.java @@ -33,7 +33,7 @@ import uk.ac.cam.cl.dtg.segue.api.monitors.IMisuseMonitor; import uk.ac.cam.cl.dtg.segue.dao.ILogManager; import uk.ac.cam.cl.dtg.segue.dao.content.ContentManagerException; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapper; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager; import uk.ac.cam.cl.dtg.util.AbstractConfigLoader; @@ -68,12 +68,12 @@ private void setUpQuestionFacade() throws ContentManagerException { GitContentManager contentManager = createMock(GitContentManager.class); ILogManager logManager = createNiceMock(ILogManager.class); // We don't care about logging. - ContentMapper contentMapper = createMock(ContentMapper.class); + ContentMapperUtils contentMapperUtils = createMock(ContentMapperUtils.class); QuestionManager questionManager = createMock(QuestionManager.class); IUserStreaksManager userStreaksManager = createMock(IUserStreaksManager.class); UserAssociationManager userAssociationManager = createMock(UserAssociationManager.class); - questionFacade = new QuestionFacade(properties, contentMapper, contentManager, userManager, userPreferenceManager, + questionFacade = new QuestionFacade(properties, contentMapperUtils, contentManager, userManager, userPreferenceManager, questionManager, logManager, misuseMonitor, userStreaksManager, userAssociationManager); String contentIndex = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; diff --git a/src/test/java/uk/ac/cam/cl/dtg/segue/dao/ContentMapperTest.java b/src/test/java/uk/ac/cam/cl/dtg/segue/dao/ContentMapperTest.java index e441a12fd8..983f0510f5 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/segue/dao/ContentMapperTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/segue/dao/ContentMapperTest.java @@ -3,7 +3,7 @@ import org.junit.Before; import org.junit.Test; import org.reflections.Reflections; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapper; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; import uk.ac.cam.cl.dtg.isaac.dos.content.CodeSnippet; import uk.ac.cam.cl.dtg.isaac.dos.content.Content; import uk.ac.cam.cl.dtg.isaac.dto.content.ContentDTO; @@ -12,11 +12,11 @@ public class ContentMapperTest { - private ContentMapper contentMapper; + private ContentMapperUtils contentMapperUtils; @Before public void setUp() { - this.contentMapper = new ContentMapper(new Reflections("uk.ac.cam.cl.dtg.isaac")); + this.contentMapperUtils = new ContentMapperUtils(new Reflections("uk.ac.cam.cl.dtg.isaac")); } @Test @@ -27,7 +27,7 @@ public void getDTOByDO_CodeSnippetDOtoDTO_ExpandableSetInDTO() { codeSnippet.setExpandable(true); // Act - ContentDTO codeSnippetDTO = contentMapper.getDTOByDO(codeSnippet); + ContentDTO codeSnippetDTO = contentMapperUtils.getDTOByDO(codeSnippet); // Assert assertTrue(codeSnippetDTO.getExpandable()); @@ -41,7 +41,7 @@ public void getDTOByDO_ContentDOtoDTO_ExpandableSetInDTO() { content.setExpandable(true); // Act - ContentDTO contentDTO = contentMapper.getDTOByDO(content); + ContentDTO contentDTO = contentMapperUtils.getDTOByDO(content); // Assert assertTrue(contentDTO.getExpandable()); diff --git a/src/test/java/uk/ac/cam/cl/dtg/segue/dao/GitContentManagerTest.java b/src/test/java/uk/ac/cam/cl/dtg/segue/dao/GitContentManagerTest.java index edda29ea05..1195d25dbd 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/segue/dao/GitContentManagerTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/segue/dao/GitContentManagerTest.java @@ -19,13 +19,13 @@ import org.junit.Test; import org.powermock.core.classloader.annotations.PowerMockIgnore; import uk.ac.cam.cl.dtg.segue.dao.content.ContentManagerException; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapper; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager; import uk.ac.cam.cl.dtg.segue.database.GitDb; import uk.ac.cam.cl.dtg.isaac.dos.content.Content; import uk.ac.cam.cl.dtg.isaac.dos.content.ContentBase; import uk.ac.cam.cl.dtg.segue.search.ISearchProvider; -import uk.ac.cam.cl.dtg.util.mappers.ContentMapperMS; +import uk.ac.cam.cl.dtg.util.mappers.ContentMapper; import java.util.*; @@ -41,8 +41,8 @@ public class GitContentManagerTest { private GitDb database; private ISearchProvider searchProvider; - private ContentMapperMS contentMapper; - private ContentMapper contentMapperUtils; + private ContentMapper contentMapper; + private ContentMapperUtils contentMapperUtils; private GitContentManager defaultGCM; @@ -58,8 +58,8 @@ public class GitContentManagerTest { public final void setUp() throws Exception { this.database = createMock(GitDb.class); this.searchProvider = createMock(ISearchProvider.class); - this.contentMapper = createMock(ContentMapperMS.class); - this.contentMapperUtils = createMock(ContentMapper.class); + this.contentMapper = createMock(ContentMapper.class); + this.contentMapperUtils = createMock(ContentMapperUtils.class); this.defaultGCM = new GitContentManager(database, searchProvider, contentMapper, contentMapperUtils); } diff --git a/src/test/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexerTest.java b/src/test/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexerTest.java index 6bea960741..41918d30ee 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexerTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexerTest.java @@ -23,7 +23,6 @@ import com.google.api.client.util.Maps; import com.google.api.client.util.Sets; import com.google.common.collect.ImmutableMap; -import org.apache.logging.log4j.core.pattern.AbstractStyleNameConverter; import org.junit.Before; import org.junit.Test; import org.powermock.core.classloader.annotations.PowerMockIgnore; @@ -33,12 +32,12 @@ import uk.ac.cam.cl.dtg.isaac.dos.IsaacNumericQuestion; import uk.ac.cam.cl.dtg.segue.api.Constants; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapper; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager; import uk.ac.cam.cl.dtg.segue.database.GitDb; import uk.ac.cam.cl.dtg.isaac.dos.content.Content; import uk.ac.cam.cl.dtg.isaac.dos.content.ContentBase; -import uk.ac.cam.cl.dtg.util.mappers.ContentMapperMS; +import uk.ac.cam.cl.dtg.util.mappers.ContentMapper; /** * Test class for the GitContentManager class. @@ -48,8 +47,8 @@ public class ContentIndexerTest { private GitDb database; private ElasticSearchIndexer searchProvider; - private ContentMapperMS contentMapper; - private ContentMapper contentMapperUtils; + private ContentMapper contentMapper; + private ContentMapperUtils contentMapperUtils; private ContentIndexer defaultContentIndexer; @@ -65,8 +64,8 @@ public class ContentIndexerTest { public final void setUp() throws Exception { this.database = createMock(GitDb.class); this.searchProvider = createMock(ElasticSearchIndexer.class); - this.contentMapper = createMock(ContentMapperMS.class); - this.contentMapperUtils = createMock(ContentMapper.class); + this.contentMapper = createMock(ContentMapper.class); + this.contentMapperUtils = createMock(ContentMapperUtils.class); this.defaultContentIndexer = new ContentIndexer(database, searchProvider, contentMapperUtils); } From dbe1594e9dbcc524a964431c8394c6025cb2855a Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Fri, 26 Sep 2025 12:12:11 +0100 Subject: [PATCH 25/65] Add default Content to ContentDTO/Wildcard mapper --- .../cam/cl/dtg/isaac/api/managers/GameManager.java | 2 +- .../uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java index 51c73f12f5..0497b9e9bb 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java @@ -1212,7 +1212,7 @@ private IsaacWildcard getWildCardById(final String id) throws ContentManagerExce Content wildcardResults = this.contentManager.getContentDOById(id); - return mapper.map(wildcardResults); + return mapper.map(wildcardResults, IsaacWildcard.class); } /** diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index a5312525b2..16f706ec98 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -51,6 +51,16 @@ default T map(ContentDTO source, Class targetClass) { } } + default T map(Content source, Class targetClass) { + if (targetClass.equals(IsaacWildcard.class)) { + return (T) mapContentToIsaacWildcard(source); + } else if (targetClass.equals(ContentDTO.class)) { + return (T) mapContent(source); + } else { + throw new UnimplementedMappingException(Content.class, targetClass); + } + } + @Mapping(target = "url", ignore = true) @Mapping(target = "supersededBy", ignore = true) @Mapping(target = "summary", ignore = true) @@ -58,7 +68,7 @@ default T map(ContentDTO source, Class targetClass) { @Mapping(target = "difficulty", ignore = true) ContentSummaryDTO mapContentDTOtoContentSummaryDTO(ContentDTO source); - IsaacWildcard map(Content source); + IsaacWildcard mapContentToIsaacWildcard(Content source); @Mapping(target = "supersededBy", ignore = true) @Mapping(target = "state", ignore = true) From 61c63b54cff932c962c62e5430260669a0b2453a Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Fri, 26 Sep 2025 16:39:33 +0100 Subject: [PATCH 26/65] Set subclass exhaustive strategy to simplify ContentBase mappings This allows ContentBase types to be returned from a normal subclass mapping method without causing abstract interface errors --- .../cl/dtg/util/mappers/ContentMapper.java | 58 +++++-------------- .../cam/cl/dtg/util/mappers/MainMapper.java | 3 +- 2 files changed, 16 insertions(+), 45 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index 16f706ec98..36d34bc205 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -3,39 +3,25 @@ import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.SubclassExhaustiveStrategy; import org.mapstruct.SubclassMapping; import org.mapstruct.factory.Mappers; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacCard; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacCardDeck; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacEventPage; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacFeaturedProfile; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacPageFragment; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacPod; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacQuiz; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacQuizSection; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacWildcard; +import uk.ac.cam.cl.dtg.isaac.dos.*; import uk.ac.cam.cl.dtg.isaac.dos.content.*; -import uk.ac.cam.cl.dtg.isaac.dto.GameboardItem; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacCardDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacCardDeckDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacEventPageDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacFeaturedProfileDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacPageFragmentDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacPodDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuizDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuizSectionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacWildcardDTO; -import uk.ac.cam.cl.dtg.isaac.dto.ResultsWrapper; +import uk.ac.cam.cl.dtg.isaac.dto.*; import uk.ac.cam.cl.dtg.isaac.dto.content.*; import java.util.List; -@Mapper +@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) public interface ContentMapper { ContentMapper INSTANCE = Mappers.getMapper(ContentMapper.class); - @SubclassMapping(source = IsaacEventPageDTO.class, target = IsaacEventPageDTO.class) - ContentDTO copy(ContentDTO source); + @SubclassMapping(source = ContentDTO.class, target = Content.class) + ContentBase map(ContentBaseDTO source); + + @SubclassMapping(source = Content.class, target = ContentDTO.class) + ContentBaseDTO map(ContentBase source); default T map(ContentDTO source, Class targetClass) { if (targetClass.equals(ContentSummaryDTO.class)) { @@ -46,6 +32,8 @@ default T map(ContentDTO source, Class targetClass) { return (T) mapContentDTOtoQuizSummaryDTO(source); } else if (targetClass.equals(DetailedQuizSummaryDTO.class)) { return (T) mapContentDTOtoDetailedQuizSummaryDTO(source); + } else if (targetClass.equals(IsaacWildcard.class)) { + return (T) map(mapContent(source), IsaacWildcard.class); } else { throw new UnimplementedMappingException(ContentDTO.class, targetClass); } @@ -142,6 +130,9 @@ default ResultsWrapper copy(ResultsWrapper source) { List mapListOfStringToListOfContentSummaryDTO(List source); + @SubclassMapping(source = IsaacEventPageDTO.class, target = IsaacEventPageDTO.class) + ContentDTO copy(ContentDTO source); + default ContentSummaryDTO mapStringToContentSummaryDTO(String source) { if (source == null) { return null; @@ -157,25 +148,4 @@ default String mapContentSummaryDTOtoString(ContentSummaryDTO source) { } return source.getId(); } - - // Needed to avoid abstract interface errors - default ContentBase map(ContentBaseDTO source) { - if (source == null) { - return null; - } else if (source instanceof ContentDTO) { - return mapContent((ContentDTO) source); - } else { - throw new UnimplementedMappingException(source.getClass(), ContentBase.class); - } - } - - default ContentBaseDTO map(ContentBase source) { - if (source == null) { - return null; - } else if (source instanceof Content) { - return mapContent((Content) source); - } else { - throw new UnimplementedMappingException(source.getClass(), ContentBaseDTO.class); - } - } } \ No newline at end of file diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java index 04d0c0d6ea..bec3986e89 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java @@ -1,9 +1,10 @@ package uk.ac.cam.cl.dtg.util.mappers; import org.mapstruct.Mapper; +import org.mapstruct.SubclassExhaustiveStrategy; import org.mapstruct.factory.Mappers; -@Mapper +@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) public interface MainMapper extends ContentMapper, UserMapper, EventMapper, GameboardMapper, QuestionValidationMapper { MainMapper INSTANCE = Mappers.getMapper(MainMapper.class); } \ No newline at end of file From 6738c4d8456b633909f2c483addc478b2f196ae3 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Fri, 26 Sep 2025 16:49:31 +0100 Subject: [PATCH 27/65] Rename GameboardMapper -> AssignmentMapper Since this actually handles quiz mappings too --- .../cl/dtg/isaac/dao/PgAssignmentPersistenceManager.java | 6 +++--- .../cl/dtg/isaac/dao/PgQuizAttemptPersistenceManager.java | 6 +++--- .../mappers/{GameboardMapper.java => AssignmentMapper.java} | 2 +- src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) rename src/main/java/uk/ac/cam/cl/dtg/util/mappers/{GameboardMapper.java => AssignmentMapper.java} (97%) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgAssignmentPersistenceManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgAssignmentPersistenceManager.java index f73d35e067..2a0e7d5cba 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgAssignmentPersistenceManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgAssignmentPersistenceManager.java @@ -23,7 +23,7 @@ import uk.ac.cam.cl.dtg.isaac.dto.AssignmentDTO; import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException; import uk.ac.cam.cl.dtg.segue.database.PostgresSqlDb; -import uk.ac.cam.cl.dtg.util.mappers.GameboardMapper; +import uk.ac.cam.cl.dtg.util.mappers.AssignmentMapper; import java.sql.Array; import java.sql.Connection; @@ -43,7 +43,7 @@ public class PgAssignmentPersistenceManager implements IAssignmentPersistenceManager { private static final Logger log = LoggerFactory.getLogger(PgAssignmentPersistenceManager.class); - private final GameboardMapper mapper; + private final AssignmentMapper mapper; private final PostgresSqlDb database; /** @@ -55,7 +55,7 @@ public class PgAssignmentPersistenceManager implements IAssignmentPersistenceMan * - An instance of an automapper that can be used for mapping to and from AssignmentDOs and DTOs. */ @Inject - public PgAssignmentPersistenceManager(final PostgresSqlDb database, final GameboardMapper mapper) { + public PgAssignmentPersistenceManager(final PostgresSqlDb database, final AssignmentMapper mapper) { this.database = database; this.mapper = mapper; } diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAttemptPersistenceManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAttemptPersistenceManager.java index ede6d855b0..3b6c1a08af 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAttemptPersistenceManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAttemptPersistenceManager.java @@ -25,7 +25,7 @@ import uk.ac.cam.cl.dtg.isaac.dto.QuizAttemptDTO; import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException; import uk.ac.cam.cl.dtg.segue.database.PostgresSqlDb; -import uk.ac.cam.cl.dtg.util.mappers.GameboardMapper; +import uk.ac.cam.cl.dtg.util.mappers.AssignmentMapper; import jakarta.annotation.Nullable; import java.sql.Array; @@ -46,7 +46,7 @@ public class PgQuizAttemptPersistenceManager implements IQuizAttemptPersistenceManager { private static final Logger log = LoggerFactory.getLogger(PgQuizAttemptPersistenceManager.class); - private final GameboardMapper mapper; + private final AssignmentMapper mapper; private final PostgresSqlDb database; /** @@ -58,7 +58,7 @@ public class PgQuizAttemptPersistenceManager implements IQuizAttemptPersistenceM * - An instance of an automapper that can be used for mapping to and from AssignmentDOs and DTOs. */ @Inject - public PgQuizAttemptPersistenceManager(final PostgresSqlDb database, final GameboardMapper mapper) { + public PgQuizAttemptPersistenceManager(final PostgresSqlDb database, final AssignmentMapper mapper) { this.database = database; this.mapper = mapper; } diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java similarity index 97% rename from src/main/java/uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java rename to src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java index 871b4b2bbc..71b4da5ef4 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/GameboardMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java @@ -13,7 +13,7 @@ import uk.ac.cam.cl.dtg.isaac.dto.QuizAttemptDTO; @Mapper -public interface GameboardMapper { +public interface AssignmentMapper { GameboardDTO map(GameboardDO source); GameboardDO map(GameboardDTO source); diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java index bec3986e89..7935b9308f 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java @@ -5,6 +5,6 @@ import org.mapstruct.factory.Mappers; @Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) -public interface MainMapper extends ContentMapper, UserMapper, EventMapper, GameboardMapper, QuestionValidationMapper { +public interface MainMapper extends ContentMapper, UserMapper, EventMapper, AssignmentMapper, QuestionValidationMapper { MainMapper INSTANCE = Mappers.getMapper(MainMapper.class); } \ No newline at end of file From 39c9b286da443aef18d07d3cdfad32c8837c35d3 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Fri, 26 Sep 2025 17:05:13 +0100 Subject: [PATCH 28/65] Rename fields for consistency, update javadocs --- .../SegueGuiceConfigurationModule.java | 13 ++++++++----- .../cl/dtg/segue/dao/content/GitContentManager.java | 12 ++++++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java index 5215a61439..38b900d1de 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java @@ -147,6 +147,7 @@ import uk.ac.cam.cl.dtg.util.locations.MaxMindIPLocationResolver; import uk.ac.cam.cl.dtg.util.locations.PostCodeIOLocationResolver; import uk.ac.cam.cl.dtg.util.locations.PostCodeLocationResolver; +import uk.ac.cam.cl.dtg.util.mappers.ContentMapper; import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import uk.ac.cam.cl.dtg.util.mappers.UserMapper; @@ -544,17 +545,19 @@ private static RestHighLevelClient getSearchConnectionInformation( * - database reference * @param searchProvider * - search provider to use - * @param contentMapperUtils - * - content mapper to use. + * @param contentMapper + * - defines the mappings for content objects + * @param mapperUtils + * - the utility class for mapping content objects * @return a fully configured content Manager. */ @Inject @Provides @Singleton - private static GitContentManager getContentManager(final GitDb database, final ISearchProvider searchProvider, final MainMapper mainMapper, - final ContentMapperUtils contentMapperUtils, final AbstractConfigLoader globalProperties) { + private static GitContentManager getContentManager(final GitDb database, final ISearchProvider searchProvider, final ContentMapper contentMapper, + final ContentMapperUtils mapperUtils, final AbstractConfigLoader globalProperties) { if (null == contentManager) { - contentManager = new GitContentManager(database, searchProvider, mainMapper, contentMapperUtils, globalProperties); + contentManager = new GitContentManager(database, searchProvider, contentMapper, mapperUtils, globalProperties); log.info("Creating singleton of ContentManager"); } diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java index 35c7e56580..987bbc78f0 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java @@ -106,15 +106,17 @@ public class GitContentManager { * @param searchProvider * - search provider that the content manager manages and controls. * @param contentMapper - * - The utility class for mapping content objects. + * - defines the mappings for content objects. + * @param mapperUtils + * - the utility class for mapping content objects. * @param globalProperties * - global properties. */ @Inject - public GitContentManager(final GitDb database, final ISearchProvider searchProvider, final ContentMapper mapper, + public GitContentManager(final GitDb database, final ISearchProvider searchProvider, final ContentMapper contentMapper, final ContentMapperUtils mapperUtils, final AbstractConfigLoader globalProperties) { this.database = database; - this.mapper = mapper; + this.mapper = contentMapper; this.mapperUtils = mapperUtils; this.searchProvider = searchProvider; this.globalProperties = globalProperties; @@ -149,7 +151,9 @@ public GitContentManager(final GitDb database, final ISearchProvider searchProvi * @param searchProvider * - search provider that the content manager manages and controls. * @param contentMapper - * - The utility class for mapping content objects. + * - defines the mappings for content objects. + * @param mapperUtils + * - the utility class for mapping content objects. */ public GitContentManager(final GitDb database, final ISearchProvider searchProvider, final ContentMapper contentMapper, ContentMapperUtils mapperUtils) { From c35b90d1820820839e3c4e0305aa0c72dbe78b9a Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Mon, 29 Sep 2025 15:30:14 +0100 Subject: [PATCH 29/65] Add remaining content subclass mappings --- .../cl/dtg/util/mappers/ContentMapper.java | 538 +++++++++++++++++- .../util/mappers/ContentMapperSubclasses.java | 480 ++++++++++++++++ 2 files changed, 991 insertions(+), 27 deletions(-) create mode 100644 src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperSubclasses.java diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index 36d34bc205..f0293bf871 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -1,8 +1,11 @@ package uk.ac.cam.cl.dtg.util.mappers; +import org.mapstruct.BeanMapping; +import org.mapstruct.InheritConfiguration; import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.Named; import org.mapstruct.SubclassExhaustiveStrategy; import org.mapstruct.SubclassMapping; import org.mapstruct.factory.Mappers; @@ -10,10 +13,14 @@ import uk.ac.cam.cl.dtg.isaac.dos.content.*; import uk.ac.cam.cl.dtg.isaac.dto.*; import uk.ac.cam.cl.dtg.isaac.dto.content.*; +import uk.ac.cam.cl.dtg.segue.dos.content.InteractiveCodeSnippet; +import uk.ac.cam.cl.dtg.segue.dto.content.InteractiveCodeSnippetDTO; +import uk.ac.cam.cl.dtg.util.locations.Address; +import uk.ac.cam.cl.dtg.util.locations.Location; import java.util.List; -@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) +@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION, uses = {ContentMapperSubclasses.class}) public interface ContentMapper { ContentMapper INSTANCE = Mappers.getMapper(ContentMapper.class); @@ -34,6 +41,8 @@ default T map(ContentDTO source, Class targetClass) { return (T) mapContentDTOtoDetailedQuizSummaryDTO(source); } else if (targetClass.equals(IsaacWildcard.class)) { return (T) map(mapContent(source), IsaacWildcard.class); + } else if (targetClass.equals(Content.class)) { + return (T) mapContent(source); } else { throw new UnimplementedMappingException(ContentDTO.class, targetClass); } @@ -49,37 +58,44 @@ default T map(Content source, Class targetClass) { } } + @Mapping(target = "searchableContent", ignore = true) + AnvilApp mapAnvilApp(AnvilAppDTO source); + + @Mapping(target = "searchableContent", ignore = true) + IsaacCard mapIsaacCard(IsaacCardDTO source); + @Mapping(target = "url", ignore = true) @Mapping(target = "supersededBy", ignore = true) @Mapping(target = "summary", ignore = true) @Mapping(target = "questionPartIds", ignore = true) @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + @SubclassMapping(source = InteractiveCodeSnippetDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = ParsonsItemDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = AnvilAppDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = ChoiceDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = CodeSnippetDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = CodeTabsDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = EmailTemplateDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = GlossaryTermDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = IsaacCardDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = IsaacCardDeckDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = IsaacEventPageDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = IsaacFeaturedProfileDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = IsaacPageFragmentDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = IsaacPodDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = IsaacWildcardDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = ItemDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = MediaDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = NotificationDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = QuestionDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = SeguePageDTO.class, target = ContentSummaryDTO.class) ContentSummaryDTO mapContentDTOtoContentSummaryDTO(ContentDTO source); - IsaacWildcard mapContentToIsaacWildcard(Content source); - - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "state", ignore = true) - @Mapping(target = "questionPartsTotal", ignore = true) - @Mapping(target = "questionPartsNotAttempted", ignore = true) - @Mapping(target = "questionPartsIncorrect", ignore = true) - @Mapping(target = "questionPartsCorrect", ignore = true) - @Mapping(target = "questionPartStates", ignore = true) - @Mapping(target = "passMark", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @Mapping(target = "description", ignore = true) - @Mapping(target = "creationContext", ignore = true) - @Mapping(target = "contentType", ignore = true) - @Mapping(target = "boardId", ignore = true) - GameboardItem mapContentDTOtoGameboardItem(ContentDTO source); - - QuizSummaryDTO mapContentDTOtoQuizSummaryDTO(ContentDTO source); - + @Named("toDetailedQuizSummaryDTO") @SubclassMapping(source = IsaacQuizDTO.class, target = DetailedQuizSummaryDTO.class) DetailedQuizSummaryDTO mapContentDTOtoDetailedQuizSummaryDTO(ContentDTO source); - DetailedQuizSummaryDTO map(IsaacQuizDTO source); - SidebarDTO map(String source); @Mapping(target = "searchableContent", ignore = true) @@ -121,17 +137,164 @@ default ResultsWrapper copy(ResultsWrapper source) { @SubclassMapping(source = IsaacQuizSection.class, target = IsaacQuizSectionDTO.class) @SubclassMapping(source = IsaacWildcard.class, target = IsaacWildcardDTO.class) @SubclassMapping(source = Item.class, target = ItemDTO.class) + @SubclassMapping(source = Media.class, target = MediaDTO.class) @SubclassMapping(source = Notification.class, target = NotificationDTO.class) @SubclassMapping(source = Question.class, target = QuestionDTO.class) - @SubclassMapping(source = IsaacQuiz.class, target = IsaacQuizDTO.class) + @SubclassMapping(source = SeguePage.class, target = SeguePageDTO.class) ContentDTO mapContent(Content source); - List mapListOfContentSummaryDtoToListOfString(List source); + List mapListOfIsaacCardDTOToListOfIsaacCard(List source); + List mapListOfIsaacCardToListOfIsaacCardDTO(List source); - List mapListOfStringToListOfContentSummaryDTO(List source); + IsaacQuizSectionDTO mapIsaacQuizSection(IsaacQuizSection source); - @SubclassMapping(source = IsaacEventPageDTO.class, target = IsaacEventPageDTO.class) - ContentDTO copy(ContentDTO source); + @Mapping(target = "requiresExactMatch", expression = "java(source.requiresExactMatch())") + @InheritConfiguration(name = "mapChoice") + Formula mapFormula(FormulaDTO source); + + @Mapping(target = "requiresExactMatch", expression = "java(source.requiresExactMatch())") + @InheritConfiguration(name = "mapChoice") + LogicFormula mapLogicFormula(LogicFormulaDTO source); + + @Mapping(target = "searchableContent", ignore = true) + @Mapping(target = "explanation", ignore = true) + @Mapping(target = "correct", ignore = true) + @SubclassMapping(source = ParsonsChoiceDTO.class, target = ParsonsChoice.class) + ItemChoice map(ItemChoiceDTO source); + + @SubclassMapping(source = ParsonsChoice.class, target = ParsonsChoiceDTO.class) + ItemChoiceDTO map(ItemChoice source); + + @Mapping(target = "searchableContent", ignore = true) + @SubclassMapping(source = InteractiveCodeSnippetDTO.class, target = InteractiveCodeSnippet.class) + CodeSnippet map(CodeSnippetDTO source); + + @SubclassMapping(source = InteractiveCodeSnippet.class, target = InteractiveCodeSnippetDTO.class) + CodeSnippetDTO map(CodeSnippet source); + + @Mapping(target = "searchableContent", ignore = true) + @SubclassMapping(source = ImageDTO.class, target = Image.class) + @SubclassMapping(source = VideoDTO.class, target = Video.class) + Media map(MediaDTO source); + + @SubclassMapping(source = Image.class, target = ImageDTO.class) + @SubclassMapping(source = Video.class, target = VideoDTO.class) + MediaDTO map(Media source); + + @Mapping(target = "searchableContent", ignore = true) + @SubclassMapping(source = FigureDTO.class, target = Figure.class) + Image map(ImageDTO source); + + @SubclassMapping(source = Figure.class, target = FigureDTO.class) + ImageDTO map(Image source); + + @Mapping(target = "searchableContent", ignore = true) + @SubclassMapping(source = ParsonsItemDTO.class, target = ParsonsItem.class) + Item map(ItemDTO source); + + @SubclassMapping(source = ParsonsItem.class, target = ParsonsItemDTO.class) + ItemDTO map(Item source); + + @Mapping(target = "searchableContent", ignore = true) + @InheritInverseConfiguration(name = "mapSeguePage") + SeguePage mapSeguePage(SeguePageDTO source); + + @Mapping(target = "userBookingStatus", ignore = true) + @Mapping(target = "placesAvailable", ignore = true) + @InheritConfiguration(name = "mapContent") + IsaacEventPageDTO map(IsaacEventPage source); + + @SubclassMapping(source = IsaacConceptPage.class, target = IsaacConceptPageDTO.class) + @SubclassMapping(source = IsaacQuestionPage.class, target = IsaacQuestionPageDTO.class) + @SubclassMapping(source = IsaacQuiz.class, target = IsaacQuizDTO.class) + @SubclassMapping(source = IsaacTopicSummaryPage.class, target = IsaacTopicSummaryPageDTO.class) + SeguePageDTO mapSeguePage(SeguePage source); + + @Mapping(target = "total", ignore = true) + @Mapping(target = "sectionTotals", ignore = true) + @Mapping(target = "individualFeedback", ignore = true) + @Mapping(target = "defaultFeedbackMode", ignore = true) + IsaacQuizDTO mapIsaacQuiz(IsaacQuiz source); + + @Mapping(target = "searchableContent", ignore = true) + IsaacQuestionPage map(IsaacQuestionPageDTO source); + + IsaacQuestionPageDTO map(IsaacQuestionPage source); + + @Mapping(target = "searchableContent", ignore = true) + @Mapping(target = "defaultFeedback", ignore = true) + @SubclassMapping(source = ChoiceQuestionDTO.class, target = ChoiceQuestion.class) + Question map(QuestionDTO source); + + @Mapping(target = "bestAttempt", ignore = true) + @SubclassMapping(source = ChoiceQuestion.class, target = ChoiceQuestionDTO.class) + QuestionDTO map(Question source); + + @Mapping(target = "searchableContent", ignore = true) + @Mapping(target = "defaultFeedback", ignore = true) + @SubclassMapping(source = IsaacQuestionBaseDTO.class, target = IsaacQuestionBase.class) + ChoiceQuestion map(ChoiceQuestionDTO source); + + @Mapping(target = "bestAttempt", ignore = true) + @SubclassMapping(source = IsaacQuestionBase.class, target = IsaacQuestionBaseDTO.class) + ChoiceQuestionDTO map(ChoiceQuestion source); + + @Mapping(target = "searchableContent", ignore = true) + @Mapping(target = "defaultFeedback", ignore = true) + @InheritInverseConfiguration(name = "mapIsaacQuestionBase") + IsaacQuestionBase mapIsaacQuestionBase(IsaacQuestionBaseDTO source); + + @Mapping(target = "significantFiguresMin", ignore = true) + @Mapping(target = "significantFiguresMax", ignore = true) + @Mapping(target = "searchableContent", ignore = true) + @Mapping(target = "defaultFeedback", ignore = true) + IsaacNumericQuestion mapIsaacNumericQuestion(IsaacNumericQuestionDTO source); + + @Mapping(target = "bestAttempt", ignore = true) + @SubclassMapping(source = IsaacAnvilQuestion.class, target = IsaacAnvilQuestionDTO.class) + @SubclassMapping(source = IsaacFreeTextQuestion.class, target = IsaacFreeTextQuestionDTO.class) + @SubclassMapping(source = IsaacItemQuestion.class, target = IsaacItemQuestionDTO.class) + @SubclassMapping(source = IsaacMultiChoiceQuestion.class, target = IsaacMultiChoiceQuestionDTO.class) + @SubclassMapping(source = IsaacNumericQuestion.class, target = IsaacNumericQuestionDTO.class) + @SubclassMapping(source = IsaacQuickQuestion.class, target = IsaacQuickQuestionDTO.class) + @SubclassMapping(source = IsaacRegexMatchQuestion.class, target = IsaacRegexMatchQuestionDTO.class) + @SubclassMapping(source = IsaacStringMatchQuestion.class, target = IsaacStringMatchQuestionDTO.class) + @SubclassMapping(source = IsaacSymbolicQuestion.class, target = IsaacSymbolicQuestionDTO.class) + IsaacQuestionBaseDTO mapIsaacQuestionBase(IsaacQuestionBase source); + + @Mapping(target = "searchableContent", ignore = true) + @Mapping(target = "defaultFeedback", ignore = true) + @InheritInverseConfiguration(name = "mapIsaacItemQuestion") + IsaacItemQuestion mapIsaacItemQuestion(IsaacItemQuestionDTO source); + + @Mapping(target = "searchableContent", ignore = true) + @Mapping(target = "detailedItemFeedback", ignore = true) + @Mapping(target = "defaultFeedback", ignore = true) + IsaacClozeQuestion mapIsaacClozeQuestion(IsaacClozeQuestionDTO source); + + @Mapping(target = "knownUnits", ignore = true) + @Mapping(target = "bestAttempt", ignore = true) + IsaacNumericQuestionDTO mapIsaacNumericQuestion(IsaacNumericQuestion source); + + @Mapping(target = "bestAttempt", ignore = true) + @SubclassMapping(source = IsaacClozeQuestion.class, target = IsaacClozeQuestionDTO.class) + @SubclassMapping(source = IsaacParsonsQuestion.class, target = IsaacParsonsQuestionDTO.class) + @SubclassMapping(source = IsaacReorderQuestion.class, target = IsaacReorderQuestionDTO.class) + IsaacItemQuestionDTO mapIsaacItemQuestion(IsaacItemQuestion source); + + @Mapping(target = "searchableContent", ignore = true) + @Mapping(target = "defaultFeedback", ignore = true) + @SubclassMapping(source = IsaacSymbolicLogicQuestionDTO.class, target = IsaacSymbolicLogicQuestion.class) + IsaacSymbolicQuestion mapIsaacSymbolicQuestion(IsaacSymbolicQuestionDTO source); + + @Mapping(target = "bestAttempt", ignore = true) + @SubclassMapping(source = IsaacSymbolicLogicQuestion.class, target = IsaacSymbolicLogicQuestionDTO.class) + IsaacSymbolicQuestionDTO mapIsaacSymbolicQuestion(IsaacSymbolicQuestion source); + + List mapListOfContentSummaryDtoToListOfString(List source); + List mapListOfStringToListOfContentSummaryDTO(List source); + List mapListOfGameboardDTOtoListOfString(List source); + List mapListOfStringToListOfGameboardDTO(List source); default ContentSummaryDTO mapStringToContentSummaryDTO(String source) { if (source == null) { @@ -148,4 +311,325 @@ default String mapContentSummaryDTOtoString(ContentSummaryDTO source) { } return source.getId(); } + + default GameboardDTO mapStringToGameboardDTO(String source) { + if (source == null) { + return null; + } + GameboardDTO gameboardDTO = new GameboardDTO(); + gameboardDTO.setId(source); + return gameboardDTO; + } + + default String mapGameboardDTOtoString(GameboardDTO source) { + if (source == null) { + return null; + } + return source.getId(); + } + + @SubclassMapping(source = ParsonsChoiceDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = FormulaDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = FreeTextRuleDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = ItemChoiceDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = LogicFormulaDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = QuantityDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = RegexPatternDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = StringChoiceDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = ChoiceDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacConceptPageDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacQuestionPageDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacQuizSectionDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacTopicSummaryPageDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacSymbolicLogicQuestionDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacClozeQuestionDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacParsonsQuestionDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacReorderQuestionDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacAnvilQuestionDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacFreeTextQuestionDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacItemQuestionDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacMultiChoiceQuestionDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacNumericQuestionDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacQuickQuestionDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacRegexMatchQuestionDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacStringMatchQuestionDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacSymbolicQuestionDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacQuestionBaseDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = ChoiceQuestionDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = QuestionDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = InteractiveCodeSnippetDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = FigureDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = ImageDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = VideoDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = ParsonsItemDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = AnvilAppDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = CodeSnippetDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = CodeTabsDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = EmailTemplateDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = GlossaryTermDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacCardDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacCardDeckDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacEventPageDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacFeaturedProfileDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacPageFragmentDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacPodDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = IsaacWildcardDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = ItemDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = MediaDTO.class, target = QuizSummaryDTO.class) + @SubclassMapping(source = NotificationDTO.class, target = QuizSummaryDTO.class) + QuizSummaryDTO mapContentDTOtoQuizSummaryDTO(ContentDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "description", ignore = true) + @SubclassMapping(source = ParsonsChoice.class, target = IsaacWildcard.class) + @SubclassMapping(source = Formula.class, target = IsaacWildcard.class) + @SubclassMapping(source = FreeTextRule.class, target = IsaacWildcard.class) + @SubclassMapping(source = ItemChoice.class, target = IsaacWildcard.class) + @SubclassMapping(source = LogicFormula.class, target = IsaacWildcard.class) + @SubclassMapping(source = Quantity.class, target = IsaacWildcard.class) + @SubclassMapping(source = RegexPattern.class, target = IsaacWildcard.class) + @SubclassMapping(source = StringChoice.class, target = IsaacWildcard.class) + @SubclassMapping(source = Choice.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacConceptPage.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacQuestionPage.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacQuiz.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacQuizSection.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacTopicSummaryPage.class, target = IsaacWildcard.class) + @SubclassMapping(source = SeguePage.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacSymbolicLogicQuestion.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacClozeQuestion.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacParsonsQuestion.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacReorderQuestion.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacAnvilQuestion.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacFreeTextQuestion.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacItemQuestion.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacMultiChoiceQuestion.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacNumericQuestion.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacQuickQuestion.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacRegexMatchQuestion.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacStringMatchQuestion.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacSymbolicQuestion.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacQuestionBase.class, target = IsaacWildcard.class) + @SubclassMapping(source = ChoiceQuestion.class, target = IsaacWildcard.class) + @SubclassMapping(source = Question.class, target = IsaacWildcard.class) + @SubclassMapping(source = InteractiveCodeSnippet.class, target = IsaacWildcard.class) + @SubclassMapping(source = Figure.class, target = IsaacWildcard.class) + @SubclassMapping(source = Image.class, target = IsaacWildcard.class) + @SubclassMapping(source = Video.class, target = IsaacWildcard.class) + @SubclassMapping(source = ParsonsItem.class, target = IsaacWildcard.class) + @SubclassMapping(source = AnvilApp.class, target = IsaacWildcard.class) + @SubclassMapping(source = CodeSnippet.class, target = IsaacWildcard.class) + @SubclassMapping(source = CodeTabs.class, target = IsaacWildcard.class) + @SubclassMapping(source = EmailTemplate.class, target = IsaacWildcard.class) + @SubclassMapping(source = GlossaryTerm.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacCard.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacCardDeck.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacEventPage.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacFeaturedProfile.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacPageFragment.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacPod.class, target = IsaacWildcard.class) + @SubclassMapping(source = IsaacWildcard.class, target = IsaacWildcard.class) + @SubclassMapping(source = Item.class, target = IsaacWildcard.class) + @SubclassMapping(source = Media.class, target = IsaacWildcard.class) + @SubclassMapping(source = Notification.class, target = IsaacWildcard.class) + IsaacWildcard mapContentToIsaacWildcard(Content source); + + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "state", ignore = true) + @Mapping(target = "questionPartsTotal", ignore = true) + @Mapping(target = "questionPartsNotAttempted", ignore = true) + @Mapping(target = "questionPartsIncorrect", ignore = true) + @Mapping(target = "questionPartsCorrect", ignore = true) + @Mapping(target = "questionPartStates", ignore = true) + @Mapping(target = "passMark", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @Mapping(target = "description", ignore = true) + @Mapping(target = "creationContext", ignore = true) + @Mapping(target = "contentType", ignore = true) + @Mapping(target = "boardId", ignore = true) + @SubclassMapping(source = ParsonsChoiceDTO.class, target = GameboardItem.class) + @SubclassMapping(source = FormulaDTO.class, target = GameboardItem.class) + @SubclassMapping(source = FreeTextRuleDTO.class, target = GameboardItem.class) + @SubclassMapping(source = ItemChoiceDTO.class, target = GameboardItem.class) + @SubclassMapping(source = LogicFormulaDTO.class, target = GameboardItem.class) + @SubclassMapping(source = QuantityDTO.class, target = GameboardItem.class) + @SubclassMapping(source = RegexPatternDTO.class, target = GameboardItem.class) + @SubclassMapping(source = StringChoiceDTO.class, target = GameboardItem.class) + @SubclassMapping(source = ChoiceDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacConceptPageDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacQuestionPageDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacQuizDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacQuizSectionDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacTopicSummaryPageDTO.class, target = GameboardItem.class) + @SubclassMapping(source = SeguePageDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacSymbolicLogicQuestionDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacClozeQuestionDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacParsonsQuestionDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacReorderQuestionDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacAnvilQuestionDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacFreeTextQuestionDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacItemQuestionDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacMultiChoiceQuestionDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacNumericQuestionDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacQuickQuestionDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacRegexMatchQuestionDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacStringMatchQuestionDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacSymbolicQuestionDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacQuestionBaseDTO.class, target = GameboardItem.class) + @SubclassMapping(source = ChoiceQuestionDTO.class, target = GameboardItem.class) + @SubclassMapping(source = QuestionDTO.class, target = GameboardItem.class) + @SubclassMapping(source = InteractiveCodeSnippetDTO.class, target = GameboardItem.class) + @SubclassMapping(source = FigureDTO.class, target = GameboardItem.class) + @SubclassMapping(source = ImageDTO.class, target = GameboardItem.class) + @SubclassMapping(source = VideoDTO.class, target = GameboardItem.class) + @SubclassMapping(source = ParsonsItemDTO.class, target = GameboardItem.class) + @SubclassMapping(source = AnvilAppDTO.class, target = GameboardItem.class) + @SubclassMapping(source = CodeSnippetDTO.class, target = GameboardItem.class) + @SubclassMapping(source = CodeTabsDTO.class, target = GameboardItem.class) + @SubclassMapping(source = EmailTemplateDTO.class, target = GameboardItem.class) + @SubclassMapping(source = GlossaryTermDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacCardDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacCardDeckDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacEventPageDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacFeaturedProfileDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacPageFragmentDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacPodDTO.class, target = GameboardItem.class) + @SubclassMapping(source = IsaacWildcardDTO.class, target = GameboardItem.class) + @SubclassMapping(source = ItemDTO.class, target = GameboardItem.class) + @SubclassMapping(source = MediaDTO.class, target = GameboardItem.class) + @SubclassMapping(source = NotificationDTO.class, target = GameboardItem.class) + GameboardItem mapContentDTOtoGameboardItem(ContentDTO source); + + @SubclassMapping(source = ContentDTO.class, target = ContentDTO.class) + ContentBaseDTO copy(ContentBaseDTO source); + + @SubclassMapping(source = ParsonsChoiceDTO.class, target = ParsonsChoiceDTO.class) + @SubclassMapping(source = FormulaDTO.class, target = FormulaDTO.class) + @SubclassMapping(source = FreeTextRuleDTO.class, target = FreeTextRuleDTO.class) + @SubclassMapping(source = ItemChoiceDTO.class, target = ItemChoiceDTO.class) + @SubclassMapping(source = LogicFormulaDTO.class, target = LogicFormulaDTO.class) + @SubclassMapping(source = QuantityDTO.class, target = QuantityDTO.class) + @SubclassMapping(source = RegexPatternDTO.class, target = RegexPatternDTO.class) + @SubclassMapping(source = StringChoiceDTO.class, target = StringChoiceDTO.class) + @SubclassMapping(source = ChoiceDTO.class, target = ChoiceDTO.class) + @SubclassMapping(source = IsaacConceptPageDTO.class, target = IsaacConceptPageDTO.class) + @SubclassMapping(source = IsaacQuestionPageDTO.class, target = IsaacQuestionPageDTO.class) + @SubclassMapping(source = IsaacQuizDTO.class, target = IsaacQuizDTO.class) + @SubclassMapping(source = IsaacQuizSectionDTO.class, target = IsaacQuizSectionDTO.class) + @SubclassMapping(source = IsaacTopicSummaryPageDTO.class, target = IsaacTopicSummaryPageDTO.class) + @SubclassMapping(source = IsaacSymbolicLogicQuestionDTO.class, target = IsaacSymbolicLogicQuestionDTO.class) + @SubclassMapping(source = IsaacClozeQuestionDTO.class, target = IsaacClozeQuestionDTO.class) + @SubclassMapping(source = IsaacParsonsQuestionDTO.class, target = IsaacParsonsQuestionDTO.class) + @SubclassMapping(source = IsaacReorderQuestionDTO.class, target = IsaacReorderQuestionDTO.class) + @SubclassMapping(source = IsaacAnvilQuestionDTO.class, target = IsaacAnvilQuestionDTO.class) + @SubclassMapping(source = IsaacFreeTextQuestionDTO.class, target = IsaacFreeTextQuestionDTO.class) + @SubclassMapping(source = IsaacItemQuestionDTO.class, target = IsaacItemQuestionDTO.class) + @SubclassMapping(source = IsaacMultiChoiceQuestionDTO.class, target = IsaacMultiChoiceQuestionDTO.class) + @SubclassMapping(source = IsaacNumericQuestionDTO.class, target = IsaacNumericQuestionDTO.class) + @SubclassMapping(source = IsaacQuickQuestionDTO.class, target = IsaacQuickQuestionDTO.class) + @SubclassMapping(source = IsaacRegexMatchQuestionDTO.class, target = IsaacRegexMatchQuestionDTO.class) + @SubclassMapping(source = IsaacStringMatchQuestionDTO.class, target = IsaacStringMatchQuestionDTO.class) + @SubclassMapping(source = IsaacSymbolicQuestionDTO.class, target = IsaacSymbolicQuestionDTO.class) + @SubclassMapping(source = IsaacQuestionBaseDTO.class, target = IsaacQuestionBaseDTO.class) + @SubclassMapping(source = ChoiceQuestionDTO.class, target = ChoiceQuestionDTO.class) + @SubclassMapping(source = QuestionDTO.class, target = QuestionDTO.class) + @SubclassMapping(source = InteractiveCodeSnippetDTO.class, target = InteractiveCodeSnippetDTO.class) + @SubclassMapping(source = FigureDTO.class, target = FigureDTO.class) + @SubclassMapping(source = ImageDTO.class, target = ImageDTO.class) + @SubclassMapping(source = VideoDTO.class, target = VideoDTO.class) + @SubclassMapping(source = ParsonsItemDTO.class, target = ParsonsItemDTO.class) + @SubclassMapping(source = AnvilAppDTO.class, target = AnvilAppDTO.class) + @SubclassMapping(source = CodeSnippetDTO.class, target = CodeSnippetDTO.class) + @SubclassMapping(source = CodeTabsDTO.class, target = CodeTabsDTO.class) + @SubclassMapping(source = EmailTemplateDTO.class, target = EmailTemplateDTO.class) + @SubclassMapping(source = GlossaryTermDTO.class, target = GlossaryTermDTO.class) + @SubclassMapping(source = IsaacCardDTO.class, target = IsaacCardDTO.class) + @SubclassMapping(source = IsaacCardDeckDTO.class, target = IsaacCardDeckDTO.class) + @SubclassMapping(source = IsaacEventPageDTO.class, target = IsaacEventPageDTO.class) + @SubclassMapping(source = IsaacFeaturedProfileDTO.class, target = IsaacFeaturedProfileDTO.class) + @SubclassMapping(source = IsaacPageFragmentDTO.class, target = IsaacPageFragmentDTO.class) + @SubclassMapping(source = IsaacPodDTO.class, target = IsaacPodDTO.class) + @SubclassMapping(source = IsaacWildcardDTO.class, target = IsaacWildcardDTO.class) + @SubclassMapping(source = ItemDTO.class, target = ItemDTO.class) + @SubclassMapping(source = MediaDTO.class, target = MediaDTO.class) + @SubclassMapping(source = NotificationDTO.class, target = NotificationDTO.class) + ContentDTO copy(ContentDTO source); + + @SubclassMapping(source = QuizSummaryDTO.class, target = QuizSummaryDTO.class) + ContentSummaryDTO copy(ContentSummaryDTO source); + + ParsonsChoiceDTO copy(ParsonsChoiceDTO source); + + @Mapping(target = "requiresExactMatch", expression = "java(source.requiresExactMatch())") + FormulaDTO copy(FormulaDTO source); + + FreeTextRuleDTO copy(FreeTextRuleDTO source); + ItemChoiceDTO copy(ItemChoiceDTO source); + + @Mapping(target = "requiresExactMatch", expression = "java(source.requiresExactMatch())") + LogicFormulaDTO copy(LogicFormulaDTO source); + + QuantityDTO copy(QuantityDTO source); + RegexPatternDTO copy(RegexPatternDTO source); + StringChoiceDTO copy(StringChoiceDTO source); + ChoiceDTO copy(ChoiceDTO source); + IsaacConceptPageDTO copy(IsaacConceptPageDTO source); + IsaacQuestionPageDTO copy(IsaacQuestionPageDTO source); + IsaacQuizDTO copy(IsaacQuizDTO source); + IsaacQuizSectionDTO copy(IsaacQuizSectionDTO source); + IsaacTopicSummaryPageDTO copy(IsaacTopicSummaryPageDTO source); + SeguePageDTO copy(SeguePageDTO source); + IsaacSymbolicLogicQuestionDTO copy(IsaacSymbolicLogicQuestionDTO source); + IsaacClozeQuestionDTO copy(IsaacClozeQuestionDTO source); + IsaacParsonsQuestionDTO copy(IsaacParsonsQuestionDTO source); + IsaacReorderQuestionDTO copy(IsaacReorderQuestionDTO source); + IsaacAnvilQuestionDTO copy(IsaacAnvilQuestionDTO source); + IsaacFreeTextQuestionDTO copy(IsaacFreeTextQuestionDTO source); + IsaacItemQuestionDTO copy(IsaacItemQuestionDTO source); + IsaacMultiChoiceQuestionDTO copy(IsaacMultiChoiceQuestionDTO source); + + @Mapping(target = "knownUnits", ignore = true) + IsaacNumericQuestionDTO copy(IsaacNumericQuestionDTO source); + + IsaacQuickQuestionDTO copy(IsaacQuickQuestionDTO source); + IsaacRegexMatchQuestionDTO copy(IsaacRegexMatchQuestionDTO source); + IsaacStringMatchQuestionDTO copy(IsaacStringMatchQuestionDTO source); + IsaacSymbolicQuestionDTO copy(IsaacSymbolicQuestionDTO source); + IsaacQuestionBaseDTO copy(IsaacQuestionBaseDTO source); + ChoiceQuestionDTO copy(ChoiceQuestionDTO source); + QuestionDTO copy(QuestionDTO source); + InteractiveCodeSnippetDTO copy(InteractiveCodeSnippetDTO source); + FigureDTO copy(FigureDTO source); + ImageDTO copy(ImageDTO source); + VideoDTO copy(VideoDTO source); + ParsonsItemDTO copy(ParsonsItemDTO source); + AnvilAppDTO copy(AnvilAppDTO source); + CodeSnippetDTO copy(CodeSnippetDTO source); + CodeTabsDTO copy(CodeTabsDTO source); + EmailTemplateDTO copy(EmailTemplateDTO source); + GlossaryTermDTO copy(GlossaryTermDTO source); + IsaacCardDTO copy(IsaacCardDTO source); + IsaacCardDeckDTO copy(IsaacCardDeckDTO source); + IsaacEventPageDTO copy(IsaacEventPageDTO source); + IsaacFeaturedProfileDTO copy(IsaacFeaturedProfileDTO source); + IsaacPageFragmentDTO copy(IsaacPageFragmentDTO source); + IsaacPodDTO copy(IsaacPodDTO source); + IsaacWildcardDTO copy(IsaacWildcardDTO source); + ItemDTO copy(ItemDTO source); + + @SubclassMapping(source = FigureDTO.class, target = FigureDTO.class) + @SubclassMapping(source = ImageDTO.class, target = ImageDTO.class) + @SubclassMapping(source = VideoDTO.class, target = VideoDTO.class) + MediaDTO copy(MediaDTO source); + + NotificationDTO copy(NotificationDTO source); + QuizSummaryDTO copy(QuizSummaryDTO source); + Location copy(Location source); + Address copy(Address source); + + List copyListOfContentBaseDTO(List source); + List copyListOfContentDTO(List source); + List copyListOfContentSummaryDTO(List source); + List copyListOfLocation(List source); + List
copyListOfAddress(List
source); } \ No newline at end of file diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperSubclasses.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperSubclasses.java new file mode 100644 index 0000000000..d45798a590 --- /dev/null +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperSubclasses.java @@ -0,0 +1,480 @@ +package uk.ac.cam.cl.dtg.util.mappers; + +import org.mapstruct.BeanMapping; +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassMapping; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacPod; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacWildcard; +import uk.ac.cam.cl.dtg.isaac.dos.content.CodeSnippet; +import uk.ac.cam.cl.dtg.isaac.dto.GameboardItem; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacAnvilQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacCardDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacCardDeckDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacClozeQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacConceptPageDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacEventPageDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacFeaturedProfileDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacFreeTextQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacItemQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacMultiChoiceQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacNumericQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacPageFragmentDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacParsonsQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacPodDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuestionBaseDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuestionPageDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuickQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuizDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuizSectionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacRegexMatchQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacReorderQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacStringMatchQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacSymbolicLogicQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacSymbolicQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacTopicSummaryPageDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacWildcardDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.AnvilAppDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.ChoiceDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.ChoiceQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.CodeSnippetDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.CodeTabsDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.ContentSummaryDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.EmailTemplateDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.FigureDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.FormulaDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.FreeTextRuleDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.GlossaryTermDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.ImageDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.ItemChoiceDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.ItemDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.LogicFormulaDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.MediaDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.NotificationDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.ParsonsChoiceDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.ParsonsItemDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.QuantityDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.QuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.RegexPatternDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.SeguePageDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.StringChoiceDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.VideoDTO; +import uk.ac.cam.cl.dtg.segue.dos.content.InteractiveCodeSnippet; +import uk.ac.cam.cl.dtg.segue.dto.content.InteractiveCodeSnippetDTO; + +@Mapper +public interface ContentMapperSubclasses { + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @SubclassMapping(source = FormulaDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = FreeTextRuleDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = ItemChoiceDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = LogicFormulaDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = QuantityDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = RegexPatternDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = StringChoiceDTO.class, target = ContentSummaryDTO.class) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(ChoiceDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(ParsonsChoiceDTO source); + + @InheritConfiguration + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(FormulaDTO source); + + @InheritConfiguration + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(FreeTextRuleDTO source); + + @InheritConfiguration + @SubclassMapping(source = ParsonsChoiceDTO.class, target = ContentSummaryDTO.class) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(ItemChoiceDTO source); + + @InheritConfiguration + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(LogicFormulaDTO source); + + @InheritConfiguration + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(QuantityDTO source); + + @InheritConfiguration + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(RegexPatternDTO source); + + @InheritConfiguration + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(StringChoiceDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @SubclassMapping(source = IsaacConceptPageDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = IsaacQuestionPageDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = IsaacQuizDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = IsaacTopicSummaryPageDTO.class, target = ContentSummaryDTO.class) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(SeguePageDTO source); + + @InheritConfiguration + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacConceptPageDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacQuestionPageDTO source); + + @InheritConfiguration + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacQuizDTO source); + + @InheritConfiguration + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacQuizSectionDTO source); + + @InheritConfiguration + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacTopicSummaryPageDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @SubclassMapping(source = ChoiceQuestionDTO.class, target = ContentSummaryDTO.class) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(QuestionDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacSymbolicLogicQuestionDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacClozeQuestionDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacParsonsQuestionDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacReorderQuestionDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacAnvilQuestionDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacFreeTextQuestionDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @SubclassMapping(source = IsaacClozeQuestionDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = IsaacParsonsQuestionDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = IsaacReorderQuestionDTO.class, target = ContentSummaryDTO.class) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacItemQuestionDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacMultiChoiceQuestionDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacNumericQuestionDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacQuickQuestionDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacRegexMatchQuestionDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacStringMatchQuestionDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @SubclassMapping(source = IsaacSymbolicLogicQuestionDTO.class, target = ContentSummaryDTO.class) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacSymbolicQuestionDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @SubclassMapping(source = IsaacAnvilQuestionDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = IsaacFreeTextQuestionDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = IsaacItemQuestionDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = IsaacMultiChoiceQuestionDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = IsaacNumericQuestionDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = IsaacQuickQuestionDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = IsaacRegexMatchQuestionDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = IsaacStringMatchQuestionDTO.class, target = ContentSummaryDTO.class) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacQuestionBaseDTO source); + + @InheritConfiguration + @SubclassMapping(source = IsaacQuestionBaseDTO.class, target = ContentSummaryDTO.class) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(ChoiceQuestionDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(FigureDTO source); + + @InheritConfiguration + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(ImageDTO source); + + @InheritConfiguration + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(VideoDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @SubclassMapping(source = FigureDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = ImageDTO.class, target = ContentSummaryDTO.class) + @SubclassMapping(source = VideoDTO.class, target = ContentSummaryDTO.class) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(MediaDTO source); + + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(InteractiveCodeSnippetDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(ParsonsItemDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(AnvilAppDTO source); + + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @SubclassMapping(source = InteractiveCodeSnippetDTO.class, target = ContentSummaryDTO.class) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(CodeSnippetDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(CodeTabsDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(EmailTemplateDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(GlossaryTermDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacCardDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacCardDeckDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacEventPageDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacFeaturedProfileDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacPageFragmentDTO source); + + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacPodDTO source); + + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(IsaacWildcardDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(ItemDTO source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @BeanMapping(resultType = ContentSummaryDTO.class) + ContentSummaryDTO mapToContentSummaryDTO(NotificationDTO source); + + @Mapping(target = "description", ignore = true) + @SubclassMapping(source = InteractiveCodeSnippet.class, target = IsaacWildcard.class) + IsaacWildcard mapToIsaacWildcard(CodeSnippet source); + + @Mapping(target = "description", ignore = true) + IsaacWildcard mapToIsaacWildcard(InteractiveCodeSnippet source); + + @Mapping(target = "description", ignore = true) + IsaacWildcard mapToIsaacWildcard(IsaacPod source); + + IsaacWildcard copy(IsaacWildcard source); + + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "state", ignore = true) + @Mapping(target = "questionPartsTotal", ignore = true) + @Mapping(target = "questionPartsNotAttempted", ignore = true) + @Mapping(target = "questionPartsIncorrect", ignore = true) + @Mapping(target = "questionPartsCorrect", ignore = true) + @Mapping(target = "questionPartStates", ignore = true) + @Mapping(target = "passMark", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @Mapping(target = "description", ignore = true) + @Mapping(target = "creationContext", ignore = true) + @Mapping(target = "contentType", ignore = true) + @Mapping(target = "boardId", ignore = true) + @SubclassMapping(source = IsaacQuestionPageDTO.class, target = GameboardItem.class) + GameboardItem mapToGameboardItem(SeguePageDTO source); + + @Mapping(target = "state", ignore = true) + @Mapping(target = "questionPartsTotal", ignore = true) + @Mapping(target = "questionPartsNotAttempted", ignore = true) + @Mapping(target = "questionPartsIncorrect", ignore = true) + @Mapping(target = "questionPartsCorrect", ignore = true) + @Mapping(target = "questionPartStates", ignore = true) + @Mapping(target = "description", ignore = true) + @Mapping(target = "creationContext", ignore = true) + @Mapping(target = "contentType", ignore = true) + @Mapping(target = "boardId", ignore = true) + GameboardItem mapToGameboardItem(IsaacQuestionPageDTO source); +} \ No newline at end of file From e81bbe294afdc77447da8b407e3da2bf61d28438 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Mon, 29 Sep 2025 16:29:47 +0100 Subject: [PATCH 30/65] Revert "Add remaining content subclass mappings" This reverts commit c35b90d1820820839e3c4e0305aa0c72dbe78b9a. --- .../cl/dtg/util/mappers/ContentMapper.java | 538 +----------------- .../util/mappers/ContentMapperSubclasses.java | 480 ---------------- 2 files changed, 27 insertions(+), 991 deletions(-) delete mode 100644 src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperSubclasses.java diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index f0293bf871..36d34bc205 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -1,11 +1,8 @@ package uk.ac.cam.cl.dtg.util.mappers; -import org.mapstruct.BeanMapping; -import org.mapstruct.InheritConfiguration; import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.Named; import org.mapstruct.SubclassExhaustiveStrategy; import org.mapstruct.SubclassMapping; import org.mapstruct.factory.Mappers; @@ -13,14 +10,10 @@ import uk.ac.cam.cl.dtg.isaac.dos.content.*; import uk.ac.cam.cl.dtg.isaac.dto.*; import uk.ac.cam.cl.dtg.isaac.dto.content.*; -import uk.ac.cam.cl.dtg.segue.dos.content.InteractiveCodeSnippet; -import uk.ac.cam.cl.dtg.segue.dto.content.InteractiveCodeSnippetDTO; -import uk.ac.cam.cl.dtg.util.locations.Address; -import uk.ac.cam.cl.dtg.util.locations.Location; import java.util.List; -@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION, uses = {ContentMapperSubclasses.class}) +@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) public interface ContentMapper { ContentMapper INSTANCE = Mappers.getMapper(ContentMapper.class); @@ -41,8 +34,6 @@ default T map(ContentDTO source, Class targetClass) { return (T) mapContentDTOtoDetailedQuizSummaryDTO(source); } else if (targetClass.equals(IsaacWildcard.class)) { return (T) map(mapContent(source), IsaacWildcard.class); - } else if (targetClass.equals(Content.class)) { - return (T) mapContent(source); } else { throw new UnimplementedMappingException(ContentDTO.class, targetClass); } @@ -58,44 +49,37 @@ default T map(Content source, Class targetClass) { } } - @Mapping(target = "searchableContent", ignore = true) - AnvilApp mapAnvilApp(AnvilAppDTO source); - - @Mapping(target = "searchableContent", ignore = true) - IsaacCard mapIsaacCard(IsaacCardDTO source); - @Mapping(target = "url", ignore = true) @Mapping(target = "supersededBy", ignore = true) @Mapping(target = "summary", ignore = true) @Mapping(target = "questionPartIds", ignore = true) @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - @SubclassMapping(source = InteractiveCodeSnippetDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = ParsonsItemDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = AnvilAppDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = ChoiceDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = CodeSnippetDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = CodeTabsDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = EmailTemplateDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = GlossaryTermDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = IsaacCardDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = IsaacCardDeckDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = IsaacEventPageDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = IsaacFeaturedProfileDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = IsaacPageFragmentDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = IsaacPodDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = IsaacWildcardDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = ItemDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = MediaDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = NotificationDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = QuestionDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = SeguePageDTO.class, target = ContentSummaryDTO.class) ContentSummaryDTO mapContentDTOtoContentSummaryDTO(ContentDTO source); - @Named("toDetailedQuizSummaryDTO") + IsaacWildcard mapContentToIsaacWildcard(Content source); + + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "state", ignore = true) + @Mapping(target = "questionPartsTotal", ignore = true) + @Mapping(target = "questionPartsNotAttempted", ignore = true) + @Mapping(target = "questionPartsIncorrect", ignore = true) + @Mapping(target = "questionPartsCorrect", ignore = true) + @Mapping(target = "questionPartStates", ignore = true) + @Mapping(target = "passMark", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @Mapping(target = "description", ignore = true) + @Mapping(target = "creationContext", ignore = true) + @Mapping(target = "contentType", ignore = true) + @Mapping(target = "boardId", ignore = true) + GameboardItem mapContentDTOtoGameboardItem(ContentDTO source); + + QuizSummaryDTO mapContentDTOtoQuizSummaryDTO(ContentDTO source); + @SubclassMapping(source = IsaacQuizDTO.class, target = DetailedQuizSummaryDTO.class) DetailedQuizSummaryDTO mapContentDTOtoDetailedQuizSummaryDTO(ContentDTO source); + DetailedQuizSummaryDTO map(IsaacQuizDTO source); + SidebarDTO map(String source); @Mapping(target = "searchableContent", ignore = true) @@ -137,164 +121,17 @@ default ResultsWrapper copy(ResultsWrapper source) { @SubclassMapping(source = IsaacQuizSection.class, target = IsaacQuizSectionDTO.class) @SubclassMapping(source = IsaacWildcard.class, target = IsaacWildcardDTO.class) @SubclassMapping(source = Item.class, target = ItemDTO.class) - @SubclassMapping(source = Media.class, target = MediaDTO.class) @SubclassMapping(source = Notification.class, target = NotificationDTO.class) @SubclassMapping(source = Question.class, target = QuestionDTO.class) - @SubclassMapping(source = SeguePage.class, target = SeguePageDTO.class) - ContentDTO mapContent(Content source); - - List mapListOfIsaacCardDTOToListOfIsaacCard(List source); - List mapListOfIsaacCardToListOfIsaacCardDTO(List source); - - IsaacQuizSectionDTO mapIsaacQuizSection(IsaacQuizSection source); - - @Mapping(target = "requiresExactMatch", expression = "java(source.requiresExactMatch())") - @InheritConfiguration(name = "mapChoice") - Formula mapFormula(FormulaDTO source); - - @Mapping(target = "requiresExactMatch", expression = "java(source.requiresExactMatch())") - @InheritConfiguration(name = "mapChoice") - LogicFormula mapLogicFormula(LogicFormulaDTO source); - - @Mapping(target = "searchableContent", ignore = true) - @Mapping(target = "explanation", ignore = true) - @Mapping(target = "correct", ignore = true) - @SubclassMapping(source = ParsonsChoiceDTO.class, target = ParsonsChoice.class) - ItemChoice map(ItemChoiceDTO source); - - @SubclassMapping(source = ParsonsChoice.class, target = ParsonsChoiceDTO.class) - ItemChoiceDTO map(ItemChoice source); - - @Mapping(target = "searchableContent", ignore = true) - @SubclassMapping(source = InteractiveCodeSnippetDTO.class, target = InteractiveCodeSnippet.class) - CodeSnippet map(CodeSnippetDTO source); - - @SubclassMapping(source = InteractiveCodeSnippet.class, target = InteractiveCodeSnippetDTO.class) - CodeSnippetDTO map(CodeSnippet source); - - @Mapping(target = "searchableContent", ignore = true) - @SubclassMapping(source = ImageDTO.class, target = Image.class) - @SubclassMapping(source = VideoDTO.class, target = Video.class) - Media map(MediaDTO source); - - @SubclassMapping(source = Image.class, target = ImageDTO.class) - @SubclassMapping(source = Video.class, target = VideoDTO.class) - MediaDTO map(Media source); - - @Mapping(target = "searchableContent", ignore = true) - @SubclassMapping(source = FigureDTO.class, target = Figure.class) - Image map(ImageDTO source); - - @SubclassMapping(source = Figure.class, target = FigureDTO.class) - ImageDTO map(Image source); - - @Mapping(target = "searchableContent", ignore = true) - @SubclassMapping(source = ParsonsItemDTO.class, target = ParsonsItem.class) - Item map(ItemDTO source); - - @SubclassMapping(source = ParsonsItem.class, target = ParsonsItemDTO.class) - ItemDTO map(Item source); - - @Mapping(target = "searchableContent", ignore = true) - @InheritInverseConfiguration(name = "mapSeguePage") - SeguePage mapSeguePage(SeguePageDTO source); - - @Mapping(target = "userBookingStatus", ignore = true) - @Mapping(target = "placesAvailable", ignore = true) - @InheritConfiguration(name = "mapContent") - IsaacEventPageDTO map(IsaacEventPage source); - - @SubclassMapping(source = IsaacConceptPage.class, target = IsaacConceptPageDTO.class) - @SubclassMapping(source = IsaacQuestionPage.class, target = IsaacQuestionPageDTO.class) @SubclassMapping(source = IsaacQuiz.class, target = IsaacQuizDTO.class) - @SubclassMapping(source = IsaacTopicSummaryPage.class, target = IsaacTopicSummaryPageDTO.class) - SeguePageDTO mapSeguePage(SeguePage source); - - @Mapping(target = "total", ignore = true) - @Mapping(target = "sectionTotals", ignore = true) - @Mapping(target = "individualFeedback", ignore = true) - @Mapping(target = "defaultFeedbackMode", ignore = true) - IsaacQuizDTO mapIsaacQuiz(IsaacQuiz source); - - @Mapping(target = "searchableContent", ignore = true) - IsaacQuestionPage map(IsaacQuestionPageDTO source); - - IsaacQuestionPageDTO map(IsaacQuestionPage source); - - @Mapping(target = "searchableContent", ignore = true) - @Mapping(target = "defaultFeedback", ignore = true) - @SubclassMapping(source = ChoiceQuestionDTO.class, target = ChoiceQuestion.class) - Question map(QuestionDTO source); - - @Mapping(target = "bestAttempt", ignore = true) - @SubclassMapping(source = ChoiceQuestion.class, target = ChoiceQuestionDTO.class) - QuestionDTO map(Question source); - - @Mapping(target = "searchableContent", ignore = true) - @Mapping(target = "defaultFeedback", ignore = true) - @SubclassMapping(source = IsaacQuestionBaseDTO.class, target = IsaacQuestionBase.class) - ChoiceQuestion map(ChoiceQuestionDTO source); - - @Mapping(target = "bestAttempt", ignore = true) - @SubclassMapping(source = IsaacQuestionBase.class, target = IsaacQuestionBaseDTO.class) - ChoiceQuestionDTO map(ChoiceQuestion source); - - @Mapping(target = "searchableContent", ignore = true) - @Mapping(target = "defaultFeedback", ignore = true) - @InheritInverseConfiguration(name = "mapIsaacQuestionBase") - IsaacQuestionBase mapIsaacQuestionBase(IsaacQuestionBaseDTO source); - - @Mapping(target = "significantFiguresMin", ignore = true) - @Mapping(target = "significantFiguresMax", ignore = true) - @Mapping(target = "searchableContent", ignore = true) - @Mapping(target = "defaultFeedback", ignore = true) - IsaacNumericQuestion mapIsaacNumericQuestion(IsaacNumericQuestionDTO source); - - @Mapping(target = "bestAttempt", ignore = true) - @SubclassMapping(source = IsaacAnvilQuestion.class, target = IsaacAnvilQuestionDTO.class) - @SubclassMapping(source = IsaacFreeTextQuestion.class, target = IsaacFreeTextQuestionDTO.class) - @SubclassMapping(source = IsaacItemQuestion.class, target = IsaacItemQuestionDTO.class) - @SubclassMapping(source = IsaacMultiChoiceQuestion.class, target = IsaacMultiChoiceQuestionDTO.class) - @SubclassMapping(source = IsaacNumericQuestion.class, target = IsaacNumericQuestionDTO.class) - @SubclassMapping(source = IsaacQuickQuestion.class, target = IsaacQuickQuestionDTO.class) - @SubclassMapping(source = IsaacRegexMatchQuestion.class, target = IsaacRegexMatchQuestionDTO.class) - @SubclassMapping(source = IsaacStringMatchQuestion.class, target = IsaacStringMatchQuestionDTO.class) - @SubclassMapping(source = IsaacSymbolicQuestion.class, target = IsaacSymbolicQuestionDTO.class) - IsaacQuestionBaseDTO mapIsaacQuestionBase(IsaacQuestionBase source); - - @Mapping(target = "searchableContent", ignore = true) - @Mapping(target = "defaultFeedback", ignore = true) - @InheritInverseConfiguration(name = "mapIsaacItemQuestion") - IsaacItemQuestion mapIsaacItemQuestion(IsaacItemQuestionDTO source); - - @Mapping(target = "searchableContent", ignore = true) - @Mapping(target = "detailedItemFeedback", ignore = true) - @Mapping(target = "defaultFeedback", ignore = true) - IsaacClozeQuestion mapIsaacClozeQuestion(IsaacClozeQuestionDTO source); - - @Mapping(target = "knownUnits", ignore = true) - @Mapping(target = "bestAttempt", ignore = true) - IsaacNumericQuestionDTO mapIsaacNumericQuestion(IsaacNumericQuestion source); - - @Mapping(target = "bestAttempt", ignore = true) - @SubclassMapping(source = IsaacClozeQuestion.class, target = IsaacClozeQuestionDTO.class) - @SubclassMapping(source = IsaacParsonsQuestion.class, target = IsaacParsonsQuestionDTO.class) - @SubclassMapping(source = IsaacReorderQuestion.class, target = IsaacReorderQuestionDTO.class) - IsaacItemQuestionDTO mapIsaacItemQuestion(IsaacItemQuestion source); - - @Mapping(target = "searchableContent", ignore = true) - @Mapping(target = "defaultFeedback", ignore = true) - @SubclassMapping(source = IsaacSymbolicLogicQuestionDTO.class, target = IsaacSymbolicLogicQuestion.class) - IsaacSymbolicQuestion mapIsaacSymbolicQuestion(IsaacSymbolicQuestionDTO source); - - @Mapping(target = "bestAttempt", ignore = true) - @SubclassMapping(source = IsaacSymbolicLogicQuestion.class, target = IsaacSymbolicLogicQuestionDTO.class) - IsaacSymbolicQuestionDTO mapIsaacSymbolicQuestion(IsaacSymbolicQuestion source); + ContentDTO mapContent(Content source); List mapListOfContentSummaryDtoToListOfString(List source); + List mapListOfStringToListOfContentSummaryDTO(List source); - List mapListOfGameboardDTOtoListOfString(List source); - List mapListOfStringToListOfGameboardDTO(List source); + + @SubclassMapping(source = IsaacEventPageDTO.class, target = IsaacEventPageDTO.class) + ContentDTO copy(ContentDTO source); default ContentSummaryDTO mapStringToContentSummaryDTO(String source) { if (source == null) { @@ -311,325 +148,4 @@ default String mapContentSummaryDTOtoString(ContentSummaryDTO source) { } return source.getId(); } - - default GameboardDTO mapStringToGameboardDTO(String source) { - if (source == null) { - return null; - } - GameboardDTO gameboardDTO = new GameboardDTO(); - gameboardDTO.setId(source); - return gameboardDTO; - } - - default String mapGameboardDTOtoString(GameboardDTO source) { - if (source == null) { - return null; - } - return source.getId(); - } - - @SubclassMapping(source = ParsonsChoiceDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = FormulaDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = FreeTextRuleDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = ItemChoiceDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = LogicFormulaDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = QuantityDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = RegexPatternDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = StringChoiceDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = ChoiceDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacConceptPageDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacQuestionPageDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacQuizSectionDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacTopicSummaryPageDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacSymbolicLogicQuestionDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacClozeQuestionDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacParsonsQuestionDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacReorderQuestionDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacAnvilQuestionDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacFreeTextQuestionDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacItemQuestionDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacMultiChoiceQuestionDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacNumericQuestionDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacQuickQuestionDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacRegexMatchQuestionDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacStringMatchQuestionDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacSymbolicQuestionDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacQuestionBaseDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = ChoiceQuestionDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = QuestionDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = InteractiveCodeSnippetDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = FigureDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = ImageDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = VideoDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = ParsonsItemDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = AnvilAppDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = CodeSnippetDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = CodeTabsDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = EmailTemplateDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = GlossaryTermDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacCardDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacCardDeckDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacEventPageDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacFeaturedProfileDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacPageFragmentDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacPodDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = IsaacWildcardDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = ItemDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = MediaDTO.class, target = QuizSummaryDTO.class) - @SubclassMapping(source = NotificationDTO.class, target = QuizSummaryDTO.class) - QuizSummaryDTO mapContentDTOtoQuizSummaryDTO(ContentDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "description", ignore = true) - @SubclassMapping(source = ParsonsChoice.class, target = IsaacWildcard.class) - @SubclassMapping(source = Formula.class, target = IsaacWildcard.class) - @SubclassMapping(source = FreeTextRule.class, target = IsaacWildcard.class) - @SubclassMapping(source = ItemChoice.class, target = IsaacWildcard.class) - @SubclassMapping(source = LogicFormula.class, target = IsaacWildcard.class) - @SubclassMapping(source = Quantity.class, target = IsaacWildcard.class) - @SubclassMapping(source = RegexPattern.class, target = IsaacWildcard.class) - @SubclassMapping(source = StringChoice.class, target = IsaacWildcard.class) - @SubclassMapping(source = Choice.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacConceptPage.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacQuestionPage.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacQuiz.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacQuizSection.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacTopicSummaryPage.class, target = IsaacWildcard.class) - @SubclassMapping(source = SeguePage.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacSymbolicLogicQuestion.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacClozeQuestion.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacParsonsQuestion.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacReorderQuestion.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacAnvilQuestion.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacFreeTextQuestion.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacItemQuestion.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacMultiChoiceQuestion.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacNumericQuestion.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacQuickQuestion.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacRegexMatchQuestion.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacStringMatchQuestion.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacSymbolicQuestion.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacQuestionBase.class, target = IsaacWildcard.class) - @SubclassMapping(source = ChoiceQuestion.class, target = IsaacWildcard.class) - @SubclassMapping(source = Question.class, target = IsaacWildcard.class) - @SubclassMapping(source = InteractiveCodeSnippet.class, target = IsaacWildcard.class) - @SubclassMapping(source = Figure.class, target = IsaacWildcard.class) - @SubclassMapping(source = Image.class, target = IsaacWildcard.class) - @SubclassMapping(source = Video.class, target = IsaacWildcard.class) - @SubclassMapping(source = ParsonsItem.class, target = IsaacWildcard.class) - @SubclassMapping(source = AnvilApp.class, target = IsaacWildcard.class) - @SubclassMapping(source = CodeSnippet.class, target = IsaacWildcard.class) - @SubclassMapping(source = CodeTabs.class, target = IsaacWildcard.class) - @SubclassMapping(source = EmailTemplate.class, target = IsaacWildcard.class) - @SubclassMapping(source = GlossaryTerm.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacCard.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacCardDeck.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacEventPage.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacFeaturedProfile.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacPageFragment.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacPod.class, target = IsaacWildcard.class) - @SubclassMapping(source = IsaacWildcard.class, target = IsaacWildcard.class) - @SubclassMapping(source = Item.class, target = IsaacWildcard.class) - @SubclassMapping(source = Media.class, target = IsaacWildcard.class) - @SubclassMapping(source = Notification.class, target = IsaacWildcard.class) - IsaacWildcard mapContentToIsaacWildcard(Content source); - - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "state", ignore = true) - @Mapping(target = "questionPartsTotal", ignore = true) - @Mapping(target = "questionPartsNotAttempted", ignore = true) - @Mapping(target = "questionPartsIncorrect", ignore = true) - @Mapping(target = "questionPartsCorrect", ignore = true) - @Mapping(target = "questionPartStates", ignore = true) - @Mapping(target = "passMark", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @Mapping(target = "description", ignore = true) - @Mapping(target = "creationContext", ignore = true) - @Mapping(target = "contentType", ignore = true) - @Mapping(target = "boardId", ignore = true) - @SubclassMapping(source = ParsonsChoiceDTO.class, target = GameboardItem.class) - @SubclassMapping(source = FormulaDTO.class, target = GameboardItem.class) - @SubclassMapping(source = FreeTextRuleDTO.class, target = GameboardItem.class) - @SubclassMapping(source = ItemChoiceDTO.class, target = GameboardItem.class) - @SubclassMapping(source = LogicFormulaDTO.class, target = GameboardItem.class) - @SubclassMapping(source = QuantityDTO.class, target = GameboardItem.class) - @SubclassMapping(source = RegexPatternDTO.class, target = GameboardItem.class) - @SubclassMapping(source = StringChoiceDTO.class, target = GameboardItem.class) - @SubclassMapping(source = ChoiceDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacConceptPageDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacQuestionPageDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacQuizDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacQuizSectionDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacTopicSummaryPageDTO.class, target = GameboardItem.class) - @SubclassMapping(source = SeguePageDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacSymbolicLogicQuestionDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacClozeQuestionDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacParsonsQuestionDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacReorderQuestionDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacAnvilQuestionDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacFreeTextQuestionDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacItemQuestionDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacMultiChoiceQuestionDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacNumericQuestionDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacQuickQuestionDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacRegexMatchQuestionDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacStringMatchQuestionDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacSymbolicQuestionDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacQuestionBaseDTO.class, target = GameboardItem.class) - @SubclassMapping(source = ChoiceQuestionDTO.class, target = GameboardItem.class) - @SubclassMapping(source = QuestionDTO.class, target = GameboardItem.class) - @SubclassMapping(source = InteractiveCodeSnippetDTO.class, target = GameboardItem.class) - @SubclassMapping(source = FigureDTO.class, target = GameboardItem.class) - @SubclassMapping(source = ImageDTO.class, target = GameboardItem.class) - @SubclassMapping(source = VideoDTO.class, target = GameboardItem.class) - @SubclassMapping(source = ParsonsItemDTO.class, target = GameboardItem.class) - @SubclassMapping(source = AnvilAppDTO.class, target = GameboardItem.class) - @SubclassMapping(source = CodeSnippetDTO.class, target = GameboardItem.class) - @SubclassMapping(source = CodeTabsDTO.class, target = GameboardItem.class) - @SubclassMapping(source = EmailTemplateDTO.class, target = GameboardItem.class) - @SubclassMapping(source = GlossaryTermDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacCardDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacCardDeckDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacEventPageDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacFeaturedProfileDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacPageFragmentDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacPodDTO.class, target = GameboardItem.class) - @SubclassMapping(source = IsaacWildcardDTO.class, target = GameboardItem.class) - @SubclassMapping(source = ItemDTO.class, target = GameboardItem.class) - @SubclassMapping(source = MediaDTO.class, target = GameboardItem.class) - @SubclassMapping(source = NotificationDTO.class, target = GameboardItem.class) - GameboardItem mapContentDTOtoGameboardItem(ContentDTO source); - - @SubclassMapping(source = ContentDTO.class, target = ContentDTO.class) - ContentBaseDTO copy(ContentBaseDTO source); - - @SubclassMapping(source = ParsonsChoiceDTO.class, target = ParsonsChoiceDTO.class) - @SubclassMapping(source = FormulaDTO.class, target = FormulaDTO.class) - @SubclassMapping(source = FreeTextRuleDTO.class, target = FreeTextRuleDTO.class) - @SubclassMapping(source = ItemChoiceDTO.class, target = ItemChoiceDTO.class) - @SubclassMapping(source = LogicFormulaDTO.class, target = LogicFormulaDTO.class) - @SubclassMapping(source = QuantityDTO.class, target = QuantityDTO.class) - @SubclassMapping(source = RegexPatternDTO.class, target = RegexPatternDTO.class) - @SubclassMapping(source = StringChoiceDTO.class, target = StringChoiceDTO.class) - @SubclassMapping(source = ChoiceDTO.class, target = ChoiceDTO.class) - @SubclassMapping(source = IsaacConceptPageDTO.class, target = IsaacConceptPageDTO.class) - @SubclassMapping(source = IsaacQuestionPageDTO.class, target = IsaacQuestionPageDTO.class) - @SubclassMapping(source = IsaacQuizDTO.class, target = IsaacQuizDTO.class) - @SubclassMapping(source = IsaacQuizSectionDTO.class, target = IsaacQuizSectionDTO.class) - @SubclassMapping(source = IsaacTopicSummaryPageDTO.class, target = IsaacTopicSummaryPageDTO.class) - @SubclassMapping(source = IsaacSymbolicLogicQuestionDTO.class, target = IsaacSymbolicLogicQuestionDTO.class) - @SubclassMapping(source = IsaacClozeQuestionDTO.class, target = IsaacClozeQuestionDTO.class) - @SubclassMapping(source = IsaacParsonsQuestionDTO.class, target = IsaacParsonsQuestionDTO.class) - @SubclassMapping(source = IsaacReorderQuestionDTO.class, target = IsaacReorderQuestionDTO.class) - @SubclassMapping(source = IsaacAnvilQuestionDTO.class, target = IsaacAnvilQuestionDTO.class) - @SubclassMapping(source = IsaacFreeTextQuestionDTO.class, target = IsaacFreeTextQuestionDTO.class) - @SubclassMapping(source = IsaacItemQuestionDTO.class, target = IsaacItemQuestionDTO.class) - @SubclassMapping(source = IsaacMultiChoiceQuestionDTO.class, target = IsaacMultiChoiceQuestionDTO.class) - @SubclassMapping(source = IsaacNumericQuestionDTO.class, target = IsaacNumericQuestionDTO.class) - @SubclassMapping(source = IsaacQuickQuestionDTO.class, target = IsaacQuickQuestionDTO.class) - @SubclassMapping(source = IsaacRegexMatchQuestionDTO.class, target = IsaacRegexMatchQuestionDTO.class) - @SubclassMapping(source = IsaacStringMatchQuestionDTO.class, target = IsaacStringMatchQuestionDTO.class) - @SubclassMapping(source = IsaacSymbolicQuestionDTO.class, target = IsaacSymbolicQuestionDTO.class) - @SubclassMapping(source = IsaacQuestionBaseDTO.class, target = IsaacQuestionBaseDTO.class) - @SubclassMapping(source = ChoiceQuestionDTO.class, target = ChoiceQuestionDTO.class) - @SubclassMapping(source = QuestionDTO.class, target = QuestionDTO.class) - @SubclassMapping(source = InteractiveCodeSnippetDTO.class, target = InteractiveCodeSnippetDTO.class) - @SubclassMapping(source = FigureDTO.class, target = FigureDTO.class) - @SubclassMapping(source = ImageDTO.class, target = ImageDTO.class) - @SubclassMapping(source = VideoDTO.class, target = VideoDTO.class) - @SubclassMapping(source = ParsonsItemDTO.class, target = ParsonsItemDTO.class) - @SubclassMapping(source = AnvilAppDTO.class, target = AnvilAppDTO.class) - @SubclassMapping(source = CodeSnippetDTO.class, target = CodeSnippetDTO.class) - @SubclassMapping(source = CodeTabsDTO.class, target = CodeTabsDTO.class) - @SubclassMapping(source = EmailTemplateDTO.class, target = EmailTemplateDTO.class) - @SubclassMapping(source = GlossaryTermDTO.class, target = GlossaryTermDTO.class) - @SubclassMapping(source = IsaacCardDTO.class, target = IsaacCardDTO.class) - @SubclassMapping(source = IsaacCardDeckDTO.class, target = IsaacCardDeckDTO.class) - @SubclassMapping(source = IsaacEventPageDTO.class, target = IsaacEventPageDTO.class) - @SubclassMapping(source = IsaacFeaturedProfileDTO.class, target = IsaacFeaturedProfileDTO.class) - @SubclassMapping(source = IsaacPageFragmentDTO.class, target = IsaacPageFragmentDTO.class) - @SubclassMapping(source = IsaacPodDTO.class, target = IsaacPodDTO.class) - @SubclassMapping(source = IsaacWildcardDTO.class, target = IsaacWildcardDTO.class) - @SubclassMapping(source = ItemDTO.class, target = ItemDTO.class) - @SubclassMapping(source = MediaDTO.class, target = MediaDTO.class) - @SubclassMapping(source = NotificationDTO.class, target = NotificationDTO.class) - ContentDTO copy(ContentDTO source); - - @SubclassMapping(source = QuizSummaryDTO.class, target = QuizSummaryDTO.class) - ContentSummaryDTO copy(ContentSummaryDTO source); - - ParsonsChoiceDTO copy(ParsonsChoiceDTO source); - - @Mapping(target = "requiresExactMatch", expression = "java(source.requiresExactMatch())") - FormulaDTO copy(FormulaDTO source); - - FreeTextRuleDTO copy(FreeTextRuleDTO source); - ItemChoiceDTO copy(ItemChoiceDTO source); - - @Mapping(target = "requiresExactMatch", expression = "java(source.requiresExactMatch())") - LogicFormulaDTO copy(LogicFormulaDTO source); - - QuantityDTO copy(QuantityDTO source); - RegexPatternDTO copy(RegexPatternDTO source); - StringChoiceDTO copy(StringChoiceDTO source); - ChoiceDTO copy(ChoiceDTO source); - IsaacConceptPageDTO copy(IsaacConceptPageDTO source); - IsaacQuestionPageDTO copy(IsaacQuestionPageDTO source); - IsaacQuizDTO copy(IsaacQuizDTO source); - IsaacQuizSectionDTO copy(IsaacQuizSectionDTO source); - IsaacTopicSummaryPageDTO copy(IsaacTopicSummaryPageDTO source); - SeguePageDTO copy(SeguePageDTO source); - IsaacSymbolicLogicQuestionDTO copy(IsaacSymbolicLogicQuestionDTO source); - IsaacClozeQuestionDTO copy(IsaacClozeQuestionDTO source); - IsaacParsonsQuestionDTO copy(IsaacParsonsQuestionDTO source); - IsaacReorderQuestionDTO copy(IsaacReorderQuestionDTO source); - IsaacAnvilQuestionDTO copy(IsaacAnvilQuestionDTO source); - IsaacFreeTextQuestionDTO copy(IsaacFreeTextQuestionDTO source); - IsaacItemQuestionDTO copy(IsaacItemQuestionDTO source); - IsaacMultiChoiceQuestionDTO copy(IsaacMultiChoiceQuestionDTO source); - - @Mapping(target = "knownUnits", ignore = true) - IsaacNumericQuestionDTO copy(IsaacNumericQuestionDTO source); - - IsaacQuickQuestionDTO copy(IsaacQuickQuestionDTO source); - IsaacRegexMatchQuestionDTO copy(IsaacRegexMatchQuestionDTO source); - IsaacStringMatchQuestionDTO copy(IsaacStringMatchQuestionDTO source); - IsaacSymbolicQuestionDTO copy(IsaacSymbolicQuestionDTO source); - IsaacQuestionBaseDTO copy(IsaacQuestionBaseDTO source); - ChoiceQuestionDTO copy(ChoiceQuestionDTO source); - QuestionDTO copy(QuestionDTO source); - InteractiveCodeSnippetDTO copy(InteractiveCodeSnippetDTO source); - FigureDTO copy(FigureDTO source); - ImageDTO copy(ImageDTO source); - VideoDTO copy(VideoDTO source); - ParsonsItemDTO copy(ParsonsItemDTO source); - AnvilAppDTO copy(AnvilAppDTO source); - CodeSnippetDTO copy(CodeSnippetDTO source); - CodeTabsDTO copy(CodeTabsDTO source); - EmailTemplateDTO copy(EmailTemplateDTO source); - GlossaryTermDTO copy(GlossaryTermDTO source); - IsaacCardDTO copy(IsaacCardDTO source); - IsaacCardDeckDTO copy(IsaacCardDeckDTO source); - IsaacEventPageDTO copy(IsaacEventPageDTO source); - IsaacFeaturedProfileDTO copy(IsaacFeaturedProfileDTO source); - IsaacPageFragmentDTO copy(IsaacPageFragmentDTO source); - IsaacPodDTO copy(IsaacPodDTO source); - IsaacWildcardDTO copy(IsaacWildcardDTO source); - ItemDTO copy(ItemDTO source); - - @SubclassMapping(source = FigureDTO.class, target = FigureDTO.class) - @SubclassMapping(source = ImageDTO.class, target = ImageDTO.class) - @SubclassMapping(source = VideoDTO.class, target = VideoDTO.class) - MediaDTO copy(MediaDTO source); - - NotificationDTO copy(NotificationDTO source); - QuizSummaryDTO copy(QuizSummaryDTO source); - Location copy(Location source); - Address copy(Address source); - - List copyListOfContentBaseDTO(List source); - List copyListOfContentDTO(List source); - List copyListOfContentSummaryDTO(List source); - List copyListOfLocation(List source); - List
copyListOfAddress(List
source); } \ No newline at end of file diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperSubclasses.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperSubclasses.java deleted file mode 100644 index d45798a590..0000000000 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperSubclasses.java +++ /dev/null @@ -1,480 +0,0 @@ -package uk.ac.cam.cl.dtg.util.mappers; - -import org.mapstruct.BeanMapping; -import org.mapstruct.InheritConfiguration; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.SubclassMapping; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacPod; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacWildcard; -import uk.ac.cam.cl.dtg.isaac.dos.content.CodeSnippet; -import uk.ac.cam.cl.dtg.isaac.dto.GameboardItem; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacAnvilQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacCardDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacCardDeckDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacClozeQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacConceptPageDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacEventPageDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacFeaturedProfileDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacFreeTextQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacItemQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacMultiChoiceQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacNumericQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacPageFragmentDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacParsonsQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacPodDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuestionBaseDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuestionPageDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuickQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuizDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuizSectionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacRegexMatchQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacReorderQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacStringMatchQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacSymbolicLogicQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacSymbolicQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacTopicSummaryPageDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacWildcardDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.AnvilAppDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.ChoiceDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.ChoiceQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.CodeSnippetDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.CodeTabsDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.ContentSummaryDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.EmailTemplateDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.FigureDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.FormulaDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.FreeTextRuleDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.GlossaryTermDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.ImageDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.ItemChoiceDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.ItemDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.LogicFormulaDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.MediaDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.NotificationDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.ParsonsChoiceDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.ParsonsItemDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.QuantityDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.QuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.RegexPatternDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.SeguePageDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.StringChoiceDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.VideoDTO; -import uk.ac.cam.cl.dtg.segue.dos.content.InteractiveCodeSnippet; -import uk.ac.cam.cl.dtg.segue.dto.content.InteractiveCodeSnippetDTO; - -@Mapper -public interface ContentMapperSubclasses { - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @SubclassMapping(source = FormulaDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = FreeTextRuleDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = ItemChoiceDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = LogicFormulaDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = QuantityDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = RegexPatternDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = StringChoiceDTO.class, target = ContentSummaryDTO.class) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(ChoiceDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(ParsonsChoiceDTO source); - - @InheritConfiguration - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(FormulaDTO source); - - @InheritConfiguration - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(FreeTextRuleDTO source); - - @InheritConfiguration - @SubclassMapping(source = ParsonsChoiceDTO.class, target = ContentSummaryDTO.class) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(ItemChoiceDTO source); - - @InheritConfiguration - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(LogicFormulaDTO source); - - @InheritConfiguration - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(QuantityDTO source); - - @InheritConfiguration - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(RegexPatternDTO source); - - @InheritConfiguration - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(StringChoiceDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @SubclassMapping(source = IsaacConceptPageDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = IsaacQuestionPageDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = IsaacQuizDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = IsaacTopicSummaryPageDTO.class, target = ContentSummaryDTO.class) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(SeguePageDTO source); - - @InheritConfiguration - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacConceptPageDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacQuestionPageDTO source); - - @InheritConfiguration - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacQuizDTO source); - - @InheritConfiguration - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacQuizSectionDTO source); - - @InheritConfiguration - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacTopicSummaryPageDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @SubclassMapping(source = ChoiceQuestionDTO.class, target = ContentSummaryDTO.class) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(QuestionDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacSymbolicLogicQuestionDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacClozeQuestionDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacParsonsQuestionDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacReorderQuestionDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacAnvilQuestionDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacFreeTextQuestionDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @SubclassMapping(source = IsaacClozeQuestionDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = IsaacParsonsQuestionDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = IsaacReorderQuestionDTO.class, target = ContentSummaryDTO.class) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacItemQuestionDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacMultiChoiceQuestionDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacNumericQuestionDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacQuickQuestionDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacRegexMatchQuestionDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacStringMatchQuestionDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @SubclassMapping(source = IsaacSymbolicLogicQuestionDTO.class, target = ContentSummaryDTO.class) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacSymbolicQuestionDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @SubclassMapping(source = IsaacAnvilQuestionDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = IsaacFreeTextQuestionDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = IsaacItemQuestionDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = IsaacMultiChoiceQuestionDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = IsaacNumericQuestionDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = IsaacQuickQuestionDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = IsaacRegexMatchQuestionDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = IsaacStringMatchQuestionDTO.class, target = ContentSummaryDTO.class) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacQuestionBaseDTO source); - - @InheritConfiguration - @SubclassMapping(source = IsaacQuestionBaseDTO.class, target = ContentSummaryDTO.class) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(ChoiceQuestionDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(FigureDTO source); - - @InheritConfiguration - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(ImageDTO source); - - @InheritConfiguration - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(VideoDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @SubclassMapping(source = FigureDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = ImageDTO.class, target = ContentSummaryDTO.class) - @SubclassMapping(source = VideoDTO.class, target = ContentSummaryDTO.class) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(MediaDTO source); - - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(InteractiveCodeSnippetDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(ParsonsItemDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(AnvilAppDTO source); - - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @SubclassMapping(source = InteractiveCodeSnippetDTO.class, target = ContentSummaryDTO.class) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(CodeSnippetDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(CodeTabsDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(EmailTemplateDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(GlossaryTermDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacCardDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacCardDeckDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacEventPageDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacFeaturedProfileDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacPageFragmentDTO source); - - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacPodDTO source); - - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(IsaacWildcardDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(ItemDTO source); - - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @BeanMapping(resultType = ContentSummaryDTO.class) - ContentSummaryDTO mapToContentSummaryDTO(NotificationDTO source); - - @Mapping(target = "description", ignore = true) - @SubclassMapping(source = InteractiveCodeSnippet.class, target = IsaacWildcard.class) - IsaacWildcard mapToIsaacWildcard(CodeSnippet source); - - @Mapping(target = "description", ignore = true) - IsaacWildcard mapToIsaacWildcard(InteractiveCodeSnippet source); - - @Mapping(target = "description", ignore = true) - IsaacWildcard mapToIsaacWildcard(IsaacPod source); - - IsaacWildcard copy(IsaacWildcard source); - - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "state", ignore = true) - @Mapping(target = "questionPartsTotal", ignore = true) - @Mapping(target = "questionPartsNotAttempted", ignore = true) - @Mapping(target = "questionPartsIncorrect", ignore = true) - @Mapping(target = "questionPartsCorrect", ignore = true) - @Mapping(target = "questionPartStates", ignore = true) - @Mapping(target = "passMark", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @Mapping(target = "description", ignore = true) - @Mapping(target = "creationContext", ignore = true) - @Mapping(target = "contentType", ignore = true) - @Mapping(target = "boardId", ignore = true) - @SubclassMapping(source = IsaacQuestionPageDTO.class, target = GameboardItem.class) - GameboardItem mapToGameboardItem(SeguePageDTO source); - - @Mapping(target = "state", ignore = true) - @Mapping(target = "questionPartsTotal", ignore = true) - @Mapping(target = "questionPartsNotAttempted", ignore = true) - @Mapping(target = "questionPartsIncorrect", ignore = true) - @Mapping(target = "questionPartsCorrect", ignore = true) - @Mapping(target = "questionPartStates", ignore = true) - @Mapping(target = "description", ignore = true) - @Mapping(target = "creationContext", ignore = true) - @Mapping(target = "contentType", ignore = true) - @Mapping(target = "boardId", ignore = true) - GameboardItem mapToGameboardItem(IsaacQuestionPageDTO source); -} \ No newline at end of file From 2aeb4ad22d7fca79c2484e65b0a227f592fc37f2 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Tue, 30 Sep 2025 12:22:21 +0100 Subject: [PATCH 31/65] Add missing content subclass mappings --- .../java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index 36d34bc205..15cbf068dd 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -88,9 +88,12 @@ default T map(Content source, Class targetClass) { @InheritInverseConfiguration(name = "mapChoice") Choice mapChoice(ChoiceDTO source); + @SubclassMapping(source = ChemicalFormula.class, target = ChemicalFormulaDTO.class) @SubclassMapping(source = Formula.class, target = FormulaDTO.class) @SubclassMapping(source = FreeTextRule.class, target = FreeTextRuleDTO.class) + @SubclassMapping(source = GraphChoice.class, target = GraphChoiceDTO.class) @SubclassMapping(source = ItemChoice.class, target = ItemChoiceDTO.class) + @SubclassMapping(source = LLMFreeTextChoice.class, target = LLMFreeTextChoiceDTO.class) @SubclassMapping(source = LogicFormula.class, target = LogicFormulaDTO.class) @SubclassMapping(source = Quantity.class, target = QuantityDTO.class) @SubclassMapping(source = RegexPattern.class, target = RegexPatternDTO.class) @@ -112,18 +115,21 @@ default ResultsWrapper copy(ResultsWrapper source) { @SubclassMapping(source = CodeTabs.class, target = CodeTabsDTO.class) @SubclassMapping(source = EmailTemplate.class, target = EmailTemplateDTO.class) @SubclassMapping(source = GlossaryTerm.class, target = GlossaryTermDTO.class) + @SubclassMapping(source = InlineRegion.class, target = InlineRegionDTO.class) @SubclassMapping(source = IsaacCard.class, target = IsaacCardDTO.class) @SubclassMapping(source = IsaacCardDeck.class, target = IsaacCardDeckDTO.class) @SubclassMapping(source = IsaacEventPage.class, target = IsaacEventPageDTO.class) @SubclassMapping(source = IsaacFeaturedProfile.class, target = IsaacFeaturedProfileDTO.class) @SubclassMapping(source = IsaacPageFragment.class, target = IsaacPageFragmentDTO.class) @SubclassMapping(source = IsaacPod.class, target = IsaacPodDTO.class) + @SubclassMapping(source = IsaacQuiz.class, target = IsaacQuizDTO.class) @SubclassMapping(source = IsaacQuizSection.class, target = IsaacQuizSectionDTO.class) @SubclassMapping(source = IsaacWildcard.class, target = IsaacWildcardDTO.class) @SubclassMapping(source = Item.class, target = ItemDTO.class) @SubclassMapping(source = Notification.class, target = NotificationDTO.class) @SubclassMapping(source = Question.class, target = QuestionDTO.class) - @SubclassMapping(source = IsaacQuiz.class, target = IsaacQuizDTO.class) + @SubclassMapping(source = Sidebar.class, target = SidebarDTO.class) + @SubclassMapping(source = SidebarEntry.class, target = SidebarEntryDTO.class) ContentDTO mapContent(Content source); List mapListOfContentSummaryDtoToListOfString(List source); From 067ee1ca50b4bda08432e1eb2e641a9d3b809557 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Tue, 30 Sep 2025 14:04:18 +0100 Subject: [PATCH 32/65] Remove ignore annotations for specific fields --- .../cl/dtg/util/mappers/AssignmentMapper.java | 14 ---------- .../cl/dtg/util/mappers/ContentMapper.java | 23 --------------- .../cam/cl/dtg/util/mappers/UserMapper.java | 28 ------------------- 3 files changed, 65 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java index 71b4da5ef4..245b54670f 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java @@ -2,7 +2,6 @@ import org.mapstruct.Mapper; -import org.mapstruct.Mapping; import uk.ac.cam.cl.dtg.isaac.dos.AssignmentDO; import uk.ac.cam.cl.dtg.isaac.dos.GameboardDO; import uk.ac.cam.cl.dtg.isaac.dos.QuizAssignmentDO; @@ -17,25 +16,12 @@ public interface AssignmentMapper { GameboardDTO map(GameboardDO source); GameboardDO map(GameboardDTO source); - @Mapping(target = "legacyId", ignore = true) - @Mapping(target = "groupName", ignore = true) - @Mapping(target = "gameboard", ignore = true) - @Mapping(target = "assignerSummary", ignore = true) AssignmentDTO map(AssignmentDO source); AssignmentDO map(AssignmentDTO source); - @Mapping(target = "userFeedback", ignore = true) - @Mapping(target = "quizSummary", ignore = true) - @Mapping(target = "quiz", ignore = true) - @Mapping(target = "attempt", ignore = true) - @Mapping(target = "assignerSummary", ignore = true) QuizAssignmentDTO map(QuizAssignmentDO source); QuizAssignmentDO map(QuizAssignmentDTO source); - @Mapping(target = "quizSummary", ignore = true) - @Mapping(target = "quizAssignment", ignore = true) - @Mapping(target = "quiz", ignore = true) - @Mapping(target = "feedbackMode", ignore = true) QuizAttemptDTO map(QuizAttemptDO source); QuizAttemptDO map(QuizAttemptDTO source); } diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index 15cbf068dd..fb10e5c8a0 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -2,7 +2,6 @@ import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.Mapper; -import org.mapstruct.Mapping; import org.mapstruct.SubclassExhaustiveStrategy; import org.mapstruct.SubclassMapping; import org.mapstruct.factory.Mappers; @@ -49,28 +48,10 @@ default T map(Content source, Class targetClass) { } } - @Mapping(target = "url", ignore = true) - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "summary", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) ContentSummaryDTO mapContentDTOtoContentSummaryDTO(ContentDTO source); IsaacWildcard mapContentToIsaacWildcard(Content source); - @Mapping(target = "supersededBy", ignore = true) - @Mapping(target = "state", ignore = true) - @Mapping(target = "questionPartsTotal", ignore = true) - @Mapping(target = "questionPartsNotAttempted", ignore = true) - @Mapping(target = "questionPartsIncorrect", ignore = true) - @Mapping(target = "questionPartsCorrect", ignore = true) - @Mapping(target = "questionPartStates", ignore = true) - @Mapping(target = "passMark", ignore = true) - @Mapping(target = "difficulty", ignore = true) - @Mapping(target = "description", ignore = true) - @Mapping(target = "creationContext", ignore = true) - @Mapping(target = "contentType", ignore = true) - @Mapping(target = "boardId", ignore = true) GameboardItem mapContentDTOtoGameboardItem(ContentDTO source); QuizSummaryDTO mapContentDTOtoQuizSummaryDTO(ContentDTO source); @@ -82,9 +63,6 @@ default T map(Content source, Class targetClass) { SidebarDTO map(String source); - @Mapping(target = "searchableContent", ignore = true) - @Mapping(target = "correct", ignore = true) - @Mapping(target = "explanation", ignore = true) @InheritInverseConfiguration(name = "mapChoice") Choice mapChoice(ChoiceDTO source); @@ -105,7 +83,6 @@ default ResultsWrapper copy(ResultsWrapper source) { return new ResultsWrapper<>(copyListOfString(source.getResults()), source.getTotalResults()); } - @Mapping(target = "searchableContent", ignore = true) @InheritInverseConfiguration(name = "mapContent") Content mapContent(ContentDTO source); diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java index 8410d6d6c7..843daf4ed6 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java @@ -30,11 +30,8 @@ public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); - @Mapping(target = "emailVerificationToken", ignore = true) - @Mapping(target = "emailToVerify", ignore = true) RegisteredUser map(RegisteredUserDTO source); - @Mapping(target = "firstLogin", ignore = true) RegisteredUserDTO map(RegisteredUser source); UserAuthenticationSettingsDTO map(UserAuthenticationSettings source); AnonymousUserDTO map(AnonymousUser source); @@ -71,31 +68,20 @@ default T map(UserSummaryDTO source, Class targetClass) { } } - @Mapping(target = "authorisedFullAccess", ignore = true) UserSummaryDTO mapUserToSummary(RegisteredUserDTO source); - @Mapping(target = "token", ignore = true) - @Mapping(target = "ownerSummary", ignore = true) - @Mapping(target = "mongoId", ignore = true) - @Mapping(target = "additionalManagers", ignore = true) - @Mapping(target = "additionalManagersUserIds", ignore = true) UserGroupDTO map(UserGroup source); - @Mapping(target = "authorisedFullAccess", ignore = true) UserSummaryForAdminUsersDTO mapUserToAdminSummaryDTO(RegisteredUserDTO source); - @Mapping(target = "authorisedFullAccess", ignore = true) UserSummaryWithEmailAddressDTO mapUserToSummaryWithEmailDTO(RegisteredUserDTO source); GroupMembership map(GroupMembershipDTO source); GroupMembershipDTO map(GroupMembership source); - @Mapping(target = "status", ignore = true) UserGroup map(UserGroupDTO source); - @Mapping(target = "groupMembershipInformation", ignore = true) - @Mapping(target = "authorisedFullAccess", ignore = true) UserSummaryWithGroupMembershipDTO mapUserToSummaryWithGroupMembershipDTO(RegisteredUserDTO source); RegisteredUser copy(RegisteredUser source); @@ -103,26 +89,12 @@ default T map(UserSummaryDTO source, Class targetClass) { GroupMembershipDTO copy(GroupMembershipDTO source); - @Mapping(target = "emailVerificationToken", ignore = true) - @Mapping(target = "emailToVerify", ignore = true) @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) void merge(RegisteredUserDTO source, @MappingTarget RegisteredUser target); - @Mapping(target = "schoolOther", ignore = true) - @Mapping(target = "schoolId", ignore = true) - @Mapping(target = "role", ignore = true) - @Mapping(target = "registrationDate", ignore = true) - @Mapping(target = "registeredContextsLastConfirmed", ignore = true) - @Mapping(target = "registeredContexts", ignore = true) - @Mapping(target = "lastUpdated", ignore = true) - @Mapping(target = "lastSeen", ignore = true) - @Mapping(target = "id", ignore = true) - @Mapping(target = "emailVerificationToken", ignore = true) - @Mapping(target = "emailToVerify", ignore = true) RegisteredUser mapUserFromAuthProviderToRegisteredUser(UserFromAuthProvider source); - @Mapping(target = "groupMembershipInformation", ignore = true) @SubclassMapping(source = UserSummaryForAdminUsersDTO.class, target = UserSummaryWithGroupMembershipDTO.class) @SubclassMapping(source = UserSummaryWithEmailAddressDTO.class, target = UserSummaryWithGroupMembershipDTO.class) @SubclassMapping(source = UserSummaryWithGroupMembershipDTO.class, target = UserSummaryWithGroupMembershipDTO.class) From a475612800ed57f39ab4c91f9333534f61fefa00 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Tue, 30 Sep 2025 16:42:30 +0100 Subject: [PATCH 33/65] Ignore all unmapped target properties Very bulky, but needed to stop warnings --- .../cl/dtg/util/mappers/AssignmentMapper.java | 47 ++++++++++++++ .../cl/dtg/util/mappers/ContentMapper.java | 64 +++++++++++++++++++ .../cam/cl/dtg/util/mappers/UserMapper.java | 31 +++++++++ 3 files changed, 142 insertions(+) diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java index 245b54670f..95fb52b9a7 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java @@ -2,26 +2,73 @@ import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import uk.ac.cam.cl.dtg.isaac.dos.AssignmentDO; +import uk.ac.cam.cl.dtg.isaac.dos.GameboardContentDescriptor; import uk.ac.cam.cl.dtg.isaac.dos.GameboardDO; import uk.ac.cam.cl.dtg.isaac.dos.QuizAssignmentDO; import uk.ac.cam.cl.dtg.isaac.dos.QuizAttemptDO; import uk.ac.cam.cl.dtg.isaac.dto.AssignmentDTO; import uk.ac.cam.cl.dtg.isaac.dto.GameboardDTO; +import uk.ac.cam.cl.dtg.isaac.dto.GameboardItem; import uk.ac.cam.cl.dtg.isaac.dto.QuizAssignmentDTO; import uk.ac.cam.cl.dtg.isaac.dto.QuizAttemptDTO; @Mapper public interface AssignmentMapper { + + @Mapping(target = "ownerUserInformation", ignore = true) + @Mapping(target = "savedToCurrentUser", ignore = true) + @Mapping(target = "percentageAttempted", ignore = true) + @Mapping(target = "percentageCorrect", ignore = true) + @Mapping(target = "lastVisited", ignore = true) + @Mapping(target = "startedQuestion", ignore = true) GameboardDTO map(GameboardDO source); GameboardDO map(GameboardDTO source); + @Mapping(target = "legacyId", ignore = true) + @Mapping(target = "gameboard", ignore = true) + @Mapping(target = "groupName", ignore = true) + @Mapping(target = "assignerSummary", ignore = true) AssignmentDTO map(AssignmentDO source); AssignmentDO map(AssignmentDTO source); + @Mapping(target = "quizSummary", ignore = true) + @Mapping(target = "assignerSummary", ignore = true) + @Mapping(target = "attempt", ignore = true) + @Mapping(target = "userFeedback", ignore = true) + @Mapping(target = "quiz", ignore = true) QuizAssignmentDTO map(QuizAssignmentDO source); QuizAssignmentDO map(QuizAssignmentDTO source); + @Mapping(target = "quizSummary", ignore = true) + @Mapping(target = "quiz", ignore = true) + @Mapping(target = "quizAssignment", ignore = true) + @Mapping(target = "feedbackMode", ignore = true) QuizAttemptDTO map(QuizAttemptDO source); QuizAttemptDO map(QuizAttemptDTO source); + + // Handle mapping the "content" field for GameboardD(T)Os + + @Mapping(source = "creationContext", target = "context") + GameboardContentDescriptor mapGameboardItemToGameboardContentDescriptor(GameboardItem source); + + @Mapping(target = "title", ignore = true) + @Mapping(target = "subtitle", ignore = true) + @Mapping(target = "description", ignore = true) + @Mapping(target = "tags", ignore = true) + @Mapping(target = "level", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @Mapping(target = "questionPartStates", ignore = true) + @Mapping(target = "questionPartsCorrect", ignore = true) + @Mapping(target = "questionPartsIncorrect", ignore = true) + @Mapping(target = "questionPartsNotAttempted", ignore = true) + @Mapping(target = "questionPartsTotal", ignore = true) + @Mapping(target = "passMark", ignore = true) + @Mapping(target = "state", ignore = true) + @Mapping(target = "boardId", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "audience", ignore = true) + @Mapping(source = "context", target = "creationContext") + GameboardItem mapGameboardItemToGameboardContentDescriptor(GameboardContentDescriptor source); } diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index fb10e5c8a0..60de236efa 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -2,6 +2,7 @@ import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.SubclassExhaustiveStrategy; import org.mapstruct.SubclassMapping; import org.mapstruct.factory.Mappers; @@ -48,19 +49,82 @@ default T map(Content source, Class targetClass) { } } + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "state", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @Mapping(target = "deprecated", ignore = true) ContentSummaryDTO mapContentDTOtoContentSummaryDTO(ContentDTO source); + @Mapping(target = "url", ignore = true) + @Mapping(target = "description", ignore = true) IsaacWildcard mapContentToIsaacWildcard(Content source); + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "state", ignore = true) + @Mapping(target = "questionPartsTotal", ignore = true) + @Mapping(target = "questionPartsNotAttempted", ignore = true) + @Mapping(target = "questionPartsIncorrect", ignore = true) + @Mapping(target = "questionPartsCorrect", ignore = true) + @Mapping(target = "questionPartStates", ignore = true) + @Mapping(target = "passMark", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @Mapping(target = "description", ignore = true) + @Mapping(target = "creationContext", ignore = true) + @Mapping(target = "contentType", ignore = true) + @Mapping(target = "boardId", ignore = true) GameboardItem mapContentDTOtoGameboardItem(ContentDTO source); + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "state", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "hiddenFromRoles", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @Mapping(target = "deprecated", ignore = true) QuizSummaryDTO mapContentDTOtoQuizSummaryDTO(ContentDTO source); + @Mapping(target = "url", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "state", ignore = true) + @Mapping(target = "rubric", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "hiddenFromRoles", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @Mapping(target = "deprecated", ignore = true) @SubclassMapping(source = IsaacQuizDTO.class, target = DetailedQuizSummaryDTO.class) DetailedQuizSummaryDTO mapContentDTOtoDetailedQuizSummaryDTO(ContentDTO source); + @Mapping(target = "url", ignore = true) + @Mapping(target = "state", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) DetailedQuizSummaryDTO map(IsaacQuizDTO source); + @Mapping(target = "version", ignore = true) + @Mapping(target = "value", ignore = true) + @Mapping(target = "type", ignore = true) + @Mapping(target = "title", ignore = true) + @Mapping(target = "tags", ignore = true) + @Mapping(target = "subtitle", ignore = true) + @Mapping(target = "sidebarEntries", ignore = true) + @Mapping(target = "relatedContent", ignore = true) + @Mapping(target = "published", ignore = true) + @Mapping(target = "level", ignore = true) + @Mapping(target = "layout", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "expandable", ignore = true) + @Mapping(target = "encoding", ignore = true) + @Mapping(target = "display", ignore = true) + @Mapping(target = "children", ignore = true) + @Mapping(target = "canonicalSourceFile", ignore = true) + @Mapping(target = "author", ignore = true) + @Mapping(target = "audience", ignore = true) + @Mapping(target = "attribution", ignore = true) SidebarDTO map(String source); @InheritInverseConfiguration(name = "mapChoice") diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java index 843daf4ed6..1639b253fd 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java @@ -30,8 +30,12 @@ public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); + @Mapping(target = "sessionToken", ignore = true) + @Mapping(target = "emailVerificationToken", ignore = true) + @Mapping(target = "emailToVerify", ignore = true) RegisteredUser map(RegisteredUserDTO source); + @Mapping(target = "firstLogin", ignore = true) RegisteredUserDTO map(RegisteredUser source); UserAuthenticationSettingsDTO map(UserAuthenticationSettings source); AnonymousUserDTO map(AnonymousUser source); @@ -68,20 +72,31 @@ default T map(UserSummaryDTO source, Class targetClass) { } } + @Mapping(target = "authorisedFullAccess", ignore = true) UserSummaryDTO mapUserToSummary(RegisteredUserDTO source); + @Mapping(target = "token", ignore = true) + @Mapping(target = "ownerSummary", ignore = true) + @Mapping(target = "mongoId", ignore = true) + @Mapping(target = "additionalManagersUserIds", ignore = true) + @Mapping(target = "additionalManagers", ignore = true) UserGroupDTO map(UserGroup source); + @Mapping(target = "authorisedFullAccess", ignore = true) UserSummaryForAdminUsersDTO mapUserToAdminSummaryDTO(RegisteredUserDTO source); + @Mapping(target = "authorisedFullAccess", ignore = true) UserSummaryWithEmailAddressDTO mapUserToSummaryWithEmailDTO(RegisteredUserDTO source); GroupMembership map(GroupMembershipDTO source); GroupMembershipDTO map(GroupMembership source); + @Mapping(target = "status", ignore = true) UserGroup map(UserGroupDTO source); + @Mapping(target = "groupMembershipInformation", ignore = true) + @Mapping(target = "authorisedFullAccess", ignore = true) UserSummaryWithGroupMembershipDTO mapUserToSummaryWithGroupMembershipDTO(RegisteredUserDTO source); RegisteredUser copy(RegisteredUser source); @@ -89,12 +104,28 @@ default T map(UserSummaryDTO source, Class targetClass) { GroupMembershipDTO copy(GroupMembershipDTO source); + @Mapping(target = "sessionToken", ignore = true) + @Mapping(target = "emailVerificationToken", ignore = true) + @Mapping(target = "emailToVerify", ignore = true) @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) void merge(RegisteredUserDTO source, @MappingTarget RegisteredUser target); + @Mapping(target = "sessionToken", ignore = true) + @Mapping(target = "schoolOther", ignore = true) + @Mapping(target = "schoolId", ignore = true) + @Mapping(target = "role", ignore = true) + @Mapping(target = "registrationDate", ignore = true) + @Mapping(target = "registeredContextsLastConfirmed", ignore = true) + @Mapping(target = "registeredContexts", ignore = true) + @Mapping(target = "lastUpdated", ignore = true) + @Mapping(target = "lastSeen", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "emailVerificationToken", ignore = true) + @Mapping(target = "emailToVerify", ignore = true) RegisteredUser mapUserFromAuthProviderToRegisteredUser(UserFromAuthProvider source); + @Mapping(target = "groupMembershipInformation", ignore = true) @SubclassMapping(source = UserSummaryForAdminUsersDTO.class, target = UserSummaryWithGroupMembershipDTO.class) @SubclassMapping(source = UserSummaryWithEmailAddressDTO.class, target = UserSummaryWithGroupMembershipDTO.class) @SubclassMapping(source = UserSummaryWithGroupMembershipDTO.class, target = UserSummaryWithGroupMembershipDTO.class) From f4cb150f375190375cf37457649ba90bdd444fa5 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Tue, 30 Sep 2025 16:46:29 +0100 Subject: [PATCH 34/65] Suppress unchecked cast warnings --- src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java | 2 ++ src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java | 3 +++ src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java | 3 +++ 3 files changed, 8 insertions(+) diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index 60de236efa..49643642ad 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -23,6 +23,7 @@ public interface ContentMapper { @SubclassMapping(source = Content.class, target = ContentDTO.class) ContentBaseDTO map(ContentBase source); + @SuppressWarnings("unchecked") default T map(ContentDTO source, Class targetClass) { if (targetClass.equals(ContentSummaryDTO.class)) { return (T) mapContentDTOtoContentSummaryDTO(source); @@ -39,6 +40,7 @@ default T map(ContentDTO source, Class targetClass) { } } + @SuppressWarnings("unchecked") default T map(Content source, Class targetClass) { if (targetClass.equals(IsaacWildcard.class)) { return (T) mapContentToIsaacWildcard(source); diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java index 0dd4e8f266..6783bf04f4 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java @@ -10,6 +10,8 @@ @Mapper(uses = UserMapper.class) public interface EventMapper { + + @SuppressWarnings("unchecked") default T map(DetailedEventBookingDTO source, Class targetClass) { if (targetClass.equals(EventBookingDTO.class)) { return (T) mapDetailedEventBookingDTOtoEventBookingDTO(source); @@ -18,6 +20,7 @@ default T map(DetailedEventBookingDTO source, Class< } } + @SuppressWarnings("unchecked") default List mapAsList(List source, Class sourceClass, Class targetClass) { if (sourceClass.equals(EventBookingDTO.class) && targetClass.equals(EventBookingDTO.class)) { return (List) copyListOfEventBookingDTO((List) source); diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java index 1639b253fd..fe0206e91a 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java @@ -40,6 +40,7 @@ public interface UserMapper { UserAuthenticationSettingsDTO map(UserAuthenticationSettings source); AnonymousUserDTO map(AnonymousUser source); + @SuppressWarnings("unchecked") default T map(RegisteredUserDTO source, Class targetClass) { if (targetClass.equals(UserSummaryDTO.class)) { return (T) mapUserToSummary(source); @@ -54,6 +55,7 @@ default T map(RegisteredUserDTO source, Class targ } } + @SuppressWarnings("unchecked") default T map(UserFromAuthProvider source, Class targetClass) { if (targetClass.equals(RegisteredUser.class)) { return (T) mapUserFromAuthProviderToRegisteredUser(source); @@ -62,6 +64,7 @@ default T map(UserFromAuthProvider source, Class targetClass) { } } + @SuppressWarnings("unchecked") default T map(UserSummaryDTO source, Class targetClass) { if (targetClass.equals(UserSummaryWithGroupMembershipDTO.class)) { return (T) mapUserSummaryDTOtoUserSummaryWithGroupMembershipDTO(source); From 600cdfff062d3e81d3d36ee1bf297d404ad2f99e Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Wed, 1 Oct 2025 11:50:01 +0100 Subject: [PATCH 35/65] Simplify & rename mappings --- .../ac/cam/cl/dtg/isaac/api/EventsFacade.java | 8 ++-- .../api/managers/QuizQuestionManager.java | 2 +- .../dao/GameboardPersistenceManager.java | 4 +- ...QuizQuestionAttemptPersistenceManager.java | 4 +- .../cl/dtg/isaac/quiz/PgQuestionAttempts.java | 4 +- .../cam/cl/dtg/segue/api/QuestionFacade.java | 6 +-- .../segue/api/managers/QuestionManager.java | 10 ++--- .../api/managers/UserAccountManager.java | 2 +- .../SegueGuiceConfigurationModule.java | 14 +++--- ...rUtils.java => ContentSubclassMapper.java} | 10 ++--- .../segue/dao/content/GitContentManager.java | 8 ++-- .../cam/cl/dtg/segue/etl/ContentIndexer.java | 6 +-- .../dtg/segue/etl/ETLConfigurationModule.java | 10 ++--- .../cam/cl/dtg/segue/etl/SchoolIndexer.java | 6 +-- .../cl/dtg/util/mappers/AssignmentMapper.java | 3 ++ .../cl/dtg/util/mappers/ContentMapper.java | 45 ++++++++++++------- .../cam/cl/dtg/util/mappers/EventMapper.java | 27 +++-------- .../cam/cl/dtg/util/mappers/MainMapper.java | 3 ++ .../mappers/QuestionValidationMapper.java | 6 +++ .../UnimplementedMappingException.java | 3 ++ .../cam/cl/dtg/util/mappers/UserMapper.java | 40 ++++++++--------- .../api/AbstractIsaacIntegrationTest.java | 4 +- .../cl/dtg/isaac/api/QuestionFacadeTest.java | 6 +-- .../api/managers/QuizQuestionManagerTest.java | 6 +-- .../segue/api/managers/UserManagerTest.java | 2 +- .../cl/dtg/segue/dao/ContentMapperTest.java | 10 ++--- .../dtg/segue/dao/GitContentManagerTest.java | 10 ++--- .../cl/dtg/segue/etl/ContentIndexerTest.java | 16 +++---- 28 files changed, 144 insertions(+), 131 deletions(-) rename src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/{ContentMapperUtils.java => ContentSubclassMapper.java} (98%) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java index b38b8e6752..5986612c6f 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java @@ -327,7 +327,7 @@ private ResultsWrapper getEventsReservedByUser(final HttpServletRequ throws SegueDatabaseException, ContentManagerException { List filteredResults = Lists.newArrayList(); - List userReservationList = this.mapper.mapAsList(bookingManager.getAllEventReservationsForUser(currentUser.getId()), DetailedEventBookingDTO.class, EventBookingDTO.class); + List userReservationList = this.mapper.map(bookingManager.getAllEventReservationsForUser(currentUser.getId())); for (EventBookingDTO booking : userReservationList) { @@ -565,7 +565,7 @@ public final Response getEventBookingForGivenGroup(@Context final HttpServletReq eventBookings = userAssociationManager.filterUnassociatedRecords(currentUser, eventBookings, booking -> booking.getUserBooked().getId()); - return Response.ok(this.mapper.mapAsList(eventBookings, EventBookingDTO.class, EventBookingDTO.class)).build(); + return Response.ok(this.mapper.copy(eventBookings)).build(); } catch (SegueDatabaseException e) { String errorMsg = String.format( "Database error occurred while trying retrieve bookings for group (%s) on event (%s).", @@ -598,7 +598,7 @@ public final Response getEventBookingForAllGroups(@Context final HttpServletRequ return new SegueErrorResponse(Status.FORBIDDEN, "You do not have permission to use this endpoint.").toResponse(); } - List eventBookings = this.mapper.mapAsList(bookingManager.getBookingsByEventId(eventId), DetailedEventBookingDTO.class, EventBookingDTO.class); + List eventBookings = this.mapper.map(bookingManager.getBookingsByEventId(eventId)); // Only allowed to see the bookings of connected users eventBookings = userAssociationManager.filterUnassociatedRecords( @@ -861,7 +861,7 @@ public final Response createReservationsForGivenUsers(@Context final HttpServlet USER_ID_LIST_FKEY_FIELDNAME, userIds.toArray(), BOOKING_STATUS_FIELDNAME, BookingStatus.RESERVED.toString() )); - return Response.ok(this.mapper.mapAsList(bookings, EventBookingDTO.class, EventBookingDTO.class)).build(); + return Response.ok(this.mapper.copy(bookings)).build(); } catch (NoUserLoggedInException e) { return SegueErrorResponse.getNotLoggedInResponse(); diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManager.java index 1b209c4055..ead122b400 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManager.java @@ -300,7 +300,7 @@ void augmentQuestionObjectWithAttemptInformation(Map testQuestion(final String questionType, final TestQuestion // For each test, check its actual results against the response of the validator on the fake question List results = Lists.newArrayList(); for (TestCase testCase : testDefinition.getTestCases()) { - Choice inferredChoiceSubclass = mapper.mapChoice(mapper.mapChoice(testCase.getAnswer())); + Choice inferredChoiceSubclass = mapper.map(mapper.map(testCase.getAnswer())); QuestionValidationResponse questionValidationResponse = questionValidator .validateQuestionResponse(testQuestion, inferredChoiceSubclass); testCase.setCorrect(questionValidationResponse.isCorrect()); @@ -646,7 +646,7 @@ public ChoiceDTO convertJsonAnswerToChoice(String jsonAnswer) throws ErrorRespon // convert submitted JSON into a Choice: Choice answerFromClient = mapperUtils.getSharedContentObjectMapper().readValue(jsonAnswer, Choice.class); // convert to a DTO so that it strips out any untrusted data. - answerFromClientDTO = mapper.mapChoice(answerFromClient); + answerFromClientDTO = mapper.map(answerFromClient); } catch (JsonMappingException | JsonParseException e) { log.info("Failed to map to any expected input...", e); SegueErrorResponse error = new SegueErrorResponse(Response.Status.NOT_FOUND, "Unable to map response to a " diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/UserAccountManager.java b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/UserAccountManager.java index b9a4b18f3a..043357bf5c 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/UserAccountManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/UserAccountManager.java @@ -1822,7 +1822,7 @@ private RegisteredUser registerUserWithFederatedProvider(final AuthenticationPro throw new NoUserException("No user returned by the provider!"); } - RegisteredUser newLocalUser = this.dtoMapper.map(userFromProvider, RegisteredUser.class); + RegisteredUser newLocalUser = this.dtoMapper.map(userFromProvider); newLocalUser.setRegistrationDate(new Date()); // register user diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java index 38b900d1de..fba7bb0d00 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java @@ -109,7 +109,7 @@ import uk.ac.cam.cl.dtg.segue.dao.PgLogManager; import uk.ac.cam.cl.dtg.segue.dao.associations.IAssociationDataManager; import uk.ac.cam.cl.dtg.segue.dao.associations.PgAssociationDataManager; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentSubclassMapper; import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager; import uk.ac.cam.cl.dtg.segue.dao.schools.SchoolListReader; import uk.ac.cam.cl.dtg.segue.dao.users.IAnonymousUserDataManager; @@ -183,7 +183,7 @@ public class SegueGuiceConfigurationModule extends AbstractModule implements Ser // Singletons - we only ever want there to be one instance of each of these. private static PostgresSqlDb postgresDB; - private static ContentMapperUtils mapper = null; + private static ContentSubclassMapper mapper = null; private static GitContentManager contentManager = null; private static RestHighLevelClient elasticSearchClient = null; private static UserAccountManager userManager = null; @@ -555,7 +555,7 @@ private static RestHighLevelClient getSearchConnectionInformation( @Provides @Singleton private static GitContentManager getContentManager(final GitDb database, final ISearchProvider searchProvider, final ContentMapper contentMapper, - final ContentMapperUtils mapperUtils, final AbstractConfigLoader globalProperties) { + final ContentSubclassMapper mapperUtils, final AbstractConfigLoader globalProperties) { if (null == contentManager) { contentManager = new GitContentManager(database, searchProvider, contentMapper, mapperUtils, globalProperties); log.info("Creating singleton of ContentManager"); @@ -610,9 +610,9 @@ private static ILogManager getLogManager(final PostgresSqlDb database, @Inject @Provides @Singleton - private static ContentMapperUtils getContentMapper() { + private static ContentSubclassMapper getContentMapper() { if (null == mapper) { - mapper = new ContentMapperUtils(getReflectionsClass("uk.ac.cam.cl.dtg")); + mapper = new ContentSubclassMapper(getReflectionsClass("uk.ac.cam.cl.dtg")); log.info("Creating Singleton of the Content Mapper"); } @@ -833,7 +833,7 @@ private IUserAccountManager getUserManager(final IUserDataManager database, fina @Inject @Provides @Singleton - private IQuestionAttemptManager getQuestionManager(final PostgresSqlDb ds, final ContentMapperUtils objectMapper) { + private IQuestionAttemptManager getQuestionManager(final PostgresSqlDb ds, final ContentSubclassMapper objectMapper) { // this needs to be a singleton as it provides a temporary cache for anonymous question attempts. if (null == questionPersistenceManager) { questionPersistenceManager = new PgQuestionAttempts(ds, objectMapper); @@ -1248,7 +1248,7 @@ private IPLocationResolver getIPLocator(final AbstractConfigLoader properties) t @Provides @Singleton private static GameboardPersistenceManager getGameboardPersistenceManager(final PostgresSqlDb database, final GitContentManager contentManager, - final MainMapper mapper, final ContentMapperUtils objectMapper) { + final MainMapper mapper, final ContentSubclassMapper objectMapper) { if (null == gameboardPersistenceManager) { gameboardPersistenceManager = new GameboardPersistenceManager(database, contentManager, mapper, objectMapper); log.info("Creating Singleton of GameboardPersistenceManager"); diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentMapperUtils.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentSubclassMapper.java similarity index 98% rename from src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentMapperUtils.java rename to src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentSubclassMapper.java index 32eac4fc53..16d4576a3f 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentMapperUtils.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentSubclassMapper.java @@ -54,8 +54,8 @@ /** * Class responsible for mapping Content objects (or contentBase objects) to their respective subclass. */ -public class ContentMapperUtils { - private static final Logger log = LoggerFactory.getLogger(ContentMapperUtils.class); +public class ContentSubclassMapper { + private static final Logger log = LoggerFactory.getLogger(ContentSubclassMapper.class); // Used for serialization into the correct POJO as well as deserialization. // Currently depends on the string key being the same text value as the type @@ -72,7 +72,7 @@ public class ContentMapperUtils { * */ @Inject - public ContentMapperUtils() { + public ContentSubclassMapper() { jsonTypes = Maps.newConcurrentMap(); mapOfDOsToDTOs = Maps.newConcurrentMap(); } @@ -84,7 +84,7 @@ public ContentMapperUtils() { * - string representing the parent package to search for content classes. e.g. uk.ac.cam.cl.dtg.segue */ @SuppressWarnings("unchecked") - public ContentMapperUtils(final Reflections configuredReflectionClass) { + public ContentSubclassMapper(final Reflections configuredReflectionClass) { this(); Objects.requireNonNull(configuredReflectionClass); @@ -294,7 +294,7 @@ public List getDTOByDOList(final List contentDOList) { * @return a jackson object mapper. */ public ObjectMapper getSharedContentObjectMapper() { - if (ContentMapperUtils.preconfiguredObjectMapper != null) { + if (ContentSubclassMapper.preconfiguredObjectMapper != null) { return preconfiguredObjectMapper; } diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java index 987bbc78f0..44dd000a1a 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java @@ -85,7 +85,7 @@ public class GitContentManager { private final GitDb database; private final ContentMapper mapper; - private final ContentMapperUtils mapperUtils; + private final ContentSubclassMapper mapperUtils; private final ISearchProvider searchProvider; private final AbstractConfigLoader globalProperties; private final boolean showOnlyPublishedContent; @@ -114,7 +114,7 @@ public class GitContentManager { */ @Inject public GitContentManager(final GitDb database, final ISearchProvider searchProvider, final ContentMapper contentMapper, - final ContentMapperUtils mapperUtils, final AbstractConfigLoader globalProperties) { + final ContentSubclassMapper mapperUtils, final AbstractConfigLoader globalProperties) { this.database = database; this.mapper = contentMapper; this.mapperUtils = mapperUtils; @@ -156,7 +156,7 @@ public GitContentManager(final GitDb database, final ISearchProvider searchProvi * - the utility class for mapping content objects. */ public GitContentManager(final GitDb database, final ISearchProvider searchProvider, - final ContentMapper contentMapper, ContentMapperUtils mapperUtils) { + final ContentMapper contentMapper, ContentSubclassMapper mapperUtils) { this.database = database; this.mapper = contentMapper; this.searchProvider = searchProvider; @@ -204,7 +204,7 @@ public final ContentDTO getContentById(final String id, final boolean failQuietl /** * Get a DTO object from a DO object. * - * This method merely wraps {@link ContentMapperUtils#getDTOByDO(Content)}, and will trust the content of the DO. + * This method merely wraps {@link ContentSubclassMapper#getDTOByDO(Content)}, and will trust the content of the DO. * Only use for DO objects obtained from {@link #getContentDOById(String)} when the DTO is also required, * to avoid the potential cache-miss and ElasticSearch round-trip of {@link #getContentById(String)}. * diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexer.java b/src/main/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexer.java index 494daf426a..3204d55092 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexer.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexer.java @@ -47,7 +47,7 @@ import uk.ac.cam.cl.dtg.isaac.dos.content.Video; import uk.ac.cam.cl.dtg.segue.api.Constants; import uk.ac.cam.cl.dtg.segue.dao.content.ContentManagerException; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentSubclassMapper; import uk.ac.cam.cl.dtg.segue.database.GitDb; import uk.ac.cam.cl.dtg.segue.search.SegueSearchException; @@ -84,13 +84,13 @@ public class ContentIndexer { private ElasticSearchIndexer es; private GitDb database; - private ContentMapperUtils mapper; + private ContentSubclassMapper mapper; private static final int MEDIA_FILE_SIZE_LIMIT = 300 * 1024; // Bytes private static final int NANOSECONDS_IN_A_MILLISECOND = 1000000; @Inject - public ContentIndexer(GitDb database, ElasticSearchIndexer es, ContentMapperUtils mapper) { + public ContentIndexer(GitDb database, ElasticSearchIndexer es, ContentSubclassMapper mapper) { this.database = database; this.es = es; this.mapper = mapper; diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/etl/ETLConfigurationModule.java b/src/main/java/uk/ac/cam/cl/dtg/segue/etl/ETLConfigurationModule.java index 83a33a9176..801dc3bb0f 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/etl/ETLConfigurationModule.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/etl/ETLConfigurationModule.java @@ -14,7 +14,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.ac.cam.cl.dtg.segue.api.Constants; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentSubclassMapper; import uk.ac.cam.cl.dtg.segue.database.GitDb; import uk.ac.cam.cl.dtg.util.AbstractConfigLoader; import uk.ac.cam.cl.dtg.util.WriteablePropertiesLoader; @@ -33,7 +33,7 @@ class ETLConfigurationModule extends AbstractModule { private static final Logger log = LoggerFactory.getLogger(ETLConfigurationModule.class); private static Injector injector = null; private static AbstractConfigLoader globalProperties = null; - private static ContentMapperUtils mapper = null; + private static ContentSubclassMapper mapper = null; private static RestHighLevelClient elasticSearchClient = null; private static SchoolIndexer schoolIndexer = null; private static ETLManager etlManager = null; @@ -114,10 +114,10 @@ protected void configure() { @Inject @Provides @Singleton - private static ContentMapperUtils getContentMapper() { + private static ContentSubclassMapper getContentMapper() { if (null == mapper) { Reflections r = new Reflections("uk.ac.cam.cl.dtg"); - mapper = new ContentMapperUtils(r); + mapper = new ContentSubclassMapper(r); } return mapper; } @@ -178,7 +178,7 @@ private static RestHighLevelClient getSearchConnectionInformation( @Singleton private SchoolIndexer getSchoolListIndexer(@Named(Constants.SCHOOL_CSV_LIST_PATH) final String schoolListPath, final ElasticSearchIndexer es, - final ContentMapperUtils mapper) { + final ContentSubclassMapper mapper) { if (null == schoolIndexer) { schoolIndexer = new SchoolIndexer(es, mapper, schoolListPath); log.info("Creating singleton of SchoolListReader"); diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/etl/SchoolIndexer.java b/src/main/java/uk/ac/cam/cl/dtg/segue/etl/SchoolIndexer.java index 4837ab97ee..1f0b177405 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/etl/SchoolIndexer.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/etl/SchoolIndexer.java @@ -9,7 +9,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.ac.cam.cl.dtg.segue.api.Constants; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentSubclassMapper; import uk.ac.cam.cl.dtg.segue.dao.schools.UnableToIndexSchoolsException; import uk.ac.cam.cl.dtg.isaac.dos.users.School; import uk.ac.cam.cl.dtg.segue.search.SegueSearchException; @@ -33,10 +33,10 @@ class SchoolIndexer { private static final Logger log = LoggerFactory.getLogger(SchoolIndexer.class); private ElasticSearchIndexer es; - private ContentMapperUtils mapper; + private ContentSubclassMapper mapper; private String schoolsListPath; - SchoolIndexer(ElasticSearchIndexer es, ContentMapperUtils mapper, String schoolsListPath) { + SchoolIndexer(ElasticSearchIndexer es, ContentSubclassMapper mapper, String schoolsListPath) { this.es = es; this.mapper = mapper; this.schoolsListPath = schoolsListPath; diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java index 95fb52b9a7..1af30b0458 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java @@ -14,6 +14,9 @@ import uk.ac.cam.cl.dtg.isaac.dto.QuizAssignmentDTO; import uk.ac.cam.cl.dtg.isaac.dto.QuizAttemptDTO; +/** + * MapStruct mapper for Gameboard, Assignment, QuizAssignment and QuizAttempt objects. + */ @Mapper public interface AssignmentMapper { diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index 49643642ad..2e7315153e 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -13,6 +13,9 @@ import java.util.List; +/** + * MapStruct mapper for Content objects. + */ @Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) public interface ContentMapper { ContentMapper INSTANCE = Mappers.getMapper(ContentMapper.class); @@ -129,8 +132,11 @@ default T map(Content source, Class targetClass) { @Mapping(target = "attribution", ignore = true) SidebarDTO map(String source); - @InheritInverseConfiguration(name = "mapChoice") - Choice mapChoice(ChoiceDTO source); + @Mapping(target = "searchableContent", ignore = true) + @Mapping(target = "prioritisedSearchableContent", ignore = true) + @Mapping(target = "explanation", ignore = true) + @Mapping(target = "correct", ignore = true) + Choice map(ChoiceDTO source); @SubclassMapping(source = ChemicalFormula.class, target = ChemicalFormulaDTO.class) @SubclassMapping(source = Formula.class, target = FormulaDTO.class) @@ -142,11 +148,12 @@ default T map(Content source, Class targetClass) { @SubclassMapping(source = Quantity.class, target = QuantityDTO.class) @SubclassMapping(source = RegexPattern.class, target = RegexPatternDTO.class) @SubclassMapping(source = StringChoice.class, target = StringChoiceDTO.class) - ChoiceDTO mapChoice(Choice source); + ChoiceDTO map(Choice source); + + List copyStringList(List source); - List copyListOfString(List source); default ResultsWrapper copy(ResultsWrapper source) { - return new ResultsWrapper<>(copyListOfString(source.getResults()), source.getTotalResults()); + return new ResultsWrapper<>(copyStringList(source.getResults()), source.getTotalResults()); } @InheritInverseConfiguration(name = "mapContent") @@ -175,21 +182,29 @@ default ResultsWrapper copy(ResultsWrapper source) { @SubclassMapping(source = SidebarEntry.class, target = SidebarEntryDTO.class) ContentDTO mapContent(Content source); - List mapListOfContentSummaryDtoToListOfString(List source); + List mapContentSummaryDTOListToStringList(List source); - List mapListOfStringToListOfContentSummaryDTO(List source); + List mapStringListToContentSummaryDTOList(List source); @SubclassMapping(source = IsaacEventPageDTO.class, target = IsaacEventPageDTO.class) ContentDTO copy(ContentDTO source); - default ContentSummaryDTO mapStringToContentSummaryDTO(String source) { - if (source == null) { - return null; - } - ContentSummaryDTO contentSummaryDTO = new ContentSummaryDTO(); - contentSummaryDTO.setId(source); - return contentSummaryDTO; - } + // Create a new ContentSummaryDTO and set the id to the source string + @Mapping(source = "source", target = "id") + @Mapping(target = "url", ignore = true) + @Mapping(target = "type", ignore = true) + @Mapping(target = "title", ignore = true) + @Mapping(target = "tags", ignore = true) + @Mapping(target = "supersededBy", ignore = true) + @Mapping(target = "summary", ignore = true) + @Mapping(target = "subtitle", ignore = true) + @Mapping(target = "state", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "level", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @Mapping(target = "deprecated", ignore = true) + @Mapping(target = "audience", ignore = true) + ContentSummaryDTO mapStringToContentSummaryDTO(String source); default String mapContentSummaryDTOtoString(ContentSummaryDTO source) { if (source == null) { diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java index 6783bf04f4..b04c406e28 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java @@ -8,32 +8,17 @@ import uk.ac.cam.cl.dtg.isaac.dto.eventbookings.DetailedEventBookingDTO; import uk.ac.cam.cl.dtg.isaac.dto.eventbookings.EventBookingDTO; +/** + * MapStruct mapper for EventBooking objects. + */ @Mapper(uses = UserMapper.class) public interface EventMapper { - @SuppressWarnings("unchecked") - default T map(DetailedEventBookingDTO source, Class targetClass) { - if (targetClass.equals(EventBookingDTO.class)) { - return (T) mapDetailedEventBookingDTOtoEventBookingDTO(source); - } else { - throw new UnimplementedMappingException(DetailedEventBookingDTO.class, targetClass); - } - } + EventBookingDTO map(DetailedEventBookingDTO source); - @SuppressWarnings("unchecked") - default List mapAsList(List source, Class sourceClass, Class targetClass) { - if (sourceClass.equals(EventBookingDTO.class) && targetClass.equals(EventBookingDTO.class)) { - return (List) copyListOfEventBookingDTO((List) source); - } else if (sourceClass.equals(DetailedEventBookingDTO.class) && targetClass.equals(EventBookingDTO.class)) { - return (List) mapListOfDetailedEventBookingDTOtoEventBookingDTO((List) source); - } else { - throw new UnimplementedMappingException(sourceClass, targetClass); - } - } + List copy(List source); + List map(List source); @Mapping(source = "userBooked", target = "userBooked", qualifiedByName = "copyUserSummaryDTO") EventBookingDTO copy(EventBookingDTO source); - List copyListOfEventBookingDTO(List source); - EventBookingDTO mapDetailedEventBookingDTOtoEventBookingDTO(DetailedEventBookingDTO source); - List mapListOfDetailedEventBookingDTOtoEventBookingDTO(List source); } \ No newline at end of file diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java index 7935b9308f..437c6d87ff 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java @@ -4,6 +4,9 @@ import org.mapstruct.SubclassExhaustiveStrategy; import org.mapstruct.factory.Mappers; +/** + * Main MapStruct mapper interface. + */ @Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) public interface MainMapper extends ContentMapper, UserMapper, EventMapper, AssignmentMapper, QuestionValidationMapper { MainMapper INSTANCE = Mappers.getMapper(MainMapper.class); diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapper.java index fc22c0d7a1..515e4dd2a6 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapper.java @@ -9,13 +9,19 @@ import uk.ac.cam.cl.dtg.isaac.dos.QuestionValidationResponse; import uk.ac.cam.cl.dtg.isaac.dto.FormulaValidationResponseDTO; import uk.ac.cam.cl.dtg.isaac.dto.ItemValidationResponseDTO; +import uk.ac.cam.cl.dtg.isaac.dto.LLMFreeTextQuestionValidationResponseDTO; import uk.ac.cam.cl.dtg.isaac.dto.QuantityValidationResponseDTO; import uk.ac.cam.cl.dtg.isaac.dto.QuestionValidationResponseDTO; +/** + * MapStruct mapper for QuestionValidationResponse objects. + */ @Mapper(uses = ContentMapper.class) public interface QuestionValidationMapper { + @SubclassMapping(source = FormulaValidationResponseDTO.class, target = FormulaValidationResponse.class) @SubclassMapping(source = ItemValidationResponseDTO.class, target = ItemValidationResponse.class) + @SubclassMapping(source = LLMFreeTextQuestionValidationResponseDTO.class, target = QuestionValidationResponse.class) @SubclassMapping(source = QuantityValidationResponseDTO.class, target = QuantityValidationResponse.class) QuestionValidationResponse map(QuestionValidationResponseDTO source); diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UnimplementedMappingException.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UnimplementedMappingException.java index a1a8161f4e..c7bf270852 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UnimplementedMappingException.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UnimplementedMappingException.java @@ -1,5 +1,8 @@ package uk.ac.cam.cl.dtg.util.mappers; +/** + * An exception that indicates no mapping implementation exists between two classes. + */ public class UnimplementedMappingException extends UnsupportedOperationException { public UnimplementedMappingException(Class sourceClass, Class targetClass) { super(String.format("Mapping from %s to %s is not implemented", sourceClass.getSimpleName(), targetClass.getSimpleName())); diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java index fe0206e91a..49c7002a38 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java @@ -26,19 +26,13 @@ import uk.ac.cam.cl.dtg.isaac.dto.users.UserSummaryWithEmailAddressDTO; import uk.ac.cam.cl.dtg.isaac.dto.users.UserSummaryWithGroupMembershipDTO; +/** + * MapStruct mapper for User objects. + */ @Mapper public interface UserMapper { - UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); - - @Mapping(target = "sessionToken", ignore = true) - @Mapping(target = "emailVerificationToken", ignore = true) - @Mapping(target = "emailToVerify", ignore = true) - RegisteredUser map(RegisteredUserDTO source); - @Mapping(target = "firstLogin", ignore = true) - RegisteredUserDTO map(RegisteredUser source); - UserAuthenticationSettingsDTO map(UserAuthenticationSettings source); - AnonymousUserDTO map(AnonymousUser source); + UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); @SuppressWarnings("unchecked") default T map(RegisteredUserDTO source, Class targetClass) { @@ -55,15 +49,6 @@ default T map(RegisteredUserDTO source, Class targ } } - @SuppressWarnings("unchecked") - default T map(UserFromAuthProvider source, Class targetClass) { - if (targetClass.equals(RegisteredUser.class)) { - return (T) mapUserFromAuthProviderToRegisteredUser(source); - } else { - throw new UnimplementedMappingException(UserFromAuthProvider.class, targetClass); - } - } - @SuppressWarnings("unchecked") default T map(UserSummaryDTO source, Class targetClass) { if (targetClass.equals(UserSummaryWithGroupMembershipDTO.class)) { @@ -102,9 +87,20 @@ default T map(UserSummaryDTO source, Class targetClass) { @Mapping(target = "authorisedFullAccess", ignore = true) UserSummaryWithGroupMembershipDTO mapUserToSummaryWithGroupMembershipDTO(RegisteredUserDTO source); + @Mapping(target = "sessionToken", ignore = true) + @Mapping(target = "emailVerificationToken", ignore = true) + @Mapping(target = "emailToVerify", ignore = true) + RegisteredUser map(RegisteredUserDTO source); + + @Mapping(target = "firstLogin", ignore = true) + RegisteredUserDTO map(RegisteredUser source); + + UserAuthenticationSettingsDTO map(UserAuthenticationSettings source); + + AnonymousUserDTO map(AnonymousUser source); + RegisteredUser copy(RegisteredUser source); RegisteredUserDTO copy(RegisteredUserDTO source); - GroupMembershipDTO copy(GroupMembershipDTO source); @Mapping(target = "sessionToken", ignore = true) @@ -126,7 +122,7 @@ default T map(UserSummaryDTO source, Class targetClass) { @Mapping(target = "id", ignore = true) @Mapping(target = "emailVerificationToken", ignore = true) @Mapping(target = "emailToVerify", ignore = true) - RegisteredUser mapUserFromAuthProviderToRegisteredUser(UserFromAuthProvider source); + RegisteredUser map(UserFromAuthProvider source); @Mapping(target = "groupMembershipInformation", ignore = true) @SubclassMapping(source = UserSummaryForAdminUsersDTO.class, target = UserSummaryWithGroupMembershipDTO.class) @@ -137,11 +133,13 @@ default T map(UserSummaryDTO source, Class targetClass) { @BeanMapping(resultType = UserSummaryDTO.class) UserSummaryDTO mapExtendedUserSummaryDTOtoBaseUserSummaryDTO(UserSummaryDTO source); + // Named mapping for use in EventMapper @Named("copyUserSummaryDTO") @SubclassMapping(source = UserSummaryForAdminUsersDTO.class, target = UserSummaryForAdminUsersDTO.class) @SubclassMapping(source = UserSummaryWithEmailAddressDTO.class, target = UserSummaryWithEmailAddressDTO.class) @SubclassMapping(source = UserSummaryWithGroupMembershipDTO.class, target = UserSummaryWithGroupMembershipDTO.class) UserSummaryDTO copy(UserSummaryDTO source); + UserSummaryForAdminUsersDTO copy(UserSummaryForAdminUsersDTO source); UserSummaryWithEmailAddressDTO copy(UserSummaryWithEmailAddressDTO source); UserSummaryWithGroupMembershipDTO copy(UserSummaryWithGroupMembershipDTO source); diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java index 27afa2a324..576719b0ac 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/AbstractIsaacIntegrationTest.java @@ -70,7 +70,7 @@ import uk.ac.cam.cl.dtg.segue.dao.ILogManager; import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException; import uk.ac.cam.cl.dtg.segue.dao.associations.PgAssociationDataManager; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentSubclassMapper; import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager; import uk.ac.cam.cl.dtg.segue.dao.schools.SchoolListReader; import uk.ac.cam.cl.dtg.segue.dao.users.IDeletionTokenPersistenceManager; @@ -242,7 +242,7 @@ public static void setUpClass() throws Exception { pgAnonymousUsers = new PgAnonymousUsers(postgresSqlDb); passwordDataManager = new PgPasswordDataManager(postgresSqlDb); - ContentMapperUtils contentMapper = new ContentMapperUtils(new Reflections("uk.ac.cam.cl.dtg")); + ContentSubclassMapper contentMapper = new ContentSubclassMapper(new Reflections("uk.ac.cam.cl.dtg")); PgQuestionAttempts pgQuestionAttempts = new PgQuestionAttempts(postgresSqlDb, contentMapper); questionManager = new QuestionManager(contentMapper, mainMapper, pgQuestionAttempts); diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/QuestionFacadeTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/QuestionFacadeTest.java index eaf31421f8..22a345f300 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/QuestionFacadeTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/QuestionFacadeTest.java @@ -33,7 +33,7 @@ import uk.ac.cam.cl.dtg.segue.api.monitors.IMisuseMonitor; import uk.ac.cam.cl.dtg.segue.dao.ILogManager; import uk.ac.cam.cl.dtg.segue.dao.content.ContentManagerException; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentSubclassMapper; import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager; import uk.ac.cam.cl.dtg.util.AbstractConfigLoader; @@ -68,12 +68,12 @@ private void setUpQuestionFacade() throws ContentManagerException { GitContentManager contentManager = createMock(GitContentManager.class); ILogManager logManager = createNiceMock(ILogManager.class); // We don't care about logging. - ContentMapperUtils contentMapperUtils = createMock(ContentMapperUtils.class); + ContentSubclassMapper contentSubclassMapper = createMock(ContentSubclassMapper.class); QuestionManager questionManager = createMock(QuestionManager.class); IUserStreaksManager userStreaksManager = createMock(IUserStreaksManager.class); UserAssociationManager userAssociationManager = createMock(UserAssociationManager.class); - questionFacade = new QuestionFacade(properties, contentMapperUtils, contentManager, userManager, userPreferenceManager, + questionFacade = new QuestionFacade(properties, contentSubclassMapper, contentManager, userManager, userPreferenceManager, questionManager, logManager, misuseMonitor, userStreaksManager, userAssociationManager); String contentIndex = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManagerTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManagerTest.java index 151d5ee4cb..b73cf69597 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManagerTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManagerTest.java @@ -84,9 +84,9 @@ public void setUp() { quizQuestionManager = new QuizQuestionManager(questionManager, contentMapper, quizQuestionAttemptPersistenceManager, quizManager, quizAttemptManager); - expect(contentMapper.mapChoice(correctAnswer)).andStubReturn(correctAnswerDTO); - expect(contentMapper.mapChoice(wrongAnswer)).andStubReturn(wrongAnswerDTO); - expect(contentMapper.mapChoice((ChoiceDTO) null)).andStubReturn(null); + expect(contentMapper.map(correctAnswer)).andStubReturn(correctAnswerDTO); + expect(contentMapper.map(wrongAnswer)).andStubReturn(wrongAnswerDTO); + expect(contentMapper.map((ChoiceDTO) null)).andStubReturn(null); registerDefaultsFor(questionManager, m -> { diff --git a/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java b/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java index 86044bb98d..c0ab04ff97 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java @@ -390,7 +390,7 @@ public final void authenticateCallback_checkNewUserIsAuthenticated_createInterna RegisteredUserDTO mappedUserDTO = new RegisteredUserDTO(); - expect(dummyMapper.map(providerUser, RegisteredUser.class)).andReturn(mappedUser).atLeastOnce(); + expect(dummyMapper.map(providerUser)).andReturn(mappedUser).atLeastOnce(); expect(dummyMapper.map(mappedUser)).andReturn(mappedUserDTO).atLeastOnce(); expect(dummyMapper.map(au)).andReturn(someAnonymousUserDTO).anyTimes(); diff --git a/src/test/java/uk/ac/cam/cl/dtg/segue/dao/ContentMapperTest.java b/src/test/java/uk/ac/cam/cl/dtg/segue/dao/ContentMapperTest.java index 983f0510f5..243dc01a74 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/segue/dao/ContentMapperTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/segue/dao/ContentMapperTest.java @@ -3,7 +3,7 @@ import org.junit.Before; import org.junit.Test; import org.reflections.Reflections; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentSubclassMapper; import uk.ac.cam.cl.dtg.isaac.dos.content.CodeSnippet; import uk.ac.cam.cl.dtg.isaac.dos.content.Content; import uk.ac.cam.cl.dtg.isaac.dto.content.ContentDTO; @@ -12,11 +12,11 @@ public class ContentMapperTest { - private ContentMapperUtils contentMapperUtils; + private ContentSubclassMapper contentSubclassMapper; @Before public void setUp() { - this.contentMapperUtils = new ContentMapperUtils(new Reflections("uk.ac.cam.cl.dtg.isaac")); + this.contentSubclassMapper = new ContentSubclassMapper(new Reflections("uk.ac.cam.cl.dtg.isaac")); } @Test @@ -27,7 +27,7 @@ public void getDTOByDO_CodeSnippetDOtoDTO_ExpandableSetInDTO() { codeSnippet.setExpandable(true); // Act - ContentDTO codeSnippetDTO = contentMapperUtils.getDTOByDO(codeSnippet); + ContentDTO codeSnippetDTO = contentSubclassMapper.getDTOByDO(codeSnippet); // Assert assertTrue(codeSnippetDTO.getExpandable()); @@ -41,7 +41,7 @@ public void getDTOByDO_ContentDOtoDTO_ExpandableSetInDTO() { content.setExpandable(true); // Act - ContentDTO contentDTO = contentMapperUtils.getDTOByDO(content); + ContentDTO contentDTO = contentSubclassMapper.getDTOByDO(content); // Assert assertTrue(contentDTO.getExpandable()); diff --git a/src/test/java/uk/ac/cam/cl/dtg/segue/dao/GitContentManagerTest.java b/src/test/java/uk/ac/cam/cl/dtg/segue/dao/GitContentManagerTest.java index 1195d25dbd..8e161f7484 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/segue/dao/GitContentManagerTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/segue/dao/GitContentManagerTest.java @@ -19,7 +19,7 @@ import org.junit.Test; import org.powermock.core.classloader.annotations.PowerMockIgnore; import uk.ac.cam.cl.dtg.segue.dao.content.ContentManagerException; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentSubclassMapper; import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager; import uk.ac.cam.cl.dtg.segue.database.GitDb; import uk.ac.cam.cl.dtg.isaac.dos.content.Content; @@ -42,7 +42,7 @@ public class GitContentManagerTest { private GitDb database; private ISearchProvider searchProvider; private ContentMapper contentMapper; - private ContentMapperUtils contentMapperUtils; + private ContentSubclassMapper contentSubclassMapper; private GitContentManager defaultGCM; @@ -59,9 +59,9 @@ public final void setUp() throws Exception { this.database = createMock(GitDb.class); this.searchProvider = createMock(ISearchProvider.class); this.contentMapper = createMock(ContentMapper.class); - this.contentMapperUtils = createMock(ContentMapperUtils.class); + this.contentSubclassMapper = createMock(ContentSubclassMapper.class); - this.defaultGCM = new GitContentManager(database, searchProvider, contentMapper, contentMapperUtils); + this.defaultGCM = new GitContentManager(database, searchProvider, contentMapper, contentSubclassMapper); } /** * Test that the getById method returns null if it is passed a null id. @@ -112,6 +112,6 @@ private GitContentManager validateReferentialIntegrity_setUpTest( Map contents = new TreeMap(); contents.put(INITIAL_VERSION, content); - return new GitContentManager(database, searchProvider, contentMapper, contentMapperUtils); + return new GitContentManager(database, searchProvider, contentMapper, contentSubclassMapper); } } diff --git a/src/test/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexerTest.java b/src/test/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexerTest.java index 41918d30ee..ff8912e50c 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexerTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/segue/etl/ContentIndexerTest.java @@ -32,7 +32,7 @@ import uk.ac.cam.cl.dtg.isaac.dos.IsaacNumericQuestion; import uk.ac.cam.cl.dtg.segue.api.Constants; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentMapperUtils; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentSubclassMapper; import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager; import uk.ac.cam.cl.dtg.segue.database.GitDb; import uk.ac.cam.cl.dtg.isaac.dos.content.Content; @@ -48,7 +48,7 @@ public class ContentIndexerTest { private GitDb database; private ElasticSearchIndexer searchProvider; private ContentMapper contentMapper; - private ContentMapperUtils contentMapperUtils; + private ContentSubclassMapper contentSubclassMapper; private ContentIndexer defaultContentIndexer; @@ -65,8 +65,8 @@ public final void setUp() throws Exception { this.database = createMock(GitDb.class); this.searchProvider = createMock(ElasticSearchIndexer.class); this.contentMapper = createMock(ContentMapper.class); - this.contentMapperUtils = createMock(ContentMapperUtils.class); - this.defaultContentIndexer = new ContentIndexer(database, searchProvider, contentMapperUtils); + this.contentSubclassMapper = createMock(ContentSubclassMapper.class); + this.defaultContentIndexer = new ContentIndexer(database, searchProvider, contentSubclassMapper); } /** @@ -109,7 +109,7 @@ public void buildSearchIndexes_sendContentToSearchProvider_checkSearchProviderIs // prepare pre-canned responses for the object mapper ObjectMapper objectMapper = createMock(ObjectMapper.class); - expect(contentMapperUtils.generateNewPreconfiguredContentMapper()).andReturn(objectMapper) + expect(contentSubclassMapper.generateNewPreconfiguredContentMapper()).andReturn(objectMapper) .once(); expect(objectMapper.writeValueAsString(content)).andReturn( uniqueObjectHash).once(); @@ -152,9 +152,9 @@ public void buildSearchIndexes_sendContentToSearchProvider_checkSearchProviderIs searchProvider.bulkIndexWithIDs(eq(INITIAL_VERSION), eq(Constants.CONTENT_INDEX_TYPE.CONTENT.toString()), anyObject()); expectLastCall().once(); - replay(searchProvider, contentMapper, contentMapperUtils, objectMapper); + replay(searchProvider, contentMapper, contentSubclassMapper, objectMapper); - ContentIndexer contentIndexer = new ContentIndexer(database, searchProvider, contentMapperUtils); + ContentIndexer contentIndexer = new ContentIndexer(database, searchProvider, contentSubclassMapper); // Method under test Whitebox.invokeMethod(contentIndexer, @@ -476,6 +476,6 @@ private GitContentManager validateReferentialIntegrity_setUpTest( Map contents = new TreeMap(); contents.put(INITIAL_VERSION, content); - return new GitContentManager(database, searchProvider, contentMapper, contentMapperUtils); + return new GitContentManager(database, searchProvider, contentMapper, contentSubclassMapper); } } From c81b269ae98deb5d555d4843a3c6d74afb737a2f Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Wed, 1 Oct 2025 16:31:49 +0100 Subject: [PATCH 36/65] Rename ContentSubclassMapper instances --- .../segue/api/managers/QuestionManager.java | 14 +++---- .../SegueGuiceConfigurationModule.java | 8 ++-- .../segue/dao/content/GitContentManager.java | 40 +++++++++---------- .../cl/dtg/util/mappers/ContentMapper.java | 6 +-- .../uk/ac/cam/cl/dtg/isaac/IsaacTest.java | 6 +-- .../api/managers/QuizQuestionManagerTest.java | 2 - 6 files changed, 37 insertions(+), 39 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java index ac9e0d1b71..28a71c9132 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java @@ -87,7 +87,7 @@ public class QuestionManager { private static final Logger log = LoggerFactory.getLogger(QuestionManager.class); - private final ContentSubclassMapper mapperUtils; + private final ContentSubclassMapper contentSubclassMapper; private final MainMapper mapper; private final IQuestionAttemptManager questionAttemptPersistenceManager; /** @@ -98,8 +98,8 @@ public class QuestionManager { * @param questionPersistenceManager - for question attempt persistence. */ @Inject - public QuestionManager(final ContentSubclassMapper mapperUtils, final MainMapper mapper, final IQuestionAttemptManager questionPersistenceManager) { - this.mapperUtils = mapperUtils; + public QuestionManager(final ContentSubclassMapper contentSubclassMapper, final MainMapper mapper, final IQuestionAttemptManager questionPersistenceManager) { + this.contentSubclassMapper = contentSubclassMapper; this.mapper = mapper; this.questionAttemptPersistenceManager = questionPersistenceManager; } @@ -123,7 +123,7 @@ public final Response validateAnswer(final Question question, final ChoiceDTO su .build(); } - Choice answerFromUser = mapper.map(submittedAnswer, Choice.class); + Choice answerFromUser = mapper.map(submittedAnswer); QuestionValidationResponse validateQuestionResponse; Histogram.Timer validatorTimer = VALIDATOR_LATENCY_HISTOGRAM.labels(validator.getClass().getSimpleName()).startTimer(); @@ -337,7 +337,7 @@ public List testQuestion(final String questionType, final TestQuestion throws BadRequestException, ValidatorUnavailableException { try { // Create a fake question - Class questionClass = mapperUtils.getClassByType(questionType); + Class questionClass = contentSubclassMapper.getClassByType(questionType); if (null == questionClass || !ChoiceQuestion.class.isAssignableFrom(questionClass)) { throw new BadRequestException(String.format("Not a valid questionType (%s)", questionType)); } @@ -622,7 +622,7 @@ public final Response generateSpecification(final ChoiceDTO answer) { .build(); } - Choice answerFromUser = mapper.map(answer, Choice.class); + Choice answerFromUser = mapper.map(answer); String specification; try { specification = specifier.createSpecification(answerFromUser); @@ -644,7 +644,7 @@ public ChoiceDTO convertJsonAnswerToChoice(String jsonAnswer) throws ErrorRespon ChoiceDTO answerFromClientDTO; try { // convert submitted JSON into a Choice: - Choice answerFromClient = mapperUtils.getSharedContentObjectMapper().readValue(jsonAnswer, Choice.class); + Choice answerFromClient = contentSubclassMapper.getSharedContentObjectMapper().readValue(jsonAnswer, Choice.class); // convert to a DTO so that it strips out any untrusted data. answerFromClientDTO = mapper.map(answerFromClient); } catch (JsonMappingException | JsonParseException e) { diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java index fba7bb0d00..45909766d9 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java @@ -547,7 +547,7 @@ private static RestHighLevelClient getSearchConnectionInformation( * - search provider to use * @param contentMapper * - defines the mappings for content objects - * @param mapperUtils + * @param contentSubclassMapper * - the utility class for mapping content objects * @return a fully configured content Manager. */ @@ -555,9 +555,9 @@ private static RestHighLevelClient getSearchConnectionInformation( @Provides @Singleton private static GitContentManager getContentManager(final GitDb database, final ISearchProvider searchProvider, final ContentMapper contentMapper, - final ContentSubclassMapper mapperUtils, final AbstractConfigLoader globalProperties) { + final ContentSubclassMapper contentSubclassMapper, final AbstractConfigLoader globalProperties) { if (null == contentManager) { - contentManager = new GitContentManager(database, searchProvider, contentMapper, mapperUtils, globalProperties); + contentManager = new GitContentManager(database, searchProvider, contentMapper, contentSubclassMapper, globalProperties); log.info("Creating singleton of ContentManager"); } @@ -792,7 +792,7 @@ private UserAuthenticationManager getUserAuthenticationManager(final IUserDataMa * - to manage temporary anonymous users * @param logManager * - so that we can log interesting user based events. - * @param mapperFacade + * @param userMapper * - for DO and DTO mapping. * @param userAuthenticationManager * - Responsible for handling the various authentication functions. diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java index 44dd000a1a..6fae5a51cf 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java @@ -85,7 +85,7 @@ public class GitContentManager { private final GitDb database; private final ContentMapper mapper; - private final ContentSubclassMapper mapperUtils; + private final ContentSubclassMapper contentSubclassMapper; private final ISearchProvider searchProvider; private final AbstractConfigLoader globalProperties; private final boolean showOnlyPublishedContent; @@ -107,17 +107,17 @@ public class GitContentManager { * - search provider that the content manager manages and controls. * @param contentMapper * - defines the mappings for content objects. - * @param mapperUtils + * @param contentSubclassMapper * - the utility class for mapping content objects. * @param globalProperties * - global properties. */ @Inject public GitContentManager(final GitDb database, final ISearchProvider searchProvider, final ContentMapper contentMapper, - final ContentSubclassMapper mapperUtils, final AbstractConfigLoader globalProperties) { + final ContentSubclassMapper contentSubclassMapper, final AbstractConfigLoader globalProperties) { this.database = database; this.mapper = contentMapper; - this.mapperUtils = mapperUtils; + this.contentSubclassMapper = contentSubclassMapper; this.searchProvider = searchProvider; this.globalProperties = globalProperties; @@ -152,15 +152,15 @@ public GitContentManager(final GitDb database, final ISearchProvider searchProvi * - search provider that the content manager manages and controls. * @param contentMapper * - defines the mappings for content objects. - * @param mapperUtils + * @param contentSubclassMapper * - the utility class for mapping content objects. */ public GitContentManager(final GitDb database, final ISearchProvider searchProvider, - final ContentMapper contentMapper, ContentSubclassMapper mapperUtils) { + final ContentMapper contentMapper, ContentSubclassMapper contentSubclassMapper) { this.database = database; this.mapper = contentMapper; this.searchProvider = searchProvider; - this.mapperUtils = mapperUtils; + this.contentSubclassMapper = contentSubclassMapper; this.globalProperties = null; this.showOnlyPublishedContent = false; this.hideRegressionTestContent = false; @@ -198,7 +198,7 @@ public final ContentDTO getContentById(final String id) throws ContentManagerExc * @throws ContentManagerException on failure to return the object or null. */ public final ContentDTO getContentById(final String id, final boolean failQuietly) throws ContentManagerException { - return this.mapperUtils.getDTOByDO(this.getContentDOById(id, failQuietly)); + return this.contentSubclassMapper.getDTOByDO(this.getContentDOById(id, failQuietly)); } /** @@ -212,7 +212,7 @@ public final ContentDTO getContentById(final String id, final boolean failQuietl * @return the DTO form of the object. */ public final ContentDTO getContentDTOByDO(final Content content) { - return this.mapperUtils.getDTOByDO(content); + return this.contentSubclassMapper.getDTOByDO(content); } /** @@ -257,7 +257,7 @@ public final Content getContentDOById(final String id, final boolean failQuietly CONTENT_TYPE, id, Constants.ID_FIELDNAME + "." + Constants.UNPROCESSED_SEARCH_FIELD_SUFFIX, 0, 1, getBaseFilters()); - List searchResults = mapperUtils.mapFromStringListToContentList(rawResults.getResults()); + List searchResults = contentSubclassMapper.mapFromStringListToContentList(rawResults.getResults()); return new ResultsWrapper<>(searchResults, rawResults.getTotalResults()); }); @@ -318,8 +318,8 @@ public ResultsWrapper getUnsafeCachedContentDTOsMatchingIds(final Co finalFilter ); - List searchResults = mapperUtils.mapFromStringListToContentList(searchHits.getResults()); - return new ResultsWrapper<>(mapperUtils.getDTOByDOList(searchResults), searchHits.getTotalResults()); + List searchResults = contentSubclassMapper.mapFromStringListToContentList(searchHits.getResults()); + return new ResultsWrapper<>(contentSubclassMapper.getDTOByDOList(searchResults), searchHits.getTotalResults()); }); } catch (final ExecutionException e) { throw new ContentManagerException(e.getCause().getMessage()); @@ -397,9 +397,9 @@ public final ResultsWrapper siteWideSearch( sortOrder ); - List searchResults = mapperUtils.mapFromStringListToContentList(searchHits.getResults()); + List searchResults = contentSubclassMapper.mapFromStringListToContentList(searchHits.getResults()); - return new ResultsWrapper<>(mapperUtils.getDTOByDOList(searchResults), searchHits.getTotalResults()); + return new ResultsWrapper<>(contentSubclassMapper.getDTOByDOList(searchResults), searchHits.getTotalResults()); } /** Search the content for questions (and fasttrack questions) that match a given user provided search string and @@ -503,9 +503,9 @@ public final ResultsWrapper questionSearch( sortOrder ); - List searchResults = mapperUtils.mapFromStringListToContentList(searchHits.getResults()); + List searchResults = contentSubclassMapper.mapFromStringListToContentList(searchHits.getResults()); - return new ResultsWrapper<>(mapperUtils.getDTOByDOList(searchResults), searchHits.getTotalResults()); + return new ResultsWrapper<>(contentSubclassMapper.getDTOByDOList(searchResults), searchHits.getTotalResults()); } public final ResultsWrapper findByFieldNames( @@ -551,9 +551,9 @@ public final ResultsWrapper findByFieldNames( // setup object mapper to use pre-configured deserializer module. // Required to deal with type polymorphism - List result = mapperUtils.mapFromStringListToContentList(searchHits.getResults()); + List result = contentSubclassMapper.mapFromStringListToContentList(searchHits.getResults()); - List contentDTOResults = mapperUtils.getDTOByDOList(result); + List contentDTOResults = contentSubclassMapper.getDTOByDOList(result); finalResults = new ResultsWrapper<>(contentDTOResults, searchHits.getTotalResults()); @@ -581,9 +581,9 @@ public final ResultsWrapper findByFieldNamesRandomOrder( // setup object mapper to use pre-configured deserializer module. // Required to deal with type polymorphism - List result = mapperUtils.mapFromStringListToContentList(searchHits.getResults()); + List result = contentSubclassMapper.mapFromStringListToContentList(searchHits.getResults()); - List contentDTOResults = mapperUtils.getDTOByDOList(result); + List contentDTOResults = contentSubclassMapper.getDTOByDOList(result); finalResults = new ResultsWrapper<>(contentDTOResults, searchHits.getTotalResults()); diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index 2e7315153e..01cc9c08e7 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -156,9 +156,6 @@ default ResultsWrapper copy(ResultsWrapper source) { return new ResultsWrapper<>(copyStringList(source.getResults()), source.getTotalResults()); } - @InheritInverseConfiguration(name = "mapContent") - Content mapContent(ContentDTO source); - @SubclassMapping(source = AnvilApp.class, target = AnvilAppDTO.class) @SubclassMapping(source = Choice.class, target = ChoiceDTO.class) @SubclassMapping(source = CodeSnippet.class, target = CodeSnippetDTO.class) @@ -182,6 +179,9 @@ default ResultsWrapper copy(ResultsWrapper source) { @SubclassMapping(source = SidebarEntry.class, target = SidebarEntryDTO.class) ContentDTO mapContent(Content source); + @InheritInverseConfiguration(name = "mapContent") + Content mapContent(ContentDTO source); + List mapContentSummaryDTOListToStringList(List source); List mapStringListToContentSummaryDTOList(List source); diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/IsaacTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/IsaacTest.java index 399543a93e..517bd5cab5 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/IsaacTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/IsaacTest.java @@ -274,9 +274,9 @@ protected void initializeMocks() throws ContentManagerException, SegueDatabaseEx groupDatabase = createMock(IUserGroupPersistenceManager.class); UserAccountManager userAccountManager = createMock(UserAccountManager.class); GameManager gameManager = createMock(GameManager.class); - MainMapper mapperFacade = createMock(MainMapper.class); + MainMapper mainMapper = createMock(MainMapper.class); groupManager = partialMockBuilder(GroupManager.class) - .withConstructor(groupDatabase, userAccountManager, gameManager, mapperFacade) + .withConstructor(groupDatabase, userAccountManager, gameManager, mainMapper) .addMockedMethod("getGroupById").addMockedMethod("isUserInGroup").addMockedMethod("getGroupMembershipList", RegisteredUserDTO.class, boolean.class) .addMockedMethod("getUsersInGroup").addMockedMethod("getUserMembershipMapForGroup").addMockedMethod("getAllGroupsOwnedAndManagedByUser") .createMock(); @@ -311,7 +311,7 @@ protected void initializeMocks() throws ContentManagerException, SegueDatabaseEx studentGroup.getId(), new GroupMembership(studentGroup.getId(), secondStudent.getId(), GroupMembershipStatus.ACTIVE, null, somePastDate) )); - replay(quizManager, groupManager, groupDatabase, userAccountManager, gameManager, mapperFacade); + replay(quizManager, groupManager, groupDatabase, userAccountManager, gameManager, mainMapper); } diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManagerTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManagerTest.java index b73cf69597..f175bd82b0 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManagerTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizQuestionManagerTest.java @@ -86,8 +86,6 @@ public void setUp() { expect(contentMapper.map(correctAnswer)).andStubReturn(correctAnswerDTO); expect(contentMapper.map(wrongAnswer)).andStubReturn(wrongAnswerDTO); - expect(contentMapper.map((ChoiceDTO) null)).andStubReturn(null); - registerDefaultsFor(questionManager, m -> { expect(m.convertQuestionValidationResponseToDTO(wrongResponse)).andStubReturn(wrongResponseDTO); From 06eedbe83e1d96a2525d5f4005df1d0a8f8425ad Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Wed, 1 Oct 2025 17:16:50 +0100 Subject: [PATCH 37/65] Fix Checkstyle warnings in mappers --- .../cl/dtg/util/mappers/AssignmentMapper.java | 4 + .../cl/dtg/util/mappers/ContentMapper.java | 180 +++++++++++------- .../cam/cl/dtg/util/mappers/EventMapper.java | 8 +- .../UnimplementedMappingException.java | 5 +- .../cam/cl/dtg/util/mappers/UserMapper.java | 89 +++++---- 5 files changed, 182 insertions(+), 104 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java index 1af30b0458..ec78819a6f 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java @@ -27,6 +27,7 @@ public interface AssignmentMapper { @Mapping(target = "lastVisited", ignore = true) @Mapping(target = "startedQuestion", ignore = true) GameboardDTO map(GameboardDO source); + GameboardDO map(GameboardDTO source); @Mapping(target = "legacyId", ignore = true) @@ -34,6 +35,7 @@ public interface AssignmentMapper { @Mapping(target = "groupName", ignore = true) @Mapping(target = "assignerSummary", ignore = true) AssignmentDTO map(AssignmentDO source); + AssignmentDO map(AssignmentDTO source); @Mapping(target = "quizSummary", ignore = true) @@ -42,6 +44,7 @@ public interface AssignmentMapper { @Mapping(target = "userFeedback", ignore = true) @Mapping(target = "quiz", ignore = true) QuizAssignmentDTO map(QuizAssignmentDO source); + QuizAssignmentDO map(QuizAssignmentDTO source); @Mapping(target = "quizSummary", ignore = true) @@ -49,6 +52,7 @@ public interface AssignmentMapper { @Mapping(target = "quizAssignment", ignore = true) @Mapping(target = "feedbackMode", ignore = true) QuizAttemptDTO map(QuizAttemptDO source); + QuizAttemptDO map(QuizAttemptDTO source); // Handle mapping the "content" field for GameboardD(T)Os diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index 01cc9c08e7..f5292f81c7 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -6,9 +6,28 @@ import org.mapstruct.SubclassExhaustiveStrategy; import org.mapstruct.SubclassMapping; import org.mapstruct.factory.Mappers; -import uk.ac.cam.cl.dtg.isaac.dos.*; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacCard; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacCardDeck; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacEventPage; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacFeaturedProfile; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacPageFragment; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacPod; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacQuiz; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacQuizSection; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacWildcard; import uk.ac.cam.cl.dtg.isaac.dos.content.*; -import uk.ac.cam.cl.dtg.isaac.dto.*; +import uk.ac.cam.cl.dtg.isaac.dto.GameboardItem; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacCardDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacCardDeckDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacEventPageDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacFeaturedProfileDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacPageFragmentDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacPodDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuizDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuizSectionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacWildcardDTO; +import uk.ac.cam.cl.dtg.isaac.dto.LLMFreeTextChoiceDTO; +import uk.ac.cam.cl.dtg.isaac.dto.ResultsWrapper; import uk.ac.cam.cl.dtg.isaac.dto.content.*; import java.util.List; @@ -20,12 +39,17 @@ public interface ContentMapper { ContentMapper INSTANCE = Mappers.getMapper(ContentMapper.class); - @SubclassMapping(source = ContentDTO.class, target = Content.class) - ContentBase map(ContentBaseDTO source); - - @SubclassMapping(source = Content.class, target = ContentDTO.class) - ContentBaseDTO map(ContentBase source); - + /** + * Mapping method to convert a ContentDTO to various other types of DTO or a Wildcard. + * + * @param + * - the target type. + * @param source + * - the source ContentDTO. + * @param targetClass + * - the target class to convert to. + * @return Returns an instance of the targetClass type mapped via the appropriate mapping method. + */ @SuppressWarnings("unchecked") default T map(ContentDTO source, Class targetClass) { if (targetClass.equals(ContentSummaryDTO.class)) { @@ -43,17 +67,80 @@ default T map(ContentDTO source, Class targetClass) { } } + /** + * Mapping method to convert a Content object to a ContentDTO or a Wildcard. + * + * @param + * - the target type. + * @param source + * - the source Content object. + * @param targetClass + * - the target class to convert to. + * @return Returns an instance of the targetClass type mapped via the appropriate mapping method. + */ @SuppressWarnings("unchecked") default T map(Content source, Class targetClass) { - if (targetClass.equals(IsaacWildcard.class)) { - return (T) mapContentToIsaacWildcard(source); - } else if (targetClass.equals(ContentDTO.class)) { + if (targetClass.equals(ContentDTO.class)) { return (T) mapContent(source); + } else if (targetClass.equals(IsaacWildcard.class)) { + return (T) mapContentToIsaacWildcard(source); } else { throw new UnimplementedMappingException(Content.class, targetClass); } } + @SubclassMapping(source = ContentDTO.class, target = Content.class) + ContentBase map(ContentBaseDTO source); + + @SubclassMapping(source = Content.class, target = ContentDTO.class) + ContentBaseDTO map(ContentBase source); + + @Mapping(target = "version", ignore = true) + @Mapping(target = "value", ignore = true) + @Mapping(target = "type", ignore = true) + @Mapping(target = "title", ignore = true) + @Mapping(target = "tags", ignore = true) + @Mapping(target = "subtitle", ignore = true) + @Mapping(target = "sidebarEntries", ignore = true) + @Mapping(target = "relatedContent", ignore = true) + @Mapping(target = "published", ignore = true) + @Mapping(target = "level", ignore = true) + @Mapping(target = "layout", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "expandable", ignore = true) + @Mapping(target = "encoding", ignore = true) + @Mapping(target = "display", ignore = true) + @Mapping(target = "children", ignore = true) + @Mapping(target = "canonicalSourceFile", ignore = true) + @Mapping(target = "author", ignore = true) + @Mapping(target = "audience", ignore = true) + @Mapping(target = "attribution", ignore = true) + SidebarDTO map(String source); + + @Mapping(target = "searchableContent", ignore = true) + @Mapping(target = "prioritisedSearchableContent", ignore = true) + @Mapping(target = "explanation", ignore = true) + @Mapping(target = "correct", ignore = true) + Choice map(ChoiceDTO source); + + @SubclassMapping(source = ChemicalFormula.class, target = ChemicalFormulaDTO.class) + @SubclassMapping(source = Formula.class, target = FormulaDTO.class) + @SubclassMapping(source = FreeTextRule.class, target = FreeTextRuleDTO.class) + @SubclassMapping(source = GraphChoice.class, target = GraphChoiceDTO.class) + @SubclassMapping(source = ItemChoice.class, target = ItemChoiceDTO.class) + @SubclassMapping(source = LLMFreeTextChoice.class, target = LLMFreeTextChoiceDTO.class) + @SubclassMapping(source = LogicFormula.class, target = LogicFormulaDTO.class) + @SubclassMapping(source = Quantity.class, target = QuantityDTO.class) + @SubclassMapping(source = RegexPattern.class, target = RegexPatternDTO.class) + @SubclassMapping(source = StringChoice.class, target = StringChoiceDTO.class) + ChoiceDTO map(Choice source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "state", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + DetailedQuizSummaryDTO map(IsaacQuizDTO source); + @Mapping(target = "url", ignore = true) @Mapping(target = "supersededBy", ignore = true) @Mapping(target = "summary", ignore = true) @@ -104,58 +191,6 @@ default T map(Content source, Class targetClass) { @SubclassMapping(source = IsaacQuizDTO.class, target = DetailedQuizSummaryDTO.class) DetailedQuizSummaryDTO mapContentDTOtoDetailedQuizSummaryDTO(ContentDTO source); - @Mapping(target = "url", ignore = true) - @Mapping(target = "state", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - DetailedQuizSummaryDTO map(IsaacQuizDTO source); - - @Mapping(target = "version", ignore = true) - @Mapping(target = "value", ignore = true) - @Mapping(target = "type", ignore = true) - @Mapping(target = "title", ignore = true) - @Mapping(target = "tags", ignore = true) - @Mapping(target = "subtitle", ignore = true) - @Mapping(target = "sidebarEntries", ignore = true) - @Mapping(target = "relatedContent", ignore = true) - @Mapping(target = "published", ignore = true) - @Mapping(target = "level", ignore = true) - @Mapping(target = "layout", ignore = true) - @Mapping(target = "id", ignore = true) - @Mapping(target = "expandable", ignore = true) - @Mapping(target = "encoding", ignore = true) - @Mapping(target = "display", ignore = true) - @Mapping(target = "children", ignore = true) - @Mapping(target = "canonicalSourceFile", ignore = true) - @Mapping(target = "author", ignore = true) - @Mapping(target = "audience", ignore = true) - @Mapping(target = "attribution", ignore = true) - SidebarDTO map(String source); - - @Mapping(target = "searchableContent", ignore = true) - @Mapping(target = "prioritisedSearchableContent", ignore = true) - @Mapping(target = "explanation", ignore = true) - @Mapping(target = "correct", ignore = true) - Choice map(ChoiceDTO source); - - @SubclassMapping(source = ChemicalFormula.class, target = ChemicalFormulaDTO.class) - @SubclassMapping(source = Formula.class, target = FormulaDTO.class) - @SubclassMapping(source = FreeTextRule.class, target = FreeTextRuleDTO.class) - @SubclassMapping(source = GraphChoice.class, target = GraphChoiceDTO.class) - @SubclassMapping(source = ItemChoice.class, target = ItemChoiceDTO.class) - @SubclassMapping(source = LLMFreeTextChoice.class, target = LLMFreeTextChoiceDTO.class) - @SubclassMapping(source = LogicFormula.class, target = LogicFormulaDTO.class) - @SubclassMapping(source = Quantity.class, target = QuantityDTO.class) - @SubclassMapping(source = RegexPattern.class, target = RegexPatternDTO.class) - @SubclassMapping(source = StringChoice.class, target = StringChoiceDTO.class) - ChoiceDTO map(Choice source); - - List copyStringList(List source); - - default ResultsWrapper copy(ResultsWrapper source) { - return new ResultsWrapper<>(copyStringList(source.getResults()), source.getTotalResults()); - } - @SubclassMapping(source = AnvilApp.class, target = AnvilAppDTO.class) @SubclassMapping(source = Choice.class, target = ChoiceDTO.class) @SubclassMapping(source = CodeSnippet.class, target = CodeSnippetDTO.class) @@ -186,9 +221,6 @@ default ResultsWrapper copy(ResultsWrapper source) { List mapStringListToContentSummaryDTOList(List source); - @SubclassMapping(source = IsaacEventPageDTO.class, target = IsaacEventPageDTO.class) - ContentDTO copy(ContentDTO source); - // Create a new ContentSummaryDTO and set the id to the source string @Mapping(source = "source", target = "id") @Mapping(target = "url", ignore = true) @@ -206,10 +238,26 @@ default ResultsWrapper copy(ResultsWrapper source) { @Mapping(target = "audience", ignore = true) ContentSummaryDTO mapStringToContentSummaryDTO(String source); + /** + * Mapping method to convert a ContentSummaryDTO to a String of its ID. + * + * @param source + * - the source Content object. + * @return Returns the source Content object's ID. + */ default String mapContentSummaryDTOtoString(ContentSummaryDTO source) { if (source == null) { return null; } return source.getId(); } + + List copyStringList(List source); + + default ResultsWrapper copy(ResultsWrapper source) { + return new ResultsWrapper<>(copyStringList(source.getResults()), source.getTotalResults()); + } + + @SubclassMapping(source = IsaacEventPageDTO.class, target = IsaacEventPageDTO.class) + ContentDTO copy(ContentDTO source); } \ No newline at end of file diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java index b04c406e28..0161a929cd 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java @@ -1,13 +1,12 @@ package uk.ac.cam.cl.dtg.util.mappers; -import java.util.List; - import org.mapstruct.Mapper; import org.mapstruct.Mapping; - import uk.ac.cam.cl.dtg.isaac.dto.eventbookings.DetailedEventBookingDTO; import uk.ac.cam.cl.dtg.isaac.dto.eventbookings.EventBookingDTO; +import java.util.List; + /** * MapStruct mapper for EventBooking objects. */ @@ -16,9 +15,10 @@ public interface EventMapper { EventBookingDTO map(DetailedEventBookingDTO source); - List copy(List source); List map(List source); @Mapping(source = "userBooked", target = "userBooked", qualifiedByName = "copyUserSummaryDTO") EventBookingDTO copy(EventBookingDTO source); + + List copy(List source); } \ No newline at end of file diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UnimplementedMappingException.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UnimplementedMappingException.java index c7bf270852..405180dff8 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UnimplementedMappingException.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UnimplementedMappingException.java @@ -4,7 +4,8 @@ * An exception that indicates no mapping implementation exists between two classes. */ public class UnimplementedMappingException extends UnsupportedOperationException { - public UnimplementedMappingException(Class sourceClass, Class targetClass) { - super(String.format("Mapping from %s to %s is not implemented", sourceClass.getSimpleName(), targetClass.getSimpleName())); + public UnimplementedMappingException(final Class sourceClass, final Class targetClass) { + super(String.format("Mapping from %s to %s is not implemented", sourceClass.getSimpleName(), + targetClass.getSimpleName())); } } diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java index 49c7002a38..de5cc3d636 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java @@ -9,7 +9,6 @@ import org.mapstruct.NullValuePropertyMappingStrategy; import org.mapstruct.SubclassMapping; import org.mapstruct.factory.Mappers; - import uk.ac.cam.cl.dtg.isaac.dos.GroupMembership; import uk.ac.cam.cl.dtg.isaac.dos.UserGroup; import uk.ac.cam.cl.dtg.isaac.dos.users.AnonymousUser; @@ -34,6 +33,17 @@ public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); + /** + * Mapping method to convert a RegisteredUserDTO to a UserSummaryDTO type determined by targetClass. + * + * @param + * - the target type. + * @param source + * - the source RegisteredUserDTO. + * @param targetClass + * - the target class to convert to. + * @return Returns an instance of the targetClass type mapped via the appropriate mapping method. + */ @SuppressWarnings("unchecked") default T map(RegisteredUserDTO source, Class targetClass) { if (targetClass.equals(UserSummaryDTO.class)) { @@ -49,6 +59,17 @@ default T map(RegisteredUserDTO source, Class targ } } + /** + * Mapping method to convert a UserSummaryDTO to a UserSummaryWithGroupMembershipDTO or UserSummaryDTO. + * + * @param + * - the target type. + * @param source + * - the source UserSummaryDTO. + * @param targetClass + * - the target class to convert to. + * @return Returns an instance of the targetClass type mapped via the appropriate mapping method. + */ @SuppressWarnings("unchecked") default T map(UserSummaryDTO source, Class targetClass) { if (targetClass.equals(UserSummaryWithGroupMembershipDTO.class)) { @@ -60,9 +81,6 @@ default T map(UserSummaryDTO source, Class targetClass) { } } - @Mapping(target = "authorisedFullAccess", ignore = true) - UserSummaryDTO mapUserToSummary(RegisteredUserDTO source); - @Mapping(target = "token", ignore = true) @Mapping(target = "ownerSummary", ignore = true) @Mapping(target = "mongoId", ignore = true) @@ -70,46 +88,25 @@ default T map(UserSummaryDTO source, Class targetClass) { @Mapping(target = "additionalManagers", ignore = true) UserGroupDTO map(UserGroup source); - @Mapping(target = "authorisedFullAccess", ignore = true) - UserSummaryForAdminUsersDTO mapUserToAdminSummaryDTO(RegisteredUserDTO source); - - @Mapping(target = "authorisedFullAccess", ignore = true) - UserSummaryWithEmailAddressDTO mapUserToSummaryWithEmailDTO(RegisteredUserDTO source); - - GroupMembership map(GroupMembershipDTO source); - - GroupMembershipDTO map(GroupMembership source); - - @Mapping(target = "status", ignore = true) - UserGroup map(UserGroupDTO source); - - @Mapping(target = "groupMembershipInformation", ignore = true) - @Mapping(target = "authorisedFullAccess", ignore = true) - UserSummaryWithGroupMembershipDTO mapUserToSummaryWithGroupMembershipDTO(RegisteredUserDTO source); - @Mapping(target = "sessionToken", ignore = true) @Mapping(target = "emailVerificationToken", ignore = true) @Mapping(target = "emailToVerify", ignore = true) RegisteredUser map(RegisteredUserDTO source); + GroupMembership map(GroupMembershipDTO source); + + GroupMembershipDTO map(GroupMembership source); + @Mapping(target = "firstLogin", ignore = true) RegisteredUserDTO map(RegisteredUser source); + @Mapping(target = "status", ignore = true) + UserGroup map(UserGroupDTO source); + UserAuthenticationSettingsDTO map(UserAuthenticationSettings source); AnonymousUserDTO map(AnonymousUser source); - RegisteredUser copy(RegisteredUser source); - RegisteredUserDTO copy(RegisteredUserDTO source); - GroupMembershipDTO copy(GroupMembershipDTO source); - - @Mapping(target = "sessionToken", ignore = true) - @Mapping(target = "emailVerificationToken", ignore = true) - @Mapping(target = "emailToVerify", ignore = true) - @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE, - nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) - void merge(RegisteredUserDTO source, @MappingTarget RegisteredUser target); - @Mapping(target = "sessionToken", ignore = true) @Mapping(target = "schoolOther", ignore = true) @Mapping(target = "schoolId", ignore = true) @@ -124,6 +121,26 @@ default T map(UserSummaryDTO source, Class targetClass) { @Mapping(target = "emailToVerify", ignore = true) RegisteredUser map(UserFromAuthProvider source); + @Mapping(target = "authorisedFullAccess", ignore = true) + UserSummaryDTO mapUserToSummary(RegisteredUserDTO source); + + @Mapping(target = "authorisedFullAccess", ignore = true) + UserSummaryForAdminUsersDTO mapUserToAdminSummaryDTO(RegisteredUserDTO source); + + @Mapping(target = "authorisedFullAccess", ignore = true) + UserSummaryWithEmailAddressDTO mapUserToSummaryWithEmailDTO(RegisteredUserDTO source); + + @Mapping(target = "groupMembershipInformation", ignore = true) + @Mapping(target = "authorisedFullAccess", ignore = true) + UserSummaryWithGroupMembershipDTO mapUserToSummaryWithGroupMembershipDTO(RegisteredUserDTO source); + + @Mapping(target = "sessionToken", ignore = true) + @Mapping(target = "emailVerificationToken", ignore = true) + @Mapping(target = "emailToVerify", ignore = true) + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE, + nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) + void merge(RegisteredUserDTO source, @MappingTarget RegisteredUser target); + @Mapping(target = "groupMembershipInformation", ignore = true) @SubclassMapping(source = UserSummaryForAdminUsersDTO.class, target = UserSummaryWithGroupMembershipDTO.class) @SubclassMapping(source = UserSummaryWithEmailAddressDTO.class, target = UserSummaryWithGroupMembershipDTO.class) @@ -133,6 +150,12 @@ default T map(UserSummaryDTO source, Class targetClass) { @BeanMapping(resultType = UserSummaryDTO.class) UserSummaryDTO mapExtendedUserSummaryDTOtoBaseUserSummaryDTO(UserSummaryDTO source); + RegisteredUser copy(RegisteredUser source); + + RegisteredUserDTO copy(RegisteredUserDTO source); + + GroupMembershipDTO copy(GroupMembershipDTO source); + // Named mapping for use in EventMapper @Named("copyUserSummaryDTO") @SubclassMapping(source = UserSummaryForAdminUsersDTO.class, target = UserSummaryForAdminUsersDTO.class) @@ -141,6 +164,8 @@ default T map(UserSummaryDTO source, Class targetClass) { UserSummaryDTO copy(UserSummaryDTO source); UserSummaryForAdminUsersDTO copy(UserSummaryForAdminUsersDTO source); + UserSummaryWithEmailAddressDTO copy(UserSummaryWithEmailAddressDTO source); + UserSummaryWithGroupMembershipDTO copy(UserSummaryWithGroupMembershipDTO source); } \ No newline at end of file From 0bb6673b5760cce49852a852ebddfc7d791386cc Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Thu, 2 Oct 2025 12:03:49 +0100 Subject: [PATCH 38/65] Fix Checkstyle warnings --- .../uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java | 3 ++- .../dtg/segue/api/managers/QuestionManager.java | 6 ++++-- .../SegueGuiceConfigurationModule.java | 15 ++++++++++----- .../dtg/segue/dao/content/GitContentManager.java | 10 ++++++---- .../ac/cam/cl/dtg/isaac/api/EventsFacadeIT.java | 3 ++- .../uk/ac/cam/cl/dtg/isaac/api/PagesFacadeIT.java | 3 ++- .../dtg/isaac/api/managers/GameManagerTest.java | 2 +- ...erTest.java => ContentSubclassMapperTest.java} | 4 ++-- 8 files changed, 29 insertions(+), 17 deletions(-) rename src/test/java/uk/ac/cam/cl/dtg/segue/dao/{ContentMapperTest.java => ContentSubclassMapperTest.java} (97%) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java index 5986612c6f..98cc9f1407 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java @@ -604,7 +604,8 @@ public final Response getEventBookingForAllGroups(@Context final HttpServletRequ eventBookings = userAssociationManager.filterUnassociatedRecords( currentUser, eventBookings, booking -> booking.getUserBooked().getId()); - eventBookings.forEach(booking -> booking.setUserBooked(mapper.map(booking.getUserBooked(), UserSummaryDTO.class))); + eventBookings.forEach(booking -> booking.setUserBooked(mapper.map(booking.getUserBooked(), + UserSummaryDTO.class))); return Response.ok(eventBookings).build(); } catch (NoUserLoggedInException e) { diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java index 28a71c9132..59388e28db 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java @@ -98,7 +98,8 @@ public class QuestionManager { * @param questionPersistenceManager - for question attempt persistence. */ @Inject - public QuestionManager(final ContentSubclassMapper contentSubclassMapper, final MainMapper mapper, final IQuestionAttemptManager questionPersistenceManager) { + public QuestionManager(final ContentSubclassMapper contentSubclassMapper, final MainMapper mapper, + final IQuestionAttemptManager questionPersistenceManager) { this.contentSubclassMapper = contentSubclassMapper; this.mapper = mapper; this.questionAttemptPersistenceManager = questionPersistenceManager; @@ -644,7 +645,8 @@ public ChoiceDTO convertJsonAnswerToChoice(String jsonAnswer) throws ErrorRespon ChoiceDTO answerFromClientDTO; try { // convert submitted JSON into a Choice: - Choice answerFromClient = contentSubclassMapper.getSharedContentObjectMapper().readValue(jsonAnswer, Choice.class); + Choice answerFromClient = contentSubclassMapper.getSharedContentObjectMapper() + .readValue(jsonAnswer, Choice.class); // convert to a DTO so that it strips out any untrusted data. answerFromClientDTO = mapper.map(answerFromClient); } catch (JsonMappingException | JsonParseException e) { diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java index 45909766d9..792611659f 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java @@ -554,10 +554,13 @@ private static RestHighLevelClient getSearchConnectionInformation( @Inject @Provides @Singleton - private static GitContentManager getContentManager(final GitDb database, final ISearchProvider searchProvider, final ContentMapper contentMapper, - final ContentSubclassMapper contentSubclassMapper, final AbstractConfigLoader globalProperties) { + private static GitContentManager getContentManager(final GitDb database, final ISearchProvider searchProvider, + final ContentMapper contentMapper, + final ContentSubclassMapper contentSubclassMapper, + final AbstractConfigLoader globalProperties) { if (null == contentManager) { - contentManager = new GitContentManager(database, searchProvider, contentMapper, contentSubclassMapper, globalProperties); + contentManager = new GitContentManager(database, searchProvider, contentMapper, contentSubclassMapper, + globalProperties); log.info("Creating singleton of ContentManager"); } @@ -1247,8 +1250,10 @@ private IPLocationResolver getIPLocator(final AbstractConfigLoader properties) t @Inject @Provides @Singleton - private static GameboardPersistenceManager getGameboardPersistenceManager(final PostgresSqlDb database, final GitContentManager contentManager, - final MainMapper mapper, final ContentSubclassMapper objectMapper) { + private static GameboardPersistenceManager getGameboardPersistenceManager(final PostgresSqlDb database, + final GitContentManager contentManager, + final MainMapper mapper, + final ContentSubclassMapper objectMapper) { if (null == gameboardPersistenceManager) { gameboardPersistenceManager = new GameboardPersistenceManager(database, contentManager, mapper, objectMapper); log.info("Creating Singleton of GameboardPersistenceManager"); diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java index 6fae5a51cf..9e8d759c3e 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/GitContentManager.java @@ -113,8 +113,9 @@ public class GitContentManager { * - global properties. */ @Inject - public GitContentManager(final GitDb database, final ISearchProvider searchProvider, final ContentMapper contentMapper, - final ContentSubclassMapper contentSubclassMapper, final AbstractConfigLoader globalProperties) { + public GitContentManager(final GitDb database, final ISearchProvider searchProvider, + final ContentMapper contentMapper, final ContentSubclassMapper contentSubclassMapper, + final AbstractConfigLoader globalProperties) { this.database = database; this.mapper = contentMapper; this.contentSubclassMapper = contentSubclassMapper; @@ -156,7 +157,7 @@ public GitContentManager(final GitDb database, final ISearchProvider searchProvi * - the utility class for mapping content objects. */ public GitContentManager(final GitDb database, final ISearchProvider searchProvider, - final ContentMapper contentMapper, ContentSubclassMapper contentSubclassMapper) { + final ContentMapper contentMapper, final ContentSubclassMapper contentSubclassMapper) { this.database = database; this.mapper = contentMapper; this.searchProvider = searchProvider; @@ -257,7 +258,8 @@ public final Content getContentDOById(final String id, final boolean failQuietly CONTENT_TYPE, id, Constants.ID_FIELDNAME + "." + Constants.UNPROCESSED_SEARCH_FIELD_SUFFIX, 0, 1, getBaseFilters()); - List searchResults = contentSubclassMapper.mapFromStringListToContentList(rawResults.getResults()); + List searchResults = contentSubclassMapper + .mapFromStringListToContentList(rawResults.getResults()); return new ResultsWrapper<>(searchResults, rawResults.getTotalResults()); }); diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacadeIT.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacadeIT.java index 3b46de5b12..b1cb25db7d 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacadeIT.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacadeIT.java @@ -35,7 +35,8 @@ public class EventsFacadeIT extends IsaacIntegrationTest { @BeforeEach public void setUp() { // Get an instance of the facade to test - eventsFacade = new EventsFacade(properties, logManager, eventBookingManager, userAccountManager, contentManager, userAssociationManager, groupManager, userAccountManager, schoolListReader, mainMapper); + eventsFacade = new EventsFacade(properties, logManager, eventBookingManager, userAccountManager, contentManager, + userAssociationManager, groupManager, userAccountManager, schoolListReader, mainMapper); } @Test diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/PagesFacadeIT.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/PagesFacadeIT.java index c2578297b0..9ccd2d1096 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/PagesFacadeIT.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/PagesFacadeIT.java @@ -42,7 +42,8 @@ public class PagesFacadeIT extends IsaacIntegrationTest{ @BeforeEach public void setUp() { this.pagesFacade = new PagesFacade(new ContentService(contentManager), properties, logManager, - mainMapper, contentManager, userAccountManager, new URIManager(properties), questionManager, gameManager, userAttemptManager); + mainMapper, contentManager, userAccountManager, new URIManager(properties), questionManager, + gameManager, userAttemptManager); } @Test diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManagerTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManagerTest.java index e16e3f1a4b..e9418bdea9 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManagerTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManagerTest.java @@ -33,9 +33,9 @@ import uk.ac.cam.cl.dtg.segue.dao.content.ContentManagerException; import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager; import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager.BooleanSearchClause; -import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import uk.ac.cam.cl.dtg.util.AbstractConfigLoader; import uk.ac.cam.cl.dtg.util.YamlLoader; +import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import java.util.Collections; import java.util.List; diff --git a/src/test/java/uk/ac/cam/cl/dtg/segue/dao/ContentMapperTest.java b/src/test/java/uk/ac/cam/cl/dtg/segue/dao/ContentSubclassMapperTest.java similarity index 97% rename from src/test/java/uk/ac/cam/cl/dtg/segue/dao/ContentMapperTest.java rename to src/test/java/uk/ac/cam/cl/dtg/segue/dao/ContentSubclassMapperTest.java index 243dc01a74..a27123e593 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/segue/dao/ContentMapperTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/segue/dao/ContentSubclassMapperTest.java @@ -3,14 +3,14 @@ import org.junit.Before; import org.junit.Test; import org.reflections.Reflections; -import uk.ac.cam.cl.dtg.segue.dao.content.ContentSubclassMapper; import uk.ac.cam.cl.dtg.isaac.dos.content.CodeSnippet; import uk.ac.cam.cl.dtg.isaac.dos.content.Content; import uk.ac.cam.cl.dtg.isaac.dto.content.ContentDTO; +import uk.ac.cam.cl.dtg.segue.dao.content.ContentSubclassMapper; import static org.junit.Assert.assertTrue; -public class ContentMapperTest { +public class ContentSubclassMapperTest { private ContentSubclassMapper contentSubclassMapper; From 66ab6372ec193697243789e28e28f5c418b65c49 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Mon, 20 Oct 2025 17:03:57 +0100 Subject: [PATCH 39/65] Change LLM questions to extend IsaacQuestionBase --- .../uk/ac/cam/cl/dtg/isaac/dos/IsaacLLMFreeTextQuestion.java | 2 +- .../uk/ac/cam/cl/dtg/isaac/dto/IsaacLLMFreeTextQuestionDTO.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dos/IsaacLLMFreeTextQuestion.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dos/IsaacLLMFreeTextQuestion.java index 690d27376f..8b972bc6f4 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dos/IsaacLLMFreeTextQuestion.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dos/IsaacLLMFreeTextQuestion.java @@ -17,7 +17,7 @@ @DTOMapping(IsaacLLMFreeTextQuestionDTO.class) @JsonContentType(LLM_FREE_TEXT_QUESTION_TYPE) @ValidatesWith(IsaacLLMFreeTextValidator.class) -public class IsaacLLMFreeTextQuestion extends Question { +public class IsaacLLMFreeTextQuestion extends IsaacQuestionBase { private String promptInstructionOverride; private List markScheme; private Integer maxMarks; diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/IsaacLLMFreeTextQuestionDTO.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/IsaacLLMFreeTextQuestionDTO.java index d7087bf16e..37a84958fa 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/IsaacLLMFreeTextQuestionDTO.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/IsaacLLMFreeTextQuestionDTO.java @@ -9,7 +9,7 @@ @JsonContentType(LLM_FREE_TEXT_QUESTION_TYPE) @ValidatesWith(IsaacLLMFreeTextValidator.class) -public class IsaacLLMFreeTextQuestionDTO extends QuestionDTO { +public class IsaacLLMFreeTextQuestionDTO extends IsaacQuestionBaseDTO { private Integer maxMarks; public IsaacLLMFreeTextQuestionDTO() { From 7b8ca9a640538a4f8d43cc74a1dda5fd034f90fe Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Mon, 20 Oct 2025 17:06:49 +0100 Subject: [PATCH 40/65] Add new mappers to Guice config --- .../SegueGuiceConfigurationModule.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java index 792611659f..23f1991e0a 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java @@ -147,8 +147,12 @@ import uk.ac.cam.cl.dtg.util.locations.MaxMindIPLocationResolver; import uk.ac.cam.cl.dtg.util.locations.PostCodeIOLocationResolver; import uk.ac.cam.cl.dtg.util.locations.PostCodeLocationResolver; +import uk.ac.cam.cl.dtg.util.mappers.AssignmentMapper; import uk.ac.cam.cl.dtg.util.mappers.ContentMapper; +import uk.ac.cam.cl.dtg.util.mappers.EventMapper; import uk.ac.cam.cl.dtg.util.mappers.MainMapper; +import uk.ac.cam.cl.dtg.util.mappers.QuestionMapper; +import uk.ac.cam.cl.dtg.util.mappers.QuestionValidationMapper; import uk.ac.cam.cl.dtg.util.mappers.UserMapper; import jakarta.annotation.Nullable; @@ -622,6 +626,55 @@ private static ContentSubclassMapper getContentMapper() { return mapper; } + @Provides + @Singleton + @Inject + public static MainMapper getMainMapperInstance() { + return MainMapper.INSTANCE; + } + + @Provides + @Singleton + @Inject + public static ContentMapper getContentMapperInstance() { + return ContentMapper.INSTANCE; + } + + @Provides + @Singleton + @Inject + public static UserMapper getUserMapperInstance() { + return UserMapper.INSTANCE; + } + + @Provides + @Singleton + @Inject + public static EventMapper getEventMapperInstance() { + return EventMapper.INSTANCE; + } + + @Provides + @Singleton + @Inject + public static AssignmentMapper getAssignmentMapperInstance() { + return AssignmentMapper.INSTANCE; + } + + @Provides + @Singleton + @Inject + public static QuestionMapper getQuestionMapperInstance() { + return QuestionMapper.INSTANCE; + } + + @Provides + @Singleton + @Inject + public static QuestionValidationMapper getQuestionValidationMapperInstance() { + return QuestionValidationMapper.INSTANCE; + } + /** * This provides an instance of the SegueLocalAuthenticator. * From 4f2381f1a134e63051696fae493d03e29d3bceea Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Mon, 20 Oct 2025 17:38:59 +0100 Subject: [PATCH 41/65] Update mappers Co-authored-by: James Sharkey --- .../ac/cam/cl/dtg/isaac/api/EventsFacade.java | 3 +- .../cl/dtg/isaac/api/GameboardsFacade.java | 4 +- .../dtg/isaac/api/managers/GameManager.java | 11 +- .../segue/api/managers/QuestionManager.java | 5 +- .../dao/content/ContentSubclassMapper.java | 40 +--- .../cl/dtg/util/mappers/AssignmentMapper.java | 2 + .../cl/dtg/util/mappers/ContentMapper.java | 179 +++++++++--------- .../cam/cl/dtg/util/mappers/EventMapper.java | 3 + .../cam/cl/dtg/util/mappers/MainMapper.java | 2 +- .../cl/dtg/util/mappers/QuestionMapper.java | 93 +++++++++ .../mappers/QuestionValidationMapper.java | 16 +- 11 files changed, 215 insertions(+), 143 deletions(-) create mode 100644 src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionMapper.java diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java index 98cc9f1407..25612ec3b1 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java @@ -1611,7 +1611,8 @@ private IsaacEventPageDTO getRawEventDTOById(final String eventId) // The Events Facade *mutates* the EventDTO returned by this method; we must return a copy of // the original object else we will poison the contentManager's cache! // TODO: might it be better to get the DO from the cache and map it to DTO here to reduce overhead? - return (IsaacEventPageDTO) mapper.copy(possibleEvent); + IsaacEventPageDTO eventPageDTO = (IsaacEventPageDTO) possibleEvent; + return mapper.copy(eventPageDTO); } return null; } diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/GameboardsFacade.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/GameboardsFacade.java index d3b7f9543e..817ee76256 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/GameboardsFacade.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/GameboardsFacade.java @@ -29,12 +29,12 @@ import uk.ac.cam.cl.dtg.isaac.api.managers.InvalidGameboardException; import uk.ac.cam.cl.dtg.isaac.api.managers.NoWildcardException; import uk.ac.cam.cl.dtg.isaac.dos.GameboardCreationMethod; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacWildcard; import uk.ac.cam.cl.dtg.isaac.dos.LightweightQuestionValidationResponse; import uk.ac.cam.cl.dtg.isaac.dos.QuestionValidationResponse; import uk.ac.cam.cl.dtg.isaac.dto.GameboardDTO; import uk.ac.cam.cl.dtg.isaac.dto.GameboardItem; import uk.ac.cam.cl.dtg.isaac.dto.GameboardListDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacWildcardDTO; import uk.ac.cam.cl.dtg.isaac.dto.SegueErrorResponse; import uk.ac.cam.cl.dtg.isaac.dto.users.AbstractSegueUserDTO; import uk.ac.cam.cl.dtg.isaac.dto.users.AnonymousUserDTO; @@ -741,7 +741,7 @@ public Response unlinkUserFromGameboard(@Context final HttpServletRequest reques public final Response getWildCards(@Context final Request request) { try { - List wildcards = gameManager.getWildcards(); + List wildcards = gameManager.getWildcards(); if (null == wildcards || wildcards.isEmpty()) { return new SegueErrorResponse(Status.NOT_FOUND, "No wildcards found.").toResponse(); } diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java index 0497b9e9bb..8c9fc4a003 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java @@ -43,6 +43,7 @@ import uk.ac.cam.cl.dtg.isaac.dto.GameboardListDTO; import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuestionPageDTO; import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuickQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacWildcardDTO; import uk.ac.cam.cl.dtg.isaac.dto.ResultsWrapper; import uk.ac.cam.cl.dtg.isaac.dto.content.ContentBaseDTO; import uk.ac.cam.cl.dtg.isaac.dto.content.ContentDTO; @@ -677,7 +678,7 @@ public List>> gatherGamePro * @throws ContentManagerException * - if we cannot access the content requested. */ - public List getWildcards() throws NoWildcardException, ContentManagerException { + public List getWildcards() throws NoWildcardException, ContentManagerException { List fieldsToMap = Lists.newArrayList(); fieldsToMap.add(new GitContentManager.BooleanSearchClause( @@ -693,10 +694,12 @@ public List getWildcards() throws NoWildcardException, ContentMan throw new NoWildcardException(); } - List result = Lists.newArrayList(); + List result = Lists.newArrayList(); for (ContentDTO c : wildcardResults.getResults()) { - IsaacWildcard wildcard = mapper.map(c, IsaacWildcard.class); - result.add(wildcard); + if ((c instanceof IsaacWildcardDTO)) { + IsaacWildcardDTO wildcard = (IsaacWildcardDTO) c; + result.add(wildcard); + } } return result; diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java index 59388e28db..12a2198e04 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/QuestionManager.java @@ -137,8 +137,7 @@ public final Response validateAnswer(final Question question, final ChoiceDTO su validatorTimer.observeDuration(); } - return Response.ok( - mapper.map(validateQuestionResponse)).build(); + return Response.ok(mapper.map(validateQuestionResponse)).build(); } @@ -634,7 +633,7 @@ public final Response generateSpecification(final ChoiceDTO answer) { ResultsWrapper results = new ResultsWrapper<>(Collections.singletonList(specification), 1L); - return Response.ok(mapper.copy(results)).build(); + return Response.ok(results).build(); } public static String extractPageIdFromQuestionId(String questionId) { diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentSubclassMapper.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentSubclassMapper.java index 16d4576a3f..20b933a8e8 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentSubclassMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentSubclassMapper.java @@ -35,14 +35,12 @@ import uk.ac.cam.cl.dtg.isaac.dos.content.LLMMarkingExpression; import uk.ac.cam.cl.dtg.isaac.dos.content.SeguePage; import uk.ac.cam.cl.dtg.isaac.dos.content.SidebarEntry; -import uk.ac.cam.cl.dtg.isaac.dto.content.ContentBaseDTO; import uk.ac.cam.cl.dtg.isaac.dto.content.ContentDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.ContentSummaryDTO; import uk.ac.cam.cl.dtg.isaac.dto.content.SeguePageDTO; import uk.ac.cam.cl.dtg.isaac.dto.content.SidebarDTO; import uk.ac.cam.cl.dtg.segue.dao.JsonLoader; import uk.ac.cam.cl.dtg.segue.dao.users.QuestionValidationResponseDeserializer; -import uk.ac.cam.cl.dtg.util.mappers.ContentMapper; +import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import java.io.IOException; import java.util.ArrayList; @@ -189,39 +187,6 @@ public Class getDTOClassByDOClass(final Class contentChildren = content.getChildren(); - if (contentChildren != null) { - List resultChildren = result.getChildren(); - for (int i = 0; i < contentChildren.size(); i++) { - ContentBase contentChild = contentChildren.get(i); - ContentBaseDTO resultChild = resultChildren.get(i); - if (contentChild instanceof Content && resultChild instanceof ContentDTO) { - this.populateRelatedContentWithIDs((Content) contentChild, (ContentDTO) resultChild); - } - } - } - if (result.getRelatedContent() != null) { - List relatedContent = Lists.newArrayList(); - for (String relatedId : content.getRelatedContent()) { - ContentSummaryDTO contentSummary = new ContentSummaryDTO(); - contentSummary.setId(relatedId); - relatedContent.add(contentSummary); - } - result.setRelatedContent(relatedContent); - } - } - /** * Populate the DTO object sidebar with a placeholder, if a page type. * @@ -256,8 +221,7 @@ public ContentDTO getDTOByDO(final Content content) { if (null == content) { return null; } - ContentDTO result = ContentMapper.INSTANCE.mapContent(content); - populateRelatedContentWithIDs(content, result); + ContentDTO result = MainMapper.INSTANCE.mapContent(content); populateSidebarWithIDs(content, result); return result; } diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java index ec78819a6f..e2595780cd 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java @@ -3,6 +3,7 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; import uk.ac.cam.cl.dtg.isaac.dos.AssignmentDO; import uk.ac.cam.cl.dtg.isaac.dos.GameboardContentDescriptor; import uk.ac.cam.cl.dtg.isaac.dos.GameboardDO; @@ -19,6 +20,7 @@ */ @Mapper public interface AssignmentMapper { + AssignmentMapper INSTANCE = Mappers.getMapper(AssignmentMapper.class); @Mapping(target = "ownerUserInformation", ignore = true) @Mapping(target = "savedToCurrentUser", ignore = true) diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index f5292f81c7..e03d9d4696 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -1,33 +1,14 @@ package uk.ac.cam.cl.dtg.util.mappers; -import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.InheritConfiguration; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.SubclassExhaustiveStrategy; import org.mapstruct.SubclassMapping; import org.mapstruct.factory.Mappers; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacCard; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacCardDeck; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacEventPage; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacFeaturedProfile; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacPageFragment; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacPod; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacQuiz; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacQuizSection; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacWildcard; +import uk.ac.cam.cl.dtg.isaac.dos.*; import uk.ac.cam.cl.dtg.isaac.dos.content.*; -import uk.ac.cam.cl.dtg.isaac.dto.GameboardItem; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacCardDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacCardDeckDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacEventPageDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacFeaturedProfileDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacPageFragmentDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacPodDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuizDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuizSectionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacWildcardDTO; -import uk.ac.cam.cl.dtg.isaac.dto.LLMFreeTextChoiceDTO; -import uk.ac.cam.cl.dtg.isaac.dto.ResultsWrapper; +import uk.ac.cam.cl.dtg.isaac.dto.*; import uk.ac.cam.cl.dtg.isaac.dto.content.*; import java.util.List; @@ -60,8 +41,6 @@ default T map(ContentDTO source, Class targetClass) { return (T) mapContentDTOtoQuizSummaryDTO(source); } else if (targetClass.equals(DetailedQuizSummaryDTO.class)) { return (T) mapContentDTOtoDetailedQuizSummaryDTO(source); - } else if (targetClass.equals(IsaacWildcard.class)) { - return (T) map(mapContent(source), IsaacWildcard.class); } else { throw new UnimplementedMappingException(ContentDTO.class, targetClass); } @@ -82,8 +61,6 @@ default T map(ContentDTO source, Class targetClass) { default T map(Content source, Class targetClass) { if (targetClass.equals(ContentDTO.class)) { return (T) mapContent(source); - } else if (targetClass.equals(IsaacWildcard.class)) { - return (T) mapContentToIsaacWildcard(source); } else { throw new UnimplementedMappingException(Content.class, targetClass); } @@ -95,46 +72,72 @@ default T map(Content source, Class targetClass) { @SubclassMapping(source = Content.class, target = ContentDTO.class) ContentBaseDTO map(ContentBase source); - @Mapping(target = "version", ignore = true) - @Mapping(target = "value", ignore = true) - @Mapping(target = "type", ignore = true) - @Mapping(target = "title", ignore = true) - @Mapping(target = "tags", ignore = true) - @Mapping(target = "subtitle", ignore = true) - @Mapping(target = "sidebarEntries", ignore = true) - @Mapping(target = "relatedContent", ignore = true) - @Mapping(target = "published", ignore = true) - @Mapping(target = "level", ignore = true) - @Mapping(target = "layout", ignore = true) - @Mapping(target = "id", ignore = true) - @Mapping(target = "expandable", ignore = true) - @Mapping(target = "encoding", ignore = true) - @Mapping(target = "display", ignore = true) - @Mapping(target = "children", ignore = true) - @Mapping(target = "canonicalSourceFile", ignore = true) - @Mapping(target = "author", ignore = true) - @Mapping(target = "audience", ignore = true) - @Mapping(target = "attribution", ignore = true) - SidebarDTO map(String source); - - @Mapping(target = "searchableContent", ignore = true) - @Mapping(target = "prioritisedSearchableContent", ignore = true) - @Mapping(target = "explanation", ignore = true) - @Mapping(target = "correct", ignore = true) - Choice map(ChoiceDTO source); @SubclassMapping(source = ChemicalFormula.class, target = ChemicalFormulaDTO.class) @SubclassMapping(source = Formula.class, target = FormulaDTO.class) @SubclassMapping(source = FreeTextRule.class, target = FreeTextRuleDTO.class) @SubclassMapping(source = GraphChoice.class, target = GraphChoiceDTO.class) + @SubclassMapping(source = ParsonsChoice.class, target = ParsonsChoiceDTO.class) @SubclassMapping(source = ItemChoice.class, target = ItemChoiceDTO.class) @SubclassMapping(source = LLMFreeTextChoice.class, target = LLMFreeTextChoiceDTO.class) @SubclassMapping(source = LogicFormula.class, target = LogicFormulaDTO.class) @SubclassMapping(source = Quantity.class, target = QuantityDTO.class) @SubclassMapping(source = RegexPattern.class, target = RegexPatternDTO.class) @SubclassMapping(source = StringChoice.class, target = StringChoiceDTO.class) + @SubclassMapping(source = CoordinateChoice.class, target = CoordinateChoiceDTO.class) ChoiceDTO map(Choice source); + @Mapping(target = "searchableContent", ignore = true) + @Mapping(target = "prioritisedSearchableContent", ignore = true) + @Mapping(target = "explanation", ignore = true) + @Mapping(target = "correct", ignore = true) + @SubclassMapping(source = ChemicalFormulaDTO.class, target = ChemicalFormula.class) + @SubclassMapping(source = FormulaDTO.class, target = Formula.class) + @SubclassMapping(source = FreeTextRuleDTO.class, target = FreeTextRule.class) + @SubclassMapping(source = GraphChoiceDTO.class, target = GraphChoice.class) + @SubclassMapping(source = ParsonsChoiceDTO.class, target = ParsonsChoice.class) + @SubclassMapping(source = ItemChoiceDTO.class, target = ItemChoice.class) + @SubclassMapping(source = LLMFreeTextChoiceDTO.class, target = LLMFreeTextChoice.class) + @SubclassMapping(source = LogicFormulaDTO.class, target = LogicFormula.class) + @SubclassMapping(source = QuantityDTO.class, target = Quantity.class) + @SubclassMapping(source = RegexPatternDTO.class, target = RegexPattern.class) + @SubclassMapping(source = StringChoiceDTO.class, target = StringChoice.class) + @SubclassMapping(source = CoordinateChoiceDTO.class, target = CoordinateChoice.class) + Choice map(ChoiceDTO source); + + @SubclassMapping(source = ParsonsItem.class, target = ParsonsItemDTO.class) + @SubclassMapping(source = CoordinateItem.class, target = CoordinateItemDTO.class) + ItemDTO map(Item source); + + @Mapping(target = "searchableContent", ignore = true) + @Mapping(target = "prioritisedSearchableContent", ignore = true) + @SubclassMapping(source = ParsonsItemDTO.class, target = ParsonsItem.class) + @SubclassMapping(source = CoordinateItemDTO.class, target = CoordinateItem.class) + Item map(ItemDTO source); + + @SubclassMapping(source = AnvilApp.class, target = AnvilAppDTO.class) + @SubclassMapping(source = Choice.class, target = ChoiceDTO.class) + @SubclassMapping(source = CodeSnippet.class, target = CodeSnippetDTO.class) + @SubclassMapping(source = CodeTabs.class, target = CodeTabsDTO.class) + @SubclassMapping(source = EmailTemplate.class, target = EmailTemplateDTO.class) + @SubclassMapping(source = GlossaryTerm.class, target = GlossaryTermDTO.class) + @SubclassMapping(source = InlineRegion.class, target = InlineRegionDTO.class) + @SubclassMapping(source = IsaacCard.class, target = IsaacCardDTO.class) + @SubclassMapping(source = IsaacCardDeck.class, target = IsaacCardDeckDTO.class) + @SubclassMapping(source = IsaacFeaturedProfile.class, target = IsaacFeaturedProfileDTO.class) + @SubclassMapping(source = IsaacPageFragment.class, target = IsaacPageFragmentDTO.class) + @SubclassMapping(source = IsaacPod.class, target = IsaacPodDTO.class) + @SubclassMapping(source = IsaacQuizSection.class, target = IsaacQuizSectionDTO.class) + @SubclassMapping(source = IsaacWildcard.class, target = IsaacWildcardDTO.class) + @SubclassMapping(source = Item.class, target = ItemDTO.class) + @SubclassMapping(source = Notification.class, target = NotificationDTO.class) + @SubclassMapping(source = Question.class, target = QuestionDTO.class) + @SubclassMapping(source = Sidebar.class, target = SidebarDTO.class) + @SubclassMapping(source = SidebarEntry.class, target = SidebarEntryDTO.class) + @SubclassMapping(source = IsaacEventPage.class, target = IsaacEventPageDTO.class) + @SubclassMapping(source = SeguePage.class, target = SeguePageDTO.class) + ContentDTO mapContent(Content source); + @Mapping(target = "url", ignore = true) @Mapping(target = "state", ignore = true) @Mapping(target = "questionPartIds", ignore = true) @@ -150,10 +153,6 @@ default T map(Content source, Class targetClass) { @Mapping(target = "deprecated", ignore = true) ContentSummaryDTO mapContentDTOtoContentSummaryDTO(ContentDTO source); - @Mapping(target = "url", ignore = true) - @Mapping(target = "description", ignore = true) - IsaacWildcard mapContentToIsaacWildcard(Content source); - @Mapping(target = "supersededBy", ignore = true) @Mapping(target = "state", ignore = true) @Mapping(target = "questionPartsTotal", ignore = true) @@ -191,36 +190,12 @@ default T map(Content source, Class targetClass) { @SubclassMapping(source = IsaacQuizDTO.class, target = DetailedQuizSummaryDTO.class) DetailedQuizSummaryDTO mapContentDTOtoDetailedQuizSummaryDTO(ContentDTO source); - @SubclassMapping(source = AnvilApp.class, target = AnvilAppDTO.class) - @SubclassMapping(source = Choice.class, target = ChoiceDTO.class) - @SubclassMapping(source = CodeSnippet.class, target = CodeSnippetDTO.class) - @SubclassMapping(source = CodeTabs.class, target = CodeTabsDTO.class) - @SubclassMapping(source = EmailTemplate.class, target = EmailTemplateDTO.class) - @SubclassMapping(source = GlossaryTerm.class, target = GlossaryTermDTO.class) - @SubclassMapping(source = InlineRegion.class, target = InlineRegionDTO.class) - @SubclassMapping(source = IsaacCard.class, target = IsaacCardDTO.class) - @SubclassMapping(source = IsaacCardDeck.class, target = IsaacCardDeckDTO.class) - @SubclassMapping(source = IsaacEventPage.class, target = IsaacEventPageDTO.class) - @SubclassMapping(source = IsaacFeaturedProfile.class, target = IsaacFeaturedProfileDTO.class) - @SubclassMapping(source = IsaacPageFragment.class, target = IsaacPageFragmentDTO.class) - @SubclassMapping(source = IsaacPod.class, target = IsaacPodDTO.class) - @SubclassMapping(source = IsaacQuiz.class, target = IsaacQuizDTO.class) - @SubclassMapping(source = IsaacQuizSection.class, target = IsaacQuizSectionDTO.class) - @SubclassMapping(source = IsaacWildcard.class, target = IsaacWildcardDTO.class) - @SubclassMapping(source = Item.class, target = ItemDTO.class) - @SubclassMapping(source = Notification.class, target = NotificationDTO.class) - @SubclassMapping(source = Question.class, target = QuestionDTO.class) - @SubclassMapping(source = Sidebar.class, target = SidebarDTO.class) - @SubclassMapping(source = SidebarEntry.class, target = SidebarEntryDTO.class) - ContentDTO mapContent(Content source); - - @InheritInverseConfiguration(name = "mapContent") - Content mapContent(ContentDTO source); - List mapContentSummaryDTOListToStringList(List source); List mapStringListToContentSummaryDTOList(List source); + List mapContentBaseDTOListToContentBaseList(List source); + // Create a new ContentSummaryDTO and set the id to the source string @Mapping(source = "source", target = "id") @Mapping(target = "url", ignore = true) @@ -254,10 +229,38 @@ default String mapContentSummaryDTOtoString(ContentSummaryDTO source) { List copyStringList(List source); - default ResultsWrapper copy(ResultsWrapper source) { - return new ResultsWrapper<>(copyStringList(source.getResults()), source.getTotalResults()); - } + IsaacEventPageDTO copy(IsaacEventPageDTO source); + + @Mapping(target = "sidebar", ignore = true) + @SubclassMapping(source = IsaacFastTrackQuestionPage.class, target = IsaacFastTrackQuestionPageDTO.class) + @SubclassMapping(source = IsaacQuestionPage.class, target = IsaacQuestionPageDTO.class) + @SubclassMapping(source = IsaacConceptPage.class, target = IsaacConceptPageDTO.class) + @SubclassMapping(source = IsaacBookIndexPage.class, target = IsaacBookIndexPageDTO.class) + SeguePageDTO map(SeguePage source); + + @Mapping(target = "userBookingStatus", ignore = true) + @Mapping(target = "placesAvailable", ignore = true) + @Mapping(target = "sidebar", ignore = true) + @InheritConfiguration(name = "mapContent") + IsaacEventPageDTO map(IsaacEventPage source); + + @Mapping(target = "sidebar", ignore = true) + @Mapping(target = "linkedGameboards", ignore = true) + @InheritConfiguration(name = "mapContent") + IsaacTopicSummaryPageDTO map(IsaacTopicSummaryPage source); + + @Mapping(target = "sidebar", ignore = true) + @Mapping(target = "gameboards", ignore = true) + @Mapping(target = "extensionGameboards", ignore = true) + @InheritConfiguration(name = "mapContent") + IsaacBookDetailPageDTO map(IsaacBookDetailPage source); + + @Mapping(target = "sidebar", ignore = true) + @Mapping(target = "gameboards", ignore = true) + @InheritConfiguration(name = "mapContent") + IsaacRevisionDetailPageDTO map(IsaacRevisionDetailPage source); - @SubclassMapping(source = IsaacEventPageDTO.class, target = IsaacEventPageDTO.class) - ContentDTO copy(ContentDTO source); + @InheritConfiguration(name = "mapContent") + @SubclassMapping(source = SidebarGroup.class, target = SidebarGroupDTO.class) + SidebarEntryDTO map(SidebarEntry source); } \ No newline at end of file diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java index 0161a929cd..ea4c832634 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java @@ -2,6 +2,7 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; import uk.ac.cam.cl.dtg.isaac.dto.eventbookings.DetailedEventBookingDTO; import uk.ac.cam.cl.dtg.isaac.dto.eventbookings.EventBookingDTO; @@ -13,6 +14,8 @@ @Mapper(uses = UserMapper.class) public interface EventMapper { + EventMapper INSTANCE = Mappers.getMapper(EventMapper.class); + EventBookingDTO map(DetailedEventBookingDTO source); List map(List source); diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java index 437c6d87ff..c2bcf96420 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java @@ -8,6 +8,6 @@ * Main MapStruct mapper interface. */ @Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) -public interface MainMapper extends ContentMapper, UserMapper, EventMapper, AssignmentMapper, QuestionValidationMapper { +public interface MainMapper extends ContentMapper, UserMapper, EventMapper, AssignmentMapper, QuestionMapper, QuestionValidationMapper { MainMapper INSTANCE = Mappers.getMapper(MainMapper.class); } \ No newline at end of file diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionMapper.java new file mode 100644 index 0000000000..d1b00fb67b --- /dev/null +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionMapper.java @@ -0,0 +1,93 @@ +package uk.ac.cam.cl.dtg.util.mappers; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacAnvilQuestion; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacClozeQuestion; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacCoordinateQuestion; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacFreeTextQuestion; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacGraphSketcherQuestion; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacItemQuestion; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacLLMFreeTextQuestion; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacMultiChoiceQuestion; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacNumericQuestion; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacParsonsQuestion; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacQuestionBase; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacQuickQuestion; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacRegexMatchQuestion; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacReorderQuestion; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacStringMatchQuestion; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacSymbolicChemistryQuestion; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacSymbolicLogicQuestion; +import uk.ac.cam.cl.dtg.isaac.dos.IsaacSymbolicQuestion; +import uk.ac.cam.cl.dtg.isaac.dos.content.ChoiceQuestion; +import uk.ac.cam.cl.dtg.isaac.dos.content.Question; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacAnvilQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacClozeQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacCoordinateQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacFreeTextQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacGraphSketcherQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacItemQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacLLMFreeTextQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacMultiChoiceQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacNumericQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacParsonsQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuestionBaseDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuickQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacRegexMatchQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacReorderQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacStringMatchQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacSymbolicChemistryQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacSymbolicLogicQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.IsaacSymbolicQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.ChoiceQuestionDTO; +import uk.ac.cam.cl.dtg.isaac.dto.content.QuestionDTO; + +/** + * MapStruct mapper for Questions. + */ +@Mapper(uses = ContentMapper.class, subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) +public interface QuestionMapper { + QuestionMapper INSTANCE = Mappers.getMapper(QuestionMapper.class); + + @Mapping(target = "bestAttempt", ignore = true) + @SubclassMapping(source = ChoiceQuestion.class, target = ChoiceQuestionDTO.class) + QuestionDTO map(Question source); + + @Mapping(target = "bestAttempt", ignore = true) + @SubclassMapping(source = IsaacQuestionBase.class, target = IsaacQuestionBaseDTO.class) + ChoiceQuestionDTO map(ChoiceQuestion source); + + @Mapping(target = "bestAttempt", ignore = true) + @SubclassMapping(source = IsaacQuickQuestion.class, target = IsaacQuickQuestionDTO.class) + @SubclassMapping(source = IsaacClozeQuestion.class, target = IsaacClozeQuestionDTO.class) + @SubclassMapping(source = IsaacFreeTextQuestion.class, target = IsaacFreeTextQuestionDTO.class) + @SubclassMapping(source = IsaacGraphSketcherQuestion.class, target = IsaacGraphSketcherQuestionDTO.class) + @SubclassMapping(source = IsaacItemQuestion.class, target = IsaacItemQuestionDTO.class) + @SubclassMapping(source = IsaacMultiChoiceQuestion.class, target = IsaacMultiChoiceQuestionDTO.class) + @SubclassMapping(source = IsaacNumericQuestion.class, target = IsaacNumericQuestionDTO.class) + @SubclassMapping(source = IsaacParsonsQuestion.class, target = IsaacParsonsQuestionDTO.class) + @SubclassMapping(source = IsaacRegexMatchQuestion.class, target = IsaacRegexMatchQuestionDTO.class) + @SubclassMapping(source = IsaacReorderQuestion.class, target = IsaacReorderQuestionDTO.class) + @SubclassMapping(source = IsaacStringMatchQuestion.class, target = IsaacStringMatchQuestionDTO.class) + @SubclassMapping(source = IsaacSymbolicChemistryQuestion.class, target = IsaacSymbolicChemistryQuestionDTO.class) + @SubclassMapping(source = IsaacSymbolicLogicQuestion.class, target = IsaacSymbolicLogicQuestionDTO.class) + @SubclassMapping(source = IsaacSymbolicQuestion.class, target = IsaacSymbolicQuestionDTO.class) + @SubclassMapping(source = IsaacCoordinateQuestion.class, target = IsaacCoordinateQuestionDTO.class) + @SubclassMapping(source = IsaacLLMFreeTextQuestion.class, target = IsaacLLMFreeTextQuestionDTO.class) + @SubclassMapping(source = IsaacAnvilQuestion.class, target = IsaacAnvilQuestionDTO.class) + IsaacQuestionBaseDTO mapIsaacQuestionBase(IsaacQuestionBase source); + + @Mapping(target = "bestAttempt", ignore = true) + @SubclassMapping(source = IsaacClozeQuestion.class, target = IsaacClozeQuestionDTO.class) + @SubclassMapping(source = IsaacParsonsQuestion.class, target = IsaacParsonsQuestionDTO.class) + @SubclassMapping(source = IsaacReorderQuestion.class, target = IsaacReorderQuestionDTO.class) + IsaacItemQuestionDTO mapIsaacItemQuestion(IsaacItemQuestion source); + + @Mapping(target = "bestAttempt", ignore = true) + @SubclassMapping(source = IsaacSymbolicLogicQuestion.class, target = IsaacSymbolicLogicQuestionDTO.class) + IsaacSymbolicQuestionDTO mapIsaacSymbolicQuestion(IsaacSymbolicQuestion source); +} diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapper.java index 515e4dd2a6..9e326835ff 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapper.java @@ -3,8 +3,10 @@ import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.Mapper; import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; import uk.ac.cam.cl.dtg.isaac.dos.FormulaValidationResponse; import uk.ac.cam.cl.dtg.isaac.dos.ItemValidationResponse; +import uk.ac.cam.cl.dtg.isaac.dos.LLMFreeTextQuestionValidationResponse; import uk.ac.cam.cl.dtg.isaac.dos.QuantityValidationResponse; import uk.ac.cam.cl.dtg.isaac.dos.QuestionValidationResponse; import uk.ac.cam.cl.dtg.isaac.dto.FormulaValidationResponseDTO; @@ -19,12 +21,14 @@ @Mapper(uses = ContentMapper.class) public interface QuestionValidationMapper { - @SubclassMapping(source = FormulaValidationResponseDTO.class, target = FormulaValidationResponse.class) - @SubclassMapping(source = ItemValidationResponseDTO.class, target = ItemValidationResponse.class) - @SubclassMapping(source = LLMFreeTextQuestionValidationResponseDTO.class, target = QuestionValidationResponse.class) - @SubclassMapping(source = QuantityValidationResponseDTO.class, target = QuantityValidationResponse.class) - QuestionValidationResponse map(QuestionValidationResponseDTO source); + QuestionValidationMapper INSTANCE = Mappers.getMapper(QuestionValidationMapper.class); - @InheritInverseConfiguration + @SubclassMapping(source = FormulaValidationResponse.class, target = FormulaValidationResponseDTO.class) + @SubclassMapping(source = ItemValidationResponse.class, target = ItemValidationResponseDTO.class) + @SubclassMapping(source = LLMFreeTextQuestionValidationResponse.class, target = LLMFreeTextQuestionValidationResponseDTO.class) + @SubclassMapping(source = QuantityValidationResponse.class, target = QuantityValidationResponseDTO.class) QuestionValidationResponseDTO map(QuestionValidationResponse source); + + @InheritInverseConfiguration + QuestionValidationResponse map(QuestionValidationResponseDTO source); } From 7759fc5f7c3ea60c3995891f679e82f8987d37a2 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Tue, 21 Oct 2025 11:42:53 +0100 Subject: [PATCH 42/65] Add subclass mapping for quizzes --- .../java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index e03d9d4696..705e4cb473 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -135,6 +135,7 @@ default T map(Content source, Class targetClass) { @SubclassMapping(source = Sidebar.class, target = SidebarDTO.class) @SubclassMapping(source = SidebarEntry.class, target = SidebarEntryDTO.class) @SubclassMapping(source = IsaacEventPage.class, target = IsaacEventPageDTO.class) + @SubclassMapping(source = IsaacQuiz.class, target = IsaacQuizDTO.class) @SubclassMapping(source = SeguePage.class, target = SeguePageDTO.class) ContentDTO mapContent(Content source); @@ -244,6 +245,10 @@ default String mapContentSummaryDTOtoString(ContentSummaryDTO source) { @InheritConfiguration(name = "mapContent") IsaacEventPageDTO map(IsaacEventPage source); + @Mapping(target = "sidebar", ignore = true) + @InheritConfiguration(name = "mapContent") + IsaacQuizDTO map(IsaacQuiz source); + @Mapping(target = "sidebar", ignore = true) @Mapping(target = "linkedGameboards", ignore = true) @InheritConfiguration(name = "mapContent") From 930e32d615adc1fde1c4aa302371ae7f16679566 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Tue, 21 Oct 2025 12:15:28 +0100 Subject: [PATCH 43/65] Add ignore annotation for end_date in event page mappings The auto-implementations of these mappings do handle mapping end_date, but because the names of the getters/setters used (getEndDate/setEndDate) don't match the property name, MapStruct doesn't know that it's been handled. The ignore annotation is needed to prevent a MapStruct warning about this. --- src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index 705e4cb473..cc1f164a58 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -230,6 +230,7 @@ default String mapContentSummaryDTOtoString(ContentSummaryDTO source) { List copyStringList(List source); + @Mapping(target = "end_date", ignore = true) IsaacEventPageDTO copy(IsaacEventPageDTO source); @Mapping(target = "sidebar", ignore = true) @@ -242,6 +243,7 @@ default String mapContentSummaryDTOtoString(ContentSummaryDTO source) { @Mapping(target = "userBookingStatus", ignore = true) @Mapping(target = "placesAvailable", ignore = true) @Mapping(target = "sidebar", ignore = true) + @Mapping(target = "end_date", ignore = true) @InheritConfiguration(name = "mapContent") IsaacEventPageDTO map(IsaacEventPage source); From cd795993ae7ff038e89d0ba9a910618b3fe9d0fc Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Tue, 21 Oct 2025 12:31:34 +0100 Subject: [PATCH 44/65] Fix CheckStyle warnings --- .../cl/dtg/util/mappers/ContentMapper.java | 92 ++++++++++--------- .../cam/cl/dtg/util/mappers/MainMapper.java | 3 +- 2 files changed, 50 insertions(+), 45 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index cc1f164a58..cc12f24d31 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -115,6 +115,54 @@ default T map(Content source, Class targetClass) { @SubclassMapping(source = CoordinateItemDTO.class, target = CoordinateItem.class) Item map(ItemDTO source); + @Mapping(target = "url", ignore = true) + @Mapping(target = "state", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + DetailedQuizSummaryDTO map(IsaacQuizDTO source); + + @Mapping(target = "sidebar", ignore = true) + @SubclassMapping(source = IsaacFastTrackQuestionPage.class, target = IsaacFastTrackQuestionPageDTO.class) + @SubclassMapping(source = IsaacQuestionPage.class, target = IsaacQuestionPageDTO.class) + @SubclassMapping(source = IsaacConceptPage.class, target = IsaacConceptPageDTO.class) + @SubclassMapping(source = IsaacBookIndexPage.class, target = IsaacBookIndexPageDTO.class) + SeguePageDTO map(SeguePage source); + + @Mapping(target = "userBookingStatus", ignore = true) + @Mapping(target = "placesAvailable", ignore = true) + @Mapping(target = "sidebar", ignore = true) + @Mapping(target = "end_date", ignore = true) + @InheritConfiguration(name = "mapContent") + IsaacEventPageDTO map(IsaacEventPage source); + + @Mapping(target = "total", ignore = true) + @Mapping(target = "sectionTotals", ignore = true) + @Mapping(target = "individualFeedback", ignore = true) + @Mapping(target = "defaultFeedbackMode", ignore = true) + @Mapping(target = "sidebar", ignore = true) + @InheritConfiguration(name = "mapContent") + IsaacQuizDTO map(IsaacQuiz source); + + @Mapping(target = "sidebar", ignore = true) + @Mapping(target = "linkedGameboards", ignore = true) + @InheritConfiguration(name = "mapContent") + IsaacTopicSummaryPageDTO map(IsaacTopicSummaryPage source); + + @Mapping(target = "sidebar", ignore = true) + @Mapping(target = "gameboards", ignore = true) + @Mapping(target = "extensionGameboards", ignore = true) + @InheritConfiguration(name = "mapContent") + IsaacBookDetailPageDTO map(IsaacBookDetailPage source); + + @Mapping(target = "sidebar", ignore = true) + @Mapping(target = "gameboards", ignore = true) + @InheritConfiguration(name = "mapContent") + IsaacRevisionDetailPageDTO map(IsaacRevisionDetailPage source); + + @InheritConfiguration(name = "mapContent") + @SubclassMapping(source = SidebarGroup.class, target = SidebarGroupDTO.class) + SidebarEntryDTO map(SidebarEntry source); + @SubclassMapping(source = AnvilApp.class, target = AnvilAppDTO.class) @SubclassMapping(source = Choice.class, target = ChoiceDTO.class) @SubclassMapping(source = CodeSnippet.class, target = CodeSnippetDTO.class) @@ -139,12 +187,6 @@ default T map(Content source, Class targetClass) { @SubclassMapping(source = SeguePage.class, target = SeguePageDTO.class) ContentDTO mapContent(Content source); - @Mapping(target = "url", ignore = true) - @Mapping(target = "state", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - DetailedQuizSummaryDTO map(IsaacQuizDTO source); - @Mapping(target = "url", ignore = true) @Mapping(target = "supersededBy", ignore = true) @Mapping(target = "summary", ignore = true) @@ -232,42 +274,4 @@ default String mapContentSummaryDTOtoString(ContentSummaryDTO source) { @Mapping(target = "end_date", ignore = true) IsaacEventPageDTO copy(IsaacEventPageDTO source); - - @Mapping(target = "sidebar", ignore = true) - @SubclassMapping(source = IsaacFastTrackQuestionPage.class, target = IsaacFastTrackQuestionPageDTO.class) - @SubclassMapping(source = IsaacQuestionPage.class, target = IsaacQuestionPageDTO.class) - @SubclassMapping(source = IsaacConceptPage.class, target = IsaacConceptPageDTO.class) - @SubclassMapping(source = IsaacBookIndexPage.class, target = IsaacBookIndexPageDTO.class) - SeguePageDTO map(SeguePage source); - - @Mapping(target = "userBookingStatus", ignore = true) - @Mapping(target = "placesAvailable", ignore = true) - @Mapping(target = "sidebar", ignore = true) - @Mapping(target = "end_date", ignore = true) - @InheritConfiguration(name = "mapContent") - IsaacEventPageDTO map(IsaacEventPage source); - - @Mapping(target = "sidebar", ignore = true) - @InheritConfiguration(name = "mapContent") - IsaacQuizDTO map(IsaacQuiz source); - - @Mapping(target = "sidebar", ignore = true) - @Mapping(target = "linkedGameboards", ignore = true) - @InheritConfiguration(name = "mapContent") - IsaacTopicSummaryPageDTO map(IsaacTopicSummaryPage source); - - @Mapping(target = "sidebar", ignore = true) - @Mapping(target = "gameboards", ignore = true) - @Mapping(target = "extensionGameboards", ignore = true) - @InheritConfiguration(name = "mapContent") - IsaacBookDetailPageDTO map(IsaacBookDetailPage source); - - @Mapping(target = "sidebar", ignore = true) - @Mapping(target = "gameboards", ignore = true) - @InheritConfiguration(name = "mapContent") - IsaacRevisionDetailPageDTO map(IsaacRevisionDetailPage source); - - @InheritConfiguration(name = "mapContent") - @SubclassMapping(source = SidebarGroup.class, target = SidebarGroupDTO.class) - SidebarEntryDTO map(SidebarEntry source); } \ No newline at end of file diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java index c2bcf96420..f302ed6084 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java @@ -8,6 +8,7 @@ * Main MapStruct mapper interface. */ @Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) -public interface MainMapper extends ContentMapper, UserMapper, EventMapper, AssignmentMapper, QuestionMapper, QuestionValidationMapper { +public interface MainMapper extends ContentMapper, UserMapper, EventMapper, AssignmentMapper, QuestionMapper, + QuestionValidationMapper { MainMapper INSTANCE = Mappers.getMapper(MainMapper.class); } \ No newline at end of file From 90896a89084f9b87aeb4f23943cd1630869b17c7 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Wed, 22 Oct 2025 14:46:14 +0100 Subject: [PATCH 45/65] Add Content DO -> DTO mapping test --- .../dtg/isaac/dos/content/RegexPattern.java | 6 +-- .../isaac/dto/content/RegexPatternDTO.java | 6 +-- .../dtg/util/mappers/ContentMapperTest.java | 48 +++++++++++++++++++ 3 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 src/test/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperTest.java diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dos/content/RegexPattern.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dos/content/RegexPattern.java index ee5ecb0195..bd1c895e20 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dos/content/RegexPattern.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dos/content/RegexPattern.java @@ -35,7 +35,7 @@ public RegexPattern() { /** * @return Whether this regex choice should allow any case to match. */ - public boolean isCaseInsensitive() { + public Boolean isCaseInsensitive() { return caseInsensitive; } @@ -51,7 +51,7 @@ public void setCaseInsensitive(final boolean caseInsensitive) { /** * @return Whether this regex pattern should have the multiline flag set. */ - public boolean isMultiLineRegex() { + public Boolean isMultiLineRegex() { return multiLineRegex; } @@ -67,7 +67,7 @@ public void setMultiLineRegex(final boolean multiLineRegex) { /** * @return Whether this regex pattern is partial or exact match. */ - public boolean isMatchWholeString() { + public Boolean isMatchWholeString() { return matchWholeString; } diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/content/RegexPatternDTO.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/content/RegexPatternDTO.java index dc444871d7..16a1f18fce 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/content/RegexPatternDTO.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/content/RegexPatternDTO.java @@ -34,7 +34,7 @@ public RegexPatternDTO() { /** * @return Whether this regex choice should allow any case to match. */ - public boolean isCaseInsensitive() { + public Boolean isCaseInsensitive() { return caseInsensitive; } @@ -50,7 +50,7 @@ public void setCaseInsensitive(final boolean caseInsensitive) { /** * @return Whether this regex pattern should have the multiline flag set. */ - public boolean isMultiLineRegex() { + public Boolean isMultiLineRegex() { return multiLineRegex; } @@ -66,7 +66,7 @@ public void setMultiLineRegex(final boolean multiLineRegex) { /** * @return Whether this regex pattern is partial or exact match. */ - public boolean ismatchWholeString() { + public Boolean ismatchWholeString() { return matchWholeString; } diff --git a/src/test/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperTest.java b/src/test/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperTest.java new file mode 100644 index 0000000000..3b8e13ecb7 --- /dev/null +++ b/src/test/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperTest.java @@ -0,0 +1,48 @@ +package uk.ac.cam.cl.dtg.util.mappers; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.reflections.Reflections; +import uk.ac.cam.cl.dtg.isaac.dos.content.Content; +import uk.ac.cam.cl.dtg.isaac.dos.content.DTOMapping; +import uk.ac.cam.cl.dtg.isaac.dto.content.ContentDTO; + +import java.lang.reflect.Modifier; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; + +public class ContentMapperTest { + private final MainMapper mapper = MainMapper.INSTANCE; + + @SuppressWarnings("unchecked") + private static Stream testCasesDOtoDTO() { + Reflections reflections = new Reflections("uk.ac.cam.cl.dtg.isaac.dos.content"); + Set> contentSubclasses = reflections.getSubTypesOf(Content.class); + contentSubclasses.add(Content.class); + + return contentSubclasses.stream() + .map(subclass -> { + if (Modifier.isAbstract(subclass.getModifiers())) { + return null; + } + Class dtoClass = (Class) subclass.getAnnotation(DTOMapping.class).value(); + return dtoClass != null ? Arguments.of(subclass, dtoClass) : null; + }) + .filter(Objects::nonNull); + } + + @ParameterizedTest + @MethodSource("testCasesDOtoDTO") + void testDOtoDTOMapping(final Class sourceClass, final Class expectedDTOClass) { + Content source; + try { + source = sourceClass.getDeclaredConstructor().newInstance(); + } catch (final Exception e) { + throw new RuntimeException("Failed to instantiate source class: " + sourceClass.getName(), e); + } + Assertions.assertEquals(expectedDTOClass, mapper.map(source).getClass()); + } +} From 75590011b8662cb66fac465e19f8c98714363cfd Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Wed, 22 Oct 2025 14:53:01 +0100 Subject: [PATCH 46/65] Add missing Content subclass mappings Also move question mappings into main ContentMapper interface --- .../SegueGuiceConfigurationModule.java | 8 -- .../dao/content/ContentSubclassMapper.java | 2 +- .../cl/dtg/util/mappers/ContentMapper.java | 76 +++++++++++---- .../cam/cl/dtg/util/mappers/MainMapper.java | 3 +- .../cl/dtg/util/mappers/QuestionMapper.java | 93 ------------------- 5 files changed, 59 insertions(+), 123 deletions(-) delete mode 100644 src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionMapper.java diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java index 23f1991e0a..6b7469f5e3 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java @@ -151,7 +151,6 @@ import uk.ac.cam.cl.dtg.util.mappers.ContentMapper; import uk.ac.cam.cl.dtg.util.mappers.EventMapper; import uk.ac.cam.cl.dtg.util.mappers.MainMapper; -import uk.ac.cam.cl.dtg.util.mappers.QuestionMapper; import uk.ac.cam.cl.dtg.util.mappers.QuestionValidationMapper; import uk.ac.cam.cl.dtg.util.mappers.UserMapper; @@ -661,13 +660,6 @@ public static AssignmentMapper getAssignmentMapperInstance() { return AssignmentMapper.INSTANCE; } - @Provides - @Singleton - @Inject - public static QuestionMapper getQuestionMapperInstance() { - return QuestionMapper.INSTANCE; - } - @Provides @Singleton @Inject diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentSubclassMapper.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentSubclassMapper.java index 20b933a8e8..ea67dcdc70 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentSubclassMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/content/ContentSubclassMapper.java @@ -221,7 +221,7 @@ public ContentDTO getDTOByDO(final Content content) { if (null == content) { return null; } - ContentDTO result = MainMapper.INSTANCE.mapContent(content); + ContentDTO result = MainMapper.INSTANCE.map(content); populateSidebarWithIDs(content, result); return result; } diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index cc12f24d31..9d0073108c 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -1,6 +1,5 @@ package uk.ac.cam.cl.dtg.util.mappers; -import org.mapstruct.InheritConfiguration; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.SubclassExhaustiveStrategy; @@ -21,7 +20,7 @@ public interface ContentMapper { ContentMapper INSTANCE = Mappers.getMapper(ContentMapper.class); /** - * Mapping method to convert a ContentDTO to various other types of DTO or a Wildcard. + * Mapping method to convert a ContentDTO to other types of DTO. * * @param * - the target type. @@ -59,8 +58,9 @@ default T map(ContentDTO source, Class targetClass) { */ @SuppressWarnings("unchecked") default T map(Content source, Class targetClass) { + // TODO fix the getWildcardById usage that makes this method necessary if (targetClass.equals(ContentDTO.class)) { - return (T) mapContent(source); + return (T) map(source); } else { throw new UnimplementedMappingException(Content.class, targetClass); } @@ -72,19 +72,19 @@ default T map(Content source, Class targetClass) { @SubclassMapping(source = Content.class, target = ContentDTO.class) ContentBaseDTO map(ContentBase source); - @SubclassMapping(source = ChemicalFormula.class, target = ChemicalFormulaDTO.class) @SubclassMapping(source = Formula.class, target = FormulaDTO.class) @SubclassMapping(source = FreeTextRule.class, target = FreeTextRuleDTO.class) @SubclassMapping(source = GraphChoice.class, target = GraphChoiceDTO.class) - @SubclassMapping(source = ParsonsChoice.class, target = ParsonsChoiceDTO.class) - @SubclassMapping(source = ItemChoice.class, target = ItemChoiceDTO.class) @SubclassMapping(source = LLMFreeTextChoice.class, target = LLMFreeTextChoiceDTO.class) @SubclassMapping(source = LogicFormula.class, target = LogicFormulaDTO.class) @SubclassMapping(source = Quantity.class, target = QuantityDTO.class) @SubclassMapping(source = RegexPattern.class, target = RegexPatternDTO.class) @SubclassMapping(source = StringChoice.class, target = StringChoiceDTO.class) + // ItemChoice subclasses must come before ItemChoice @SubclassMapping(source = CoordinateChoice.class, target = CoordinateChoiceDTO.class) + @SubclassMapping(source = ParsonsChoice.class, target = ParsonsChoiceDTO.class) + @SubclassMapping(source = ItemChoice.class, target = ItemChoiceDTO.class) ChoiceDTO map(Choice source); @Mapping(target = "searchableContent", ignore = true) @@ -95,14 +95,15 @@ default T map(Content source, Class targetClass) { @SubclassMapping(source = FormulaDTO.class, target = Formula.class) @SubclassMapping(source = FreeTextRuleDTO.class, target = FreeTextRule.class) @SubclassMapping(source = GraphChoiceDTO.class, target = GraphChoice.class) - @SubclassMapping(source = ParsonsChoiceDTO.class, target = ParsonsChoice.class) - @SubclassMapping(source = ItemChoiceDTO.class, target = ItemChoice.class) @SubclassMapping(source = LLMFreeTextChoiceDTO.class, target = LLMFreeTextChoice.class) @SubclassMapping(source = LogicFormulaDTO.class, target = LogicFormula.class) @SubclassMapping(source = QuantityDTO.class, target = Quantity.class) @SubclassMapping(source = RegexPatternDTO.class, target = RegexPattern.class) @SubclassMapping(source = StringChoiceDTO.class, target = StringChoice.class) + // ItemChoiceDTO subclasses must come before ItemChoiceDTO @SubclassMapping(source = CoordinateChoiceDTO.class, target = CoordinateChoice.class) + @SubclassMapping(source = ParsonsChoiceDTO.class, target = ParsonsChoice.class) + @SubclassMapping(source = ItemChoiceDTO.class, target = ItemChoice.class) Choice map(ChoiceDTO source); @SubclassMapping(source = ParsonsItem.class, target = ParsonsItemDTO.class) @@ -131,8 +132,7 @@ default T map(Content source, Class targetClass) { @Mapping(target = "userBookingStatus", ignore = true) @Mapping(target = "placesAvailable", ignore = true) @Mapping(target = "sidebar", ignore = true) - @Mapping(target = "end_date", ignore = true) - @InheritConfiguration(name = "mapContent") + @Mapping(target = "end_date", ignore = true) // end_date isn't actually ignored - see implementation IsaacEventPageDTO map(IsaacEventPage source); @Mapping(target = "total", ignore = true) @@ -140,29 +140,32 @@ default T map(Content source, Class targetClass) { @Mapping(target = "individualFeedback", ignore = true) @Mapping(target = "defaultFeedbackMode", ignore = true) @Mapping(target = "sidebar", ignore = true) - @InheritConfiguration(name = "mapContent") IsaacQuizDTO map(IsaacQuiz source); @Mapping(target = "sidebar", ignore = true) @Mapping(target = "linkedGameboards", ignore = true) - @InheritConfiguration(name = "mapContent") IsaacTopicSummaryPageDTO map(IsaacTopicSummaryPage source); @Mapping(target = "sidebar", ignore = true) @Mapping(target = "gameboards", ignore = true) @Mapping(target = "extensionGameboards", ignore = true) - @InheritConfiguration(name = "mapContent") IsaacBookDetailPageDTO map(IsaacBookDetailPage source); @Mapping(target = "sidebar", ignore = true) @Mapping(target = "gameboards", ignore = true) - @InheritConfiguration(name = "mapContent") IsaacRevisionDetailPageDTO map(IsaacRevisionDetailPage source); - @InheritConfiguration(name = "mapContent") @SubclassMapping(source = SidebarGroup.class, target = SidebarGroupDTO.class) SidebarEntryDTO map(SidebarEntry source); + @Mapping(target = "bestAttempt", ignore = true) + @SubclassMapping(source = ChoiceQuestion.class, target = ChoiceQuestionDTO.class) + QuestionDTO map(Question source); + + @Mapping(target = "bestAttempt", ignore = true) + @SubclassMapping(source = IsaacQuestionBase.class, target = IsaacQuestionBaseDTO.class) + ChoiceQuestionDTO map(ChoiceQuestion source); + @SubclassMapping(source = AnvilApp.class, target = AnvilAppDTO.class) @SubclassMapping(source = Choice.class, target = ChoiceDTO.class) @SubclassMapping(source = CodeSnippet.class, target = CodeSnippetDTO.class) @@ -182,10 +185,17 @@ default T map(Content source, Class targetClass) { @SubclassMapping(source = Question.class, target = QuestionDTO.class) @SubclassMapping(source = Sidebar.class, target = SidebarDTO.class) @SubclassMapping(source = SidebarEntry.class, target = SidebarEntryDTO.class) + // Media subclasses. Figure must come before Image. + @SubclassMapping(source = Figure.class, target = FigureDTO.class) + @SubclassMapping(source = Image.class, target = ImageDTO.class) + @SubclassMapping(source = Video.class, target = VideoDTO.class) + // Segue pages. More specific subclasses must come first. + @SubclassMapping(source = IsaacBookDetailPage.class, target = IsaacBookDetailPageDTO.class) + @SubclassMapping(source = IsaacRevisionDetailPage.class, target = IsaacRevisionDetailPageDTO.class) @SubclassMapping(source = IsaacEventPage.class, target = IsaacEventPageDTO.class) @SubclassMapping(source = IsaacQuiz.class, target = IsaacQuizDTO.class) @SubclassMapping(source = SeguePage.class, target = SeguePageDTO.class) - ContentDTO mapContent(Content source); + ContentDTO map(Content source); @Mapping(target = "url", ignore = true) @Mapping(target = "supersededBy", ignore = true) @@ -257,7 +267,7 @@ default T map(Content source, Class targetClass) { ContentSummaryDTO mapStringToContentSummaryDTO(String source); /** - * Mapping method to convert a ContentSummaryDTO to a String of its ID. + * Mapping method to convert a ContentSummaryDTO to a String of its ID. Needed to map related content. * * @param source * - the source Content object. @@ -270,8 +280,36 @@ default String mapContentSummaryDTOtoString(ContentSummaryDTO source) { return source.getId(); } - List copyStringList(List source); - @Mapping(target = "end_date", ignore = true) IsaacEventPageDTO copy(IsaacEventPageDTO source); + + @Mapping(target = "bestAttempt", ignore = true) + @SubclassMapping(source = IsaacQuickQuestion.class, target = IsaacQuickQuestionDTO.class) + @SubclassMapping(source = IsaacClozeQuestion.class, target = IsaacClozeQuestionDTO.class) + @SubclassMapping(source = IsaacFreeTextQuestion.class, target = IsaacFreeTextQuestionDTO.class) + @SubclassMapping(source = IsaacGraphSketcherQuestion.class, target = IsaacGraphSketcherQuestionDTO.class) + @SubclassMapping(source = IsaacItemQuestion.class, target = IsaacItemQuestionDTO.class) + @SubclassMapping(source = IsaacMultiChoiceQuestion.class, target = IsaacMultiChoiceQuestionDTO.class) + @SubclassMapping(source = IsaacNumericQuestion.class, target = IsaacNumericQuestionDTO.class) + @SubclassMapping(source = IsaacParsonsQuestion.class, target = IsaacParsonsQuestionDTO.class) + @SubclassMapping(source = IsaacRegexMatchQuestion.class, target = IsaacRegexMatchQuestionDTO.class) + @SubclassMapping(source = IsaacReorderQuestion.class, target = IsaacReorderQuestionDTO.class) + @SubclassMapping(source = IsaacStringMatchQuestion.class, target = IsaacStringMatchQuestionDTO.class) + @SubclassMapping(source = IsaacSymbolicChemistryQuestion.class, target = IsaacSymbolicChemistryQuestionDTO.class) + @SubclassMapping(source = IsaacSymbolicLogicQuestion.class, target = IsaacSymbolicLogicQuestionDTO.class) + @SubclassMapping(source = IsaacSymbolicQuestion.class, target = IsaacSymbolicQuestionDTO.class) + @SubclassMapping(source = IsaacCoordinateQuestion.class, target = IsaacCoordinateQuestionDTO.class) + @SubclassMapping(source = IsaacLLMFreeTextQuestion.class, target = IsaacLLMFreeTextQuestionDTO.class) + @SubclassMapping(source = IsaacAnvilQuestion.class, target = IsaacAnvilQuestionDTO.class) + IsaacQuestionBaseDTO mapIsaacQuestionBase(IsaacQuestionBase source); + + @Mapping(target = "bestAttempt", ignore = true) + @SubclassMapping(source = IsaacClozeQuestion.class, target = IsaacClozeQuestionDTO.class) + @SubclassMapping(source = IsaacParsonsQuestion.class, target = IsaacParsonsQuestionDTO.class) + @SubclassMapping(source = IsaacReorderQuestion.class, target = IsaacReorderQuestionDTO.class) + IsaacItemQuestionDTO mapIsaacItemQuestion(IsaacItemQuestion source); + + @Mapping(target = "bestAttempt", ignore = true) + @SubclassMapping(source = IsaacSymbolicLogicQuestion.class, target = IsaacSymbolicLogicQuestionDTO.class) + IsaacSymbolicQuestionDTO mapIsaacSymbolicQuestion(IsaacSymbolicQuestion source); } \ No newline at end of file diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java index f302ed6084..437c6d87ff 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java @@ -8,7 +8,6 @@ * Main MapStruct mapper interface. */ @Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) -public interface MainMapper extends ContentMapper, UserMapper, EventMapper, AssignmentMapper, QuestionMapper, - QuestionValidationMapper { +public interface MainMapper extends ContentMapper, UserMapper, EventMapper, AssignmentMapper, QuestionValidationMapper { MainMapper INSTANCE = Mappers.getMapper(MainMapper.class); } \ No newline at end of file diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionMapper.java deleted file mode 100644 index d1b00fb67b..0000000000 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/QuestionMapper.java +++ /dev/null @@ -1,93 +0,0 @@ -package uk.ac.cam.cl.dtg.util.mappers; - -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.SubclassExhaustiveStrategy; -import org.mapstruct.SubclassMapping; -import org.mapstruct.factory.Mappers; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacAnvilQuestion; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacClozeQuestion; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacCoordinateQuestion; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacFreeTextQuestion; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacGraphSketcherQuestion; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacItemQuestion; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacLLMFreeTextQuestion; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacMultiChoiceQuestion; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacNumericQuestion; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacParsonsQuestion; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacQuestionBase; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacQuickQuestion; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacRegexMatchQuestion; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacReorderQuestion; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacStringMatchQuestion; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacSymbolicChemistryQuestion; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacSymbolicLogicQuestion; -import uk.ac.cam.cl.dtg.isaac.dos.IsaacSymbolicQuestion; -import uk.ac.cam.cl.dtg.isaac.dos.content.ChoiceQuestion; -import uk.ac.cam.cl.dtg.isaac.dos.content.Question; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacAnvilQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacClozeQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacCoordinateQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacFreeTextQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacGraphSketcherQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacItemQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacLLMFreeTextQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacMultiChoiceQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacNumericQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacParsonsQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuestionBaseDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuickQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacRegexMatchQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacReorderQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacStringMatchQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacSymbolicChemistryQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacSymbolicLogicQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.IsaacSymbolicQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.ChoiceQuestionDTO; -import uk.ac.cam.cl.dtg.isaac.dto.content.QuestionDTO; - -/** - * MapStruct mapper for Questions. - */ -@Mapper(uses = ContentMapper.class, subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) -public interface QuestionMapper { - QuestionMapper INSTANCE = Mappers.getMapper(QuestionMapper.class); - - @Mapping(target = "bestAttempt", ignore = true) - @SubclassMapping(source = ChoiceQuestion.class, target = ChoiceQuestionDTO.class) - QuestionDTO map(Question source); - - @Mapping(target = "bestAttempt", ignore = true) - @SubclassMapping(source = IsaacQuestionBase.class, target = IsaacQuestionBaseDTO.class) - ChoiceQuestionDTO map(ChoiceQuestion source); - - @Mapping(target = "bestAttempt", ignore = true) - @SubclassMapping(source = IsaacQuickQuestion.class, target = IsaacQuickQuestionDTO.class) - @SubclassMapping(source = IsaacClozeQuestion.class, target = IsaacClozeQuestionDTO.class) - @SubclassMapping(source = IsaacFreeTextQuestion.class, target = IsaacFreeTextQuestionDTO.class) - @SubclassMapping(source = IsaacGraphSketcherQuestion.class, target = IsaacGraphSketcherQuestionDTO.class) - @SubclassMapping(source = IsaacItemQuestion.class, target = IsaacItemQuestionDTO.class) - @SubclassMapping(source = IsaacMultiChoiceQuestion.class, target = IsaacMultiChoiceQuestionDTO.class) - @SubclassMapping(source = IsaacNumericQuestion.class, target = IsaacNumericQuestionDTO.class) - @SubclassMapping(source = IsaacParsonsQuestion.class, target = IsaacParsonsQuestionDTO.class) - @SubclassMapping(source = IsaacRegexMatchQuestion.class, target = IsaacRegexMatchQuestionDTO.class) - @SubclassMapping(source = IsaacReorderQuestion.class, target = IsaacReorderQuestionDTO.class) - @SubclassMapping(source = IsaacStringMatchQuestion.class, target = IsaacStringMatchQuestionDTO.class) - @SubclassMapping(source = IsaacSymbolicChemistryQuestion.class, target = IsaacSymbolicChemistryQuestionDTO.class) - @SubclassMapping(source = IsaacSymbolicLogicQuestion.class, target = IsaacSymbolicLogicQuestionDTO.class) - @SubclassMapping(source = IsaacSymbolicQuestion.class, target = IsaacSymbolicQuestionDTO.class) - @SubclassMapping(source = IsaacCoordinateQuestion.class, target = IsaacCoordinateQuestionDTO.class) - @SubclassMapping(source = IsaacLLMFreeTextQuestion.class, target = IsaacLLMFreeTextQuestionDTO.class) - @SubclassMapping(source = IsaacAnvilQuestion.class, target = IsaacAnvilQuestionDTO.class) - IsaacQuestionBaseDTO mapIsaacQuestionBase(IsaacQuestionBase source); - - @Mapping(target = "bestAttempt", ignore = true) - @SubclassMapping(source = IsaacClozeQuestion.class, target = IsaacClozeQuestionDTO.class) - @SubclassMapping(source = IsaacParsonsQuestion.class, target = IsaacParsonsQuestionDTO.class) - @SubclassMapping(source = IsaacReorderQuestion.class, target = IsaacReorderQuestionDTO.class) - IsaacItemQuestionDTO mapIsaacItemQuestion(IsaacItemQuestion source); - - @Mapping(target = "bestAttempt", ignore = true) - @SubclassMapping(source = IsaacSymbolicLogicQuestion.class, target = IsaacSymbolicLogicQuestionDTO.class) - IsaacSymbolicQuestionDTO mapIsaacSymbolicQuestion(IsaacSymbolicQuestion source); -} From 43073e58c51a4f7dc079d143110da5b5aa7c02d3 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Wed, 22 Oct 2025 17:25:25 +0100 Subject: [PATCH 47/65] Add copy method for Wildcards, remove now-unused Content -> Wildcard mapping delegator --- .../dtg/isaac/api/managers/GameManager.java | 7 +++++- .../cl/dtg/util/mappers/ContentMapper.java | 23 ++----------------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java index 8c9fc4a003..416e31e992 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java @@ -1215,7 +1215,12 @@ private IsaacWildcard getWildCardById(final String id) throws ContentManagerExce Content wildcardResults = this.contentManager.getContentDOById(id); - return mapper.map(wildcardResults, IsaacWildcard.class); + if (wildcardResults instanceof IsaacWildcard) { + // Create a copy to avoid modifying the original object + IsaacWildcard wildcard = (IsaacWildcard) wildcardResults; + return mapper.copy(wildcard); + } + return null; } /** diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index 9d0073108c..9597aad453 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -45,27 +45,6 @@ default T map(ContentDTO source, Class targetClass) { } } - /** - * Mapping method to convert a Content object to a ContentDTO or a Wildcard. - * - * @param - * - the target type. - * @param source - * - the source Content object. - * @param targetClass - * - the target class to convert to. - * @return Returns an instance of the targetClass type mapped via the appropriate mapping method. - */ - @SuppressWarnings("unchecked") - default T map(Content source, Class targetClass) { - // TODO fix the getWildcardById usage that makes this method necessary - if (targetClass.equals(ContentDTO.class)) { - return (T) map(source); - } else { - throw new UnimplementedMappingException(Content.class, targetClass); - } - } - @SubclassMapping(source = ContentDTO.class, target = Content.class) ContentBase map(ContentBaseDTO source); @@ -280,6 +259,8 @@ default String mapContentSummaryDTOtoString(ContentSummaryDTO source) { return source.getId(); } + IsaacWildcard copy(IsaacWildcard source); + @Mapping(target = "end_date", ignore = true) IsaacEventPageDTO copy(IsaacEventPageDTO source); From 106f398cfbf306a6102ce4f2058dc8b799246b76 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Thu, 23 Oct 2025 14:21:13 +0100 Subject: [PATCH 48/65] Include all Content subclasses in mapper test --- .../java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperTest.java b/src/test/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperTest.java index 3b8e13ecb7..656b28b082 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperTest.java @@ -19,7 +19,7 @@ public class ContentMapperTest { @SuppressWarnings("unchecked") private static Stream testCasesDOtoDTO() { - Reflections reflections = new Reflections("uk.ac.cam.cl.dtg.isaac.dos.content"); + Reflections reflections = new Reflections("uk.ac.cam.cl.dtg.isaac.dos"); Set> contentSubclasses = reflections.getSubTypesOf(Content.class); contentSubclasses.add(Content.class); From 8817104cd4a9a9fbb76f9b910fc0d4db7a40b0d5 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Thu, 23 Oct 2025 14:24:13 +0100 Subject: [PATCH 49/65] Add more Content subclass mappings And rename some existing ones for clarity --- .../cam/cl/dtg/util/mappers/ContentMapper.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index 9597aad453..4f693d74b6 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -106,6 +106,7 @@ default T map(ContentDTO source, Class targetClass) { @SubclassMapping(source = IsaacQuestionPage.class, target = IsaacQuestionPageDTO.class) @SubclassMapping(source = IsaacConceptPage.class, target = IsaacConceptPageDTO.class) @SubclassMapping(source = IsaacBookIndexPage.class, target = IsaacBookIndexPageDTO.class) + @SubclassMapping(source = IsaacTopicSummaryPage.class, target = IsaacTopicSummaryPageDTO.class) SeguePageDTO map(SeguePage source); @Mapping(target = "userBookingStatus", ignore = true) @@ -176,6 +177,12 @@ default T map(ContentDTO source, Class targetClass) { @SubclassMapping(source = SeguePage.class, target = SeguePageDTO.class) ContentDTO map(Content source); + @Mapping(target = "searchableContent", ignore = true) + @Mapping(target = "prioritisedSearchableContent", ignore = true) + @SubclassMapping(source = ChoiceDTO.class, target = Choice.class) + @SubclassMapping(source = ItemDTO.class, target = Item.class) + Content map(ContentDTO source); + @Mapping(target = "url", ignore = true) @Mapping(target = "supersededBy", ignore = true) @Mapping(target = "summary", ignore = true) @@ -243,7 +250,7 @@ default T map(ContentDTO source, Class targetClass) { @Mapping(target = "difficulty", ignore = true) @Mapping(target = "deprecated", ignore = true) @Mapping(target = "audience", ignore = true) - ContentSummaryDTO mapStringToContentSummaryDTO(String source); + ContentSummaryDTO map(String source); /** * Mapping method to convert a ContentSummaryDTO to a String of its ID. Needed to map related content. @@ -252,7 +259,7 @@ default T map(ContentDTO source, Class targetClass) { * - the source Content object. * @return Returns the source Content object's ID. */ - default String mapContentSummaryDTOtoString(ContentSummaryDTO source) { + default String map(ContentSummaryDTO source) { if (source == null) { return null; } @@ -282,15 +289,15 @@ default String mapContentSummaryDTOtoString(ContentSummaryDTO source) { @SubclassMapping(source = IsaacCoordinateQuestion.class, target = IsaacCoordinateQuestionDTO.class) @SubclassMapping(source = IsaacLLMFreeTextQuestion.class, target = IsaacLLMFreeTextQuestionDTO.class) @SubclassMapping(source = IsaacAnvilQuestion.class, target = IsaacAnvilQuestionDTO.class) - IsaacQuestionBaseDTO mapIsaacQuestionBase(IsaacQuestionBase source); + IsaacQuestionBaseDTO map(IsaacQuestionBase source); @Mapping(target = "bestAttempt", ignore = true) @SubclassMapping(source = IsaacClozeQuestion.class, target = IsaacClozeQuestionDTO.class) @SubclassMapping(source = IsaacParsonsQuestion.class, target = IsaacParsonsQuestionDTO.class) @SubclassMapping(source = IsaacReorderQuestion.class, target = IsaacReorderQuestionDTO.class) - IsaacItemQuestionDTO mapIsaacItemQuestion(IsaacItemQuestion source); + IsaacItemQuestionDTO map(IsaacItemQuestion source); @Mapping(target = "bestAttempt", ignore = true) @SubclassMapping(source = IsaacSymbolicLogicQuestion.class, target = IsaacSymbolicLogicQuestionDTO.class) - IsaacSymbolicQuestionDTO mapIsaacSymbolicQuestion(IsaacSymbolicQuestion source); + IsaacSymbolicQuestionDTO map(IsaacSymbolicQuestion source); } \ No newline at end of file From e2c139f031f8dc9fb3b4c1f7a2010bad1346e8da Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Fri, 24 Oct 2025 12:27:06 +0100 Subject: [PATCH 50/65] Avoid unnecessarily copying group membership DTO Also call mapping to userSummaryWithGroupMembershipDTO directly for clarity --- .../uk/ac/cam/cl/dtg/segue/api/managers/GroupManager.java | 4 ++-- src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/GroupManager.java b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/GroupManager.java index 6401eade76..282696c113 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/GroupManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/GroupManager.java @@ -650,9 +650,9 @@ public void convertToUserSummaryGroupMembership(UserGroupDTO group, List userMembershipMapforMap = this.getUserMembershipMapForGroup(group.getId()); for(UserSummaryDTO dto : summarisedMemberInfo) { - UserSummaryWithGroupMembershipDTO newDTO = dtoMapper.map(dto, UserSummaryWithGroupMembershipDTO.class); + UserSummaryWithGroupMembershipDTO newDTO = dtoMapper.mapToUserSummaryWithGroupMembershipDTO(dto); GroupMembershipDTO groupMembershipDTO = userMembershipMapforMap.get(newDTO.getId()); - newDTO.setGroupMembershipInformation(dtoMapper.copy(groupMembershipDTO)); + newDTO.setGroupMembershipInformation(groupMembershipDTO); result.add(newDTO); } diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java index de5cc3d636..e04b237eb8 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java @@ -72,9 +72,7 @@ default T map(RegisteredUserDTO source, Class targ */ @SuppressWarnings("unchecked") default T map(UserSummaryDTO source, Class targetClass) { - if (targetClass.equals(UserSummaryWithGroupMembershipDTO.class)) { - return (T) mapUserSummaryDTOtoUserSummaryWithGroupMembershipDTO(source); - } else if (targetClass.equals(UserSummaryDTO.class)) { + if (targetClass.equals(UserSummaryDTO.class)) { return (T) mapExtendedUserSummaryDTOtoBaseUserSummaryDTO(source); } else { throw new UnimplementedMappingException(UserSummaryDTO.class, targetClass); @@ -145,7 +143,7 @@ default T map(UserSummaryDTO source, Class targetClass) { @SubclassMapping(source = UserSummaryForAdminUsersDTO.class, target = UserSummaryWithGroupMembershipDTO.class) @SubclassMapping(source = UserSummaryWithEmailAddressDTO.class, target = UserSummaryWithGroupMembershipDTO.class) @SubclassMapping(source = UserSummaryWithGroupMembershipDTO.class, target = UserSummaryWithGroupMembershipDTO.class) - UserSummaryWithGroupMembershipDTO mapUserSummaryDTOtoUserSummaryWithGroupMembershipDTO(UserSummaryDTO source); + UserSummaryWithGroupMembershipDTO mapToUserSummaryWithGroupMembershipDTO(UserSummaryDTO source); @BeanMapping(resultType = UserSummaryDTO.class) UserSummaryDTO mapExtendedUserSummaryDTOtoBaseUserSummaryDTO(UserSummaryDTO source); From d171bfa3ba90e97a349564e33fa916cb131fd782 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Fri, 24 Oct 2025 12:32:36 +0100 Subject: [PATCH 51/65] Use copy instead of map where source class matches target class And remove now-unused mappings --- .../ac/cam/cl/dtg/isaac/api/EventsFacade.java | 3 +-- .../cam/cl/dtg/util/mappers/UserMapper.java | 23 ------------------- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java index 25612ec3b1..b162ca1c9a 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java @@ -604,8 +604,7 @@ public final Response getEventBookingForAllGroups(@Context final HttpServletRequ eventBookings = userAssociationManager.filterUnassociatedRecords( currentUser, eventBookings, booking -> booking.getUserBooked().getId()); - eventBookings.forEach(booking -> booking.setUserBooked(mapper.map(booking.getUserBooked(), - UserSummaryDTO.class))); + eventBookings.forEach(booking -> booking.setUserBooked(mapper.copy(booking.getUserBooked()))); return Response.ok(eventBookings).build(); } catch (NoUserLoggedInException e) { diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java index e04b237eb8..f0f0a7d804 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java @@ -59,26 +59,6 @@ default T map(RegisteredUserDTO source, Class targ } } - /** - * Mapping method to convert a UserSummaryDTO to a UserSummaryWithGroupMembershipDTO or UserSummaryDTO. - * - * @param - * - the target type. - * @param source - * - the source UserSummaryDTO. - * @param targetClass - * - the target class to convert to. - * @return Returns an instance of the targetClass type mapped via the appropriate mapping method. - */ - @SuppressWarnings("unchecked") - default T map(UserSummaryDTO source, Class targetClass) { - if (targetClass.equals(UserSummaryDTO.class)) { - return (T) mapExtendedUserSummaryDTOtoBaseUserSummaryDTO(source); - } else { - throw new UnimplementedMappingException(UserSummaryDTO.class, targetClass); - } - } - @Mapping(target = "token", ignore = true) @Mapping(target = "ownerSummary", ignore = true) @Mapping(target = "mongoId", ignore = true) @@ -145,9 +125,6 @@ default T map(UserSummaryDTO source, Class targetClass) { @SubclassMapping(source = UserSummaryWithGroupMembershipDTO.class, target = UserSummaryWithGroupMembershipDTO.class) UserSummaryWithGroupMembershipDTO mapToUserSummaryWithGroupMembershipDTO(UserSummaryDTO source); - @BeanMapping(resultType = UserSummaryDTO.class) - UserSummaryDTO mapExtendedUserSummaryDTOtoBaseUserSummaryDTO(UserSummaryDTO source); - RegisteredUser copy(RegisteredUser source); RegisteredUserDTO copy(RegisteredUserDTO source); From 61941a9275a6e453e2b714edfb1343b5b2c0bb56 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Fri, 24 Oct 2025 13:48:05 +0100 Subject: [PATCH 52/65] Revert "Use copy instead of map where source class matches target class" This reverts commit d171bfa3ba90e97a349564e33fa916cb131fd782. --- .../ac/cam/cl/dtg/isaac/api/EventsFacade.java | 3 ++- .../cam/cl/dtg/util/mappers/UserMapper.java | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java index b162ca1c9a..25612ec3b1 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java @@ -604,7 +604,8 @@ public final Response getEventBookingForAllGroups(@Context final HttpServletRequ eventBookings = userAssociationManager.filterUnassociatedRecords( currentUser, eventBookings, booking -> booking.getUserBooked().getId()); - eventBookings.forEach(booking -> booking.setUserBooked(mapper.copy(booking.getUserBooked()))); + eventBookings.forEach(booking -> booking.setUserBooked(mapper.map(booking.getUserBooked(), + UserSummaryDTO.class))); return Response.ok(eventBookings).build(); } catch (NoUserLoggedInException e) { diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java index f0f0a7d804..e04b237eb8 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java @@ -59,6 +59,26 @@ default T map(RegisteredUserDTO source, Class targ } } + /** + * Mapping method to convert a UserSummaryDTO to a UserSummaryWithGroupMembershipDTO or UserSummaryDTO. + * + * @param + * - the target type. + * @param source + * - the source UserSummaryDTO. + * @param targetClass + * - the target class to convert to. + * @return Returns an instance of the targetClass type mapped via the appropriate mapping method. + */ + @SuppressWarnings("unchecked") + default T map(UserSummaryDTO source, Class targetClass) { + if (targetClass.equals(UserSummaryDTO.class)) { + return (T) mapExtendedUserSummaryDTOtoBaseUserSummaryDTO(source); + } else { + throw new UnimplementedMappingException(UserSummaryDTO.class, targetClass); + } + } + @Mapping(target = "token", ignore = true) @Mapping(target = "ownerSummary", ignore = true) @Mapping(target = "mongoId", ignore = true) @@ -125,6 +145,9 @@ default T map(RegisteredUserDTO source, Class targ @SubclassMapping(source = UserSummaryWithGroupMembershipDTO.class, target = UserSummaryWithGroupMembershipDTO.class) UserSummaryWithGroupMembershipDTO mapToUserSummaryWithGroupMembershipDTO(UserSummaryDTO source); + @BeanMapping(resultType = UserSummaryDTO.class) + UserSummaryDTO mapExtendedUserSummaryDTOtoBaseUserSummaryDTO(UserSummaryDTO source); + RegisteredUser copy(RegisteredUser source); RegisteredUserDTO copy(RegisteredUserDTO source); From 7f45f0a3091ad9caaacc84aa8f5c18e662ec250f Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Fri, 24 Oct 2025 14:18:06 +0100 Subject: [PATCH 53/65] Simplify mapping UserSummaryDTO subclasses to parent class --- .../ac/cam/cl/dtg/isaac/api/EventsFacade.java | 3 +-- .../cam/cl/dtg/util/mappers/UserMapper.java | 26 +++---------------- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java index 25612ec3b1..d5bfd050c7 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java @@ -604,8 +604,7 @@ public final Response getEventBookingForAllGroups(@Context final HttpServletRequ eventBookings = userAssociationManager.filterUnassociatedRecords( currentUser, eventBookings, booking -> booking.getUserBooked().getId()); - eventBookings.forEach(booking -> booking.setUserBooked(mapper.map(booking.getUserBooked(), - UserSummaryDTO.class))); + eventBookings.forEach(booking -> booking.setUserBooked(mapper.mapToUserSummaryDTO(booking.getUserBooked()))); return Response.ok(eventBookings).build(); } catch (NoUserLoggedInException e) { diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java index e04b237eb8..528813aa40 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java @@ -59,26 +59,6 @@ default T map(RegisteredUserDTO source, Class targ } } - /** - * Mapping method to convert a UserSummaryDTO to a UserSummaryWithGroupMembershipDTO or UserSummaryDTO. - * - * @param - * - the target type. - * @param source - * - the source UserSummaryDTO. - * @param targetClass - * - the target class to convert to. - * @return Returns an instance of the targetClass type mapped via the appropriate mapping method. - */ - @SuppressWarnings("unchecked") - default T map(UserSummaryDTO source, Class targetClass) { - if (targetClass.equals(UserSummaryDTO.class)) { - return (T) mapExtendedUserSummaryDTOtoBaseUserSummaryDTO(source); - } else { - throw new UnimplementedMappingException(UserSummaryDTO.class, targetClass); - } - } - @Mapping(target = "token", ignore = true) @Mapping(target = "ownerSummary", ignore = true) @Mapping(target = "mongoId", ignore = true) @@ -139,15 +119,15 @@ default T map(UserSummaryDTO source, Class targetClass) { nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) void merge(RegisteredUserDTO source, @MappingTarget RegisteredUser target); + // Map subclasses of UserSummaryDTO to UserSummaryDTO + UserSummaryDTO mapToUserSummaryDTO(UserSummaryDTO source); + @Mapping(target = "groupMembershipInformation", ignore = true) @SubclassMapping(source = UserSummaryForAdminUsersDTO.class, target = UserSummaryWithGroupMembershipDTO.class) @SubclassMapping(source = UserSummaryWithEmailAddressDTO.class, target = UserSummaryWithGroupMembershipDTO.class) @SubclassMapping(source = UserSummaryWithGroupMembershipDTO.class, target = UserSummaryWithGroupMembershipDTO.class) UserSummaryWithGroupMembershipDTO mapToUserSummaryWithGroupMembershipDTO(UserSummaryDTO source); - @BeanMapping(resultType = UserSummaryDTO.class) - UserSummaryDTO mapExtendedUserSummaryDTOtoBaseUserSummaryDTO(UserSummaryDTO source); - RegisteredUser copy(RegisteredUser source); RegisteredUserDTO copy(RegisteredUserDTO source); From 4dd4321c579a577ef28b18bae4deb27ca2c7afb8 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Fri, 24 Oct 2025 14:44:58 +0100 Subject: [PATCH 54/65] Add QuestionValidationResponse DO -> DTO mapping test --- .../mappers/QuestionValidationMapperTest.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/test/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapperTest.java diff --git a/src/test/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapperTest.java b/src/test/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapperTest.java new file mode 100644 index 0000000000..30e9506714 --- /dev/null +++ b/src/test/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapperTest.java @@ -0,0 +1,47 @@ +package uk.ac.cam.cl.dtg.util.mappers; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.reflections.Reflections; +import uk.ac.cam.cl.dtg.isaac.dos.QuestionValidationResponse; +import uk.ac.cam.cl.dtg.isaac.dos.content.DTOMapping; +import uk.ac.cam.cl.dtg.isaac.dto.QuestionValidationResponseDTO; + +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; + +public class QuestionValidationMapperTest { + private final MainMapper mapper = MainMapper.INSTANCE; + + @SuppressWarnings("unchecked") + private static Stream testCasesDOtoDTO() { + Reflections reflections = new Reflections("uk.ac.cam.cl.dtg.isaac.dos"); + Set> subclasses = reflections.getSubTypesOf(QuestionValidationResponse.class); + subclasses.add(QuestionValidationResponse.class); + + return subclasses.stream() + .map(subclass -> { + if (subclass.isAnnotationPresent(DTOMapping.class)) { + Class dtoClass = (Class) subclass.getAnnotation(DTOMapping.class).value(); + return dtoClass != null ? Arguments.of(subclass, dtoClass) : null; + } + return null; + }) + .filter(Objects::nonNull); + } + + @ParameterizedTest + @MethodSource("testCasesDOtoDTO") + void testDOtoDTOMapping(final Class sourceDOClass, final Class expectedDTOClass) { + QuestionValidationResponse source; + try { + source = sourceDOClass.getDeclaredConstructor().newInstance(); + } catch (final Exception e) { + throw new RuntimeException("Failed to instantiate source class: " + sourceDOClass.getName(), e); + } + Assertions.assertEquals(expectedDTOClass, mapper.map(source).getClass()); + } +} From 778dd21211b18f1dc3d710b8f324470677a3587a Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Mon, 27 Oct 2025 15:44:11 +0000 Subject: [PATCH 55/65] Name mapping methods consistently Only use "map" for DO <-> DTO mappings. For all other mappings, include the target type in the method name. --- .../ac/cam/cl/dtg/isaac/api/EventsFacade.java | 4 +- .../api/managers/UserAccountManager.java | 2 +- .../cl/dtg/util/mappers/AssignmentMapper.java | 4 +- .../cl/dtg/util/mappers/ContentMapper.java | 76 +++++++++---------- .../cam/cl/dtg/util/mappers/EventMapper.java | 4 +- .../cam/cl/dtg/util/mappers/UserMapper.java | 36 ++++----- .../segue/api/managers/UserManagerTest.java | 2 +- 7 files changed, 64 insertions(+), 64 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java index d5bfd050c7..f52721cf90 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java @@ -327,7 +327,7 @@ private ResultsWrapper getEventsReservedByUser(final HttpServletRequ throws SegueDatabaseException, ContentManagerException { List filteredResults = Lists.newArrayList(); - List userReservationList = this.mapper.map(bookingManager.getAllEventReservationsForUser(currentUser.getId())); + List userReservationList = this.mapper.mapToListOfEventBookingDTO(bookingManager.getAllEventReservationsForUser(currentUser.getId())); for (EventBookingDTO booking : userReservationList) { @@ -598,7 +598,7 @@ public final Response getEventBookingForAllGroups(@Context final HttpServletRequ return new SegueErrorResponse(Status.FORBIDDEN, "You do not have permission to use this endpoint.").toResponse(); } - List eventBookings = this.mapper.map(bookingManager.getBookingsByEventId(eventId)); + List eventBookings = this.mapper.mapToListOfEventBookingDTO(bookingManager.getBookingsByEventId(eventId)); // Only allowed to see the bookings of connected users eventBookings = userAssociationManager.filterUnassociatedRecords( diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/UserAccountManager.java b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/UserAccountManager.java index 043357bf5c..d34729803c 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/UserAccountManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/UserAccountManager.java @@ -1822,7 +1822,7 @@ private RegisteredUser registerUserWithFederatedProvider(final AuthenticationPro throw new NoUserException("No user returned by the provider!"); } - RegisteredUser newLocalUser = this.dtoMapper.map(userFromProvider); + RegisteredUser newLocalUser = this.dtoMapper.mapToRegisteredUser(userFromProvider); newLocalUser.setRegistrationDate(new Date()); // register user diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java index e2595780cd..8bfca72e94 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/AssignmentMapper.java @@ -60,7 +60,7 @@ public interface AssignmentMapper { // Handle mapping the "content" field for GameboardD(T)Os @Mapping(source = "creationContext", target = "context") - GameboardContentDescriptor mapGameboardItemToGameboardContentDescriptor(GameboardItem source); + GameboardContentDescriptor mapToGameboardContentDescriptor(GameboardItem source); @Mapping(target = "title", ignore = true) @Mapping(target = "subtitle", ignore = true) @@ -79,5 +79,5 @@ public interface AssignmentMapper { @Mapping(target = "supersededBy", ignore = true) @Mapping(target = "audience", ignore = true) @Mapping(source = "context", target = "creationContext") - GameboardItem mapGameboardItemToGameboardContentDescriptor(GameboardContentDescriptor source); + GameboardItem mapToGameboardItem(GameboardContentDescriptor source); } diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index 4f693d74b6..c68fa53e68 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -95,12 +95,6 @@ default T map(ContentDTO source, Class targetClass) { @SubclassMapping(source = CoordinateItemDTO.class, target = CoordinateItem.class) Item map(ItemDTO source); - @Mapping(target = "url", ignore = true) - @Mapping(target = "state", ignore = true) - @Mapping(target = "questionPartIds", ignore = true) - @Mapping(target = "difficulty", ignore = true) - DetailedQuizSummaryDTO map(IsaacQuizDTO source); - @Mapping(target = "sidebar", ignore = true) @SubclassMapping(source = IsaacFastTrackQuestionPage.class, target = IsaacFastTrackQuestionPageDTO.class) @SubclassMapping(source = IsaacQuestionPage.class, target = IsaacQuestionPageDTO.class) @@ -183,6 +177,42 @@ default T map(ContentDTO source, Class targetClass) { @SubclassMapping(source = ItemDTO.class, target = Item.class) Content map(ContentDTO source); + @Mapping(target = "bestAttempt", ignore = true) + @SubclassMapping(source = IsaacQuickQuestion.class, target = IsaacQuickQuestionDTO.class) + @SubclassMapping(source = IsaacClozeQuestion.class, target = IsaacClozeQuestionDTO.class) + @SubclassMapping(source = IsaacFreeTextQuestion.class, target = IsaacFreeTextQuestionDTO.class) + @SubclassMapping(source = IsaacGraphSketcherQuestion.class, target = IsaacGraphSketcherQuestionDTO.class) + @SubclassMapping(source = IsaacItemQuestion.class, target = IsaacItemQuestionDTO.class) + @SubclassMapping(source = IsaacMultiChoiceQuestion.class, target = IsaacMultiChoiceQuestionDTO.class) + @SubclassMapping(source = IsaacNumericQuestion.class, target = IsaacNumericQuestionDTO.class) + @SubclassMapping(source = IsaacParsonsQuestion.class, target = IsaacParsonsQuestionDTO.class) + @SubclassMapping(source = IsaacRegexMatchQuestion.class, target = IsaacRegexMatchQuestionDTO.class) + @SubclassMapping(source = IsaacReorderQuestion.class, target = IsaacReorderQuestionDTO.class) + @SubclassMapping(source = IsaacStringMatchQuestion.class, target = IsaacStringMatchQuestionDTO.class) + @SubclassMapping(source = IsaacSymbolicChemistryQuestion.class, target = IsaacSymbolicChemistryQuestionDTO.class) + @SubclassMapping(source = IsaacSymbolicLogicQuestion.class, target = IsaacSymbolicLogicQuestionDTO.class) + @SubclassMapping(source = IsaacSymbolicQuestion.class, target = IsaacSymbolicQuestionDTO.class) + @SubclassMapping(source = IsaacCoordinateQuestion.class, target = IsaacCoordinateQuestionDTO.class) + @SubclassMapping(source = IsaacLLMFreeTextQuestion.class, target = IsaacLLMFreeTextQuestionDTO.class) + @SubclassMapping(source = IsaacAnvilQuestion.class, target = IsaacAnvilQuestionDTO.class) + IsaacQuestionBaseDTO map(IsaacQuestionBase source); + + @Mapping(target = "bestAttempt", ignore = true) + @SubclassMapping(source = IsaacClozeQuestion.class, target = IsaacClozeQuestionDTO.class) + @SubclassMapping(source = IsaacParsonsQuestion.class, target = IsaacParsonsQuestionDTO.class) + @SubclassMapping(source = IsaacReorderQuestion.class, target = IsaacReorderQuestionDTO.class) + IsaacItemQuestionDTO map(IsaacItemQuestion source); + + @Mapping(target = "bestAttempt", ignore = true) + @SubclassMapping(source = IsaacSymbolicLogicQuestion.class, target = IsaacSymbolicLogicQuestionDTO.class) + IsaacSymbolicQuestionDTO map(IsaacSymbolicQuestion source); + + @Mapping(target = "url", ignore = true) + @Mapping(target = "state", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + DetailedQuizSummaryDTO mapToDetailedQuizSummaryDTO(IsaacQuizDTO source); + @Mapping(target = "url", ignore = true) @Mapping(target = "supersededBy", ignore = true) @Mapping(target = "summary", ignore = true) @@ -250,7 +280,7 @@ default T map(ContentDTO source, Class targetClass) { @Mapping(target = "difficulty", ignore = true) @Mapping(target = "deprecated", ignore = true) @Mapping(target = "audience", ignore = true) - ContentSummaryDTO map(String source); + ContentSummaryDTO mapToContentSummaryDTO(String source); /** * Mapping method to convert a ContentSummaryDTO to a String of its ID. Needed to map related content. @@ -259,7 +289,7 @@ default T map(ContentDTO source, Class targetClass) { * - the source Content object. * @return Returns the source Content object's ID. */ - default String map(ContentSummaryDTO source) { + default String mapToString(ContentSummaryDTO source) { if (source == null) { return null; } @@ -270,34 +300,4 @@ default String map(ContentSummaryDTO source) { @Mapping(target = "end_date", ignore = true) IsaacEventPageDTO copy(IsaacEventPageDTO source); - - @Mapping(target = "bestAttempt", ignore = true) - @SubclassMapping(source = IsaacQuickQuestion.class, target = IsaacQuickQuestionDTO.class) - @SubclassMapping(source = IsaacClozeQuestion.class, target = IsaacClozeQuestionDTO.class) - @SubclassMapping(source = IsaacFreeTextQuestion.class, target = IsaacFreeTextQuestionDTO.class) - @SubclassMapping(source = IsaacGraphSketcherQuestion.class, target = IsaacGraphSketcherQuestionDTO.class) - @SubclassMapping(source = IsaacItemQuestion.class, target = IsaacItemQuestionDTO.class) - @SubclassMapping(source = IsaacMultiChoiceQuestion.class, target = IsaacMultiChoiceQuestionDTO.class) - @SubclassMapping(source = IsaacNumericQuestion.class, target = IsaacNumericQuestionDTO.class) - @SubclassMapping(source = IsaacParsonsQuestion.class, target = IsaacParsonsQuestionDTO.class) - @SubclassMapping(source = IsaacRegexMatchQuestion.class, target = IsaacRegexMatchQuestionDTO.class) - @SubclassMapping(source = IsaacReorderQuestion.class, target = IsaacReorderQuestionDTO.class) - @SubclassMapping(source = IsaacStringMatchQuestion.class, target = IsaacStringMatchQuestionDTO.class) - @SubclassMapping(source = IsaacSymbolicChemistryQuestion.class, target = IsaacSymbolicChemistryQuestionDTO.class) - @SubclassMapping(source = IsaacSymbolicLogicQuestion.class, target = IsaacSymbolicLogicQuestionDTO.class) - @SubclassMapping(source = IsaacSymbolicQuestion.class, target = IsaacSymbolicQuestionDTO.class) - @SubclassMapping(source = IsaacCoordinateQuestion.class, target = IsaacCoordinateQuestionDTO.class) - @SubclassMapping(source = IsaacLLMFreeTextQuestion.class, target = IsaacLLMFreeTextQuestionDTO.class) - @SubclassMapping(source = IsaacAnvilQuestion.class, target = IsaacAnvilQuestionDTO.class) - IsaacQuestionBaseDTO map(IsaacQuestionBase source); - - @Mapping(target = "bestAttempt", ignore = true) - @SubclassMapping(source = IsaacClozeQuestion.class, target = IsaacClozeQuestionDTO.class) - @SubclassMapping(source = IsaacParsonsQuestion.class, target = IsaacParsonsQuestionDTO.class) - @SubclassMapping(source = IsaacReorderQuestion.class, target = IsaacReorderQuestionDTO.class) - IsaacItemQuestionDTO map(IsaacItemQuestion source); - - @Mapping(target = "bestAttempt", ignore = true) - @SubclassMapping(source = IsaacSymbolicLogicQuestion.class, target = IsaacSymbolicLogicQuestionDTO.class) - IsaacSymbolicQuestionDTO map(IsaacSymbolicQuestion source); } \ No newline at end of file diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java index ea4c832634..b5211c8652 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java @@ -16,9 +16,9 @@ public interface EventMapper { EventMapper INSTANCE = Mappers.getMapper(EventMapper.class); - EventBookingDTO map(DetailedEventBookingDTO source); + EventBookingDTO mapToEventBookingDTO(DetailedEventBookingDTO source); - List map(List source); + List mapToListOfEventBookingDTO(List source); @Mapping(source = "userBooked", target = "userBooked", qualifiedByName = "copyUserSummaryDTO") EventBookingDTO copy(EventBookingDTO source); diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java index 528813aa40..bbe942b312 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java @@ -47,13 +47,13 @@ public interface UserMapper { @SuppressWarnings("unchecked") default T map(RegisteredUserDTO source, Class targetClass) { if (targetClass.equals(UserSummaryDTO.class)) { - return (T) mapUserToSummary(source); + return (T) mapToUserSummaryDTO(source); } else if (targetClass.equals(UserSummaryForAdminUsersDTO.class)) { - return (T) mapUserToAdminSummaryDTO(source); + return (T) mapToUserSummaryForAdminUsersDTO(source); } else if (targetClass.equals(UserSummaryWithEmailAddressDTO.class)) { - return (T) mapUserToSummaryWithEmailDTO(source); + return (T) mapToUserSummaryWithEmailAddressDTO(source); } else if (targetClass.equals(UserSummaryWithGroupMembershipDTO.class)) { - return (T) mapUserToSummaryWithGroupMembershipDTO(source); + return (T) mapToUserSummaryWithGroupMembershipDTO(source); } else { throw new UnimplementedMappingException(RegisteredUserDTO.class, targetClass); } @@ -97,20 +97,29 @@ default T map(RegisteredUserDTO source, Class targ @Mapping(target = "id", ignore = true) @Mapping(target = "emailVerificationToken", ignore = true) @Mapping(target = "emailToVerify", ignore = true) - RegisteredUser map(UserFromAuthProvider source); + RegisteredUser mapToRegisteredUser(UserFromAuthProvider source); @Mapping(target = "authorisedFullAccess", ignore = true) - UserSummaryDTO mapUserToSummary(RegisteredUserDTO source); + UserSummaryDTO mapToUserSummaryDTO(RegisteredUserDTO source); + + // Map subclasses of UserSummaryDTO to UserSummaryDTO + UserSummaryDTO mapToUserSummaryDTO(UserSummaryDTO source); @Mapping(target = "authorisedFullAccess", ignore = true) - UserSummaryForAdminUsersDTO mapUserToAdminSummaryDTO(RegisteredUserDTO source); + UserSummaryForAdminUsersDTO mapToUserSummaryForAdminUsersDTO(RegisteredUserDTO source); @Mapping(target = "authorisedFullAccess", ignore = true) - UserSummaryWithEmailAddressDTO mapUserToSummaryWithEmailDTO(RegisteredUserDTO source); + UserSummaryWithEmailAddressDTO mapToUserSummaryWithEmailAddressDTO(RegisteredUserDTO source); @Mapping(target = "groupMembershipInformation", ignore = true) @Mapping(target = "authorisedFullAccess", ignore = true) - UserSummaryWithGroupMembershipDTO mapUserToSummaryWithGroupMembershipDTO(RegisteredUserDTO source); + UserSummaryWithGroupMembershipDTO mapToUserSummaryWithGroupMembershipDTO(RegisteredUserDTO source); + + @Mapping(target = "groupMembershipInformation", ignore = true) + @SubclassMapping(source = UserSummaryForAdminUsersDTO.class, target = UserSummaryWithGroupMembershipDTO.class) + @SubclassMapping(source = UserSummaryWithEmailAddressDTO.class, target = UserSummaryWithGroupMembershipDTO.class) + @SubclassMapping(source = UserSummaryWithGroupMembershipDTO.class, target = UserSummaryWithGroupMembershipDTO.class) + UserSummaryWithGroupMembershipDTO mapToUserSummaryWithGroupMembershipDTO(UserSummaryDTO source); @Mapping(target = "sessionToken", ignore = true) @Mapping(target = "emailVerificationToken", ignore = true) @@ -119,15 +128,6 @@ default T map(RegisteredUserDTO source, Class targ nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) void merge(RegisteredUserDTO source, @MappingTarget RegisteredUser target); - // Map subclasses of UserSummaryDTO to UserSummaryDTO - UserSummaryDTO mapToUserSummaryDTO(UserSummaryDTO source); - - @Mapping(target = "groupMembershipInformation", ignore = true) - @SubclassMapping(source = UserSummaryForAdminUsersDTO.class, target = UserSummaryWithGroupMembershipDTO.class) - @SubclassMapping(source = UserSummaryWithEmailAddressDTO.class, target = UserSummaryWithGroupMembershipDTO.class) - @SubclassMapping(source = UserSummaryWithGroupMembershipDTO.class, target = UserSummaryWithGroupMembershipDTO.class) - UserSummaryWithGroupMembershipDTO mapToUserSummaryWithGroupMembershipDTO(UserSummaryDTO source); - RegisteredUser copy(RegisteredUser source); RegisteredUserDTO copy(RegisteredUserDTO source); diff --git a/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java b/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java index c0ab04ff97..9164c048c0 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java @@ -390,7 +390,7 @@ public final void authenticateCallback_checkNewUserIsAuthenticated_createInterna RegisteredUserDTO mappedUserDTO = new RegisteredUserDTO(); - expect(dummyMapper.map(providerUser)).andReturn(mappedUser).atLeastOnce(); + expect(dummyMapper.mapToRegisteredUser(providerUser)).andReturn(mappedUser).atLeastOnce(); expect(dummyMapper.map(mappedUser)).andReturn(mappedUserDTO).atLeastOnce(); expect(dummyMapper.map(au)).andReturn(someAnonymousUserDTO).anyTimes(); From 7ecb76fc8b999dbf78e4fbc440b0836f37f64c7d Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Mon, 27 Oct 2025 15:51:56 +0000 Subject: [PATCH 56/65] Rename EventMapper -> EventBookingMapper --- .../segue/configuration/SegueGuiceConfigurationModule.java | 6 +++--- .../mappers/{EventMapper.java => EventBookingMapper.java} | 4 ++-- src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java | 3 ++- src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) rename src/main/java/uk/ac/cam/cl/dtg/util/mappers/{EventMapper.java => EventBookingMapper.java} (86%) diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java index 6b7469f5e3..cc658a6400 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java @@ -149,7 +149,7 @@ import uk.ac.cam.cl.dtg.util.locations.PostCodeLocationResolver; import uk.ac.cam.cl.dtg.util.mappers.AssignmentMapper; import uk.ac.cam.cl.dtg.util.mappers.ContentMapper; -import uk.ac.cam.cl.dtg.util.mappers.EventMapper; +import uk.ac.cam.cl.dtg.util.mappers.EventBookingMapper; import uk.ac.cam.cl.dtg.util.mappers.MainMapper; import uk.ac.cam.cl.dtg.util.mappers.QuestionValidationMapper; import uk.ac.cam.cl.dtg.util.mappers.UserMapper; @@ -649,8 +649,8 @@ public static UserMapper getUserMapperInstance() { @Provides @Singleton @Inject - public static EventMapper getEventMapperInstance() { - return EventMapper.INSTANCE; + public static EventBookingMapper getEventBookingMapperInstance() { + return EventBookingMapper.INSTANCE; } @Provides diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventBookingMapper.java similarity index 86% rename from src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java rename to src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventBookingMapper.java index b5211c8652..98d2ca9c32 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/EventBookingMapper.java @@ -12,9 +12,9 @@ * MapStruct mapper for EventBooking objects. */ @Mapper(uses = UserMapper.class) -public interface EventMapper { +public interface EventBookingMapper { - EventMapper INSTANCE = Mappers.getMapper(EventMapper.class); + EventBookingMapper INSTANCE = Mappers.getMapper(EventBookingMapper.class); EventBookingDTO mapToEventBookingDTO(DetailedEventBookingDTO source); diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java index 437c6d87ff..16399fabbf 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/MainMapper.java @@ -8,6 +8,7 @@ * Main MapStruct mapper interface. */ @Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) -public interface MainMapper extends ContentMapper, UserMapper, EventMapper, AssignmentMapper, QuestionValidationMapper { +public interface MainMapper extends ContentMapper, UserMapper, EventBookingMapper, AssignmentMapper, + QuestionValidationMapper { MainMapper INSTANCE = Mappers.getMapper(MainMapper.class); } \ No newline at end of file diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java index bbe942b312..2dee8ef991 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/UserMapper.java @@ -134,7 +134,7 @@ default T map(RegisteredUserDTO source, Class targ GroupMembershipDTO copy(GroupMembershipDTO source); - // Named mapping for use in EventMapper + // Named mapping for use in EventBookingMapper @Named("copyUserSummaryDTO") @SubclassMapping(source = UserSummaryForAdminUsersDTO.class, target = UserSummaryForAdminUsersDTO.class) @SubclassMapping(source = UserSummaryWithEmailAddressDTO.class, target = UserSummaryWithEmailAddressDTO.class) From c504ab7a0c10f4e75281c0cacc4789927c8e4483 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Mon, 27 Oct 2025 15:56:03 +0000 Subject: [PATCH 57/65] Remove @Inject annotations from mapper instance methods --- .../segue/configuration/SegueGuiceConfigurationModule.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java index cc658a6400..17d22f969e 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java +++ b/src/main/java/uk/ac/cam/cl/dtg/segue/configuration/SegueGuiceConfigurationModule.java @@ -627,42 +627,36 @@ private static ContentSubclassMapper getContentMapper() { @Provides @Singleton - @Inject public static MainMapper getMainMapperInstance() { return MainMapper.INSTANCE; } @Provides @Singleton - @Inject public static ContentMapper getContentMapperInstance() { return ContentMapper.INSTANCE; } @Provides @Singleton - @Inject public static UserMapper getUserMapperInstance() { return UserMapper.INSTANCE; } @Provides @Singleton - @Inject public static EventBookingMapper getEventBookingMapperInstance() { return EventBookingMapper.INSTANCE; } @Provides @Singleton - @Inject public static AssignmentMapper getAssignmentMapperInstance() { return AssignmentMapper.INSTANCE; } @Provides @Singleton - @Inject public static QuestionValidationMapper getQuestionValidationMapperInstance() { return QuestionValidationMapper.INSTANCE; } From 51f0f0a314b53ed0740b4d48454e351ef76ae7e0 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Mon, 27 Oct 2025 15:57:57 +0000 Subject: [PATCH 58/65] Only attempt to use public constructors in mapper tests --- .../uk/ac/cam/cl/dtg/util/mappers/ContentMapperTest.java | 6 +++--- .../cl/dtg/util/mappers/QuestionValidationMapperTest.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperTest.java b/src/test/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperTest.java index 656b28b082..b561d3b44a 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapperTest.java @@ -36,12 +36,12 @@ private static Stream testCasesDOtoDTO() { @ParameterizedTest @MethodSource("testCasesDOtoDTO") - void testDOtoDTOMapping(final Class sourceClass, final Class expectedDTOClass) { + void testDOtoDTOMapping(final Class sourceDOClass, final Class expectedDTOClass) { Content source; try { - source = sourceClass.getDeclaredConstructor().newInstance(); + source = sourceDOClass.getConstructor().newInstance(); } catch (final Exception e) { - throw new RuntimeException("Failed to instantiate source class: " + sourceClass.getName(), e); + throw new RuntimeException("Failed to instantiate source class: " + sourceDOClass.getName(), e); } Assertions.assertEquals(expectedDTOClass, mapper.map(source).getClass()); } diff --git a/src/test/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapperTest.java b/src/test/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapperTest.java index 30e9506714..f71a8edce8 100644 --- a/src/test/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapperTest.java +++ b/src/test/java/uk/ac/cam/cl/dtg/util/mappers/QuestionValidationMapperTest.java @@ -38,7 +38,7 @@ private static Stream testCasesDOtoDTO() { void testDOtoDTOMapping(final Class sourceDOClass, final Class expectedDTOClass) { QuestionValidationResponse source; try { - source = sourceDOClass.getDeclaredConstructor().newInstance(); + source = sourceDOClass.getConstructor().newInstance(); } catch (final Exception e) { throw new RuntimeException("Failed to instantiate source class: " + sourceDOClass.getName(), e); } From 0f4309960f711844d7e762d430b3414260406fc5 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Thu, 30 Oct 2025 12:14:12 +0000 Subject: [PATCH 59/65] Remove redundant UserSummaryDTO mapping within event bookings The implementation of the event booking mapper maps the userBooked to a userSummaryDTO anyway, so this explicit mapping is no longer needed --- src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java index f52721cf90..d18f4b95b2 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java @@ -604,8 +604,6 @@ public final Response getEventBookingForAllGroups(@Context final HttpServletRequ eventBookings = userAssociationManager.filterUnassociatedRecords( currentUser, eventBookings, booking -> booking.getUserBooked().getId()); - eventBookings.forEach(booking -> booking.setUserBooked(mapper.mapToUserSummaryDTO(booking.getUserBooked()))); - return Response.ok(eventBookings).build(); } catch (NoUserLoggedInException e) { return SegueErrorResponse.getNotLoggedInResponse(); From 254913fd35b8f9e4bd2d9f9c61f585804c223b0f Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Wed, 5 Nov 2025 16:42:31 +0000 Subject: [PATCH 60/65] Clarify DetailedEventBookingDTO -> EventBookingDTO mapping --- src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java index d18f4b95b2..40a5713414 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java @@ -557,7 +557,7 @@ public final Response getEventBookingForGivenGroup(@Context final HttpServletReq .collect(Collectors.toList()); // Filter eventBookings based on whether the booked user is a member of the given group - List eventBookings = bookingManager.getBookingsByEventId(eventId) + List eventBookings = bookingManager.getBookingsByEventId(eventId) .stream().filter(booking -> groupMemberIds.contains(booking.getUserBooked().getId())) .collect(Collectors.toList()); @@ -565,7 +565,7 @@ public final Response getEventBookingForGivenGroup(@Context final HttpServletReq eventBookings = userAssociationManager.filterUnassociatedRecords(currentUser, eventBookings, booking -> booking.getUserBooked().getId()); - return Response.ok(this.mapper.copy(eventBookings)).build(); + return Response.ok(this.mapper.mapToListOfEventBookingDTO(eventBookings)).build(); } catch (SegueDatabaseException e) { String errorMsg = String.format( "Database error occurred while trying retrieve bookings for group (%s) on event (%s).", From 934b9d4fd933e135f437a95aeee4e675c28325a8 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Wed, 5 Nov 2025 16:46:18 +0000 Subject: [PATCH 61/65] Delete unused method --- .../dtg/isaac/api/managers/GameManager.java | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java index 416e31e992..4a86dad265 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java @@ -1198,31 +1198,6 @@ private void augmentGameItemWithAttemptInformation( gameItem.setState(state); } - /** - * Get a wildcard by id. - * - * @param id - * - of wildcard - * @return wildcard or an exception. - * @throws ContentManagerException - * - if we cannot access the content requested. - */ - private IsaacWildcard getWildCardById(final String id) throws ContentManagerException { - Map, List> fieldsToMap = Maps.newHashMap(); - - fieldsToMap.put(immutableEntry(BooleanOperator.AND, ID_FIELDNAME), Collections.singletonList(id)); - fieldsToMap.put(immutableEntry(BooleanOperator.AND, TYPE_FIELDNAME), Collections.singletonList(WILDCARD_TYPE)); - - Content wildcardResults = this.contentManager.getContentDOById(id); - - if (wildcardResults instanceof IsaacWildcard) { - // Create a copy to avoid modifying the original object - IsaacWildcard wildcard = (IsaacWildcard) wildcardResults; - return mapper.copy(wildcard); - } - return null; - } - /** * Helper method to generate field to match requirements for search queries (specialised for isaac-filtering rules) * From 42f8fdbf818e24868b4e95851939de2acf1658dd Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Thu, 6 Nov 2025 15:34:25 +0000 Subject: [PATCH 62/65] Preserve supersededBy in SeguePageDTO -> GameboardItem mapping --- .../ac/cam/cl/dtg/util/mappers/ContentMapper.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index c68fa53e68..0fbb40f302 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -235,8 +235,23 @@ default T map(ContentDTO source, Class targetClass) { @Mapping(target = "creationContext", ignore = true) @Mapping(target = "contentType", ignore = true) @Mapping(target = "boardId", ignore = true) + @SubclassMapping(source = SeguePageDTO.class, target = GameboardItem.class) GameboardItem mapContentDTOtoGameboardItem(ContentDTO source); + @Mapping(target = "state", ignore = true) + @Mapping(target = "questionPartsTotal", ignore = true) + @Mapping(target = "questionPartsNotAttempted", ignore = true) + @Mapping(target = "questionPartsIncorrect", ignore = true) + @Mapping(target = "questionPartsCorrect", ignore = true) + @Mapping(target = "questionPartStates", ignore = true) + @Mapping(target = "passMark", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @Mapping(target = "description", ignore = true) + @Mapping(target = "creationContext", ignore = true) + @Mapping(target = "contentType", ignore = true) + @Mapping(target = "boardId", ignore = true) + GameboardItem mapSeguePageDTOtoGameboardItem(SeguePageDTO source); + @Mapping(target = "url", ignore = true) @Mapping(target = "supersededBy", ignore = true) @Mapping(target = "summary", ignore = true) From f9575a581a974595c3b02b3b71b15cd0e902b081 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Thu, 6 Nov 2025 17:33:05 +0000 Subject: [PATCH 63/65] Preserve summary in SeguePageDTO -> ContentSummaryDTO mapping --- .../java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index 0fbb40f302..ab0928f9c0 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -220,8 +220,15 @@ default T map(ContentDTO source, Class targetClass) { @Mapping(target = "questionPartIds", ignore = true) @Mapping(target = "difficulty", ignore = true) @Mapping(target = "deprecated", ignore = true) + @SubclassMapping(source = SeguePageDTO.class, target = ContentSummaryDTO.class) ContentSummaryDTO mapContentDTOtoContentSummaryDTO(ContentDTO source); + @Mapping(target = "url", ignore = true) + @Mapping(target = "state", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + ContentSummaryDTO mapSeguePageDTOtoContentSummaryDTO(SeguePageDTO source); + @Mapping(target = "supersededBy", ignore = true) @Mapping(target = "state", ignore = true) @Mapping(target = "questionPartsTotal", ignore = true) From 4bb54e64dc636f41c7ed480741a308dded2b724f Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Fri, 7 Nov 2025 12:00:45 +0000 Subject: [PATCH 64/65] Preserve deprecated & hiddenFromRoles in QuizDTO -> QuizSummaryDTO mapping --- .../uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java index ab0928f9c0..d5f2602b9e 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java +++ b/src/main/java/uk/ac/cam/cl/dtg/util/mappers/ContentMapper.java @@ -2,6 +2,7 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.Named; import org.mapstruct.SubclassExhaustiveStrategy; import org.mapstruct.SubclassMapping; import org.mapstruct.factory.Mappers; @@ -267,8 +268,16 @@ default T map(ContentDTO source, Class targetClass) { @Mapping(target = "hiddenFromRoles", ignore = true) @Mapping(target = "difficulty", ignore = true) @Mapping(target = "deprecated", ignore = true) + @SubclassMapping(source = IsaacQuizDTO.class, target = QuizSummaryDTO.class, qualifiedByName = "mapQuizDTOtoQuizSummaryDTO") QuizSummaryDTO mapContentDTOtoQuizSummaryDTO(ContentDTO source); + @Mapping(target = "url", ignore = true) + @Mapping(target = "state", ignore = true) + @Mapping(target = "questionPartIds", ignore = true) + @Mapping(target = "difficulty", ignore = true) + @Named("mapQuizDTOtoQuizSummaryDTO") + QuizSummaryDTO mapQuizDTOtoQuizSummaryDTO(IsaacQuizDTO source); + @Mapping(target = "url", ignore = true) @Mapping(target = "supersededBy", ignore = true) @Mapping(target = "summary", ignore = true) From 8f83434dc8610394856571f76ebd9d6080eab496 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Mon, 10 Nov 2025 14:53:05 +0000 Subject: [PATCH 65/65] Avoid setting null exam board to the empty string in glossary terms --- .../uk/ac/cam/cl/dtg/isaac/dto/content/GlossaryTermDTO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/content/GlossaryTermDTO.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/content/GlossaryTermDTO.java index 38d44d358a..46573292aa 100644 --- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/content/GlossaryTermDTO.java +++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/content/GlossaryTermDTO.java @@ -38,7 +38,7 @@ public GlossaryTermDTO(@JsonProperty("explanation") ContentDTO explanation, @JsonProperty("examBoard") String examBoard, @Nullable @JsonProperty("stage") Set stages) { this.explanation = explanation; - this.examBoard = examBoard != null ? examBoard : ""; + this.examBoard = examBoard; this.stages = stages; }