Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
CHANGES.md
### 代码内容：
Change Log
==========

### 5.2.1 - tba

#### Fixes
*  Fixed XXE vulnerabilities when parsing XML files. [#12](https://github.com/3dcitydb/web-feature-service/pull/12/files)

### 5.2.0 - 2022-05-23

This release is based on the Importer/Exporter version 5.2.0 libraries, and thus incorporates all bug fixes and updates
introduced in that version ([more information](https://github.com/3dcitydb/importer-exporter/releases/tag/v5.2.0)).

##### Additions
* Added support for 3DCityDB v4.3
* Added `replaceResourceIds` configuration option to replace all identifiers of features and geometries with UUIDs.

##### Fixes
* The database port was not correctly set when using the `CITYDB_PORT` environment variable.

##### Miscellaneous
* Updated PostgreSQL driver to 42.3.4
* Updated Oracle driver to 21.3.0.0
* Updated GeoTools to 26.4.

### 5.1.0 - 2022-04-12

This release is based on the Importer/Exporter version 5.1.1 libraries, and thus incorporates all bug fixes and updates
introduced in that version ([more information](https://github.com/3dcitydb/importer-exporter/releases/tag/v5.1.1)).

##### Changes
* Changed the handling of date-time values and timezones. Before this change, `xsd:date` values in exports could be
  different from the values stored in the database in case different timezone settings were used for the database
  server and the import and export operations.
* Simplified database queries used by the `GetFeature` and `GetPropertyValue` operations for top-level features
  involving many nested features/tables to avoid extremely large result sets causing performance issues.

##### Fixes
* Fixed bug in parsing of XPath expressions of XML queries that caused a slash `/` being part of a literal value to
  be interpreted as step operator.
* Empty strings for gml:id attributes are no longer imported but the `GMLID` column is set to `NULL` instead.

##### Miscellaneous
* Updated H2 database used for local caching to 2.1.210.
* Updated PostgreSQL driver to 42.3.1 and PostGIS driver to 2021.1.0.
* Updated citygml4j to 2.12.0.

### 5.0.0 - 2021-10-08

##### Changes
* Added Docker files to build your own images for the WFS service. Pre-built Docker images
  are available from Docker Hub at https://hub.docker.com/r/3dcitydb/wfs. [#9](https://github.com/3dcitydb/web-feature-service/pull/9)
* Support for the WFS 2.0 operations `GetFeature`, `GetPropertyValue`. `CreateStoredQuery`, `DropStoredQuery`
  with both XML and KVP encodings.
* Support for ad-hoc queries and stored queries.
* New filter capabilities including spatial, thematic and logical filters based on OGC Filter Encoding.
* Support for XPath expressions in filter expressions to allow queries on complex attributes and nested features.
* Support for exporting local and global appearances of the requested city objects. Texture images are provided
  through a separate RESTful service that is included in the WFS package and automatically started with the WFS
  service.
* Support for response paging allowing a client to scroll through large sets of features or property values
  based on the `count` and `startIndex` parameters.
* The city objects and property values in a response document can now be sorted by thematic attributes.
* Address attributes and 3DCityDB metadata can be used in query expressions based on corresponding CityGML ADEs
  like with the Importer/Exporter.
* Management of stored queries is now possible.
* Individual WFS operations can now be secured using IP- and token-based access control rules.
* Support for setting the time zone to be used for queries involving date and time attributes.
* JDBC drivers are now kept in `WEB-INF/lib` to better support running the WFS in a Docker environment. So, there
  is no need to copy JDBC drivers to a global or shared lib folder anymore.
* This release is based on the Importer/Exporter version 5.0.0, and thus incorporates all bug fixes and updates
  introduced in that version.
* Added support for providing database connection details via environment variables.
* CityJSON output now uses the latest CityJSON version 1.0.3.
* Many bugfixes and improvements.

##### Miscellaneous
* Updated citygml4j to 2.11.4.
* Updated PostgreSQL driver to 42.2.23 and Oracle driver to 21.1.0.

### 4.3.0 - 2021-04-28

##### Changes
* Improved export performance (up to 10-15 times faster in case the WFS is not running on the same machine or
  in the same local network as the database server).
* The default `config.xml` file and ADE extensions folder can now be changed using the environment variables
  `VC_WFS_CONFIG_FILE` and `VC_WFS_ADE_EXTENSIONS_PATH`.
* Enhanced LoD filter constraint with the option to only export the minimum or maximum LoD from the list of
  selected LoDs.
* Updated WFS web client UI and added support for XML highlighting to the input and output fields.
* Added `countDefault` constraint to limit the maximum number of returned features.
* Added CityJSON output format option `removeDuplicateChildGeometries` to avoid exporting duplicate geometries
  for nested child objects.
* This release is based on the Importer/Exporter version 4.3.0, and thus incorporates all bug fixes and updates
  introduced in that version.

##### Fixes
* Fixed error when exporting to CityJSON and no top-level feature is returned.

##### Miscellaneous
* Updated citygml4j to 2.11.3.
* Upgrade PostgreSQL driver to 42.2.14 and PostGIS to 2.5.0.

### 4.2.3 - 2020-07-16

##### Changes
* CityJSON output now uses the latest CityJSON version 1.0.1.
* This release is based on the Importer/Exporter version 4.2.3, and thus incorporates all bug fixes and updates introduced
in that version ([more information](https://github.com/3dcitydb/importer-exporter/releases/tag/v4.2.3)).

##### Miscellaneous
* Updated citygml4j to 2.10.5.
* Upgrade to latest PostgreSQL driver 42.2.10
* Upgrade Oracle driver to 19.3.

### 4.2 - 2019-02-13

##### Changes
* CityJSON output now uses the latest CityJSON version 0.9.

##### Fixes
* Using UTF-8 encoding for CityJSON output per default.
* This release is based on the Importer/Exporter version 4.2.0, and thus incorporates all bug fixes and updates introduced
in that version ([more information](https://github.com/3dcitydb/importer-exporter/releases/tag/v4.2.0)).

##### Miscellaneous
* Updated citygml4j to 2.9.1.

### 4.1 - 2019-01-09

##### Changes
* renamed the parameter `<useCityDBADE>` to `<exportCityDBMetadata>` in the `config.xml` file. This parameter controls
whether metadata such as the `LINEAGE` attribute of the `CITYOBJECT` table should be written to the response document.
If set to `true`, the 3DCityDB ADE will be used for storing the information with the city objects.

##### Fixes
* This release is based on the Importer/Exporter version 4.1.0, and thus incorporates all bug fixes and updates
introduced in that version ([more information](https://github.com/3dcitydb/importer-exporter/releases/tag/v4.1.0)).

##### Miscellaneous
* Upgrade to latest PostgreSQL driver v42.2.5 and PostGIS driver v2.3.0.
* Upgrade to latest Oracle driver 18.3.

### 4.0 - 2018-09-18

##### Additions
* Added support for CityGML ADEs through ADE extensions.
* Added KVP over HTTP GET as additional service binding to simplify the integration with GIS and ETL software such as FME.
* Added [CityJSON](http://www.cityjson.org/) as additional output format besides CityGML.
* New LoD filter for WFS responses.
* Added support for CORS.
* Database connections are not established at service startup but lazily when required.
* Major update to Importer/Exporter library 4.0.
* Switched from Ant to Gradle as build system.

##### Miscellaneous
* [3DCityDB Docker images](https://github.com/tum-gis/3dcitydb-docker-postgis) are now available for a range of 3DCityDB
and WFS versions to support continuous integration workflows.

## 内容2
### 文件路径：
build.gradle
### 代码内容：
import org.apache.tools.ant.filters.ReplaceTokens

plugins {
    id 'java'
    id 'war'
    id 'distribution'
}

group 'org.citydb.wfs'
version '5.2.0'

apply from: 'properties.gradle'

ext {
    genSrcDir = 'src-gen/main/java'
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
    }
    compileJava {
        options.release = 8
    }
    sourceSets {
        main.java.srcDir genSrcDir
    }
}

configurations {
    jdbc
    jaxb
}

repositories {
    maven {
        url 'https://repo.osgeo.org/repository/release'
    }
    maven {
        url 'https://citydb.jfrog.io/artifactory/maven'
    }
    maven {
        url 'https://oss.sonatype.org/content/repositories/snapshots/'
    }
    mavenCentral()
}

dependencies {
    implementation 'org.citydb:impexp-core:5.2.1-SNAPSHOT'
    implementation 'com.github.seancfoley:ipaddress:5.3.4'
    implementation 'org.glassfish.jersey.containers:jersey-container-servlet:2.35'
    implementation 'org.glassfish.jersey.inject:jersey-hk2:2.35'

    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'

    jaxb 'org.glassfish.jaxb:jaxb-xjc:2.3.2'
    jaxb 'org.jvnet.jaxb2_commons:jaxb2-basics:0.12.0'
    jaxb 'org.jvnet.jaxb2_commons:jaxb2-namespace-prefix:1.3'
    jaxb 'org.jvnet.jaxb2_commons:jaxb2-basics-annotate:1.1.0'
    jaxb 'org.slf4j:slf4j-simple:1.7.25'
}

task processLicense(type: Copy) {
    from("resources/license/LICENSE.txt") {
        filteringCharset = 'UTF-8'
        filter(ReplaceTokens, tokens: [
                name: project.citydbWfsName,
                vendorName: project.vendorName,
                vendorStreet: project.vendorStreet,
                vendorTown: project.vendorTown,
                vendorCountry: project.vendorCountry,
                vendorCopyright: project.vendorCopyright,
                vendorWebsiteUrl: project.vendorWebsiteUrl
        ])
    }
    into "$buildDir/tmp/license"
}

processResources {
    filteringCharset = 'ISO-8859-1'
    filesMatching('**/application.properties') {
        filter(ReplaceTokens, tokens: [
                name: project.citydbWfsName,
                version: project.version,
                vendorName: project.vendorName,
                vendorStreet: project.vendorStreet,
                vendorTown: project.vendorTown,
                vendorCountry: project.vendorCountry,
                vendorCopyright: project.vendorCopyright,
                vendorWebsiteUrl: project.vendorWebsiteUrl
        ])
    }
}

task generateJaxb(group: 'jaxb') {
    outputs.dir genSrcDir

    doLast {
        mkdir genSrcDir

        System.setProperty('javax.xml.accessExternalSchema', 'file')
        ant.taskdef(name: 'xjc', classname: 'com.sun.tools.xjc.XJC2Task', classpath: configurations.jaxb.asPath)
        ant.xjc(destdir: genSrcDir,
                schema: 'resources/jaxb/wfs-jaxb.xsd',
                binding: 'resources/jaxb/binding.xjb',
                extension: 'true',
                encoding: 'UTF-8') {
            produces(dir: genSrcDir, includes: '**/*.java')
            arg(line: '-Xnamespace-prefix -Xsetters -Xsetters-mode=direct -Xinheritance -Xannotate -XremoveAnnotation')
        }

        delete "$genSrcDir/net/opengis/citygml"
        delete "$genSrcDir/net/opengis/gml"
        delete "$genSrcDir/org"
        delete "$genSrcDir/oasis"
    }
}

task cleanJaxb(type: Delete, group: 'jaxb') {
    delete 'src-gen'
}

distributions.main {
    distributionBaseName = project.appName
    contents {
        from war
        from 'CHANGES.md'
        from 'README.md'
        into('license') {
            from "$rootDir/resources/license/APACHE-2.0.txt"
            from processLicense
        }
    }
}

war {
    archiveFileName = project.name + '.war'
    manifest {
        attributes('Implementation-Title': project.appName,
                'Implementation-Version': project.version,
                'Implementation-Vendor': project.vendorName + ', ' + project.vendorWebsiteUrl
        )
    }
    into('META-INF') {
        from processLicense
    }
}
## 内容3
### 文件路径：
src/main/java/vcs/citydb/wfs/WFSService.java
### 代码内容：
package vcs.citydb.wfs;

import net.opengis.wfs._2.*;
import org.citydb.config.Config;
import org.citydb.core.database.connection.DatabaseConnectionPool;
import org.citydb.core.registry.ObjectRegistry;
import org.citydb.core.util.Util;
import org.citydb.util.concurrent.SingleWorkerPool;
import org.citydb.util.log.Logger;
import org.citydb.util.xml.SecureXMLProcessors;
import org.citygml4j.builder.jaxb.CityGMLBuilder;
import org.citygml4j.xml.schema.SchemaHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import vcs.citydb.wfs.config.Constants;
import vcs.citydb.wfs.config.WFSConfig;
import vcs.citydb.wfs.config.operation.EncodingMethod;
import vcs.citydb.wfs.exception.AccessControlException;
import vcs.citydb.wfs.exception.WFSException;
import vcs.citydb.wfs.exception.WFSExceptionCode;
import vcs.citydb.wfs.exception.WFSExceptionReportHandler;
import vcs.citydb.wfs.kvp.*;
import vcs.citydb.wfs.operation.describefeaturetype.DescribeFeatureTypeHandler;
import vcs.citydb.wfs.operation.getcapabilities.GetCapabilitiesHandler;
import vcs.citydb.wfs.operation.getfeature.GetFeatureHandler;
import vcs.citydb.wfs.operation.getpropertyvalue.GetPropertyValueHandler;
import vcs.citydb.wfs.operation.storedquery.*;
import vcs.citydb.wfs.paging.PageRequest;
import vcs.citydb.wfs.paging.PagingCacheManager;
import vcs.citydb.wfs.paging.PagingHandler;
import vcs.citydb.wfs.security.AccessController;
import vcs.citydb.wfs.util.*;
import vcs.citydb.wfs.util.xml.NamespaceFilter;
import vcs.citydb.wfs.util.xml.ValidationEventHandlerImpl;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.UnmarshallerHandler;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

@WebServlet(Constants.WFS_SERVICE_PATH)
public class WFSService extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private final Logger log = Logger.getInstance();
	private final DatabaseConnectionPool connectionPool = DatabaseConnectionPool.getInstance();

	private CityGMLBuilder cityGMLBuilder;
	private RequestLimiter limiter;
	private AccessController accessController;
	private WFSConfig wfsConfig;
	private Config config;

	private SAXParserFactory saxParserFactory;
	private Schema wfsSchema;
	private SingleWorkerPool<CacheCleanerWork> cacheCleanerPool;
	private WFSExceptionReportHandler exceptionReportHandler;

	@Override
	public void init() throws ServletException {
		// check whether servlet initialization threw an error
		Object error = getServletContext().getAttribute(Constants.INIT_ERROR_ATTRNAME);
		if (error instanceof ServletException)
			throw (ServletException) error;

		log.info("WFS service is loaded by the servlet container.");

		// service specific initialization
		ObjectRegistry registry = ObjectRegistry.getInstance();
		config = registry.getConfig();
		cityGMLBuilder = registry.getCityGMLBuilder();
		limiter = registry.lookup(RequestLimiter.class);
		accessController = registry.lookup(AccessController.class);
		wfsConfig = registry.lookup(WFSConfig.class);

		exceptionReportHandler = new WFSExceptionReportHandler(cityGMLBuilder);

		try {
			saxParserFactory = SecureXMLProcessors.newSAXParserFactory();
			saxParserFactory.setNamespaceAware(true);
		} catch (Throwable e) {
			String message = "Failed to enable secure processing of XML queries.";
			log.error(message);
			log.error(e.getMessage());
			throw new ServletException(message, e);
		}

		try {
			StoredQueryManager storedQueryManager = new StoredQueryManager(cityGMLBuilder, saxParserFactory, getServletContext().getRealPath(Constants.STORED_QUERIES_PATH), wfsConfig);
			registry.register(storedQueryManager);
		} catch (Throwable e) {
			String message = "Failed to initialize stored query manager.";
			log.error(message);
			log.error(e.getMessage());
			throw new ServletException(message, e);
		}

		// read WFS 2.0 schema to validate requests
		if (wfsConfig.getOperations().getRequestEncoding().isUseXMLValidation()) {
			try {
				SchemaHandler schemaHandler = registry.lookup(SchemaHandler.class);
				schemaHandler.parseSchema(new File(getServletContext().getRealPath(Constants.SCHEMAS_PATH + "/ogc/wfs/2.0.2/wfs.xsd")));
				schemaHandler.parseSchema(new File(getServletContext().getRealPath(Constants.SCHEMAS_PATH + "/ogc/wfs/extensions/wfs-vcs.xsd")));
				SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
				wfsSchema = schemaFactory.newSchema(schemaHandler.getSchemaSources());
			} catch (SAXException e) {
				String message = "Failed to read WFS XML Schema from " + Constants.SCHEMAS_PATH + "/ogc/wfs.";
				log.error(message);
				log.error(e.getMessage());
				throw new ServletException(message, e);
			}
		}

		// register cache cleaner pool
		cacheCleanerPool = new SingleWorkerPool<>(
				"cache_cleaner",
				CacheCleanerWorker::new,
				wfsConfig.getServer().getMaxParallelRequests());

		cacheCleanerPool.prestartCoreWorker();
		registry.register(CacheCleanerWorker.class.getName(), cacheCleanerPool);

		// register paging cache manager
		if (wfsConfig.getConstraints().isUseResultPaging()) {
			PagingCacheManager pagingCacheManager = new PagingCacheManager(cacheCleanerPool, wfsConfig);
			registry.register(pagingCacheManager);
		}
	}

	@Override
	public void destroy() {
		log.info("WFS service is destroyed by the servlet container.");

		// destroy resources which may otherwise cause memory leaks
		try {
			cacheCleanerPool.shutdownAndWait();
		} catch (InterruptedException e) {
			String message = "Failed to shutdown cache cleaner pool.";
			log.error(message);
			log.error(e.getMessage());
		}
	}

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
		// set CORS http headers
		if (wfsConfig.getServer().isEnableCORS())
			addCORSHeaders(request, response, false);

		KVPRequestReader reader = null;
		boolean isPagingRequest = ServerUtil.containsParameter(request, KVPConstants.PAGE_ID);

		try {
			if (wfsConfig.getOperations().getRequestEncoding().getMethod() == EncodingMethod.XML && !isPagingRequest)
				throw new WFSException(WFSExceptionCode.OPTION_NOT_SUPPORTED, "KVP encoding of requests is not advertised.");

			if (isPagingRequest && !wfsConfig.getConstraints().isUseResultPaging())
				throw new WFSException(WFSExceptionCode.OPTION_NOT_SUPPORTED, "Result paging is not advertised.");

			// parse parameters
			Map<String, String> parameters = new HashMap<>();
			for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
				String name = entry.getKey().toUpperCase();
				String value = entry.getValue()[0];

				for (int i = 1; i < entry.getValue().length; i++) {
					if (!value.equals(entry.getValue()[i]))
						throw new WFSException(WFSExceptionCode.INVALID_PARAMETER_VALUE, "Found inconsistent values for parameter " + name + ": " + Util.collection2string(Arrays.asList(entry.getValue()), ", "), name);
				}

				String previous = parameters.get(name);
				if (previous != null && !value.equals(parameters.get(previous)))
					throw new WFSException(WFSExceptionCode.INVALID_PARAMETER_VALUE, "Found inconsistent values for parameter " + name + ": " + value + ", " + parameters.get(name), name);

				if (!value.isEmpty())
					parameters.put(name, value);
			}

			String operationName = parameters.get(KVPConstants.REQUEST);
			if (operationName != null) {
				switch (operationName) {
					case KVPConstants.GET_CAPABILITIES:
						reader = new GetCapabilitiesReader(parameters, wfsConfig);
						break;
					case KVPConstants.DESCRIBE_FEATURE_TYPE:
						reader = new DescribeFeatureTypeReader(parameters, wfsConfig);
						break;
					case KVPConstants.GET_PROPERTY_VALUE:
						reader = new GetPropertyValueReader(parameters, wfsSchema, cityGMLBuilder, wfsConfig);
						break;
					case KVPConstants.GET_FEATURE:
						reader = new GetFeatureReader(parameters, wfsSchema, cityGMLBuilder, wfsConfig);
						break;
					case KVPConstants.LIST_STORED_QUERIES:
						reader = new ListStoredQueriesReader(parameters, wfsConfig);
						break;
					case KVPConstants.DESCRIBE_STORED_QUERIES:
						reader = new DescribeStoredQueriesReader(parameters, wfsConfig);
						break;
					case KVPConstants.DROP_STORED_QUERY:
						reader = new DropStoredQueryReader(parameters, wfsConfig);
						break;
					case KVPConstants.CREATE_STORED_QUERY:
					default:
						throw new WFSException(WFSExceptionCode.INVALID_PARAMETER_VALUE, "The operation " + operationName + " is not supported by this WFS implementation.", KVPConstants.REQUEST);
				}
			} else if (isPagingRequest) {
				reader = new PagingReader(parameters, wfsConfig);
			} else
				throw new WFSException(WFSExceptionCode.MISSING_PARAMETER_VALUE, "The request lacks the mandatory " + KVPConstants.REQUEST + " parameter.", KVPConstants.REQUEST);

			// parse and process request
			Object wfsRequest = reader.readRequest();
			handleRequest(wfsRequest, reader.getOperationName(), reader.getNamespaces(), request, response);
		} catch (WFSException e) {
			String operationName = reader != null ? reader.getOperationName() : null;
			exceptionReportHandler.sendErrorResponse(e, operationName, request, response);
		}
	}

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
		// set CORS http headers
		if (wfsConfig.getServer().isEnableCORS())
			addCORSHeaders(request, response, false);

		String operationName = null;
		try {
			if (wfsConfig.getOperations().getRequestEncoding().getMethod() == EncodingMethod.KVP)
				throw new WFSException(WFSExceptionCode.OPTION_NOT_SUPPORTED, "XML encoding of requests is not advertised.");

			Unmarshaller unmarshaller = cityGMLBuilder.getJAXBContext().createUnmarshaller();
			ValidationEventHandlerImpl validationEventHandler = null;

			// support XML validation
			if (wfsConfig.getOperations().getRequestEncoding().isUseXMLValidation()) {
				unmarshaller.setSchema(wfsSchema);
				validationEventHandler = new ValidationEventHandlerImpl();
				unmarshaller.setEventHandler(validationEventHandler);
			}

			UnmarshallerHandler unmarshallerHandler = unmarshaller.getUnmarshallerHandler();

			// use SAX parser to keep track of namespace declarations
			SAXParser parser = saxParserFactory.newSAXParser();
			XMLReader reader = parser.getXMLReader();
			NamespaceFilter namespaceFilter = new NamespaceFilter(reader);
			namespaceFilter.setContentHandler(unmarshallerHandler);

			try {
				namespaceFilter.parse(new InputSource(request.getInputStream()));
			} catch (SAXException e) {
				if (validationEventHandler != null && !validationEventHandler.isValid())
					throw new WFSException(WFSExceptionCode.OPERATION_PARSING_FAILED, validationEventHandler.getCause());
				else throw e;
			}

			// unmarshal WFS request
			Object object = unmarshallerHandler.getResult();
			if (!(object instanceof JAXBElement<?>))
				throw new WFSException(WFSExceptionCode.INVALID_PARAMETER_VALUE, "Failed to parse XML document received through HTTP POST.", KVPConstants.REQUEST);

			JAXBElement<?> element = (JAXBElement<?>) object;
			operationName = element.getName().getLocalPart();

			// process request
			handleRequest(element.getValue(), operationName, namespaceFilter, request, response);
		} catch (JAXBException | SAXException | ParserConfigurationException e) {
			exceptionReportHandler.sendErrorResponse(new WFSException(WFSExceptionCode.OPERATION_PARSING_FAILED, "Failed to parse the XML message.", e), operationName, request, response);
		} catch (WFSException e) {
			exceptionReportHandler.sendErrorResponse(e, operationName, request, response);
		}
	}

	private void handleRequest(Object wfsRequest, String operationName, NamespaceFilter namespaceFilter, HttpServletRequest request, HttpServletResponse response) throws WFSException {
		// check access permission
		try {
			accessController.requireAccess(operationName, request);
		} catch (AccessControlException e) {
			log.error("Access denied: " + e.getMessage());
			if (e.getCause() != null)
				log.logStackTrace(e.getCause());

			throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "Access denied for client '" + request.getRemoteHost() + "'.");
		}

		// check database connection
		if (!connectionPool.isConnected())
			DatabaseConnector.connect(config);

		try {
			if (wfsRequest instanceof GetFeatureType) {
				// make sure we only serve a maximum number of requests in parallel
				limiter.requireServiceSlot(request, operationName);
				GetFeatureHandler getFeatureHandler = new GetFeatureHandler(cityGMLBuilder, wfsConfig, config);
				getFeatureHandler.doOperation((GetFeatureType) wfsRequest, namespaceFilter, request, response);
			}

			else if (wfsRequest instanceof GetPropertyValueType) {
				// make sure we only serve a maximum number of requests in parallel
				limiter.requireServiceSlot(request, operationName);
				GetPropertyValueHandler getPropertyValueHandler = new GetPropertyValueHandler(cityGMLBuilder, wfsConfig, config);
				getPropertyValueHandler.doOperation((GetPropertyValueType) wfsRequest, namespaceFilter, request, response);
			}

			else if (wfsRequest instanceof DescribeFeatureTypeType) {
				DescribeFeatureTypeHandler describeFeatureTypeHandler = new DescribeFeatureTypeHandler(wfsConfig);
				describeFeatureTypeHandler.doOperation((DescribeFeatureTypeType)wfsRequest, getServletContext(), request, response);
			}

			else if (wfsRequest instanceof GetCapabilitiesType) {
				GetCapabilitiesHandler getCapabilitiesHandler = new GetCapabilitiesHandler(cityGMLBuilder, wfsConfig);
				getCapabilitiesHandler.doOperation((GetCapabilitiesType)wfsRequest, request, response);
			}

			else if (wfsRequest instanceof ListStoredQueriesType) {
				ListStoredQueriesHandler listStoredQueriesHandler = new ListStoredQueriesHandler(cityGMLBuilder, wfsConfig);
				listStoredQueriesHandler.doOperation((ListStoredQueriesType)wfsRequest, request, response);
			}

			else if (wfsRequest instanceof DescribeStoredQueriesType) {
				DescribeStoredQueriesHandler describeStoredQueriesHandler = new DescribeStoredQueriesHandler(wfsConfig);
				describeStoredQueriesHandler.doOperation((DescribeStoredQueriesType)wfsRequest, request, response);
			}

			else if (wfsRequest instanceof CreateStoredQueryType) {
				CreateStoredQueryHandler createStoredQueriesHandler = new CreateStoredQueryHandler(cityGMLBuilder, wfsConfig);
				createStoredQueriesHandler.doOperation((CreateStoredQueryType)wfsRequest, namespaceFilter, request, response);
			}

			else if (wfsRequest instanceof DropStoredQueryType) {
				DropStoredQueryHandler dropStoredQueriesHandler = new DropStoredQueryHandler(cityGMLBuilder, wfsConfig);
				dropStoredQueriesHandler.doOperation((DropStoredQueryType)wfsRequest, request, response);
			}

			else if (wfsRequest instanceof PageRequest) {
				// make sure we only serve a maximum number of requests in parallel
				limiter.requireServiceSlot(request, operationName);
				PagingHandler pagingHandler = new PagingHandler(cityGMLBuilder, wfsConfig, config);
				pagingHandler.doOperation((PageRequest) wfsRequest, request, response);
			}

			else
				throw new WFSException(WFSExceptionCode.OPERATION_NOT_SUPPORTED, "The operation " + operationName + " is not supported by this WFS implementation.", operationName);

		} catch (JAXBException e) {
			throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "A fatal JAXB error occurred whilst processing the request.", operationName, e);
		} finally {
			// release slot from limiter
			limiter.releaseServiceSlot(request);
		}
	}

	@Override
	protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws IOException {
		// support CORS preflight requests
		if (wfsConfig.getServer().isEnableCORS())
			addCORSHeaders(request, response, true);
	}

	private void addCORSHeaders(HttpServletRequest request, HttpServletResponse response, boolean isOptions) {
		// do nothing if this is not a CORS request
		if (request.getHeader("Origin") == null)
			return;

		response.setHeader("Access-Control-Allow-Origin", "*");

		// add preflight headers
		if (isOptions && request.getHeader("Access-Control-Request-Method") != null) {
			response.setHeader("Access-Control-Allow-Methods", "GET, POST");
			response.setHeader("Access-Control-Max-Age", "86400");

			String requestCORSHeaders = request.getHeader("Access-Control-Request-Headers");
			if (requestCORSHeaders != null)
				response.setHeader("Access-Control-Allow-Headers", requestCORSHeaders);
		}
	}
}

## 内容4
### 文件路径：
src/main/java/vcs/citydb/wfs/config/WFSConfigSchemaWriter.java
### 代码内容：
package vcs.citydb.wfs.config;

import org.citydb.config.util.ConfigConstants;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.SchemaOutputResolver;
import javax.xml.transform.Result;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.IOException;

public class WFSConfigSchemaWriter {

	public static void main(String[] args) throws Exception {
		System.out.print("Generating XML schema in " + Constants.CONFIG_SCHEMA_FILE + "... ");

		JAXBContext ctx = JAXBContext.newInstance(WFSConfig.class);
		ctx.generateSchema(new SchemaOutputResolver() {

			@Override
			public Result createOutput(String namespaceUri, String suggestedFileName) throws IOException {
				File file;

				if (namespaceUri.equals(ConfigConstants.CITYDB_CONFIG_NAMESPACE_URI))
					file = new File(Constants.CONFIG_SCHEMA_FILE);
				else
					file = new File(Constants.CONFIG_SCHEMA_PATH + "/ows/" + suggestedFileName);

				file.getAbsoluteFile().getParentFile().mkdirs();

				StreamResult res = new StreamResult();
				res.setSystemId(file.toURI().toString());
				return res;
			}

		});

		System.out.println("finished.");
	}

}

## 内容5
### 文件路径：
src/main/java/vcs/citydb/wfs/operation/getfeature/citygml/CityGMLWriterBuilder.java
### 代码内容：
package vcs.citydb.wfs.operation.getfeature.citygml;

import net.opengis.wfs._2.GetFeatureType;
import net.opengis.wfs._2.ResultTypeType;
import org.citydb.ade.model.module.CityDBADE100Module;
import org.citydb.ade.model.module.CityDBADE200Module;
import org.citydb.config.Config;
import org.citydb.core.database.schema.mapping.FeatureType;
import org.citydb.core.operation.common.cache.IdCacheManager;
import org.citydb.core.operation.exporter.util.InternalConfig;
import org.citydb.core.operation.exporter.writer.FeatureWriteException;
import org.citydb.util.log.Logger;
import org.citydb.util.xml.SecureXMLProcessors;
import org.citygml4j.model.module.Module;
import org.citygml4j.model.module.ModuleContext;
import org.citygml4j.model.module.Modules;
import org.citygml4j.model.module.ade.ADEModule;
import org.citygml4j.model.module.citygml.CityGMLModule;
import org.citygml4j.model.module.citygml.CityGMLModuleType;
import org.citygml4j.model.module.citygml.CityGMLVersion;
import org.citygml4j.util.internal.xml.TransformerChainFactory;
import org.citygml4j.util.xml.SAXWriter;
import vcs.citydb.wfs.config.Constants;
import vcs.citydb.wfs.config.WFSConfig;
import vcs.citydb.wfs.operation.getfeature.FeatureWriter;
import vcs.citydb.wfs.operation.getfeature.GetFeatureResponseBuilder;
import vcs.citydb.wfs.operation.getfeature.QueryExpression;
import vcs.citydb.wfs.util.GeometryStripper;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.stream.StreamSource;
import java.io.File;
import java.io.Writer;
import java.util.List;
import java.util.Map;

public class CityGMLWriterBuilder implements GetFeatureResponseBuilder {
	private final Logger log = Logger.getInstance();
	private final String PRETTY_PRINT = "prettyPrint";

	private CityGMLVersion version;
	private GeometryStripper geometryStripper;
	private IdCacheManager idCacheManager;
	private InternalConfig internalConfig;
	private Config config;
	private Object eventChannel;

	private SAXWriter saxWriter;
	private TransformerChainFactory transformerChainFactory;

	@Override
	public String getMimeType() {
		return "text/xml";
	}

	@Override
	public boolean supportsHitsResponse() {
		return true;
	}

	@Override
	public void initializeContext(
			GetFeatureType wfsRequest,
			List<QueryExpression> queryExpressions,
			Map<String, String> formatOptions,
			GeometryStripper geometryStripper,
			IdCacheManager idCacheManager,
			Object eventChannel,
			InternalConfig internalConfig,
			WFSConfig wfsConfig,
			Config config) throws FeatureWriteException {
		this.geometryStripper = geometryStripper;
		this.idCacheManager = idCacheManager;
		this.eventChannel = eventChannel;
		this.internalConfig = internalConfig;
		this.config = config;

		version = queryExpressions.get(0).getTargetVersion();

		saxWriter = new SAXWriter();
		saxWriter.setWriteEncoding(true);

		if ("true".equals(formatOptions.get(PRETTY_PRINT)))
			saxWriter.setIndentString(" ");

		// set WFS prefix and schema location in case we do not have to return the bare feature
		if (queryExpressions.size() > 1 || !queryExpressions.get(0).isGetFeatureById()) {
			saxWriter.setPrefix(Constants.WFS_NAMESPACE_PREFIX, Constants.WFS_NAMESPACE_URI);
			saxWriter.setSchemaLocation(Constants.WFS_NAMESPACE_URI, Constants.WFS_SCHEMA_LOCATION);
		}

		// set CityGML prefixes and schema locations if we have to return feature instances
		if (wfsRequest.getResultType() == ResultTypeType.RESULTS) {
			// add default prefixes and schema locations
			ModuleContext moduleContext = new ModuleContext(version);

			Module core = moduleContext.getModule(CityGMLModuleType.CORE);
			Module generics = moduleContext.getModule(CityGMLModuleType.GENERICS);
			saxWriter.setPrefix(core.getNamespacePrefix(), core.getNamespaceURI());
			saxWriter.setPrefix(generics.getNamespacePrefix(), generics.getNamespaceURI());
			saxWriter.setSchemaLocation(generics.getNamespaceURI(), generics.getSchemaLocation());
			if (wfsConfig.getConstraints().isExportAppearance()) {
				Module appearance = moduleContext.getModule(CityGMLModuleType.APPEARANCE);
				saxWriter.setPrefix(appearance.getNamespacePrefix(), appearance.getNamespaceURI());
				saxWriter.setSchemaLocation(appearance.getNamespaceURI(), appearance.getSchemaLocation());
			}

			// add XML prefixes and schema locations for non-CityGML modules
			for (Module module : moduleContext.getModules()) {
				if (!(module instanceof CityGMLModule)) {
					// skip 3DCityDB ADE prefix and namespace if metadata shall not be exported
					if ((module == CityDBADE200Module.v3_0 || module == CityDBADE100Module.v3_0)
							&& !wfsConfig.getConstraints().isExportCityDBMetadata())
						continue;

					saxWriter.setPrefix(module.getNamespacePrefix(), module.getNamespaceURI());
					if (module instanceof ADEModule)
						saxWriter.setSchemaLocation(module.getNamespaceURI(), module.getSchemaLocation());
				}
			}

			// set XML prefixes and schema locations for selected feature types
			for (QueryExpression queryExpression : queryExpressions) {
				for (FeatureType featureType : queryExpression.getFeatureTypeFilter().getFeatureTypes()) {
					if (featureType.isAvailableForCityGML(version)) {
						CityGMLModule module = Modules.getCityGMLModule(featureType.getSchema().getNamespace(version).getURI());
						if (module != null) {
							saxWriter.setPrefix(module.getNamespacePrefix(), module.getNamespaceURI());
							saxWriter.setSchemaLocation(module.getNamespaceURI(), module.getSchemaLocation());
						}
					}
				}
			}

			// build XSLT transformer chain
			if (wfsConfig.getPostProcessing().getXSLTransformation().isEnabled()
					&& wfsConfig.getPostProcessing().getXSLTransformation().isSetStylesheets()) {
				try {
					List<String> stylesheets = wfsConfig.getPostProcessing().getXSLTransformation().getStylesheets();
					SAXTransformerFactory factory = (SAXTransformerFactory) SecureXMLProcessors.newTransformerFactory();
					Templates[] templates = new Templates[stylesheets.size()];

					for (int i = 0; i < stylesheets.size(); i++) {
						Templates template = factory.newTemplates(new StreamSource(new File(stylesheets.get(i))));
						templates[i] = template;
					}

					transformerChainFactory = new TransformerChainFactory(templates);
				} catch (TransformerConfigurationException e) {
					log.error("Failed to compile XSLT stylesheets.");
					log.error("Cause: " + e.getMessage());
					throw new FeatureWriteException("Failed to configure the XSL transformation.");
				}
			}
		}
	}

	@Override
	public FeatureWriter buildFeatureWriter(Writer writer) throws FeatureWriteException {
		try {
			saxWriter.setOutput(writer);
			return new CityGMLWriter(saxWriter, version, transformerChainFactory, geometryStripper, idCacheManager, eventChannel, internalConfig, config);
		} catch (DatatypeConfigurationException e) {
			throw new FeatureWriteException("Failed to create CityGML response writer.", e);
		}
	}

}

## 内容6
### 文件路径：
src/main/java/vcs/citydb/wfs/operation/getpropertyvalue/GetPropertyValueResponseBuilder.java
### 代码内容：
package vcs.citydb.wfs.operation.getpropertyvalue;

import net.opengis.wfs._2.GetPropertyValueType;
import net.opengis.wfs._2.ResultTypeType;
import org.citydb.ade.model.module.CityDBADE100Module;
import org.citydb.ade.model.module.CityDBADE200Module;
import org.citydb.config.Config;
import org.citydb.core.database.schema.mapping.FeatureType;
import org.citydb.core.operation.exporter.writer.FeatureWriteException;
import org.citydb.util.log.Logger;
import org.citydb.util.xml.SecureXMLProcessors;
import org.citygml4j.model.module.Module;
import org.citygml4j.model.module.ModuleContext;
import org.citygml4j.model.module.Modules;
import org.citygml4j.model.module.ade.ADEModule;
import org.citygml4j.model.module.citygml.CityGMLModule;
import org.citygml4j.model.module.citygml.CityGMLModuleType;
import org.citygml4j.model.module.citygml.CityGMLVersion;
import org.citygml4j.util.internal.xml.TransformerChainFactory;
import org.citygml4j.util.xml.SAXWriter;
import vcs.citydb.wfs.config.Constants;
import vcs.citydb.wfs.config.WFSConfig;
import vcs.citydb.wfs.util.GeometryStripper;
import vcs.citydb.wfs.util.xml.NamespaceFilter;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.stream.StreamSource;
import java.io.File;
import java.io.Writer;
import java.util.List;
import java.util.Map;

public class GetPropertyValueResponseBuilder {
	private final Logger log = Logger.getInstance();
	private final String PRETTY_PRINT = "prettyPrint";

	private String valueReference;
	private NamespaceFilter namespaceFilter;
	private CityGMLVersion version;
	private GeometryStripper geometryStripper;
	private Config config;
	private Object eventChannel;

	private SAXWriter saxWriter;
	private TransformerChainFactory transformerChainFactory;
	private boolean useSequentialWriting;

	public String getMimeType() {
		return "text/xml";
	}

	public void initializeContext(GetPropertyValueType wfsRequest,
			QueryExpression queryExpression,
			Map<String, String> formatOptions,
			NamespaceFilter namespaceFilter,
			Object eventChannel,
			WFSConfig wfsConfig,
			Config config) throws FeatureWriteException {
		this.namespaceFilter = namespaceFilter;
		this.eventChannel = eventChannel;
		this.config = config;

		valueReference = queryExpression.getValueReference();
		version = queryExpression.getTargetVersion();
		geometryStripper = wfsConfig.getConstraints().isStripGeometry() ? new GeometryStripper() : null;

		// require sequential writing if a sorting clause is defined for the query
		useSequentialWriting = queryExpression.isSetSorting();

		saxWriter = new SAXWriter();
		saxWriter.setWriteEncoding(true);

		if ("true".equals(formatOptions.get(PRETTY_PRINT)))
			saxWriter.setIndentString(" ");

		// set WFS prefix and schema location
		saxWriter.setPrefix(Constants.WFS_NAMESPACE_PREFIX, Constants.WFS_NAMESPACE_URI);
		saxWriter.setSchemaLocation(Constants.WFS_NAMESPACE_URI, Constants.WFS_SCHEMA_LOCATION);

		// set CityGML prefixes and schema locations
		if (wfsRequest.getResultType() == ResultTypeType.RESULTS) {
			// add default prefixes and schema locations
			ModuleContext moduleContext = new ModuleContext(version);

			Module core = moduleContext.getModule(CityGMLModuleType.CORE);
			Module generics = moduleContext.getModule(CityGMLModuleType.GENERICS);
			saxWriter.setPrefix(core.getNamespacePrefix(), core.getNamespaceURI());
			saxWriter.setPrefix(generics.getNamespacePrefix(), generics.getNamespaceURI());
			saxWriter.setSchemaLocation(generics.getNamespaceURI(), generics.getSchemaLocation());
			if (wfsConfig.getConstraints().isExportAppearance()) {
				Module appearance = moduleContext.getModule(CityGMLModuleType.APPEARANCE);
				saxWriter.setPrefix(appearance.getNamespacePrefix(), appearance.getNamespaceURI());
				saxWriter.setSchemaLocation(appearance.getNamespaceURI(), appearance.getSchemaLocation());
			}

			// add XML prefixes and schema locations for non-CityGML modules
			for (Module module : moduleContext.getModules()) {
				if (!(module instanceof CityGMLModule)) {
					// skip 3DCityDB ADE prefix and namespace if metadata shall not be exported
					if ((module == CityDBADE200Module.v3_0 || module == CityDBADE100Module.v3_0)
							&& !wfsConfig.getConstraints().isExportCityDBMetadata())
						continue;

					saxWriter.setPrefix(module.getNamespacePrefix(), module.getNamespaceURI());
					if (module instanceof ADEModule)
						saxWriter.setSchemaLocation(module.getNamespaceURI(), module.getSchemaLocation());
				}
			}

			// set XML prefixes and schema locations for selected feature types
			for (FeatureType featureType : queryExpression.getFeatureTypeFilter().getFeatureTypes()) {
				CityGMLModule module = Modules.getCityGMLModule(featureType.getSchema().getNamespace(version).getURI());
				if (module != null) {
					saxWriter.setPrefix(module.getNamespacePrefix(), module.getNamespaceURI());
					saxWriter.setSchemaLocation(module.getNamespaceURI(), module.getSchemaLocation());
				}
			}

			// build XSLT transformer chain
			if (wfsConfig.getPostProcessing().getXSLTransformation().isEnabled()
					&& wfsConfig.getPostProcessing().getXSLTransformation().isSetStylesheets()) {
				try {
					List<String> stylesheets = wfsConfig.getPostProcessing().getXSLTransformation().getStylesheets();
					SAXTransformerFactory factory = (SAXTransformerFactory) SecureXMLProcessors.newTransformerFactory();
					Templates[] templates = new Templates[stylesheets.size()];

					for (int i = 0; i < stylesheets.size(); i++) {
						Templates template = factory.newTemplates(new StreamSource(new File(stylesheets.get(i))));
						templates[i] = template;
					}

					transformerChainFactory = new TransformerChainFactory(templates);
				} catch (TransformerConfigurationException e) {
					log.error("Failed to compile XSLT stylesheets.");
					log.error("Cause: " + e.getMessage());
					throw new FeatureWriteException("Failed to configure the XSL transformation.");
				}
			}
		}
	}

	public PropertyValueWriter buildPropertyValueWriter(Writer writer) throws FeatureWriteException {
		try {
			saxWriter.setOutput(writer);
			return new PropertyValueWriter(valueReference,
					saxWriter,
					version,
					transformerChainFactory,
					namespaceFilter,
					geometryStripper,
					useSequentialWriting,
					eventChannel,
					config);
		} catch (DatatypeConfigurationException e) {
			throw new FeatureWriteException("Failed to create CityGML response writer.", e);
		}
	}

}

## 内容7
### 文件路径：
src/main/java/vcs/citydb/wfs/operation/storedquery/StoredQueryManager.java
### 代码内容：
package vcs.citydb.wfs.operation.storedquery;

import net.opengis.fes._2.AbstractQueryExpressionType;
import net.opengis.fes._2.FilterType;
import net.opengis.fes._2.ResourceIdType;
import net.opengis.wfs._2.*;
import org.citydb.util.xml.SecureXMLProcessors;
import org.citygml4j.builder.jaxb.CityGMLBuilder;
import org.citygml4j.model.module.citygml.CityGMLModule;
import org.citygml4j.model.module.citygml.CityGMLModuleType;
import org.citygml4j.model.module.citygml.CityGMLVersion;
import org.w3c.dom.Element;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import vcs.citydb.wfs.config.Constants;
import vcs.citydb.wfs.config.WFSConfig;
import vcs.citydb.wfs.exception.WFSException;
import vcs.citydb.wfs.exception.WFSExceptionCode;
import vcs.citydb.wfs.kvp.KVPConstants;
import vcs.citydb.wfs.util.xml.NamespaceFilter;

import javax.xml.XMLConstants;
import javax.xml.bind.*;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

public class StoredQueryManager {
	private final String GET_FEATURE_BY_ID_NAME = "http://www.opengis.net/def/query/OGC-WFS/0/GetFeatureById";
	private final String DEPRECATED_GET_FEATURE_BY_ID_NAME = "urn:ogc:def:query:OGC-WFS::GetFeatureById";

	private final StoredQuery DEFAULT_QUERY;
	private final CityGMLBuilder cityGMLBuilder;
	private final SAXParserFactory saxParserFactory;
	private final TransformerFactory transformerFactory;
	private final DocumentBuilderFactory documentBuilderFactory;
	private final XMLOutputFactory xmlOutputFactory;
	private final Path storedQueriesPath;
	private final ObjectFactory wfsFactory;
	private final WFSConfig wfsConfig;
	private final MessageDigest md5;

	public StoredQueryManager(CityGMLBuilder cityGMLBuilder, SAXParserFactory saxParserFactory, String path, WFSConfig wfsConfig) throws ParserConfigurationException, SAXException, NoSuchAlgorithmException, IOException, TransformerConfigurationException {
		this.cityGMLBuilder = cityGMLBuilder;
		this.saxParserFactory = saxParserFactory;
		this.wfsConfig = wfsConfig;

		md5 = MessageDigest.getInstance("MD5");
		wfsFactory = new ObjectFactory();
		transformerFactory = SecureXMLProcessors.newTransformerFactory();
		xmlOutputFactory = XMLOutputFactory.newInstance();
		documentBuilderFactory = SecureXMLProcessors.newDocumentBuilderFactory();
		documentBuilderFactory.setNamespaceAware(true);

		storedQueriesPath = Paths.get(path);
		Files.createDirectories(storedQueriesPath);
		if (!Files.isDirectory(storedQueriesPath) || !Files.isReadable(storedQueriesPath))
			throw new IOException("Path for stored queries is not readable.");

		DEFAULT_QUERY = createDefaultStoredQuery();
	}

	public List<StoredQueryAdapter> listStoredQueries(String handle) throws WFSException {
		List<StoredQueryAdapter> storedQueries = new ArrayList<>();
		storedQueries.add(new StoredQueryAdapter(DEFAULT_QUERY.getId()));

		try {
			try (DirectoryStream<Path> stream = Files.newDirectoryStream(storedQueriesPath)) {
				for (Path file : stream) {
					if (Files.isRegularFile(file))
						storedQueries.add(new StoredQueryAdapter(file));
				}
			}
		} catch (IOException e) {
			throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "Failed to list stored queries.", handle, e);
		}

		return storedQueries;
	}

	public StoredQuery getStoredQuery(StoredQueryAdapter adapter, String handle) throws WFSException {
		// urn:ogc:def:query:OGC-WFS::GetFeatureById is deprecated since 2.0.2 but still supported
		if (GET_FEATURE_BY_ID_NAME.equals(adapter.getId()) || DEPRECATED_GET_FEATURE_BY_ID_NAME.equals(adapter.getId()))
			return DEFAULT_QUERY;

		Path file = adapter.isSetId() ? storedQueriesPath.resolve(getFileName(adapter.getId())) : adapter.getFile();
		if (!Files.exists(file))
			throw new WFSException(WFSExceptionCode.INVALID_PARAMETER_VALUE, "A stored query with identifier '" + adapter.getId() + "' is not offered by this server.", KVPConstants.STOREDQUERY_ID);

		return unmarshalStoredQuery(file, handle);
	}

	public Element parseStoredQuery(StoredQueryAdapter adapter, String handle) throws WFSException {
		// urn:ogc:def:query:OGC-WFS::GetFeatureById is deprecated since 2.0.2 but still supported
		if (GET_FEATURE_BY_ID_NAME.equals(adapter.getId()) || DEPRECATED_GET_FEATURE_BY_ID_NAME.equals(adapter.getId()))
			return DEFAULT_QUERY.toDOMElement(handle);

		Path file = adapter.isSetId() ? storedQueriesPath.resolve(getFileName(adapter.getId())) : adapter.getFile();
		if (!Files.exists(file))
			throw new WFSException(WFSExceptionCode.INVALID_PARAMETER_VALUE, "A stored query with identifier '" + adapter.getId() + "' is not offered by this server.", KVPConstants.STOREDQUERY_ID);

		return parseStoredQuery(file, handle);
	}

	public StoredQuery createStoredQuery(StoredQueryDescriptionType description, NamespaceFilter namespaceFilter, String handle) throws WFSException {
		if (!description.isSetId())
			throw new WFSException(WFSExceptionCode.MISSING_PARAMETER_VALUE, "The stored query description lacks the mandatory identifier.", KVPConstants.STOREDQUERY_ID);

		// urn:ogc:def:query:OGC-WFS::GetFeatureById is deprecated since 2.0.2 but still supported
		if (GET_FEATURE_BY_ID_NAME.equals(description.getId()) || DEPRECATED_GET_FEATURE_BY_ID_NAME.equals(description.getId()))
			throw new WFSException(WFSExceptionCode.DUPLICATE_STORED_QUERY_ID_VALUE, "The identifier '" + description.getId() + "' is associated to the mandatory GetFeatureById query. Choose another identifier.", description.getId());

		// serialize stored query to file
		Path file = storedQueriesPath.resolve(getFileName(description.getId()));
		if (Files.exists(file))
			throw new WFSException(WFSExceptionCode.DUPLICATE_STORED_QUERY_ID_VALUE, "The identifier '" + description.getId() + "' has already been associated with to a stored query. Drop the stored query first or choose another identifier.", description.getId());

		StoredQuery storedQuery = new StoredQuery(description, namespaceFilter, this);
		storedQuery.validate(handle);

		try {
			DescribeStoredQueriesResponseType response = new DescribeStoredQueriesResponseType();
			response.getStoredQueryDescription().add(description);
			JAXBElement<DescribeStoredQueriesResponseType> jaxbElement = wfsFactory.createDescribeStoredQueriesResponse(response);

			Marshaller marshaller = cityGMLBuilder.getJAXBContext().createMarshaller();
			marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
			marshaller.marshal(jaxbElement, file.toFile());
		} catch (JAXBException e) {
			throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "Failed to persist stored query.", handle, e);
		}

		return storedQuery;
	}

	public void dropStoredQuery(String id, String handle) throws WFSException {
		if (GET_FEATURE_BY_ID_NAME.equals(id) || DEPRECATED_GET_FEATURE_BY_ID_NAME.equals(id))
			throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "The mandatory stored query '" + id + "' must not be dropped.", handle);

		Path file = storedQueriesPath.resolve(getFileName(id));
		if (!Files.exists(file))
			throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "A stored query with identifier '" + id + "' is not offered by this server.", handle);

		try {
			Files.delete(file);
		} catch (IOException e) {
			throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "Failed to drop the stored query with identifier '" + id + "'.", handle);
		}
	}

	public void compileQuery(AbstractQueryExpressionType abstractQuery, List<QueryType> queries, NamespaceFilter namespaceFilter, String handle) throws WFSException {
		if (abstractQuery instanceof QueryType) {
			if (!wfsConfig.getConstraints().isSupportAdHocQueries())
				throw new WFSException(WFSExceptionCode.OPTION_NOT_SUPPORTED, "Ad hoc queries are not advertised.", handle);

			queries.add((QueryType)abstractQuery);
		}

		else if (abstractQuery instanceof StoredQueryType) {
			StoredQueryType query = (StoredQueryType)abstractQuery;

			StoredQuery storedQuery = getStoredQuery(new StoredQueryAdapter(query.getId()), handle);
			if (storedQuery != null) {
				if (storedQuery.getId().equals(DEFAULT_QUERY.getId())) {
					QueryType queryType = (QueryType) storedQuery.compile(query, namespaceFilter).iterator().next();

					if (queryType.isSetAbstractSelectionClause() && queryType.getAbstractSelectionClause().getValue() instanceof FilterType) {
						FilterType filter = (FilterType) queryType.getAbstractSelectionClause().getValue();
						if (filter.isSet_Id() && filter.get_Id().get(0).getValue() instanceof ResourceIdType) {
							ResourceIdType resourceId = (ResourceIdType) filter.get_Id().get(0).getValue();
							queryType.setFeatureIdentifier(resourceId.getRid());
						}
					}

					if (!queryType.isSetFeatureIdentifier())
						throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "Lacking identifier for the '" + DEFAULT_QUERY.getId() + "' stored query.", handle);

					queries.add(queryType);
				} else {
					for (AbstractQueryExpressionType compiled : storedQuery.compile(query, namespaceFilter))
						compileQuery(compiled, queries, namespaceFilter, handle);
				}
			} else
				throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "No stored query with identifier '" + query.getId() + "' is offered by this server.", handle);
		}

		else
			throw new WFSException(WFSExceptionCode.OPTION_NOT_SUPPORTED, "Only ad hoc and stored query expressions are supported.", handle);
	}

	public boolean containsStoredQuery(String id, String handle) {
		// urn:ogc:def:query:OGC-WFS::GetFeatureById is deprecated since 2.0.2 but still supported
		return GET_FEATURE_BY_ID_NAME.equals(id)
				|| DEPRECATED_GET_FEATURE_BY_ID_NAME.equals(id)
				|| Files.exists(storedQueriesPath.resolve(getFileName(id)));
	}

	private StoredQuery unmarshalStoredQuery(Path file, String handle) throws WFSException {
		if (!Files.isReadable(file))
			throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "Failed to read the stored query file.", handle);

		Object object;
		NamespaceFilter namespaceFilter;

		try {
			Unmarshaller unmarshaller = cityGMLBuilder.getJAXBContext().createUnmarshaller();
			UnmarshallerHandler unmarshallerHandler = unmarshaller.getUnmarshallerHandler();

			// use SAX parser to keep track of namespace declarations
			SAXParser parser = saxParserFactory.newSAXParser();
			XMLReader reader = parser.getXMLReader();
			namespaceFilter = new NamespaceFilter(reader);
			namespaceFilter.setContentHandler(unmarshallerHandler);

			namespaceFilter.parse(new InputSource(new BufferedReader(new FileReader(file.toFile()))));
			object = unmarshallerHandler.getResult();

		} catch (JAXBException | SAXException | IOException | ParserConfigurationException e) {
			throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "Fatal JAXB error whilst processing the stored query.", handle, e);
		}

		if (!(object instanceof JAXBElement<?>))
			throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "Failed to parse the stored query file.", handle);

		JAXBElement<?> jaxbElement = (JAXBElement<?>)object;
		if (!(jaxbElement.getValue() instanceof DescribeStoredQueriesResponseType))
			throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "Invalid content of the stored query file.", handle);

		DescribeStoredQueriesResponseType response = (DescribeStoredQueriesResponseType)jaxbElement.getValue();
		if (response.getStoredQueryDescription().size() == 0)
			throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "Failed to parse the stored query file. No stored query description provided.", handle);

		StoredQueryDescriptionType description = response.getStoredQueryDescription().get(0);
		if (!file.getFileName().toString().equals(getFileName(description.getId())))
			throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "The stored query identifier '" + description.getId() + "' does not match the storage identifier.", handle);

		return new StoredQuery(description, namespaceFilter, this);
	}

	private Element parseStoredQuery(Path file, String handle) throws WFSException {
		if (!Files.isReadable(file))
			throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "Failed to read the stored query file.", handle);

		try {
			Document document = documentBuilderFactory.newDocumentBuilder().parse(file.toFile());
			return processStoredQueryElement(document.getDocumentElement(), handle);
		} catch (SAXException | IOException | ParserConfigurationException e) {
			throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "Fatal error whilst processing the stored query.", handle, e);
		}
	}

	protected Element processStoredQueryElement(Element root, String handle) throws WFSException {
		try {
			NodeList nodeList = root.getElementsByTagNameNS(Constants.WFS_NAMESPACE_URI, "StoredQueryDescription");
			if (nodeList.getLength() == 0 || nodeList.getLength() > 1)
				throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "Failed to parse the stored query file. No stored query description provided.", handle);

			Element description = (Element)nodeList.item(0);

			// copy namespace attributes from root element
			NamedNodeMap attributes = root.getAttributes();
			for (int i = 0; i < attributes.getLength(); i++) {
				Attr attribute = (Attr)attributes.item(i);
				if (attribute.getNamespaceURI().equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) {
					if (attribute.getValue().equals("http://www.w3.org/2001/SMIL20/")
							|| attribute.getValue().equals("http://www.w3.org/2001/SMIL20/Language"))
						continue;

					description.setAttributeNS(attribute.getNamespaceURI(), attribute.getName(), attribute.getValue());
				}
			}

			// remove empty text nodes
			XPathFactory xpathFactory = XPathFactory.newInstance();
			XPathExpression xpathExp = xpathFactory.newXPath().compile("//text()[normalize-space(.) = '']");
			NodeList emptyNodeList = (NodeList)xpathExp.evaluate(root, XPathConstants.NODESET);
			for (int i = 0; i < emptyNodeList.getLength(); i++) {
				Node emptyNode = emptyNodeList.item(i);
				emptyNode.getParentNode().removeChild(emptyNode);
			}

			return description;
		} catch (XPathExpressionException e) {
			throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "Fatal error whilst processing the stored query.", handle, e);
		}
	}

	private String getFileName(String id) {
		byte[] md5Hash = md5.digest(id.getBytes());

		StringBuilder hexString = new StringBuilder();
		for (byte item : md5Hash)
			hexString.append(Integer.toString((item & 0xff) + 0x100, 16).substring(1));

		return hexString.append(".xml").toString();
	}

	protected CityGMLBuilder getCityGMLBuilder() {
		return cityGMLBuilder;
	}

	protected TransformerFactory getTransformerFactory() {
		return transformerFactory;
	}

	protected DocumentBuilderFactory getDocumentBuilderFactory() {
		return documentBuilderFactory;
	}

	protected XMLOutputFactory getXMLOutputFactory() {
		return xmlOutputFactory;
	}

	protected ObjectFactory getObjectFactory() {
		return wfsFactory;
	}

	protected WFSConfig getWFSConfig() {
		return wfsConfig;
	}

	private StoredQuery createDefaultStoredQuery() throws ParserConfigurationException, SAXException {
		// GetFeatureById query according to the WFS 2.0 spec
		StoredQueryDescriptionType description = new StoredQueryDescriptionType();

		description.setId(!Constants.DEFAULT_WFS_VERSION.equals("2.0.0") ?
				GET_FEATURE_BY_ID_NAME : DEPRECATED_GET_FEATURE_BY_ID_NAME);

		Title queryTitle = new Title();
		queryTitle.setLang("en");
		queryTitle.setValue("Get feature by identifier");
		description.getTitle().add(queryTitle);
		Abstract queryAbstract = new Abstract();
		queryAbstract.setLang("en");
		queryAbstract.setValue("Retrieves a feature by its gml:id.");
		description.getAbstract().add(queryAbstract);

		ParameterExpressionType parameter = new ParameterExpressionType();
		parameter.setName("id");
		parameter.setType(XSDataType.XS_STRING.getName());
		Title parameterTitle = new Title();
		parameterTitle.setLang("en");
		parameterTitle.setValue("Identifier");
		parameter.getTitle().add(parameterTitle);
		Abstract parameterAbstract = new Abstract();
		parameterAbstract.setLang("en");
		parameterAbstract.setValue("The gml:id of the feature to be retrieved.");
		parameter.getAbstract().add(parameterAbstract);
		description.getParameter().add(parameter);

		Document document = documentBuilderFactory.newDocumentBuilder().newDocument();
		Element query = document.createElementNS(Constants.WFS_NAMESPACE_URI, Constants.WFS_NAMESPACE_PREFIX + ":Query");
		query.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:" + Constants.WFS_NAMESPACE_PREFIX, Constants.WFS_NAMESPACE_URI);

		NamespaceFilter namespaceFilter = new NamespaceFilter();
		CityGMLVersion version = wfsConfig.getFeatureTypes().getDefaultVersion();
		boolean multipleVersions = wfsConfig.getFeatureTypes().getVersions().size() > 1;
		CityGMLModule module = version.getCityGMLModule(CityGMLModuleType.CORE);
		String prefix = module.getNamespacePrefix();
		if (multipleVersions)
			prefix += (version == CityGMLVersion.v2_0_0) ? "2" : "1";

		namespaceFilter.startPrefixMapping(prefix, module.getNamespaceURI());
		namespaceFilter.startPrefixMapping("xs", XMLConstants.W3C_XML_SCHEMA_NS_URI);
		query.setAttribute("typeNames", "schema-element(" + prefix + ':' + "_CityObject)");
		query.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:" + prefix, module.getNamespaceURI());

		Element filter = document.createElementNS(Constants.FES_NAMESPACE_URI, Constants.FES_NAMESPACE_PREFIX + ":Filter");
		filter.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:" + Constants.FES_NAMESPACE_PREFIX, Constants.FES_NAMESPACE_URI);
		Element resourceId = document.createElementNS(Constants.FES_NAMESPACE_URI, Constants.FES_NAMESPACE_PREFIX + ":ResourceId");
		resourceId.setAttribute("rid", "${id}");
		filter.appendChild(resourceId);
		query.appendChild(filter);

		QueryExpressionTextType queryExpression = new QueryExpressionTextType();
		queryExpression.getContent().add(query);
		queryExpression.setIsPrivate(false);
		queryExpression.setLanguage("en");
		queryExpression.setReturnFeatureTypes(new ArrayList<>());
		queryExpression.setLanguage(StoredQuery.DEFAULT_LANGUAGE);
		description.getQueryExpressionText().add(queryExpression);

		return new StoredQuery(description, namespaceFilter, this);
	}

}


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
1.x/src/rogatkin/web/WarRoller.java
### 代码内容：
package rogatkin.web;

