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
}