def resolveSchemaLocation()

in daffodil-lib/src/main/scala/org/apache/daffodil/lib/xml/XMLUtils.scala [1451:1557]


  def resolveSchemaLocation(
    schemaLocation: String,
    optContextURI: Option[DaffodilSchemaSource]
  ): Option[(URISchemaSource, Boolean)] = {
    val uri =
      try {
        new URI(schemaLocation)
      } catch {
        case e: URISyntaxException =>
          throw new IllegalArgumentException(
            "schemaLocation is not a valid URI: " + schemaLocation,
            e
          )
      }

    val uriIsJustPathComponent =
      uri.getScheme == null &&
        uri.getAuthority == null &&
        uri.getQuery == null &&
        uri.getFragment == null &&
        uri.getPath != null

    val optResolved: Option[(URISchemaSource, Boolean)] =
      if (uri.isAbsolute) {
        // an absolute URI is one with a scheme. In this case, we expect to be able to resolve
        // the URI and do not try anything else (e.g. filesystem, classpath). Since this function
        // is for schemaLocation attributes, we may eventually want to disallow this, and only
        // allow relative URIs (i.e. URIs without a scheme). We do have some places that use
        // absolute URIs in includes/imports and cannot remove this yet.
        try {
          uri.toURL.openStream.close
          val uss = URISchemaSource(Misc.uriToDiagnosticFile(uri), uri)
          Some(uss, false)
        } catch {
          case e: IOException => None
        }
      } else if (!uriIsJustPathComponent) {
        // this is not an absolute URI so we don't have a scheme. This should just be a path, so
        // throw an IllegalArgumentException if that's not the case
        val msg =
          s"Non-absolute schemaLocation URI can only contain a path component: $schemaLocation"
        throw new IllegalArgumentException(msg)
      } else if (uri.getPath.startsWith("/")) {
        // The None.orElse{ ... }.orElse { ... } pattern below is useful to evaluate each
        // alternative way to resolve a schema location, stopping only when a Some is returned.
        // This makes for easily adding/removing/reordering resolution approaches by changing
        // orElse blocks
        val optResolvedAbsolute = None
          .orElse {
            // Search for the schemaLocation on the classpath. The schemaLocation path is
            // absolute so we can use it as-is so getResource does not care what package this
            // class is in.
            val resource = this.getClass.getResource(uri.getPath)
            if (resource != null) {
              val uss = URISchemaSource(new File(uri.getPath), resource.toURI)
              Some((uss, false))
            } else
              None
          }
          .orElse {
            // Search for the schemaLocation path on the file system. This path is absolute so it
            // must exist. If it does not exist, this orElse block results in a None
            val file = Paths.get(uri.getPath).toFile
            if (file.exists) {
              val uss = URISchemaSource(file, file.toURI)
              Some((uss, false))
            } else {
              None
            }
          }
        optResolvedAbsolute
      } else {
        // the schema location is a relative path, we must have a context resolve it
        Assert.usage(optContextURI.isDefined)
        val contextURI = optContextURI.get
        val optResolvedRelative = None
          .orElse {
            // This is a relative path, so look up the schemaLocation path relative to the context
            val relativeURI =
              Misc.getResourceRelativeOnlyOption(uri.getPath, contextURI.uriForLoading).map {
                u =>
                  val contextURIDiagnosticFile = contextURI.diagnosticFile
                  val relativeDiagnosticFile =
                    contextURIDiagnosticFile.toPath
                      .resolveSibling(Paths.get(uri.getPath))
                      .normalize()
                      .toFile
                  (URISchemaSource(relativeDiagnosticFile, u), false)
              }
            relativeURI
          }
          .orElse {
            val uriPath = "/" + uri.getPath
            // The user might have meant an absolute schemaLocation but left off the leading
            // slash accidentally. Past versions of Daffodil allowed this, so we allow it too,
            // but return a boolean if a relative path was found absolutely so callers can warn
            // if needed. Future versions of Daffodil may want to remove this orElse block so we
            // are strict about how absolute vs relative schemaLocations are resolved.
            val pair = Option(this.getClass.getResource(uriPath))
              .map { r => URISchemaSource(new File(uriPath), r.toURI) }
              .map { (_, true) }
            pair
          }
        optResolvedRelative
      }
    optResolved
  }