org.openmrs.module.metadatasharing.web.controller.ImportController.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.module.metadatasharing.web.controller.ImportController.java

Source

/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.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://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.module.metadatasharing.web.controller;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.api.context.Context;
import org.openmrs.module.metadatasharing.ImportConfig;
import org.openmrs.module.metadatasharing.ImportMode;
import org.openmrs.module.metadatasharing.ImportType;
import org.openmrs.module.metadatasharing.ImportedItem;
import org.openmrs.module.metadatasharing.ImportedItemsStats;
import org.openmrs.module.metadatasharing.ImportedPackage;
import org.openmrs.module.metadatasharing.MetadataSharing;
import org.openmrs.module.metadatasharing.MetadataSharingConsts;
import org.openmrs.module.metadatasharing.SubscriptionStatus;
import org.openmrs.module.metadatasharing.downloader.Downloader;
import org.openmrs.module.metadatasharing.downloader.DownloaderFactory;
import org.openmrs.module.metadatasharing.handler.Handler;
import org.openmrs.module.metadatasharing.model.validator.PackageContainerValidator;
import org.openmrs.module.metadatasharing.model.validator.ValidateCustomUtil;
import org.openmrs.module.metadatasharing.subscription.SubscriptionHeader;
import org.openmrs.module.metadatasharing.task.Task;
import org.openmrs.module.metadatasharing.updater.SubscriptionUpdater;
import org.openmrs.module.metadatasharing.web.bean.AssessItemForm;
import org.openmrs.module.metadatasharing.web.bean.DatatablesRequest;
import org.openmrs.module.metadatasharing.web.bean.DatatablesResponse;
import org.openmrs.module.metadatasharing.web.bean.UploadForm;
import org.openmrs.module.metadatasharing.web.bean.validator.UploadFormValidator;
import org.openmrs.module.metadatasharing.web.utils.WebUtils;
import org.openmrs.module.metadatasharing.web.view.JsonObjectView;
import org.openmrs.module.metadatasharing.wrapper.PackageImporter;
import org.openmrs.serialization.SerializationException;
import org.openmrs.web.WebConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.servlet.ModelAndView;

/**
 * Controller for package import pages.
 */
@Controller(MetadataSharingConsts.MODULE_ID + ".ImportController")
@SessionAttributes({ ImportController.IMPORTER, ImportController.IMPORTED_PACKAGE, ImportController.DOWNLOADER,
        ImportController.ITEMS, ImportController.ITEMS_STATS, ImportController.PARTS })
public class ImportController {

    public static final String IMPORTER = "importer";

    public static final String ITEMS = "items";

    public static final String ITEMS_STATS = "itemsStats";

    public static final String ITEM = "item";

    public static final String PARTS = "parts";

    public static final String IMPORTED_PACKAGE = "importedPackage";

    public static final String DOWNLOADER = "downloader";

    public static final String IMPORT_PATH = MetadataSharingConsts.MODULE_PATH + "/import";

    public static final String LIST_PATH = IMPORT_PATH + "/list";

    public static final String TOGGLE_SUBSCRIBE_PATH = IMPORT_PATH + "/toggleSubscribe";

    public static final String DELETE_SUBSCRIBE_PATH = IMPORT_PATH + "/deleteSubscribe";

    public static final String JSON_CHECK_UPDATES_PATH = IMPORT_PATH + "/json/checkUpdates";

    public static final String DOWNLOAD_PATH = IMPORT_PATH + "/download";

    public static final String JSON_INITIALIZE_DOWNLOAD_PATH = IMPORT_PATH + "/json/initializeDownload";

    public static final String JSON_PERFORM_DOWNLOAD_PATH = IMPORT_PATH + "/json/performDownload";

    public static final String INVALID_SUBSCRIPTION_PATH = IMPORT_PATH + "/invalidSubscription";

    public static final String UPLOAD_PATH = IMPORT_PATH + "/upload";

    public static final String VALIDATE_PATH = IMPORT_PATH + "/validate";

    public static final String MODE_PATH = IMPORT_PATH + "/mode";

    public static final String CONFIG_PATH = IMPORT_PATH + "/config";

