org.olat.ims.qti.qpool.QTIImportProcessor.java Source code

Java tutorial

Introduction

Here is the source code for org.olat.ims.qti.qpool.QTIImportProcessor.java

Source

/**
 * <a href="http://www.openolat.org">
 * OpenOLAT - Online Learning and Training</a><br>
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); <br>
 * you may not use this file except in compliance with the License.<br>
 * You may obtain a copy of the License at the
 * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
 * <p>
 * Unless required by applicable law or agreed to in writing,<br>
 * software distributed under the License is distributed on an "AS IS" BASIS, <br>
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
 * See the License for the specific language governing permissions and <br>
 * limitations under the License.
 * <p>
 * Initial code contributed and copyrighted by<br>
 * frentix GmbH, http://www.frentix.com
 * <p>
 */
package org.olat.ims.qti.qpool;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.commons.io.IOUtils;
import org.cyberneko.html.parsers.SAXParser;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.olat.core.commons.persistence.DB;
import org.olat.core.id.Identity;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.FileUtils;
import org.olat.core.util.PathUtils.CopyVisitor;
import org.olat.core.util.PathUtils.YesMatcher;
import org.olat.core.util.StringHelper;
import org.olat.core.util.ZipUtil;
import org.olat.core.util.vfs.LocalImpl;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.xml.XMLParser;
import org.olat.ims.qti.QTIConstants;
import org.olat.ims.qti.editor.beecom.objects.QTIDocument;
import org.olat.ims.qti.editor.beecom.objects.Question;
import org.olat.ims.qti.editor.beecom.parser.ItemParser;
import org.olat.ims.qti.questionimport.ItemAndMetadata;
import org.olat.ims.resources.IMSEntityResolver;
import org.olat.modules.qpool.QuestionItem;
import org.olat.modules.qpool.QuestionType;
import org.olat.modules.qpool.TaxonomyLevel;
import org.olat.modules.qpool.manager.QEducationalContextDAO;
import org.olat.modules.qpool.manager.QItemTypeDAO;
import org.olat.modules.qpool.manager.QLicenseDAO;
import org.olat.modules.qpool.manager.QPoolFileStorage;
import org.olat.modules.qpool.manager.QuestionItemDAO;
import org.olat.modules.qpool.manager.TaxonomyLevelDAO;
import org.olat.modules.qpool.model.QEducationalContext;
import org.olat.modules.qpool.model.QItemType;
import org.olat.modules.qpool.model.QLicense;
import org.olat.modules.qpool.model.QuestionItemImpl;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * This class is NOT thread-safe
 * 
 * Initial date: 07.03.2013<br>
 * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
 *
 */
class QTIImportProcessor {

    private static final OLog log = Tracing.createLoggerFor(QTIImportProcessor.class);

    private static final String OPENOLAT_MOVIE_MARKER = "BPlayer.insertPlayer(";

    private final Identity owner;
    private final Locale defaultLocale;
    private final String importedFilename;
    private final File importedFile;

    private final DB dbInstance;
    private final QLicenseDAO qLicenseDao;
    private final QItemTypeDAO qItemTypeDao;
    private final QPoolFileStorage qpoolFileStorage;
    private final QuestionItemDAO questionItemDao;
    private final TaxonomyLevelDAO taxonomyLevelDao;
    private final QEducationalContextDAO qEduContextDao;

    public QTIImportProcessor(Identity owner, Locale defaultLocale, QuestionItemDAO questionItemDao,
            QItemTypeDAO qItemTypeDao, QEducationalContextDAO qEduContextDao, TaxonomyLevelDAO taxonomyLevelDao,
            QLicenseDAO qLicenseDao, QPoolFileStorage qpoolFileStorage, DB dbInstance) {
        this(owner, defaultLocale, null, null, questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao,
                qLicenseDao, qpoolFileStorage, dbInstance);
    }

    public QTIImportProcessor(Identity owner, Locale defaultLocale, String importedFilename, File importedFile,
            QuestionItemDAO questionItemDao, QItemTypeDAO qItemTypeDao, QEducationalContextDAO qEduContextDao,
            TaxonomyLevelDAO taxonomyLevelDao, QLicenseDAO qLicenseDao, QPoolFileStorage qpoolFileStorage,
            DB dbInstance) {
        this.owner = owner;
        this.dbInstance = dbInstance;
        this.defaultLocale = defaultLocale;
        this.importedFilename = importedFilename;
        this.importedFile = importedFile;
        this.qLicenseDao = qLicenseDao;
        this.qItemTypeDao = qItemTypeDao;
        this.questionItemDao = questionItemDao;
        this.qEduContextDao = qEduContextDao;
        this.qpoolFileStorage = qpoolFileStorage;
        this.taxonomyLevelDao = taxonomyLevelDao;
    }

