/**
   * Attempts to create a fresh {@link TestSessionState} wrapped into a {@link
   * TestSessionController} for the given {@link Delivery}.
   *
   * <p>This will return null if the test can't be started because its {@link TestProcessingMap}
   * can't be created, e.g. if its XML can't be parsed.
   */
  public TestSessionController createNewTestSessionStateAndController(
      final User candidate,
      final Delivery delivery,
      final NotificationRecorder notificationRecorder) {
    ensureTestDelivery(delivery);

    /* Resolve the underlying JQTI+ object */
    final AssessmentPackage assessmentPackage =
        assessmentDataService.ensureSelectedAssessmentPackage(delivery);
    final TestProcessingMap testProcessingMap =
        assessmentObjectManagementService.getTestProcessingMap(assessmentPackage);
    if (testProcessingMap == null) {
      return null;
    }

    /* Generate a test plan for this session */
    final TestPlanner testPlanner = new TestPlanner(testProcessingMap);
    if (notificationRecorder != null) {
      testPlanner.addNotificationListener(notificationRecorder);
    }
    final TestPlan testPlan = testPlanner.generateTestPlan();

    /* Create fresh state for session */
    final TestSessionState testSessionState = new TestSessionState(testPlan);

    /* Create config for TestSessionController */
    final DeliverySettings testDeliverySettings =
        assessmentDataService.getEffectiveDeliverySettings(candidate, delivery);
    final TestSessionControllerSettings testSessionControllerSettings =
        new TestSessionControllerSettings();
    testSessionControllerSettings.setTemplateProcessingLimit(
        computeTemplateProcessingLimit(testDeliverySettings));

    /* Create controller and wire up notification recorder */
    final TestSessionController result =
        new TestSessionController(
            jqtiExtensionManager,
            testSessionControllerSettings,
            testProcessingMap,
            testSessionState);
    if (notificationRecorder != null) {
      result.addNotificationListener(notificationRecorder);
    }
    return result;
  }
  /**
   * Wraps the given {@link ItemSessionState} in a {@link ItemSessionController}.
   *
   * <p>It is assumed that the item was runnable, so this will never return null.
   */
  public ItemSessionController createItemSessionController(
      final CandidateSession candidateSession,
      final ItemSessionState itemSessionState,
      final NotificationRecorder notificationRecorder) {
    final User candidate = candidateSession.getCandidate();
    final Delivery delivery = candidateSession.getDelivery();
    ensureItemDelivery(delivery);
    Assert.notNull(itemSessionState, "itemSessionState");

    /* Try to resolve the underlying JQTI+ object */
    final AssessmentPackage assessmentPackage =
        assessmentDataService.ensureSelectedAssessmentPackage(delivery);
    final ItemProcessingMap itemProcessingMap =
        assessmentObjectManagementService.getItemProcessingMap(assessmentPackage);
    if (itemProcessingMap == null) {
      throw new QtiWorksLogicException("Expected this item to be runnable");
    }

    /* Create config for ItemSessionController */
    final ItemDeliverySettings itemDeliverySettings =
        (ItemDeliverySettings)
            assessmentDataService.getEffectiveDeliverySettings(candidate, delivery);
    final ItemSessionControllerSettings itemSessionControllerSettings =
        new ItemSessionControllerSettings();
    itemSessionControllerSettings.setTemplateProcessingLimit(
        computeTemplateProcessingLimit(itemDeliverySettings));
    itemSessionControllerSettings.setMaxAttempts(itemDeliverySettings.getMaxAttempts());

    /* Create controller and wire up notification recorder (if passed) */
    final ItemSessionController result =
        new ItemSessionController(
            jqtiExtensionManager,
            itemSessionControllerSettings,
            itemProcessingMap,
            itemSessionState);
    if (notificationRecorder != null) {
      result.addNotificationListener(notificationRecorder);
    }

    return result;
  }
  /**
   * Wraps the given {@link TestSessionState} in a {@link TestSessionController}.
   *
   * <p>It is assumed that the test was runnable, so this will never return null.
   */
  public TestSessionController createTestSessionController(
      final CandidateSession candidateSession,
      final TestSessionState testSessionState,
      final NotificationRecorder notificationRecorder) {
    final User candidate = candidateSession.getCandidate();
    final Delivery delivery = candidateSession.getDelivery();
    ensureTestDelivery(delivery);
    Assert.notNull(testSessionState, "testSessionState");

    /* Try to resolve the underlying JQTI+ object */
    final AssessmentPackage assessmentPackage =
        assessmentDataService.ensureSelectedAssessmentPackage(delivery);
    final TestProcessingMap testProcessingMap =
        assessmentObjectManagementService.getTestProcessingMap(assessmentPackage);
    if (testProcessingMap == null) {
      return null;
    }

    /* Create config for TestSessionController */
    final TestDeliverySettings testDeliverySettings =
        (TestDeliverySettings)
            assessmentDataService.getEffectiveDeliverySettings(candidate, delivery);
    final TestSessionControllerSettings testSessionControllerSettings =
        new TestSessionControllerSettings();
    testSessionControllerSettings.setTemplateProcessingLimit(
        computeTemplateProcessingLimit(testDeliverySettings));

    /* Create controller and wire up notification recorder (if passed) */
    final TestSessionController result =
        new TestSessionController(
            jqtiExtensionManager,
            testSessionControllerSettings,
            testProcessingMap,
            testSessionState);
    if (notificationRecorder != null) {
      result.addNotificationListener(notificationRecorder);
    }

    return result;
  }
  /**
   * Attempts to create a fresh {@link ItemSessionState} wrapped into a {@link
   * ItemSessionController} for the given {@link Delivery}.
   *
   * <p>This will return null if the item can't be started because its {@link ItemProcessingMap}
   * can't be created, e.g. if its XML can't be parsed.
   *
   * @param delivery
   * @param notificationRecorder
   * @return
   */
  public ItemSessionController createNewItemSessionStateAndController(
      final User candidate,
      final Delivery delivery,
      final NotificationRecorder notificationRecorder) {
    ensureItemDelivery(delivery);

    /* Resolve the underlying JQTI+ object */
    final AssessmentPackage assessmentPackage =
        assessmentDataService.ensureSelectedAssessmentPackage(delivery);
    final ItemProcessingMap itemProcessingMap =
        assessmentObjectManagementService.getItemProcessingMap(assessmentPackage);
    if (itemProcessingMap == null) {
      return null;
    }

    /* Create fresh state for session */
    final ItemSessionState itemSessionState = new ItemSessionState();

    /* Create config for ItemSessionController */
    final ItemDeliverySettings itemDeliverySettings =
        (ItemDeliverySettings)
            assessmentDataService.getEffectiveDeliverySettings(candidate, delivery);
    final ItemSessionControllerSettings itemSessionControllerSettings =
        new ItemSessionControllerSettings();
    itemSessionControllerSettings.setTemplateProcessingLimit(
        computeTemplateProcessingLimit(itemDeliverySettings));
    itemSessionControllerSettings.setMaxAttempts(itemDeliverySettings.getMaxAttempts());

    /* Create controller and wire up notification recorder */
    final ItemSessionController result =
        new ItemSessionController(
            jqtiExtensionManager,
            itemSessionControllerSettings,
            itemProcessingMap,
            itemSessionState);
    if (notificationRecorder != null) {
      result.addNotificationListener(notificationRecorder);
    }
    return result;
  }