import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;

import javax.servlet.ServletException;

import Acme.Utils;
import Acme.Serve.Serve;
import Acme.Serve.WarDeployer;

public class WarRoller implements WarDeployer {

    public static final String DEPLOY_ARCH_EXT = ".war";

    public static final String DEPLOYMENT_DIR_TARGET = ".web-apps-target";

    public static final String DEF_DEPLOY_DYNAMICALLY = "tjws.wardeploy.dynamically";

    public static final String DEF_DEPLOY_NOINCREMENTAL = "tjws.wardeploy.noincremental";

    public static final String DEF_VIRTUAL = "tjws.virtual";

    public static final String DEPLOY_FAILED_EXT = ".failed";

    /**
     * in deploy mode scans for all wars in war directory (app deployment dir)
     * for each war looks in corresponding place of deploy directory and figures
     * a difference, like any file in war exists and no corresponding file in
     * deploy directory or it's older if difference positive, then delete target
     * deploy directory unpack war if run mode process all WEB-INF/web.xml and
     * build app descriptor, including context name, servlet names, servlet
     * urls, class parameters process every app descriptor as standard servlet
     * connection proc dispatch for every context name assigned an app
     * dispatcher, it uses the rest to find servlet and do resource mapping
     *
     */

    public void deploy(File warDir, final File deployTarDir, final String virtualHost) {
	// by list
	if (warDir.listFiles(new FileFilter() {
	    public boolean accept(File pathname) {
		if (pathname.isFile() && pathname.getName().toLowerCase().endsWith(DEPLOY_ARCH_EXT)) {
		    deployWar(pathname, deployTarDir);
		    return true;
		}
		return false;
	    }
	}).length == 0)
	    server.log("No .war packaged web apps found in " + (virtualHost == null ? "default" : virtualHost));
	if (deployTarDir.listFiles(new FileFilter() {
	    public boolean accept(File file) {
		if (file.isDirectory())
		    try {
			attachApp(WebAppServlet.create(file, file.getName(), server, virtualHost), virtualHost);
			markSucceeded(file.getParentFile(), file.getName()); // assumes that parent always exists
			return true;
		    } catch (ServletException se) {
			server.log(
				"Deployment of aplication " + file.getName() + " failed, reason: " + se.getRootCause(),
				se.getRootCause());
		    } catch (Throwable t) {
			if (t instanceof ThreadDeath)
			    throw (ThreadDeath) t;
			server.log("Unexpected problem in deployment of application  " + file.getName(), t);
		    }
		return false;
	    }
	}).length == 0)
	    server.log("No web apps have been deployed in " + (virtualHost == null ? "default" : virtualHost));
    }