    public static final String LOAD_PATH = IMPORT_PATH + "/load";

    public static final String VIEW_PATH = IMPORT_PATH + "/view";

    public static final String JSON_IMPORT_ITEMS_PATH = IMPORT_PATH + "/json/importItems";

    public static final String JSON_ITEMS_PATH = IMPORT_PATH + "/json/items";

    public static final String ITEM_PATH = IMPORT_PATH + "/assessItem";

    public static final String ASSESS_NEXT_ITEM_PATH = IMPORT_PATH + "/assessNextItem";

    public static final String COMPLETE_PATH = IMPORT_PATH + "/complete";

    protected final Log log = LogFactory.getLog(getClass());

    @Autowired
    private PackageContainerValidator packageContainerValidator;

    @Autowired
    private UploadFormValidator uploadFormValidator;

    @Autowired
    private JsonObjectView jsonObjectView;

    @Autowired
    private DownloaderFactory downloaderFactory;

    /**
     * The updater we will be using for checking for updates while adding brand-new subscription
     */
    @Autowired
    private SubscriptionUpdater updater;

    @RequestMapping(LIST_PATH)
    public void list(Model model) {
        model.addAttribute("subscriptions", MetadataSharing.getService().getAllImportedPackages());
    }

    @RequestMapping(value = TOGGLE_SUBSCRIBE_PATH, method = RequestMethod.GET)
    public String toggleSubscribe(Integer id) {
        ImportedPackage importedPackage = MetadataSharing.getService().getImportedPackageById(id);
        if (importedPackage != null) {
            if (importedPackage.isSubscribed()) {
                importedPackage.setSubscriptionStatus(SubscriptionStatus.DISABLED);
            } else {
                importedPackage.setSubscriptionStatus(SubscriptionStatus.NEVER_CHECKED);
            }
            MetadataSharing.getService().saveImportedPackage(importedPackage);
        }
        return WebUtils.redirect(LIST_PATH);
    }

    @RequestMapping(value = DELETE_SUBSCRIBE_PATH, method = RequestMethod.GET)
    public String purgeSubscribe(Integer id) {
        ImportedPackage importedPackage = MetadataSharing.getService().getImportedPackageById(id);
        if (importedPackage != null) {
            MetadataSharing.getService().deleteImportedPackage(importedPackage);
        }
        return WebUtils.redirect(LIST_PATH);
    }

    @RequestMapping(value = JSON_CHECK_UPDATES_PATH, method = RequestMethod.GET)
    public ModelAndView jsonCheckUpdates(Integer id, ModelMap model) {
        ImportedPackage importedPackage = MetadataSharing.getService().getImportedPackageById(id);
        if (importedPackage != null && importedPackage.isSubscribed()) {
            updater.checkForUpdates(importedPackage);
            MetadataSharing.getService().saveImportedPackage(importedPackage);
            model.addAttribute("object", importedPackage);
            return new ModelAndView(jsonObjectView);
        }
        return null;
    }

    @RequestMapping(value = DOWNLOAD_PATH, method = RequestMethod.GET, params = "id")
    public void download(Integer id, Model model) {
        ImportedPackage importedPackage = MetadataSharing.getService().getImportedPackageById(id);
        model.addAttribute(IMPORTED_PACKAGE, importedPackage);
    }

    /**
     * Download if {@link ImportedPackage} set.
     */
    @RequestMapping(value = DOWNLOAD_PATH, method = RequestMethod.GET)
    public void download() {
    }

    /**
     * A page where we ask user if he wants to add a subscription the initial checkout of which
     * produced errors
     */
    @RequestMapping(value = INVALID_SUBSCRIPTION_PATH, method = RequestMethod.GET)
    public String invalidSubscription(@ModelAttribute(IMPORTED_PACKAGE) ImportedPackage importedPackage,
            @RequestParam(value = "add", required = false) Boolean add, HttpSession session, Model model) {
        if (add != null && add.equals(true)) {
            MetadataSharing.getService().saveImportedPackage(importedPackage);
            session.setAttribute(WebConstants.OPENMRS_MSG_ATTR, "metadatasharing.subscription.added");
            return WebUtils.redirect(LIST_PATH);
        }
        return null;
    }