    public List<QuestionItem> process() {
        List<QuestionItem> qItems = new ArrayList<QuestionItem>();
        try {
            List<DocInfos> docInfoList = getDocInfos();
            if (docInfoList != null) {
                for (DocInfos docInfos : docInfoList) {
                    List<QuestionItem> processdItems = process(docInfos);
                    qItems.addAll(processdItems);
                    dbInstance.commit();
                }
            }
        } catch (IOException e) {
            log.error("", e);
        }
        return qItems;
    }

    private List<QuestionItem> process(DocInfos docInfos) {
        List<QuestionItem> qItems = new ArrayList<>();
        if (docInfos.doc != null) {
            List<ItemInfos> itemInfos = getItemList(docInfos);
            for (ItemInfos itemInfo : itemInfos) {
                QuestionItemImpl qItem = processItem(docInfos, itemInfo, null);
                if (qItem != null) {
                    processFiles(qItem, itemInfo, docInfos);
                    qItem = questionItemDao.merge(qItem);
                    qItems.add(qItem);
                }
            }
        }
        return qItems;
    }

    protected List<ItemInfos> getItemList(DocInfos doc) {
        Document document = doc.getDocument();
        List<ItemInfos> itemElements = new ArrayList<ItemInfos>();
        Element item = (Element) document.selectSingleNode("/questestinterop/item");
        Element assessment = (Element) document.selectSingleNode("/questestinterop/assessment");
        if (item != null) {
            ItemInfos itemInfos = new ItemInfos(item, true);
            Element comment = (Element) document.selectSingleNode("/questestinterop/qticomment");
            String qtiComment = getText(comment);
            itemInfos.setComment(qtiComment);
            itemElements.add(itemInfos);
        } else if (assessment != null) {
            @SuppressWarnings("unchecked")
            List<Element> items = assessment.selectNodes("//item");
            for (Element it : items) {
                itemElements.add(new ItemInfos(it, false));
            }
        }
        return itemElements;
    }

    protected QuestionItemImpl processItem(DocInfos docInfos, ItemInfos itemInfos, ItemAndMetadata metadata) {
        Element itemEl = itemInfos.getItemEl();
        String comment = itemInfos.getComment();
        String originalFilename = null;
        if (itemInfos.isOriginalItem()) {
            originalFilename = docInfos.filename;
        }
        return processItem(itemEl, comment, originalFilename, null, null, docInfos, metadata);
    }

    protected QuestionItemImpl processItem(Element itemEl, String comment, String originalItemFilename,
            String editor, String editorVersion, DocInfos docInfos, ItemAndMetadata metadata) {
        //filename
        String filename;
        String ident = getAttributeValue(itemEl, "ident");
        if (originalItemFilename != null) {
            filename = originalItemFilename;
        } else if (StringHelper.containsNonWhitespace(ident)) {
            filename = StringHelper.transformDisplayNameToFileSystemName(ident) + ".xml";
        } else {
            filename = "item.xml";
        }
        String dir = qpoolFileStorage.generateDir();

        //title
        String title = getAttributeValue(itemEl, "title");
        if (!StringHelper.containsNonWhitespace(title)) {
            title = ident;
        }
        if (!StringHelper.containsNonWhitespace(title)) {
            title = importedFilename;
        }

        QuestionItemImpl poolItem = questionItemDao.create(title, QTIConstants.QTI_12_FORMAT, dir, filename);
        //description
        poolItem.setDescription(comment);
        //language from default
        poolItem.setLanguage(defaultLocale.getLanguage());
        //question type first
        boolean ooFormat = processItemQuestionType(poolItem, ident, itemEl);
        if (StringHelper.containsNonWhitespace(editor)) {
            poolItem.setEditor(editor);
            poolItem.setEditor(editorVersion);
        } else if (ooFormat) {
            poolItem.setEditor("OpenOLAT");
        }
        //if question type not found, can be overridden by the metadatas
        processItemMetadata(poolItem, itemEl);
        if (poolItem.getType() == null) {
            QItemType defType = qItemTypeDao.loadByType(QuestionType.UNKOWN.name());
            poolItem.setType(defType);
        }
        if (docInfos != null) {
            processSidecarMetadata(poolItem, docInfos);
        }
        if (metadata != null) {
            processItemMetadata(poolItem, metadata);
        }
        questionItemDao.persist(owner, poolItem);
        return poolItem;
    }