    public boolean deployWar(File warFile, File deployTarDir) {
	String context = warFile.getName();
	assert context.toLowerCase().endsWith(DEPLOY_ARCH_EXT);
	context = context.substring(0, context.length() - DEPLOY_ARCH_EXT.length());
	File failedMark = new File(deployTarDir, context + DEPLOY_FAILED_EXT);
	if (failedMark.exists() && failedMark.lastModified() > warFile.lastModified())
	    return false; // skipping deploy failed

	server.log("Deploying " + context);
	ZipFile zipFile = null;
	File deployDir = new File(deployTarDir, context);
	boolean noincremental = System.getProperty(DEF_DEPLOY_NOINCREMENTAL) != null;
	if (assureDir(deployDir) == false) {
	    server.log("Can't reach deployment dir " + deployDir);
	    return false;
	}
	Exception lastException = null;
	deploy: do {
	    try {
		// some overhead didn't check that doesn't exist
		zipFile = new ZipFile(warFile);
		Enumeration<? extends ZipEntry> entries = zipFile.entries();
		while (entries.hasMoreElements()) {
		    ZipEntry ze = entries.nextElement();
		    String en = ze.getName();
		    if (File.separatorChar == '/')
			en = en.replace('\\', File.separatorChar);
		    File outFile = new File(deployDir, en);
		    if (ze.isDirectory()) {
			outFile.mkdirs();
		    } else {
			OutputStream os = null;
			InputStream is = null;
			File parentFile = outFile.getParentFile();
			if (parentFile.exists() == false)
			    parentFile.mkdirs();
			if (outFile.exists() && outFile.lastModified() >= ze.getTime()) {
			    continue;
			}
			if (noincremental) {
			    deleteFiles(deployDir, deployDir.list());
			    noincremental = false;
			    continue deploy;
			}
			try {
			    os = new FileOutputStream(outFile);
			    is = zipFile.getInputStream(ze);
			    copyStream(is, os);
			} catch (IOException ioe2) {
			    server.log("Problem in extracting " + en + " " + ioe2);
			    // TODO decide to propagate the exception up and stop deployment?
			    lastException = ioe2;
			} finally {
			    try {
				os.close();
			    } catch (Exception e2) {

			    }
			    try {
				is.close();
			    } catch (Exception e2) {

			    }
			}
			outFile.setLastModified(ze.getTime());
		    }
		}
	    } catch (ZipException ze) {
		server.log("Invalid .war format");
		lastException = ze;
	    } catch (IOException ioe) {
		server.log("Can't read " + warFile + "/ " + ioe);
		lastException = ioe;
	    } finally {
		try {
		    zipFile.close();
		} catch (Exception e) {

		}
		zipFile = null;
	    }
	} while (false);
	if (lastException == null) {
		deployDir.setLastModified(warFile.lastModified());
		return true;
	}
	deployDir.setLastModified(0);
	return false;
    }