    /**
     * An AJAX request where we initialize the package download process.
     */
    @RequestMapping(value = JSON_INITIALIZE_DOWNLOAD_PATH, method = RequestMethod.GET)
    public ModelAndView jsonInitializeDownload(@ModelAttribute(IMPORTED_PACKAGE) ImportedPackage importedPackage,
            Model model) throws MalformedURLException {
        SubscriptionHeader header = updater.checkForUpdates(importedPackage);
        if (!importedPackage.hasSubscriptionErrors()) {
            URL subscriptionUrl = new URL(importedPackage.getSubscriptionUrl());
            URL packageContentUrl = new URL(subscriptionUrl, header.getContentUri().toString());
            Downloader downloader = downloaderFactory.getDownloader(packageContentUrl);
            model.addAttribute(DOWNLOADER, downloader);
        }
        model.addAttribute("object", importedPackage.getSubscriptionStatus().getValue());
        return new ModelAndView(jsonObjectView);
    }

    /**
     * An AJAX request where we perform the actual package download process.
     */
    @RequestMapping(value = JSON_PERFORM_DOWNLOAD_PATH, method = RequestMethod.GET)
    public ModelAndView jsonPerformDownload(@ModelAttribute(DOWNLOADER) Downloader downloader, HttpSession session,
            Model model) throws IOException {
        PackageImporter metadataImporter = MetadataSharing.getInstance().newPackageImporter();
        ByteArrayInputStream in = null;
        try {
            in = new ByteArrayInputStream(downloader.downloadAsByteArray());
        } catch (Exception e) {
            model.addAttribute("object", SubscriptionStatus.FATAL_ERROR);
            return new ModelAndView(jsonObjectView);
        }
        metadataImporter.loadSerializedPackageStream(in);
        session.setAttribute(ImportController.IMPORTER, metadataImporter);

        model.asMap().remove(DOWNLOADER);
        model.addAttribute("object", SubscriptionStatus.UPDATE_AVAILABLE);
        return new ModelAndView(jsonObjectView);
    }

    @RequestMapping(value = UPLOAD_PATH, method = RequestMethod.GET)
    public void upload(SessionStatus status) {
        status.setComplete();
    }

    @RequestMapping(value = UPLOAD_PATH, method = RequestMethod.POST)
    public String uploadPOST(UploadForm uploadForm, Errors errors, Model model) throws IOException {
        uploadFormValidator.validate(uploadForm, errors);
        if (errors.hasErrors()) {
            return UPLOAD_PATH;
        }

        if (StringUtils.isBlank(uploadForm.getUrl())) {
            PackageImporter importer = MetadataSharing.getInstance().newPackageImporter();
            InputStream in = null;
            try {
                in = uploadForm.getFile().getInputStream();
                importer.loadSerializedPackageStream(in);
                in.close();
            } finally {
                IOUtils.closeQuietly(in);
            }

            model.addAttribute(IMPORTER, importer);
            return WebUtils.redirect(VALIDATE_PATH);
        } else {
            ImportedPackage importedPackage = new ImportedPackage();
            importedPackage.setGroupUuid(null);
            importedPackage.setSubscriptionUrl(uploadForm.getUrl());
            updater.checkForUpdates(importedPackage);

            if (importedPackage.hasSubscriptionErrors()) {
                if (importedPackage.getSubscriptionStatus().equals(SubscriptionStatus.DUPLICATE_SUBSCRIPTION)) {
                    errors.rejectValue("url", "metadatasharing.error.subscription.duplicate");
                    return UPLOAD_PATH;
                } else {
                    model.addAttribute(IMPORTED_PACKAGE, importedPackage);
                    return WebUtils.redirect(INVALID_SUBSCRIPTION_PATH);
                }
            }

            model.addAttribute(IMPORTED_PACKAGE, importedPackage);
            return WebUtils.redirect(DOWNLOAD_PATH);
        }
    }