    private void processItemMetadata(QuestionItemImpl poolItem, ItemAndMetadata metadata) {
        //non heuristic set of question type
        int questionType = metadata.getQuestionType();
        if (questionType >= 0) {
            String typeStr;
            switch (questionType) {
            case Question.TYPE_MC:
                typeStr = QuestionType.MC.name();
                break;
            case Question.TYPE_SC:
                typeStr = QuestionType.SC.name();
                break;
            case Question.TYPE_FIB:
                typeStr = QuestionType.FIB.name();
                break;
            case Question.TYPE_ESSAY:
                typeStr = QuestionType.ESSAY.name();
                break;
            default:
                typeStr = null;
            }
            if (typeStr != null) {
                QItemType type = qItemTypeDao.loadByType(typeStr);
                if (type != null) {
                    poolItem.setType(type);
                }
            }
        }

        String coverage = metadata.getCoverage();
        if (StringHelper.containsNonWhitespace(coverage)) {
            poolItem.setCoverage(coverage);
        }

        String language = metadata.getLanguage();
        if (StringHelper.containsNonWhitespace(language)) {
            poolItem.setLanguage(language);
        }

        String keywords = metadata.getKeywords();
        if (StringHelper.containsNonWhitespace(keywords)) {
            poolItem.setKeywords(keywords);
        }

        String taxonomyPath = metadata.getTaxonomyPath();
        if (StringHelper.containsNonWhitespace(taxonomyPath)) {
            QTIMetadataConverter converter = new QTIMetadataConverter(qItemTypeDao, qLicenseDao, taxonomyLevelDao,
                    qEduContextDao);
            TaxonomyLevel taxonomyLevel = converter.toTaxonomy(taxonomyPath);
            poolItem.setTaxonomyLevel(taxonomyLevel);
        }

        String level = metadata.getLevel();
        if (StringHelper.containsNonWhitespace(level)) {
            QTIMetadataConverter converter = new QTIMetadataConverter(qItemTypeDao, qLicenseDao, taxonomyLevelDao,
                    qEduContextDao);
            QEducationalContext educationalContext = converter.toEducationalContext(level);
            poolItem.setEducationalContext(educationalContext);
        }

        String time = metadata.getTypicalLearningTime();
        if (StringHelper.containsNonWhitespace(time)) {
            poolItem.setEducationalLearningTime(time);
        }

        String editor = metadata.getEditor();
        if (StringHelper.containsNonWhitespace(editor)) {
            poolItem.setEditor(editor);
        }

        String editorVersion = metadata.getEditorVersion();
        if (StringHelper.containsNonWhitespace(editorVersion)) {
            poolItem.setEditorVersion(editorVersion);
        }

        int numOfAnswerAlternatives = metadata.getNumOfAnswerAlternatives();
        if (numOfAnswerAlternatives > 0) {
            poolItem.setNumOfAnswerAlternatives(numOfAnswerAlternatives);
        }

        poolItem.setDifficulty(metadata.getDifficulty());
        poolItem.setDifferentiation(metadata.getDifferentiation());
        poolItem.setStdevDifficulty(metadata.getStdevDifficulty());

        String license = metadata.getLicense();
        if (StringHelper.containsNonWhitespace(license)) {
            QTIMetadataConverter converter = new QTIMetadataConverter(qItemTypeDao, qLicenseDao, taxonomyLevelDao,
                    qEduContextDao);
            QLicense qLicense = converter.toLicense(license);
            poolItem.setLicense(qLicense);
        }
    }

    private void processItemMetadata(QuestionItemImpl poolItem, Element itemEl) {
        @SuppressWarnings("unchecked")
        List<Element> qtiMetadataFieldList = itemEl.selectNodes("./itemmetadata/qtimetadata/qtimetadatafield");
        for (Element qtiMetadataField : qtiMetadataFieldList) {
            Element labelEl = (Element) qtiMetadataField.selectSingleNode("./fieldlabel");
            Element entryEl = (Element) qtiMetadataField.selectSingleNode("./fieldentry");
            if (labelEl != null && entryEl != null) {
                processMetadataField(poolItem, labelEl, entryEl);
            }
        }
    }