    protected void attachApp(WebAppServlet appServlet, String virtualHost) {
	server.addServlet(appServlet.contextPath + "/*", appServlet, virtualHost);
    }

    /** Returns auto deployment directory
     * <p>
     * The method can be overriden to give more control of choosing the directory
     * @return autodeployment directory location as local file system string
     */
    protected String getDeployDirectory() {
    	String webapp_dir = System.getProperty(WebApp.DEF_WEBAPP_AUTODEPLOY_DIR);
    	if (webapp_dir == null)
    	    webapp_dir = System.getProperty("user.dir") + File.separator + "webapps";
    	return webapp_dir;
    }

    public void deploy(Serve server) {
	this.server = server;
	final File file_webapp = new File(getDeployDirectory());
	if (assureDir(file_webapp) == false) {
	    server.log("Deployment source location " + file_webapp + " isn't a directory, deployment is impossible.");
	    return;
	}
	final File file_deployDir = new File(file_webapp, DEPLOYMENT_DIR_TARGET);
	if (assureDir(file_deployDir) == false) {
	    server.log("Target deployment location " + file_deployDir + " isn't a directory, deployment is impossible.");
	    return;
	}
	deploy(file_webapp, file_deployDir, null);

	int td = 0;
	if (System.getProperty(DEF_DEPLOY_DYNAMICALLY) != null) {
	    td = 20;
	    try {
		td = Integer.parseInt(System.getProperty(DEF_DEPLOY_DYNAMICALLY));
	    } catch (NumberFormatException nfe) {
		server.log("Default redeployment check interval: " + td + " is used");
	    }
	}
	final int interval = td * 1000;
	createWatcherThread(file_webapp, file_deployDir, interval, null);
	if (null != System.getProperty(DEF_VIRTUAL)) {
	    file_webapp.listFiles(new FileFilter() {
		@Override
		public boolean accept(File pathname) {
		    String virtualHost;
		    if (pathname.isDirectory()
			    && (virtualHost = pathname.getName()).equals(DEPLOYMENT_DIR_TARGET) == false) {

			final File file_deployDir = new File(pathname, DEPLOYMENT_DIR_TARGET);
			if (assureDir(file_deployDir) == false) {
			    WarRoller.this.server.log("Target deployment location " + file_deployDir
				    + " isn't a directory, deployment is impossible.");
			} else {
			    deploy(pathname, file_deployDir, virtualHost);
			    createWatcherThread(pathname, file_deployDir, interval, virtualHost);
			    return true;
			}
		    }
		    return false;
		}
	    });
	}
    }