    @RequestMapping(value = VALIDATE_PATH, method = RequestMethod.GET)
    public void validate(@ModelAttribute(IMPORTER) PackageImporter importer, Errors errors)
            throws SerializationException {
        packageContainerValidator.validateForErrorsAndWarnings(importer, errors);
    }

    @RequestMapping(value = VALIDATE_PATH, method = RequestMethod.POST)
    public String validatePOST(@ModelAttribute(IMPORTER) PackageImporter importer, Errors errors) {
        packageContainerValidator.validate(importer, errors);
        if (errors.hasErrors()) {
            packageContainerValidator.validateForWarnings(importer, errors);
            return VALIDATE_PATH;
        } else {
            return WebUtils.redirect(MODE_PATH);
        }
    }

    @RequestMapping(value = MODE_PATH, method = RequestMethod.GET)
    public void mode() {
    }

    @RequestMapping(value = MODE_PATH, method = RequestMethod.POST)
    public String modePOST(@ModelAttribute(IMPORTER) PackageImporter importer, String importMode) {
        ImportMode mode = ImportMode.valueOf(importMode);
        if (mode.isPrevious()) {
            ImportedPackage previous = MetadataSharing.getService()
                    .getImportedPackageByGroup(importer.getImportedPackage().getGroupUuid());
            if (previous != null) {
                previous.getImportConfig().setPrevious(true);
                importer.setImportConfig(previous.getImportConfig());
            }
        } else {
            importer.setImportConfig(ImportConfig.valueOf(mode));
        }
        return WebUtils.redirect(CONFIG_PATH);
    }

    @RequestMapping(value = CONFIG_PATH, method = RequestMethod.GET)
    public void config(@ModelAttribute(IMPORTER) PackageImporter importer) {
    }

    @RequestMapping(value = CONFIG_PATH, method = RequestMethod.POST)
    public String configPOST(@ModelAttribute(IMPORTER) PackageImporter importer, Model model) throws IOException {
        ImportedPackage importedPackage = MetadataSharing.getService()
                .getImportedPackageByGroup(importer.getImportedPackage().getGroupUuid());
        if (importedPackage != null) {
            importedPackage.loadPackage(importer.getImportedPackage());
        } else {
            importedPackage = importer.getImportedPackage();
        }
        MetadataSharing.getService().saveImportedPackage(importedPackage);

        model.addAttribute("part", 0);
        Map<Integer, Boolean> parts = new TreeMap<Integer, Boolean>();
        for (int i = 0; i < importer.getPartsCount(); i++) {
            parts.put(i, false);
        }
        model.addAttribute(PARTS, parts);
        return WebUtils.redirect(LOAD_PATH);
    }

    @RequestMapping(value = LOAD_PATH, params = "part")
    public String load(@ModelAttribute(IMPORTER) PackageImporter importer,
            @ModelAttribute(PARTS) Map<Integer, Boolean> parts, Integer part, Model model) {
        importer.clearState();

        List<ImportedItem> importItems = new ArrayList<ImportedItem>();
        for (ImportedItem importItem : importer.getImportedItems(part)) {
            if (!Handler.isHidden(importItem.getIncoming())) {
                importItems.add(importItem);
            }
        }

        ImportedItemsStats itemsStats = importer.getImportedItemsStats(part);
        parts.put(itemsStats.getPart(), (itemsStats.getNotAssessedTotalCount() == 0));
        for (Entry<Integer, Boolean> p : parts.entrySet()) {
            if (!p.getValue()) {
                model.addAttribute("loadNext", p.getKey());
            }
        }

        model.addAttribute(ITEMS, importItems);
        model.addAttribute(ITEMS_STATS, itemsStats);
        return VIEW_PATH;
    }

    @RequestMapping(LOAD_PATH)
    public String load(@ModelAttribute(IMPORTER) PackageImporter importer,
            @ModelAttribute(ITEMS_STATS) ImportedItemsStats stats,
            @ModelAttribute(PARTS) Map<Integer, Boolean> parts, Model model) {
        ImportedItemsStats itemsStats = importer.getImportedItemsStats(stats.getPart());
        parts.put(itemsStats.getPart(), (itemsStats.getNotAssessedTotalCount() == 0));
        for (Entry<Integer, Boolean> p : parts.entrySet()) {
            if (!p.getValue()) {
                model.addAttribute("loadNext", p.getKey());
            }
        }

        model.addAttribute(ITEMS_STATS, itemsStats);
        return VIEW_PATH;
    }