    /**
     * <ul>
     *  <li>qmd_computerscored</li>
     *  <li>qmd_feedbackpermitted</li>
     *  <li>qmd_hintspermitted</li>
     *  <li>qmd_itemtype -> (check is made on the content of the item)</li>
     *  <li>qmd_levelofdifficulty -> educational context</li>
     *  <li>qmd_maximumscore</li>
     *  <li>qmd_renderingtype</li>
     *  <li>qmd_responsetype</li>
     *  <li>qmd_scoringpermitted</li>
     *  <li>qmd_solutionspermitted</li>
     *  <li>qmd_status</li>
     *  <li>qmd_timedependence</li>
     *  <li>qmd_timelimit</li>
     *  <li>qmd_toolvendor -> editor</li>
     *  <li>qmd_topic</li>
     *  <li>qmd_material</li>
     *  <li>qmd_typeofsolution</li>
     *  <li>qmd_weighting</li>
     * </ul> 
     * @param poolItem
     * @param labelEl
     * @param entryEl
     */
    private void processMetadataField(QuestionItemImpl poolItem, Element labelEl, Element entryEl) {
        String label = labelEl.getText();
        String entry = entryEl.getText();

        if (QTIConstants.META_LEVEL_OF_DIFFICULTY.equals(label)) {
            if (StringHelper.containsNonWhitespace(entry)) {
                QEducationalContext context = qEduContextDao.loadByLevel(entry);
                if (context == null) {
                    context = qEduContextDao.create(entry, true);
                }
                poolItem.setEducationalContext(context);
            }
        } else if (QTIConstants.META_ITEM_TYPE.equals(label)) {
            if (poolItem.getType() == null && StringHelper.containsNonWhitespace(entry)) {
                //some heuristic
                String typeStr = entry;
                if (typeStr.equalsIgnoreCase("MCQ") || typeStr.equalsIgnoreCase("Multiple choice")) {
                    typeStr = QuestionType.MC.name();
                } else if (typeStr.equalsIgnoreCase("SCQ") || typeStr.equalsIgnoreCase("Single choice")) {
                    typeStr = QuestionType.SC.name();
                } else if (typeStr.equalsIgnoreCase("fill-in") || typeStr.equals("Fill-in-the-Blank")
                        || typeStr.equalsIgnoreCase("Fill-in-Blank")
                        || typeStr.equalsIgnoreCase("Fill In the Blank")) {
                    typeStr = QuestionType.FIB.name();
                } else if (typeStr.equalsIgnoreCase("Essay")) {
                    typeStr = QuestionType.ESSAY.name();
                }

                QItemType type = qItemTypeDao.loadByType(entry);
                if (type == null) {
                    type = qItemTypeDao.create(entry, true);
                }
                poolItem.setType(type);
            }
        } else if (QTIConstants.META_TOOLVENDOR.equals(label)) {
            poolItem.setEditor(entry);
        }
    }

    /**
     * Save the item element in a <questestinterop> cartridge if needed
     * @param item
     * @param itemEl
     */
    protected void processFiles(QuestionItemImpl item, ItemInfos itemInfos, DocInfos docInfos) {
        if (itemInfos.originalItem) {
            processItemFiles(item, docInfos);
        } else {
            //an assessment package
            processAssessmentFiles(item, itemInfos);
        }
    }

    protected void processAssessmentFiles(QuestionItemImpl item, ItemInfos itemInfos) {
        //a package with an item
        String dir = item.getDirectory();
        String rootFilename = item.getRootFilename();
        VFSContainer container = qpoolFileStorage.getContainer(dir);
        VFSLeaf endFile = container.createChildLeaf(rootFilename);

        //embed in <questestinterop>
        DocumentFactory df = DocumentFactory.getInstance();
        Document itemDoc = df.createDocument();
        Element questestinteropEl = df.createElement(QTIDocument.DOCUMENT_ROOT);
        itemDoc.setRootElement(questestinteropEl);
        Element deepClone = (Element) itemInfos.getItemEl().clone();
        questestinteropEl.add(deepClone);

        //write
        try {
            OutputStream os = endFile.getOutputStream(false);
            ;
            XMLWriter xw = new XMLWriter(os, new OutputFormat("  ", true));
            xw.write(itemDoc.getRootElement());
            xw.close();
            os.close();
        } catch (IOException e) {
            log.error("", e);
        }

        //there perhaps some other materials
        if (importedFilename.toLowerCase().endsWith(".zip")) {
            processAssessmentMaterials(deepClone, container);
        }
    }