    protected void createWatcherThread(final File file_webapp, final File file_deployDir, final int interval,
	    final String virtualHost) {
	if (interval <= 0)
	    return;
	Thread watcher = new Thread("Deploy update watcher for " + (virtualHost == null ? "main" : virtualHost)) {
	    public void run() {
		for (;;)
		    try {
			deployWatch(file_webapp, file_deployDir, virtualHost);
		    } catch (Throwable t) {
			if (t instanceof ThreadDeath)
			    throw (ThreadDeath) t;
			WarRoller.this.server.log("Unhandled " + t, t);
		    } finally {
			try {
			    Thread.sleep(interval);
			} catch (InterruptedException e) {
			    break;
			}
		    }
	    }
	};
	watcher.setDaemon(true);
	watcher.start();
    }

    protected boolean assureDir(File fileDir) {
	if (fileDir.exists() == false)
	    fileDir.mkdirs();
	return fileDir.isDirectory();
    }

    protected synchronized void deployWatch(File warDir, final File deployTarDir, String virtualHost) {
	server.setHost(virtualHost);
	final HashSet<String> apps = new HashSet<String>();
	warDir.listFiles(new FileFilter() {
	    public boolean accept(File file) {
		if (file.isDirectory() == false) {
		    String name = file.getName();
		    if (name.endsWith(DEPLOY_ARCH_EXT))
			apps.add(name.substring(0, name.length() - DEPLOY_ARCH_EXT.length()));
		}
		return false;
	    }
	});
	Enumeration se = server.getServlets();
	ArrayList<WebAppServlet> markedServlets = new ArrayList<WebAppServlet>(10);
	while (se.hasMoreElements()) {
	    Object servlet = se.nextElement();
	    if (servlet instanceof WebAppServlet) {
		WebAppServlet was = (WebAppServlet) servlet;
		String name = was.deployDir.getName();
		File war = new File(warDir, name + DEPLOY_ARCH_EXT);
		apps.remove(name);
		if (war.exists() && war.lastModified() > was.deployDir.lastModified()) {
		    // deployWar(new File(warDir, was.deployDir.getName() +
		    // DEPLOY_ARCH_EXT), deployTarDir);
		    markedServlets.add(was);
		}
	    }
	}
	for (WebAppServlet was : markedServlets) {
	    redeploy(warDir, deployTarDir, was, virtualHost);
	}
	for (String name : apps) {
	    // remaining not deployed yet apps
	    try {
		if (deployWar(new File(warDir, name + DEPLOY_ARCH_EXT), deployTarDir)) {
		    WebAppServlet was = WebAppServlet.create(new File(deployTarDir, name), name, server, virtualHost);
		    attachApp(was, virtualHost);
		}
	    } catch (Throwable t) {
		if (t instanceof ThreadDeath)
		    throw (ThreadDeath) t;
		markFailed(deployTarDir, name);
		server.log("Unexpected problem in deployment of aplication  " + name, t);
	    }
	}
    }