    @RequestMapping(VIEW_PATH)
    public void view(@ModelAttribute(IMPORTER) PackageImporter importer,
            @ModelAttribute(ITEMS_STATS) ImportedItemsStats stats,
            @ModelAttribute(PARTS) Map<Integer, Boolean> parts, Model model) {
        ImportedItemsStats itemsStats = importer.getImportedItemsStats(stats.getPart());
        parts.put(itemsStats.getPart(), (itemsStats.getNotAssessedTotalCount() == 0));
        for (Entry<Integer, Boolean> p : parts.entrySet()) {
            if (!p.getValue()) {
                model.addAttribute("loadNext", p.getKey());
            }
        }
        model.addAttribute(ITEMS_STATS, itemsStats);
    }

    @RequestMapping(value = COMPLETE_PATH)
    public String complete(@ModelAttribute(IMPORTER) PackageImporter importer, Errors errors, Model model,
            SessionStatus session) {
        Task task = importer.schedulePackageImport();

        session.setComplete();

        return WebUtils.redirect(TaskController.DETAILS_PATH, "uuid=" + task.getUuid());
    }

    @RequestMapping(value = JSON_IMPORT_ITEMS_PATH, method = RequestMethod.GET)
    public ModelAndView jsonImportItems(@ModelAttribute(ITEMS) List<ImportedItem> importItems,
            HttpServletRequest httpRequest, Model model) {
        DatatablesRequest request = DatatablesRequest.parseRequest(httpRequest);

        int totalRecords = importItems.size();
        int iDisplayEnd = request.getiDisplayStart() + request.getiDisplayLength();
        iDisplayEnd = (iDisplayEnd > totalRecords) ? totalRecords : iDisplayEnd;

        DatatablesResponse response = DatatablesResponse.newResponse(request);
        response.setsColumns("index", "type", "incoming", "existing", "importType", "assessed");
        response.setiTotalRecords(totalRecords);
        response.setiTotalDisplayRecords(totalRecords);

        List<ImportedItem> items = importItems.subList(request.getiDisplayStart(), iDisplayEnd);
        int i = 0;
        for (ImportedItem item : items) {
            Integer index = request.getiDisplayStart() + i;
            String type = Handler.getRegisteredType(item.getIncoming());
            String incoming = Handler.getName(item.getIncoming());
            String existing = "";
            if (item.getExisting() != null) {
                existing = Handler.getName(item.getExisting());
            }

            String importType;
            switch (item.getImportType()) {
            case OMIT:
                importType = "Skip";
                break;
            case PREFER_THEIRS:
                importType = "Merge (Prefer Theirs)";
                break;
            case PREFER_MINE:
                importType = "Merge (Prefer Mine)";
                break;
            case CREATE:
                importType = "Create New";
                break;
            default:
                importType = item.getImportType().toString();
            }

            String accepted = new Boolean(item.isAssessed()).toString();

            response.addRow(index.toString(), type, incoming, existing, importType, accepted);

            i++;
        }
        model.addAttribute("object", response);
        return new ModelAndView(jsonObjectView);
    }

    @RequestMapping(JSON_ITEMS_PATH)
    public ModelAndView jsonItems(String type, HttpServletRequest httpRequest, Model model) throws IOException {
        DatatablesRequest request = DatatablesRequest.parseRequest(httpRequest);

        Class<?> clazz = Handler.getRegisteredClass(type);

        int totalRecords = Handler.getItemsCount(clazz, true, request.getsSearch());

        List<Object> items = Handler.getItems(clazz, true, request.getsSearch(), request.getiDisplayStart(),
                request.getiDisplayLength());

        DatatablesResponse response = DatatablesResponse.newResponse(request);
        response.setsColumns("uuid", "id", "retired", "name", "description");
        response.setiTotalRecords(totalRecords);
        response.setiTotalDisplayRecords(totalRecords);

        for (Object item : items) {
            Map<String, String> row = new HashMap<String, String>();
            row.put("uuid", Handler.getUuid(item));
            if (Handler.getId(item) != null) {
                row.put("id", Handler.getId(item).toString());
            }
            if (Handler.getRetired(item) != null) {
                row.put("retired", Handler.getRetired(item).toString());
            }
            row.put("name", Handler.getName(item));
            row.put("description", Handler.getDescription(item));

            response.addRow(row);
        }

        model.addAttribute("object", response);

        return new ModelAndView(jsonObjectView);
    }