    private void processAssessmentMaterials(Element itemEl, VFSContainer container) {
        List<String> materials = getMaterials(itemEl);

        try {
            InputStream in = new FileInputStream(importedFile);
            ZipInputStream zis = new ZipInputStream(in);

            ZipEntry entry;
            try {
                while ((entry = zis.getNextEntry()) != null) {
                    String name = entry.getName();
                    if (materials.contains(name)) {

                        VFSLeaf leaf = container.createChildLeaf(name);
                        OutputStream out = leaf.getOutputStream(false);
                        BufferedOutputStream bos = new BufferedOutputStream(out);
                        FileUtils.cpio(new BufferedInputStream(zis), bos, "unzip:" + entry.getName());
                        bos.flush();
                        bos.close();
                        out.close();
                    }
                }
            } catch (Exception e) {
                log.error("", e);
            } finally {
                IOUtils.closeQuietly(zis);
                IOUtils.closeQuietly(in);
            }
        } catch (IOException e) {
            log.error("", e);
        }
    }

    @SuppressWarnings("unchecked")
    protected List<String> getMaterials(Element el) {
        List<String> materialPath = new ArrayList<String>();
        //mattext
        List<Element> mattextList = el.selectNodes(".//mattext");
        for (Element mat : mattextList) {
            Attribute texttypeAttr = mat.attribute("texttype");
            if (texttypeAttr != null) {
                String texttype = texttypeAttr.getValue();
                if ("text/html".equals(texttype)) {
                    String content = mat.getStringValue();
                    findMaterialInMatText(content, materialPath);
                }
            }
        }
        //matimage uri
        List<Element> matList = new ArrayList<Element>();
        matList.addAll(el.selectNodes(".//matimage"));
        matList.addAll(el.selectNodes(".//mataudio"));
        matList.addAll(el.selectNodes(".//matvideo"));

        for (Element mat : matList) {
            Attribute uriAttr = mat.attribute("uri");
            String uri = uriAttr.getValue();
            materialPath.add(uri);
        }
        return materialPath;
    }

    /**
     * Parse the content and collect the images source
     * @param content
     * @param materialPath
     */
    protected void findMaterialInMatText(String content, List<String> materialPath) {
        try {
            SAXParser parser = new SAXParser();
            HTMLHandler contentHandler = new HTMLHandler(materialPath);
            parser.setContentHandler(contentHandler);
            parser.parse(new InputSource(new StringReader(content)));
        } catch (SAXException e) {
            log.error("", e);
        } catch (IOException e) {
            log.error("", e);
        } catch (Exception e) {
            log.error("", e);
        }
    }

    private static class HTMLHandler extends DefaultHandler {
        private final List<String> materialPath;

        private StringBuffer scriptBuffer;

        public HTMLHandler(List<String> materialPath) {
            this.materialPath = materialPath;
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) {
            String elem = localName.toLowerCase();
            if ("img".equals(elem)) {
                String imgSrc = attributes.getValue("src");
                if (StringHelper.containsNonWhitespace(imgSrc)) {
                    materialPath.add(imgSrc);
                }
            } else if ("script".equals(elem)) {
                scriptBuffer = new StringBuffer();
            }
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            if (scriptBuffer != null) {
                scriptBuffer.append(ch, start, length);
            }
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            String elem = localName.toLowerCase();
            if ("script".equals(elem)) {
                String content = scriptBuffer == null ? "" : scriptBuffer.toString();
                processScriptContent(content);
                scriptBuffer = null;
            }
        }

        private void processScriptContent(String content) {
            int markerIndex = content.indexOf(OPENOLAT_MOVIE_MARKER);
            if (markerIndex >= 0) {
                int beginIndex = markerIndex + OPENOLAT_MOVIE_MARKER.length();
                char quote = content.charAt(beginIndex);
                int endIndex = content.indexOf(quote, beginIndex + 1);
                if (endIndex > beginIndex) {
                    String media = content.substring(beginIndex + 1, endIndex);
                    if (StringHelper.containsNonWhitespace(media)) {
                        materialPath.add(media.trim());
                    }
                }
            }
        }
    }