    public void redeploy(File warDir, File deployTarDir, WebAppServlet was, String virtualHost) {
	was = (WebAppServlet) server.unloadServlet(was);
	if (was == null)
	    return;
	server.unloadSessions(was.getServletContext());
	was.destroy();

	// TODO use pre-saved war name
	if (deployWar(new File(warDir, was.deployDir.getName() + DEPLOY_ARCH_EXT), deployTarDir))
	try {
	    was = WebAppServlet.create(was.deployDir, was.deployDir.getName(), server, virtualHost);
	    attachApp(was, virtualHost);
	    server.restoreSessions(was.getServletContext());
	    markSucceeded(deployTarDir, was.deployDir.getName());
	} catch (ServletException sex) {
	    markFailed(deployTarDir, was.deployDir.getName());
	    server.log("Deployment of a web app " + was.contextName + " failed due " + sex.getRootCause(),
		    sex.getRootCause());
	} catch (Throwable t) {
	    if (t instanceof ThreadDeath)
		throw (ThreadDeath) t;
	    markFailed(deployTarDir, was.deployDir.getName());
	    server.log("Unexpected problem in deployment of aplication  " + was.contextName, t);
	}
    }

    static private boolean markFailed(File deployTarDir, String appName) {
    	File markFile = new File(deployTarDir, appName + DEPLOY_FAILED_EXT);

	if (markFile.exists()) {
		File appDeployDir = new File(deployTarDir, appName);
		if (appDeployDir.exists())
		markFile.setLastModified(appDeployDir.lastModified()+1);
	    return true;
	}
	try {
	    return markFile.createNewFile();
	} catch (IOException e) {
	    return false;
	}
    }