    @RequestMapping(value = ITEM_PATH, method = RequestMethod.GET)
    public void assessItem(@ModelAttribute(ITEMS) List<ImportedItem> items, Integer index, Model model,
            HttpServletRequest request) {
        ImportedItem importedItem = items.get(index);
        importedItem.loadExisting();

        try {
            ValidateCustomUtil.validate(importedItem.getIncoming());
        } catch (Exception e) {
            log.error(e);
            String message = e.getMessage();
            if (message == null) {
                message = e.toString();
            }

            model.addAttribute("incomingInvalid", message);
            if (importedItem.getImportType().isCreate()) {
                importedItem.setImportType(ImportType.OMIT);
            }
        }

        Context.addProxyPrivilege("Manage Concepts");
        Context.addProxyPrivilege("View Concepts");
        request.getSession().setAttribute("existingItem", importedItem.getExisting());
        request.getSession().setAttribute("incomingItem", importedItem.getIncoming());
        Context.removeProxyPrivilege("Manage Concepts");
        Context.removeProxyPrivilege("View Concepts");

        model.addAttribute("type", Handler.getRegisteredType(importedItem.getIncoming()));
        model.addAttribute(ITEM, importedItem);
        AssessItemForm assessItemForm = new AssessItemForm();
        assessItemForm.setIndex(index);
        assessItemForm.setImportType(importedItem.getImportType());
        model.addAttribute(assessItemForm);
    }

    @RequestMapping(value = ITEM_PATH, method = RequestMethod.GET, params = "uuid")
    public void assessItem(@ModelAttribute(ITEMS) List<ImportedItem> items, Integer index, String uuid, Model model,
            HttpServletRequest request) {
        ImportedItem importedItem = items.get(index);

        if (importedItem.isExistingReplaceable()) {
            Object existing = Handler.getItemByUuid(Handler.getRegisteredClass(importedItem.getIncoming()), uuid);
            if (existing != null) {
                importedItem.setExisting(existing);
                importedItem.setImportType(ImportType.PREFER_MINE);
            }
        }

        assessItem(items, index, model, request);
    }

    @RequestMapping(value = ITEM_PATH, method = RequestMethod.POST)
    public String assessItemPOST(@ModelAttribute(ITEMS) List<ImportedItem> items, AssessItemForm assessItemForm,
            Model model) {
        ImportedItem importedItem = items.get(assessItemForm.getIndex());
        importedItem.setImportType(assessItemForm.getImportType());
        importedItem.setAssessed(true);
        MetadataSharing.getService().persistImportedItem(importedItem);
        model.addAttribute("index", assessItemForm.getIndex());
        return WebUtils.redirect(ASSESS_NEXT_ITEM_PATH);
    }

    @RequestMapping(ASSESS_NEXT_ITEM_PATH)
    public String asssessNextItem(@ModelAttribute(ITEMS) List<ImportedItem> items,
            @RequestParam(required = false) Integer index, Model model) {
        if (index == null) {
            index = 0;
        }

        ImportedItem next = null;
        int nextIndex = index;

        for (int i = index + 1; i <= items.size(); i++) {
            if (items.size() == i) {
                i = 0;
            }

            if (!items.get(i).isAssessed()) {
                next = items.get(i);
                nextIndex = i;
                break;
            }

            if (i == index) {
                break;
            }
        }

        if (next != null) {
            model.addAttribute("index", nextIndex);
            return WebUtils.redirect(ITEM_PATH);
        } else {
            return WebUtils.redirect(VIEW_PATH);
        }
    }
}