Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions pre-registration-booking-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
<artifactId>pre-registration-booking-service</artifactId>
<version>1.4.0-SNAPSHOT</version>
<name>pre-registration-booking-service</name>
<description>Booking service of MOSIP Pre-registration</description>
<description>Booking service of MOSIP Pre-registration</description>
<url>https://github.com/mosip/mosip-ref-impl</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<io.micrometer.prometheus.version>1.4.2</io.micrometer.prometheus.version>
<pre.registration.core.version>1.3.0</pre.registration.core.version>
<pre.registration.core.version>1.4.0-SNAPSHOT</pre.registration.core.version>
<kernel.core.version>1.3.1-rc.1</kernel.core.version>
<kernel.bom.version>1.3.1-rc.1</kernel.bom.version>
<java.version>21</java.version>
Expand Down Expand Up @@ -70,6 +70,7 @@
<maven.javadoc.version>3.2.0</maven.javadoc.version>
<maven.source.plugin.version>3.3.0</maven.source.plugin.version>
<central.publishing.maven.plugin.version>0.7.0</central.publishing.maven.plugin.version>

<!-- Test & Logging -->
<junit.version>4.12</junit.version>
<logback.version>1.1.6</logback.version>
Expand Down Expand Up @@ -283,7 +284,7 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<version>${spring.boot.version}</version>
<configuration>
<executable>true</executable>
<layout>ZIP</layout>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import io.mosip.preregistration.booking.exception.RecordNotFoundException;
import io.mosip.preregistration.booking.exception.util.BookingExceptionCatcher;
import io.mosip.preregistration.booking.repository.impl.BookingDAO;
import io.mosip.preregistration.core.common.service.ApplicationIdentityMigrationService;
import io.mosip.preregistration.booking.service.util.BookingLock;
import io.mosip.preregistration.booking.service.util.BookingServiceUtil;
import io.mosip.preregistration.core.code.AuditLogVariables;
Expand Down Expand Up @@ -83,6 +84,26 @@ public class BookingService implements BookingServiceIntf {
@Autowired
BookingServiceUtil serviceUtil;

/**
* Best-effort canonical-identity backfill.
*
* <p><b>Propagation differs by call site, deliberately.</b> The backfill is
* annotated {@code REQUIRED}, so in {@link #deleteBooking(String)} - which is
* {@code REQUIRES_NEW} - it joins that transaction and rolls back with it,
* while {@link #book(String, BookingRequestDTO)} and
* {@link #cancelBooking(String, boolean)} are intentionally non-transactional
* (see the note on their declarations, which predates this change), so there it
* runs in its own short transaction that commits independently of the booking.
*
* <p>Both are acceptable: the backfill is idempotent, converts only
* still-raw values, re-runs on the user's next activity, and is backstopped by
* the nightly identity reconciliation job. An independent commit is in fact the
* better outcome - the conversion survives even if the surrounding booking
* later fails. This class alters no transactional annotation.
*/
@Autowired
private ApplicationIdentityMigrationService applicationIdentityMigrationService;

/**
* Reference for ${preregistration.availability.sync} from property file
*/
Expand Down Expand Up @@ -641,8 +662,23 @@ public BookingStatusDTO book(String preRegistrationId, BookingRequestDTO booking
" and Date and Time " + availableEntity.getRegDate() + " " + availableEntity.getFromTime());
if (serviceUtil.isKiosksAvailable(availableEntity)) {
/* Updating booking */
bookingDAO.saveRegistrationEntityForBooking(
RegistrationBookingEntity bookingEntity = bookingDAO.saveRegistrationEntityForBooking(
serviceUtil.bookingEntitySetter(preRegistrationId, bookingRequestDTO));
/*
* Best-effort backfill: a failure here must never fail the booking. The
* booking's own crBy is already canonical at this point, so ownership and
* auth are unaffected; missed rows self-heal on the user's next activity and
* are swept by the nightly identity reconciliation job.
*/
try {
applicationIdentityMigrationService.migrateRawUserToEffectiveUser(preRegistrationId,
bookingEntity.getCrBy());
} catch (Exception migrationEx) {
log.error("sessionId", "idType", "id",
"Identity migration failed for preRegistrationId " + preRegistrationId
+ ", booking continues - " + migrationEx.getMessage());
log.debug("sessionId", "idType", "id", ExceptionUtils.getStackTrace(migrationEx));
}
/* Reduce Availability */
availableEntity.setAvailableKiosks(availableEntity.getAvailableKiosks() - 1);
AvailibityEntity availableUpdate = bookingDAO.updateAvailibityEntity(availableEntity);
Expand Down Expand Up @@ -704,6 +740,21 @@ public CancelBookingResponseDTO cancelBooking(String preRegistrationId, boolean

serviceUtil.timeSpanCheckForCancle(bookedDateTime);
}
/*
* Best-effort backfill. The resolved id is not used beyond this call, so
* an unresolvable legacy crBy must not block a cancellation.
*/
try {
String effectiveUserId = applicationIdentityMigrationService
.resolveEffectiveUserId(bookingEntity.getCrBy());
applicationIdentityMigrationService.migrateRawUserToEffectiveUser(preRegistrationId,
effectiveUserId);
} catch (Exception migrationEx) {
log.error("sessionId", "idType", "id",
"Identity migration failed for preRegistrationId " + preRegistrationId
+ ", cancellation continues - " + migrationEx.getMessage());
log.debug("sessionId", "idType", "id", ExceptionUtils.getStackTrace(migrationEx));
}
/* Deleting the canceled booking */
// bookingDAO.deleteRegistrationEntity(bookingEntity);
bookingDAO.deleteByPreRegistrationId(preRegistrationId);
Expand Down Expand Up @@ -765,6 +816,19 @@ public MainResponseDTO<DeleteBookingDTO> deleteBooking(String preregId) {
if (validationUtil.requstParamValidator(requestParamMap)
&& serviceUtil.checkApplicationStatus(preregId)) {
RegistrationBookingEntity registrationEntityList = bookingDAO.findByPreRegistrationId(preregId);
/*
* Best-effort backfill: a failure here must never fail the deletion. The
* backfill resolves each column from its own stored value, so the booking's
* own crBy is all it needs.
*/
try {
applicationIdentityMigrationService.migrateRawUserToEffectiveUser(preregId,
registrationEntityList.getCrBy());
} catch (Exception migrationEx) {
log.error("sessionId", "idType", "id", "Identity migration failed for preRegistrationId "
+ preregId + ", deletion continues - " + migrationEx.getMessage());
log.debug("sessionId", "idType", "id", ExceptionUtils.getStackTrace(migrationEx));
}
String str = registrationEntityList.getRegDate() + " " + registrationEntityList.getSlotFromTime();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime bookedDateTime = LocalDateTime.parse(str, formatter);
Expand All @@ -782,8 +846,22 @@ public MainResponseDTO<DeleteBookingDTO> deleteBooking(String preregId) {
bookingDAO.updateAvailibityEntity(availableEntity);

deleteDto.setPreRegistrationId(registrationEntityList.getPreregistrationId());
deleteDto.setDeletedBy(registrationEntityList.getCrBy());
deleteDto.setDeletedDateTime(new Date(System.currentTimeMillis()));
/*
* Response-only attribution, resolved after the deletion so it can never
* block it. An unresolvable legacy identifier leaves the field unset - it
* must never fall back to the raw value, which would put the plaintext
* identifier back on the wire. The audit trail is unaffected: it records the
* authenticated caller, not this value.
*/
try {
deleteDto.setDeletedBy(applicationIdentityMigrationService
.resolveEffectiveUserId(registrationEntityList.getCrBy()));
} catch (Exception resolveEx) {
log.error("sessionId", "idType", "id", "Could not resolve deletedBy for preRegistrationId "
+ preregId + ", field omitted - " + resolveEx.getMessage());
log.debug("sessionId", "idType", "id", ExceptionUtils.getStackTrace(resolveEx));
}

}
isSaveSuccess = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
import io.mosip.preregistration.booking.exception.BookingPreIdNotFoundException;
import io.mosip.preregistration.booking.exception.BookingRegistrationCenterIdNotFoundException;
import io.mosip.preregistration.booking.exception.BookingTimeSlotNotSeletectedException;
import io.mosip.preregistration.booking.exception.AppointmentBookingFailedException;
import io.mosip.preregistration.booking.exception.DemographicGetStatusException;
import io.mosip.preregistration.booking.exception.InvalidDateTimeFormatException;
import io.mosip.preregistration.booking.exception.RecordNotFoundException;
Expand All @@ -87,6 +88,9 @@
import io.mosip.preregistration.core.exception.RestCallException;
import io.mosip.preregistration.core.util.UUIDGeneratorUtil;
import io.mosip.preregistration.core.util.ValidationUtil;
import io.mosip.preregistration.core.common.service.UserDetailsService;
import io.mosip.preregistration.core.exception.UserLookupException;
import io.mosip.preregistration.core.util.GenericUtil;

/**
* This class provides the utility methods for Booking application.
Expand All @@ -107,6 +111,9 @@ public class BookingServiceUtil {
@Autowired
private RestTemplate restTemplate;

@Autowired
private UserDetailsService userDetailsService;

/**
* Reference for ${regCenter.url} from property file
*/
Expand Down Expand Up @@ -535,7 +542,8 @@ public RegistrationBookingEntity bookingEntitySetter(String preRegistrationId,
entity.setRegistrationCenterId(bookingRequestDTO.getRegistrationCenterId());
entity.setId(UUIDGeneratorUtil.generateId());
entity.setLangCode("12L");
entity.setCrBy(authUserDetails().getUserId());
String userId = authUserDetails().getUserId();
entity.setCrBy(resolveEffectiveCrBy(userId));
entity.setCrDate(DateUtils2.parseDateToLocalDateTime(new Date()));
entity.setRegDate(LocalDate.parse(bookingRequestDTO.getRegDate()));
entity.setSlotFromTime(LocalTime.parse(bookingRequestDTO.getSlotFromTime()));
Expand All @@ -544,6 +552,36 @@ public RegistrationBookingEntity bookingEntitySetter(String preRegistrationId,
return entity;
}

private String resolveEffectiveCrBy(String userId) {
String maskedUserId = GenericUtil.maskIdentifier(userId);
/*
* getOrCreateInternalUserId returns null for a null or blank id rather than
* throwing, so without this guard a missing authenticated user would flow
* through as a null crBy and fail only at the NOT NULL constraint - surfacing
* as an opaque "table not accessible" error instead of an identity one.
*/
if (userId == null || userId.isBlank()) {
log.warn("sessionId", "idType", "id",
"Cannot resolve effective booking user id: authenticated user id is absent");
throw new AppointmentBookingFailedException(ErrorCodes.PRG_BOOK_RCI_005.getCode(),
ErrorMessages.APPOINTMENT_BOOKING_FAILED.getMessage());
}
try {
String effectiveCrBy = userDetailsService.getOrCreateInternalUserId(userId);
boolean canonicalApplied = effectiveCrBy != null && !effectiveCrBy.isBlank()
&& !effectiveCrBy.trim().equals(userId == null ? "" : userId.trim());
log.info("sessionId", "idType", "id",
"Resolved effective user id for booking write. maskedUserId=" + maskedUserId
+ ", canonicalApplied=" + canonicalApplied);
return effectiveCrBy;
} catch (UserLookupException ex) {
log.warn("sessionId", "idType", "id",
"Failed to resolve effective booking user id for " + maskedUserId);
throw new AppointmentBookingFailedException(ErrorCodes.PRG_BOOK_RCI_005.getCode(),
ErrorMessages.APPOINTMENT_BOOKING_FAILED.getMessage());
}
}

/**
*
* @param notificationDTO
Expand Down Expand Up @@ -734,3 +772,4 @@ public MainResponseDTO<String> getApplicationStatus(String applicationId) {
// }

}

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ server.port=9095
health.config.enabled=false
mosip.preregistration.booking.id.book=mosip.pre-registration.booking.book
mosip.id.preregistration.booking.book=mosip.pre-registration.booking.book
## Not read by booking itself. Kept as a packaged default because booking's
## component scan loads app-service beans, six of which @Value this key with no
## fallback - dropping it would make booking startup depend on the config server.
mosip.prereg.pii.backward.compatibility=true

#disabling health check so that client doesnt try to load properties from sprint config server every
# 5 minutes (should not be done in production)
Expand Down
Loading
Loading