    static private boolean markSucceeded(File deployTarDir, String appName) {
	if (new File(deployTarDir, appName + DEPLOY_FAILED_EXT).exists())
	    return new File(deployTarDir, appName+DEPLOY_FAILED_EXT).delete();
	return true;
    }

    static void copyStream(InputStream is, OutputStream os) throws IOException {
	Utils.copyStream(is, os, -1);
    }

    static void deleteFiles(File folder, String[] files) throws IOException {
	for (String fn : files) {
	    File f = new File(folder, fn);
	    if (f.isDirectory()) {
		deleteFiles(f, f.list());
		if (f.delete() == false)
		    throw new IOException("Can't delete :" + f);
	    } else {
		if (f.delete() == false)
		    throw new IOException("Can't delete :" + f);
	    }
	}
    }

    protected Serve server;
}

Output:
【漏洞类型】路径遍历漏洞
【严重性】High
【漏洞摘要】这是一个路径遍历漏洞。代码在处理WAR文件中的文件名时，未验证文件名是否包含目录遍历的模式（如`../`或`/..`），这可能导致攻击者通过构造恶意的WAR文件，访问或修改服务器上的任意文件。
【关键代码模式】`File outFile = new File(deployDir, en);`，`en.contains("../") || en.contains("/..")`
【修复方法说明】修复方法通过在处理文件名时检查是否存在目录遍历的模式，抛出异常以防止恶意路径的使用。适用于的最佳实践是严格验证并过滤用户输入，以避免目录遍历攻击。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
src/main/java/org/lemsml/jlems/io/util/JUtil.java
### 代码内容：
package org.lemsml.jlems.io.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import org.lemsml.jlems.ResourceRoot;
import org.lemsml.jlems.core.logging.E;
import org.lemsml.jlems.core.sim.ContentError;

