<commit_before>/**
 *  Copyright (C) 2007 Orbeon, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the
 *  GNU Lesser General Public License as published by the Free Software Foundation; either version
 *  2.1 of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *  See the GNU Lesser General Public License for more details.
 *
 *  The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.xforms

import java.util.{List ⇒ JList}

import org.apache.commons.lang3.StringUtils
import org.orbeon.errorified.Exceptions
import org.orbeon.oxf.common.{OXFException, OrbeonLocationException}
import org.orbeon.oxf.resources.ResourceManagerWrapper
import org.orbeon.oxf.util.XPath
import org.orbeon.oxf.webapp.HttpStatusCodeException
import org.orbeon.oxf.xforms.action.XFormsAPI._
import org.orbeon.oxf.xforms.event.XFormsEventTarget
import org.orbeon.oxf.xforms.model.DataModel.Reason
import org.orbeon.oxf.xforms.processor.handlers.xhtml.XHTMLBodyHandler
import org.orbeon.oxf.xforms.xbl.XBLContainer
import org.orbeon.oxf.xml._
import org.orbeon.oxf.xml.dom4j.LocationData
import org.orbeon.saxon.dom4j.DocumentWrapper
import org.orbeon.saxon.om.NodeInfo
import org.orbeon.saxon.trans.XPathException
import org.orbeon.scaxon.XML._

import scala.collection.JavaConverters._

// Represent a non-fatal server XForms error
case class ServerError(
    message  : String,
    fileOpt  : Option[String],
    lineOpt  : Option[Int],
    colOpt   : Option[Int],
    classOpt : Option[String]
)

object ServerError {

    def apply(t: Throwable): ServerError = {
        val root = Exceptions.getRootThrowable(t)
        ServerError(
            root.getMessage,
            OrbeonLocationException.getRootLocationData(t),
            Some(root.getClass.getName)
        )
    }
    
    def apply(message: String, location : Option[LocationData], classOpt : Option[String] = None): ServerError =
        ServerError(
            StringUtils.trimToEmpty(message),
            location flatMap (l ⇒ Option(l.getSystemID)),
            location map     (_.getLine) filter (_ >= 0),
            location map     (_.getCol)  filter (_ >= 0),
            classOpt
        )

    private val attributes  = List("file", "line", "column", "exception")
    private val description = List("in",   "line", "column", "cause")

    private def collectList(error: ServerError, names: List[String]) = (
        names
        zip List(error.fileOpt, error.lineOpt, error.colOpt, error.classOpt)
        collect { case (k, Some(v)) ⇒ List(k, v.toString) }
        flatten
    )

    def getDetailsAsArray(error: ServerError) =
        collectList(error, attributes) toArray

    // NOTE: A similar concatenation logic is in AjaxServer.js
    def getDetailsAsUserMessage(error: ServerError) =
        error.message :: collectList(error, description) mkString " "
    
    def errorsAsHTMLElem(errors: TraversableOnce[ServerError]) =
        <ul>{
            for (error ← errors)
                yield <li>{XMLUtils.escapeXMLMinimal(ServerError.getDetailsAsUserMessage(error))}</li>
        }</ul>
    
    def errorsAsXHTMLElem(errors: TraversableOnce[ServerError]) =
        <ul xmlns="http://www.w3.org/1999/xhtml">{
            for (error ← errors)
                yield <li>{XMLUtils.escapeXMLMinimal(ServerError.getDetailsAsUserMessage(error))}</li>
        }</ul>
}

object XFormsError {

    // What kind of errors get here:
    //
    // - setvalue errors (binding exceptions except in actions)
    //    - @calculate, @xxf:default
    //    - write xf:upload file metadata
    //    - store external value from control
    //    - xf:setvalue
    //    - use of XFormsAPI's setvalue
    //    - instance mirror (XBL, xxf:dynamic)
    //    - xf:switch/@caseref and xf:repeat/@indexref
    //    - xf:submission[@replace = 'text']
    // - XPath errors
    //    - during model rebuild
    //    - evaluating MIPs
    //    - evaluating variables
    //    - evaluating bindings except for actions
    //        - control and LHHA bindings
    //        - itemsets
    //        - xf:submission/@ref
    //        - submission headers
    //        - xf:upload/xf:output metadata
    //    - evaluating value attributes, as with xf:label/@value
    //    - evaluating control AVTs
    //    - evaluating control @format, @unformat, @value
    // - action errors
    //    - XPath errors
    //    - any other action error

    def handleNonFatalSetvalueError(target: XFormsEventTarget, locationData: LocationData, reason: Reason): Unit = {
        val containingDocument = target.container.getContainingDocument
        containingDocument.indentedLogger.logDebug("", reason.message)
        containingDocument.addServerError(ServerError(reason.message, Option(locationData)))
    }

    def handleNonFatalXPathError(container: XBLContainer, t: Throwable): Unit =
        handleNonFatalXFormsError(container, "exception while evaluating XPath expression", t)

    def handleNonFatalActionError(target: XFormsEventTarget, t: Throwable): Unit =
        handleNonFatalXFormsError(target.container, "exception while running action", t)

    private def handleNonFatalXFormsError(container: XBLContainer, message: String, t: Throwable): Unit = {

        def causesContainDynamicXPathError =
            Exceptions.causesIterator(t) exists {
                case e: XPathException if ! e.isStaticError ⇒ true
                case _                                      ⇒ false
            }

        if (container.getPartAnalysis.isTopLevel           &&   // LATER: Other sub-parts could be fatal, depending on settings on xxf:dynamic. 
            container.getContainingDocument.isInitializing &&
            ! causesContainDynamicXPathError) {
            throw new OXFException(t)
        } else {
            val containingDocument = container.getContainingDocument
            containingDocument.indentedLogger.logDebug("", message, t)
            containingDocument.addServerError(ServerError(t))
        }
    }

    // Output the Ajax error panel with a placeholder for errors
    def outputAjaxErrorPanel(
        containingDocument : XFormsContainingDocument,<commit_msg>Working fix for #2194<commit_after>/**
 *  Copyright (C) 2007 Orbeon, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the
 *  GNU Lesser General Public License as published by the Free Software Foundation; either version
 *  2.1 of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *  See the GNU Lesser General Public License for more details.
 *
 *  The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.xforms

import java.util.{List ⇒ JList}

import org.apache.commons.lang3.StringUtils
import org.orbeon.errorified.Exceptions
import org.orbeon.oxf.common.{OXFException, OrbeonLocationException}
import org.orbeon.oxf.resources.ResourceManagerWrapper
import org.orbeon.oxf.util.XPath
import org.orbeon.oxf.webapp.HttpStatusCode
import org.orbeon.oxf.xforms.action.XFormsAPI._
import org.orbeon.oxf.xforms.event.XFormsEventTarget
import org.orbeon.oxf.xforms.model.DataModel.Reason
import org.orbeon.oxf.xforms.processor.handlers.xhtml.XHTMLBodyHandler
import org.orbeon.oxf.xforms.xbl.XBLContainer
import org.orbeon.oxf.xml._
import org.orbeon.oxf.xml.dom4j.LocationData
import org.orbeon.saxon.dom4j.DocumentWrapper
import org.orbeon.saxon.om.NodeInfo
import org.orbeon.saxon.trans.XPathException
import org.orbeon.scaxon.XML._

import scala.collection.JavaConverters._

// Represent a non-fatal server XForms error
case class ServerError(
    message  : String,
    fileOpt  : Option[String],
    lineOpt  : Option[Int],
    colOpt   : Option[Int],
    classOpt : Option[String]
)

object ServerError {

    def apply(t: Throwable): ServerError = {
        val root = Exceptions.getRootThrowable(t)
        ServerError(
            root.getMessage,
            OrbeonLocationException.getRootLocationData(t),
            Some(root.getClass.getName)
        )
    }
    
    def apply(message: String, location : Option[LocationData], classOpt : Option[String] = None): ServerError =
        ServerError(
            StringUtils.trimToEmpty(message),
            location flatMap (l ⇒ Option(l.getSystemID)),
            location map     (_.getLine) filter (_ >= 0),
            location map     (_.getCol)  filter (_ >= 0),
            classOpt
        )

    private val attributes  = List("file", "line", "column", "exception")
    private val description = List("in",   "line", "column", "cause")

    private def collectList(error: ServerError, names: List[String]) = (
        names
        zip List(error.fileOpt, error.lineOpt, error.colOpt, error.classOpt)
        collect { case (k, Some(v)) ⇒ List(k, v.toString) }
        flatten
    )

    def getDetailsAsArray(error: ServerError) =
        collectList(error, attributes) toArray

    // NOTE: A similar concatenation logic is in AjaxServer.js
    def getDetailsAsUserMessage(error: ServerError) =
        error.message :: collectList(error, description) mkString " "
    
    def errorsAsHTMLElem(errors: TraversableOnce[ServerError]) =
        <ul>{
            for (error ← errors)
                yield <li>{XMLUtils.escapeXMLMinimal(ServerError.getDetailsAsUserMessage(error))}</li>
        }</ul>
    
    def errorsAsXHTMLElem(errors: TraversableOnce[ServerError]) =
        <ul xmlns="http://www.w3.org/1999/xhtml">{
            for (error ← errors)
                yield <li>{XMLUtils.escapeXMLMinimal(ServerError.getDetailsAsUserMessage(error))}</li>
        }</ul>
}

object XFormsError {

    // What kind of errors get here:
    //
    // - setvalue errors (binding exceptions except in actions)
    //    - @calculate, @xxf:default
    //    - write xf:upload file metadata
    //    - store external value from control
    //    - xf:setvalue
    //    - use of XFormsAPI's setvalue
    //    - instance mirror (XBL, xxf:dynamic)
    //    - xf:switch/@caseref and xf:repeat/@indexref
    //    - xf:submission[@replace = 'text']
    // - XPath errors
    //    - during model rebuild
    //    - evaluating MIPs
    //    - evaluating variables
    //    - evaluating bindings except for actions
    //        - control and LHHA bindings
    //        - itemsets
    //        - xf:submission/@ref
    //        - submission headers
    //        - xf:upload/xf:output metadata
    //    - evaluating value attributes, as with xf:label/@value
    //    - evaluating control AVTs
    //    - evaluating control @format, @unformat, @value
    // - action errors
    //    - XPath errors
    //    - any other action error

    def handleNonFatalSetvalueError(target: XFormsEventTarget, locationData: LocationData, reason: Reason): Unit = {
        val containingDocument = target.container.getContainingDocument
        containingDocument.indentedLogger.logDebug("", reason.message)
        containingDocument.addServerError(ServerError(reason.message, Option(locationData)))
    }

    def handleNonFatalXPathError(container: XBLContainer, t: Throwable): Unit =
        handleNonFatalXFormsError(container, "exception while evaluating XPath expression", t)

    def handleNonFatalActionError(target: XFormsEventTarget, t: Throwable): Unit =
        handleNonFatalXFormsError(target.container, "exception while running action", t)

    private def handleNonFatalXFormsError(container: XBLContainer, message: String, t: Throwable): Unit = {

        // NOTE: We want to catch a status code exception which happen during an XPathException. And in that case, the XPathException
        // is dynamic, so we cannot simply exclude dynamic XPath exceptions. So we have to be inclusive and consider which types of
        // errors are fatal. See https://github.com/orbeon/orbeon-forms/issues/2194
        def causesContainFatalError =
            Exceptions.causesIterator(t) exists {
                case e: XPathException if e.isStaticError ⇒ true
                case e: HttpStatusCode                    ⇒ true
                case _                                    ⇒ false
            }

        if (container.getPartAnalysis.isTopLevel           &&   // LATER: Other sub-parts could be fatal, depending on settings on xxf:dynamic. 
            container.getContainingDocument.isInitializing &&
            causesContainFatalError) {
            throw new OXFException(t)
        } else {
            val containingDocument = container.getContainingDocument
            containingDocument.indentedLogger.logDebug("", message, t)
            containingDocument.addServerError(ServerError(t))
        }
    }

    // Output the Ajax error panel with a placeholder for errors
    def outputAjaxErrorPanel(
        containingDocument : XFormsContainingDocument,
{'fraction_non_alphanumeric': 0.09485868911605533, 'fraction_numerical': 0.003006614552014432, 'mean_word_length': 2.7020033388981637, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 23, 'https://': 1, 'lorem ipsum': 0, 'www.': 4, 'xml': 6}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2977067', 'n_tokens_mistral': 3875, 'n_tokens_neox': 3751, 'n_words': 986}