    /**
     * Process the file of an item's package
     * @param item
     * @param itemInfos
     */
    protected void processItemFiles(QuestionItemImpl item, DocInfos docInfos) {
        //a package with an item
        String dir = item.getDirectory();
        String rootFilename = item.getRootFilename();
        VFSContainer container = qpoolFileStorage.getContainer(dir);

        if (docInfos != null && docInfos.getRoot() != null) {
            try {
                Path destDir = ((LocalImpl) container).getBasefile().toPath();
                //unzip to container
                Path path = docInfos.getRoot();
                Files.walkFileTree(path, new CopyVisitor(path, destDir, new YesMatcher()));
            } catch (IOException e) {
                log.error("", e);
            }
        } else if (importedFilename.toLowerCase().endsWith(".zip")) {
            ZipUtil.unzipStrict(importedFile, container);
        } else {
            VFSLeaf endFile = container.createChildLeaf(rootFilename);

            OutputStream out = null;
            FileInputStream in = null;
            try {
                out = endFile.getOutputStream(false);
                in = new FileInputStream(importedFile);
                IOUtils.copy(in, out);
            } catch (IOException e) {
                log.error("", e);
            } finally {
                IOUtils.closeQuietly(out);
                IOUtils.closeQuietly(in);
            }
        }
    }

    private boolean processSidecarMetadata(QuestionItemImpl item, DocInfos docInfos) {
        InputStream metadataIn = null;
        try {
            Path path = docInfos.root;
            if (path != null && path.getFileName() != null) {
                Path metadata = path.resolve(path.getFileName().toString() + "_metadata.xml");
                metadataIn = Files.newInputStream(metadata);
                SAXReader reader = new SAXReader();
                Document document = reader.read(metadataIn);
                Element rootElement = document.getRootElement();
                QTIMetadataConverter enricher = new QTIMetadataConverter(rootElement, qItemTypeDao, qLicenseDao,
                        taxonomyLevelDao, qEduContextDao);
                enricher.toQuestion(item);
            }
            return true;
        } catch (NoSuchFileException e) {
            //nothing to do
            return true;
        } catch (Exception e) {
            log.error("", e);
            return false;
        } finally {
            IOUtils.closeQuietly(metadataIn);
        }
    }

    private boolean processItemQuestionType(QuestionItemImpl poolItem, String ident, Element itemEl) {
        boolean openolatFormat = false;

        //question type: mc, sc...
        QuestionType type = null;
        //test with openolat ident 
        if (ident != null && ident.startsWith(ItemParser.ITEM_PREFIX_SCQ)) {
            type = QuestionType.SC;
            openolatFormat = true;
        } else if (ident != null && ident.startsWith(ItemParser.ITEM_PREFIX_MCQ)) {
            type = QuestionType.MC;
            openolatFormat = true;
        } else if (ident != null && ident.startsWith(ItemParser.ITEM_PREFIX_FIB)) {
            type = QuestionType.FIB;
            openolatFormat = true;
        } else if (ident != null && ident.startsWith(ItemParser.ITEM_PREFIX_ESSAY)) {
            type = QuestionType.ESSAY;
            openolatFormat = true;
        } else if (ident != null && ident.startsWith(ItemParser.ITEM_PREFIX_KPRIM)) {
            type = QuestionType.KPRIM;
            openolatFormat = true;
        } else if (itemEl.selectNodes("//render_choice").size() == 1) {
            Element lidEl = (Element) itemEl.selectSingleNode("//response_lid");
            String rcardinality = getAttributeValue(lidEl, "rcardinality");
            if ("Single".equals(rcardinality)) {
                type = QuestionType.SC;
            } else if ("Multiple".equals(rcardinality)) {
                type = QuestionType.MC;
            }
        } else if (itemEl.selectNodes("//render_fib").size() == 1) {
            type = QuestionType.FIB;
        }
        if (type != null) {
            QItemType itemType = qItemTypeDao.loadByType(type.name());
            poolItem.setType(itemType);
        }

        return openolatFormat;
    }

    private String getAttributeValue(Element el, String attrName) {
        if (el == null)
            return null;
        Attribute attr = el.attribute(attrName);
        return (attr == null) ? null : attr.getStringValue();
    }

    private String getText(Element el) {
        if (el == null)
            return null;
        return el.getText();
    }