public final class JUtil {

   static Class<?> rootClass = ResourceRoot.class;

   static String fileSep;
   static String rootPath;

   static String osarch;

   static {
      fileSep = "/"; // System.getProperty("file.separator");
      rootPath = "org" + fileSep + "psics";
   }

   private JUtil() {

   }

   public static void setResourceRoot(Class<?> cls)
   {
	   rootClass=cls;
   }
   public static String getRelativeResource(Object obj, String path) throws ContentError {
      return getRelativeResource(obj.getClass(), path);
   }

   public static String getRelativeResource(String s) throws ContentError {
      return getRelativeResource(rootClass, s);
   }

   public static String getRelativeResource(Class<?> cls, String path) throws ContentError {
      String sret = null;

      try {
        if (path.contains("..")) {
            path = (new File(path)).getCanonicalPath();
        }
        InputStream fis = cls.getResourceAsStream(path);
        sret = readInputStream(fis);

      } catch (Exception ex) {
         throw new ContentError("ResourceAccess - can't get resource " + path + " relative to " + cls + ": " + ex);
      }
      return sret;
   }

   public static void copyResource(Object obj, String fnm, File fdest) throws IOException, ContentError {
	   String s = getRelativeResource(obj, fnm);
	   FileUtil.writeStringToFile(s, new File(fdest, fnm));
   }

   public static String getXMLResource(String path) throws ContentError {
      String sp = null;
      if (path.endsWith(".xml") || path.indexOf(".") < 0) {
         E.warning("getXMLReousrce should have a dot path, not " + path);
         sp = path;
      } else {
     //    E.info("replacing dots in " + path + " with " + fileSep);

         sp = path.replaceAll("\\.", fileSep) + ".xml";
      }
       return getResource(sp);
   }

   public static String getFileResource(String path, String fnm) throws ContentError {
      String sp = path.replaceAll("\\.", fileSep) + fileSep + fnm;
      return getResource(sp);
   }

   private static String getResource(String pathin) throws ContentError {
	   String path = pathin;
      String sret = null;

      try {
         if (path.startsWith(rootPath)) {
            path = path.substring(rootPath.length()+1, path.length());

         //   E.info("seeking stream rel to root class " + path + " " + rootClass.getName());
            InputStream fis = rootClass.getResourceAsStream(path);
            sret = readInputStream(fis);

         } else {
            E.warning("reading foreign resource from class path?");
            InputStream fis = ClassLoader.getSystemResourceAsStream(path);
            sret = readInputStream(fis);
         }

      } catch (Exception ex) {
        throw new ContentError("ResourceAccess - cant get " + path + " " + ex);

      }
      return sret;
   }

   private static String readInputStream(InputStream fis)
         throws NullPointerException, IOException {
      String sret = null;

      InputStreamReader insr = new InputStreamReader(fis);
      BufferedReader fr = new BufferedReader(insr);

      StringBuffer sb = new StringBuffer();
      while (fr.ready()) {
         sb.append(fr.readLine());
         sb.append("\n");
      }
      fr.close();
      sret = sb.toString();

      return sret;
   }

   public static void copyBinaryResource(String respathin, File dest) throws FileNotFoundException, IOException {
      String respath = respathin;
	   if (dest.exists()) {
      //   E.info("destination file already exists - not copying " + dest);
         return;
      }

     // E.info("installing " + dest);

         if (respath.startsWith(rootPath)) {
            respath = respath.substring(rootPath.length()+1,respath.length());
         }
         extractRelativeResource(rootClass, respath, dest);
   }

   public static void extractRelativeResource(Class<?> c, String path, File dest) throws
        FileNotFoundException, IOException {
	   InputStream in = c.getResourceAsStream(path);

       OutputStream out = new FileOutputStream(dest);
       byte[] buf = new byte[1024];
       int len = in.read(buf);
       while (true) {
          if (len > 0) {
        	  out.write(buf, 0, len);
        	  len = in.read(buf);
          } else {
        	  break;
          }
       }
       in.close();
       out.close();
   }

   public static String shortClassName(Object ov) {
      String cnm = ov.getClass().getName();
      cnm = cnm.substring(cnm.lastIndexOf(".") + 1, cnm.length());
      return cnm;
   }

   public static void unpackJar(File fjar, File fout) throws IOException {

      JarFile jf = new JarFile(fjar);
      Enumeration<JarEntry> en = jf.entries();

      while (en.hasMoreElements()) {
         JarEntry je = en.nextElement();
         java.io.File f = new File(fout,  je.getName());
         if (je.isDirectory()) {
            f.mkdirs();
            continue;

         } else {
            // f.getParentFile().mkdirs();

            if (f.getPath().indexOf("META-INF") >= 0) {
               // skip it
            } else {
            f.getParentFile().mkdirs();
            java.io.InputStream is = jf.getInputStream(je);
            java.io.FileOutputStream fos = new FileOutputStream(f);

            // EFF - buffering, file channels??
            while (is.available() > 0) {
               fos.write(is.read());
            }
            fos.close();
            is.close();
         }
         }
      }

    //  E.info("unpacked jar to " + fout);

   }

   public static String absPath(Class<?> base, String pckgname) {
	   String rcnm = base.getName();
	    rcnm = rcnm.substring(0, rcnm.lastIndexOf("."));
	    String ppath = rcnm.replace(".", "/");
	    String path = ppath + "/" + pckgname;

	    if (path.endsWith("/.")) {
	    	path = path.substring(0, path.length()-2);
	    }

	    return path;
   }

   public static String[] getResourceList(Class<?> base, String pckgname, String ext) {

	    String path = absPath(base, pckgname);

	   	ArrayList<String> als = new ArrayList<String>();
		try {
			ClassLoader cld = Thread.currentThread().getContextClassLoader();
			URL resource = cld.getResource(path);
			File dir = new File(resource.getFile());

			if (dir.exists()) {
				// we're running from the file system;
				for (String fnm : dir.list()) {
					if (ext == null || fnm.endsWith(ext)) {
						als.add(fnm);
					}
				}

			} else {
				// we're running from a jar file;
				JarURLConnection conn = (JarURLConnection) resource.openConnection();
				String starts = conn.getEntryName();
				JarFile jfile = conn.getJarFile();
				Enumeration<JarEntry> e = jfile.entries();
				while (e.hasMoreElements()) {
					ZipEntry entry = e.nextElement();
					String entryname = entry.getName();
					if (entryname.startsWith(starts)
							&& (entryname.lastIndexOf('/') <= starts.length())) {
						String rnm = entryname.substring(starts.length()+1, entryname.length());
						if (rnm.length() > 1 && (ext == null || rnm.endsWith(ext))) {
							als.add(rnm);
						}
					}

				}

			}

		} catch (Exception ex) {
			E.error("cant list resources? " + ex);
		}
		return als.toArray(new String[als.size()]);
	}

   	public static void unpackPackage(Class<?> base, String pkgname, File dir) throws FileNotFoundException, IOException {

   		String[] sa = getResourceList(base, pkgname, null);
   		for (String s : sa) {
   			E.info("resource to unpack " + s);
   		}

   		for (String s : sa) {
   			File fdest = new File(dir, s);
   			extractRelativeResource(base, pkgname + "/" + s, fdest);
   		}

   	}

public static void showThreads() {

	   // Find the root thread group
    ThreadGroup root = Thread.currentThread().getThreadGroup();
    while (root.getParent() != null) {
        root = root.getParent();
    }

    // Visit each thread group
    visit(root, 0);

}

    // This method recursively visits all thread groups under `group'.
    public static void visit(ThreadGroup group, int level) {
        // Get threads in `group'
        int numThreads = group.activeCount();
        Thread[] threads = new Thread[numThreads*2];
        numThreads = group.enumerate(threads, false);

        // Enumerate each thread in `group'
        for (int i=0; i<numThreads; i++) {
            // Get thread
            Thread thread = threads[i];
            E.info("Thread: " + thread.isDaemon() + " " + thread);
        }

        // Get thread subgroups of `group'
        int numGroups = group.activeGroupCount();
        ThreadGroup[] groups = new ThreadGroup[numGroups*2];
        numGroups = group.enumerate(groups, false);

        // Recursively visit each subgroup
        for (int i=0; i<numGroups; i++) {
            visit(groups[i], level+1);
        }
    }

	public static String getOSArchitecture() throws ContentError {
		if (osarch == null) {
			String osn = System.getProperty("os.name").toLowerCase();

			if (osn.startsWith("linux")) {
				osarch = "linux";
			} else if (osn.startsWith("mac")) {
				osarch = "mac";
			} else if (osn.startsWith("windows")) {
				osarch = "windows";
			} else {
				throw new ContentError("unrecognized os " + osn);
			}
		}
		return osarch;
	}
}

Output:
【漏洞类型】路径遍历漏洞（Zip Slip Vulnerability）
【严重性】High
【漏洞摘要】这是一个路径遍历漏洞。具体来说，`unpackJar` 函数中存在未验证的 Zip 条目路径问题，导致攻击者可以从压缩包中提取文件到目标目录之外。问题行在 `unpackJar` 函数中，`java.io.File f = new File(fout, je.getName());`。根本原因在于代码没有对提取的文件路径进行校验，允许相对路径（如 `../`）将文件提取到目标目录之外。
【关键代码模式】关键代码模式包括未验证的 Zip 条目路径、`File` 对象的构造函数、`JarFile` 和 `JarEntry` 的使用。关键词包括 `unpackJar`、`File`、`JarEntry`、`getCanonicalPath`、`getCanonicalFile`。
【修复方法说明】修复方法通过在构造 `File` 对象时添加路径规范化检查，确保提取的文件路径在目标目录之下，从而防止路径遍历攻击。最佳实践是始终对用户输入和外部文件路径进行验证和规范化。