    protected List<DocInfos> getDocInfos() throws IOException {
        List<DocInfos> doc;
        if (importedFilename.toLowerCase().endsWith(".zip")) {
            //doc = traverseZip(importedFile);
            doc = traverseZip_nio(importedFile);
        } else {
            doc = Collections.singletonList(traverseFile(importedFile));
        }
        return doc;
    }

    private DocInfos traverseFile(File file) throws IOException {
        InputStream in = new FileInputStream(file);
        try {
            Document doc = readXml(in);
            if (doc != null) {
                DocInfos d = new DocInfos();
                d.doc = doc;
                d.filename = file.getName();
                return d;
            }
            return null;
        } catch (Exception e) {
            log.error("", e);
            return null;
        } finally {
            IOUtils.closeQuietly(in);
        }
    }

    /*
    private List<DocInfos> traverseZip(File file) throws IOException {
       InputStream in = new FileInputStream(file);
       ZipInputStream zis = new ZipInputStream(in);
       List<DocInfos> docInfos = new ArrayList<>();
        
       ZipEntry entry;
       try {
     while ((entry = zis.getNextEntry()) != null) {
        String name = entry.getName();
        if(name != null && name.toLowerCase().endsWith(".xml")) {
           Document doc = readXml(new ShieldInputStream(zis));
           if(doc != null) {
              DocInfos d = new DocInfos();
              d.doc = doc;
              d.filename = name;
              docInfos.add(d);
           }
        }
     }
       } catch(Exception e) {
     log.error("", e);
       } finally {
     IOUtils.closeQuietly(zis);
     IOUtils.closeQuietly(in);
       }
       return docInfos;
    }
    */

    private List<DocInfos> traverseZip_nio(File file) throws IOException {
        List<DocInfos> docInfos = new ArrayList<>();

        Path fPath = FileSystems.newFileSystem(file.toPath(), null).getPath("/");
        if (fPath != null) {
            DocInfosVisitor visitor = new DocInfosVisitor();
            Files.walkFileTree(fPath, visitor);

            List<Path> xmlFiles = visitor.getXmlFiles();
            for (Path xmlFile : xmlFiles) {
                InputStream in = Files.newInputStream(xmlFile);

                Document doc = readXml(in);
                if (doc != null) {
                    DocInfos d = new DocInfos();
                    d.setDocument(doc);
                    d.setRoot(xmlFile.getParent());
                    d.setFilename(xmlFile.getFileName().toString());
                    docInfos.add(d);
                }

            }
        }

        return docInfos;
    }

    public static class DocInfosVisitor extends SimpleFileVisitor<Path> {

        private final List<Path> xmlFiles = new ArrayList<>();

        public List<Path> getXmlFiles() {
            return xmlFiles;
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            String name = file.getFileName().toString();
            if (name != null && name.toLowerCase().endsWith(".xml")) {
                xmlFiles.add(file);
            }
            return FileVisitResult.CONTINUE;
        }
    }

    private Document readXml(InputStream in) {
        Document doc = null;
        try {
            XMLParser xmlParser = new XMLParser(new IMSEntityResolver());
            doc = xmlParser.parse(in, false);
            return doc;
        } catch (Exception e) {
            return null;
        }
    }

    public static class ItemInfos {
        private String comment;
        private final Element itemEl;
        private final boolean originalItem;

        public ItemInfos(Element itemEl, boolean originalItem) {
            this.itemEl = itemEl;
            this.originalItem = originalItem;
        }

        public Element getItemEl() {
            return itemEl;
        }

        public boolean isOriginalItem() {
            return originalItem;
        }

        public String getComment() {
            return comment;
        }

        public void setComment(String comment) {
            this.comment = comment;
        }
    }

    public static class DocInfos {
        private Document doc;
        private String filename;
        private Path root;
        private Path metadata;
        private String qtiComment;

        public String getFilename() {
            return filename;
        }

        public void setFilename(String filename) {
            this.filename = filename;
        }

        public Document getDocument() {
            return doc;
        }

        public void setDocument(Document doc) {
            this.doc = doc;
        }

        public Path getMetadata() {
            return metadata;
        }

        public void setMetadata(Path metadata) {
            this.metadata = metadata;
        }

        public Path getRoot() {
            return root;
        }

        public void setRoot(Path root) {
            this.root = root;
        }

        public String getQtiComment() {
            return qtiComment;
        }

        public void setQtiComment(String qtiComment) {
            this.qtiComment = qtiComment;
        }
    }
}