/*
* 3D City Database Web Feature Service
* http://www.3dcitydb.org/
*
* Copyright 2014 - 2016
* virtualcitySYSTEMS GmbH
* Tauentzienstrasse 7b/c
* 10789 Berlin, Germany
* http://www.virtualcitysystems.de/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package vcs.citydb.wfs.operation.getfeature;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.sql.SQLException;
import java.util.List;
import java.util.zip.GZIPOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBException;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.namespace.QName;
import net.opengis.wfs._2.GetFeatureType;
import org.citydb.api.concurrent.PoolSizeAdaptationStrategy;
import org.citydb.api.concurrent.SingleWorkerPool;
import org.citydb.api.concurrent.Worker;
import org.citydb.api.concurrent.WorkerFactory;
import org.citydb.api.concurrent.WorkerPool;
import org.citydb.api.event.Event;
import org.citydb.api.event.EventDispatcher;
import org.citydb.api.event.EventHandler;
import org.citydb.api.registry.ObjectRegistry;
import org.citydb.config.Config;
import org.citydb.database.DatabaseConnectionPool;
import org.citydb.modules.citygml.common.database.cache.CacheTableManager;
import org.citydb.modules.citygml.common.database.uid.UIDCacheManager;
import org.citydb.modules.citygml.common.database.uid.UIDCacheType;
import org.citydb.modules.citygml.common.database.xlink.DBXlink;
import org.citydb.modules.citygml.exporter.concurrent.DBExportWorkerFactory;
import org.citydb.modules.citygml.exporter.database.content.DBSplittingResult;
import org.citydb.modules.citygml.exporter.database.uid.GeometryGmlIdCache;
import org.citydb.modules.common.concurrent.IOWriterWorkerFactory;
import org.citydb.modules.common.event.EventType;
import org.citydb.modules.common.event.InterruptEvent;
import org.citydb.modules.common.filter.ExportFilter;
import org.citydb.util.Util;
import org.citygml4j.builder.jaxb.JAXBBuilder;
import org.citygml4j.model.module.Module;
import org.citygml4j.model.module.ModuleContext;
import org.citygml4j.model.module.Modules;
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.xml.SAXEventBuffer;
import org.citygml4j.util.xml.SAXWriter;
import org.xml.sax.SAXException;
import vcs.citydb.wfs.config.Constants;
import vcs.citydb.wfs.config.WFSConfig;
import vcs.citydb.wfs.config.operation.GetFeatureOutputFormat;
import vcs.citydb.wfs.exception.WFSException;
import vcs.citydb.wfs.exception.WFSExceptionCode;
import vcs.citydb.wfs.util.CacheTableCleanerWorker;
import vcs.citydb.wfs.util.NullWorker;
public class ExportController implements EventHandler {
private final JAXBBuilder jaxbBuilder;
private final WFSConfig wfsConfig;
private final Config exporterConfig;
private final DatabaseConnectionPool connectionPool;
private final EventDispatcher eventDispatcher;
private final Object eventChannel = new Object();
private WFSException wfsException;
public ExportController(JAXBBuilder jaxbBuilder, WFSConfig wfsConfig, Config exporterConfig) {
this.jaxbBuilder = jaxbBuilder;
this.wfsConfig = wfsConfig;
this.exporterConfig = exporterConfig;
connectionPool = DatabaseConnectionPool.getInstance();
eventDispatcher = ObjectRegistry.getInstance().getEventDispatcher();
eventDispatcher.addEventHandler(EventType.INTERRUPT, this);
}
@SuppressWarnings("unchecked")
public void doExport(GetFeatureType wfsRequest,
List<QueryExpression> queryExpressions,
CityGMLVersion version,
HttpServletRequest request,
HttpServletResponse response) throws WFSException {
// define queue size for worker pools
int queueSize = exporterConfig.getProject().getExporter().getResources().getThreadPool().getDefaultPool().getMaxThreads() * 2;
// prepare SAXWriter
exporterConfig.getProject().getExporter().setCityGMLVersion(Util.fromCityGMLVersion(version));
SAXWriter saxWriter = new SAXWriter();
saxWriter.setWriteEncoding(true);
saxWriter.setIndentString(" ");
try {
response.setContentType("text/xml");
response.setCharacterEncoding("UTF-8");
// gzipped or XML output
if (wfsRequest.isSetOutputFormat() &&
GetFeatureOutputFormat.GML3_1_GZIP.value().equals(wfsRequest.getOutputFormat()) &&
wfsConfig.getOperations().getGetFeature().supportsOutputFormat(GetFeatureOutputFormat.GML3_1_GZIP)) {
response.addHeader("Content-Encoding", "gzip");
saxWriter.setOutput(new GZIPOutputStream(response.getOutputStream()), "UTF-8");
} else
saxWriter.setOutput(new OutputStreamWriter(response.getOutputStream(), "UTF-8"));
} catch (IOException e) {
throw new WFSException(WFSExceptionCode.INTERNAL_SERVER_ERROR, "Failed to initialize XML response writer.", e);
}
// 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
ModuleContext moduleContext = new ModuleContext(version);
for (Module module : moduleContext.getModules()) {
if (!(module instanceof CityGMLModule) || module.getType() == CityGMLModuleType.CORE)
saxWriter.setPrefix(module.getNamespacePrefix(), module.getNamespaceURI());
else if (module.getType() == CityGMLModuleType.GENERICS) {
saxWriter.setPrefix(module.getNamespacePrefix(), module.getNamespaceURI());
saxWriter.setSchemaLocation(module.getNamespaceURI(), module.getSchemaLocation());
}
}
for (QueryExpression queryExpression : queryExpressions) {
for (QName featureTypeName : queryExpression.getFeatureTypeNames()) {
Module module = Modules.getCityGMLModule(featureTypeName.getNamespaceURI());
if (module != null) {
saxWriter.setPrefix(module.getNamespacePrefix(), module.getNamespaceURI());
saxWriter.setSchemaLocation(module.getNamespaceURI(), module.getSchemaLocation());
}
}
}
// general cityobjectgroup settings
exporterConfig.getProject().getExporter().getCityObjectGroup().setExportMemberAsXLinks(true);
// general appearance settings
exporterConfig.getProject().getExporter().getAppearances().setExportAppearances(false);
exporterConfig.getProject().getExporter().getAppearances().setExportTextureFiles(false);
exporterConfig.getInternal().setExportGlobalAppearances(false);
UIDCacheManager uidCacheManager = null;
CacheTableManager cacheTableManager = null;
SingleWorkerPool<SAXEventBuffer> writerPool = null;
SingleWorkerPool<DBXlink> xlinkPool = null;
WorkerPool<DBSplittingResult> databaseWorkerPool = null;
try {
// create instance of cache table manager
try {
cacheTableManager = new CacheTableManager(
connectionPool,
exporterConfig.getProject().getExporter().getResources().getThreadPool().getDefaultPool().getMaxThreads(),
exporterConfig);
} catch (SQLException e) {
throw new WFSException(WFSExceptionCode.INTERNAL_SERVER_ERROR, "Failed to initialize internal cache manager.", e);
} catch (IOException e) {
throw new WFSException(WFSExceptionCode.INTERNAL_SERVER_ERROR, "Failed to initialize internal cache manager.", e);
}
// create instance of gml:id lookup server manager...
uidCacheManager = new UIDCacheManager();
// create export filter
// TODO: replace with new filter layer
ExportFilter exportFilter = new ExportFilter(exporterConfig);
// ...and start servers
try {
uidCacheManager.initCache(
UIDCacheType.GEOMETRY,
new GeometryGmlIdCache(cacheTableManager,
exporterConfig.getProject().getExporter().getResources().getGmlIdCache().getGeometry().getPartitions(),
exporterConfig.getProject().getDatabase().getUpdateBatching().getGmlIdCacheBatchValue()),
exporterConfig.getProject().getExporter().getResources().getGmlIdCache().getGeometry().getCacheSize(),
exporterConfig.getProject().getExporter().getResources().getGmlIdCache().getGeometry().getPageFactor(),
exporterConfig.getProject().getExporter().getResources().getThreadPool().getDefaultPool().getMaxThreads());
uidCacheManager.initCache(
UIDCacheType.FEATURE,
new GeometryGmlIdCache(cacheTableManager,
exporterConfig.getProject().getExporter().getResources().getGmlIdCache().getFeature().getPartitions(),
exporterConfig.getProject().getDatabase().getUpdateBatching().getGmlIdCacheBatchValue()),
exporterConfig.getProject().getExporter().getResources().getGmlIdCache().getFeature().getCacheSize(),
exporterConfig.getProject().getExporter().getResources().getGmlIdCache().getFeature().getPageFactor(),
exporterConfig.getProject().getExporter().getResources().getThreadPool().getDefaultPool().getMaxThreads());
} catch (SQLException e) {
throw new WFSException(WFSExceptionCode.INTERNAL_SERVER_ERROR, "Failed to initialize internal gml:id caches.", e);
}
// create worker pools
writerPool = new SingleWorkerPool<SAXEventBuffer>(
"citygml_writer_pool",
new IOWriterWorkerFactory(saxWriter),
queueSize,
false);
// TODO: currently XLinks to texture images and library objects are not resolved
xlinkPool = new SingleWorkerPool<DBXlink>(
"xlink_exporter_pool",
new WorkerFactory<DBXlink>() {
public Worker<DBXlink> createWorker() {
return new NullWorker<DBXlink>();
}
},
1,
false);
FeatureMemberWriterFactory writerFactory = new FeatureMemberWriterFactory(writerPool, uidCacheManager, jaxbBuilder, wfsConfig, exporterConfig);
databaseWorkerPool = new WorkerPool<DBSplittingResult>(
"db_exporter_pool",
exporterConfig.getProject().getExporter().getResources().getThreadPool().getDefaultPool().getMinThreads(),
exporterConfig.getProject().getExporter().getResources().getThreadPool().getDefaultPool().getMaxThreads(),
PoolSizeAdaptationStrategy.AGGRESSIVE,
new DBExportWorkerFactory(
connectionPool,
jaxbBuilder,
writerFactory,
xlinkPool,
uidCacheManager,
cacheTableManager,
exportFilter,
exporterConfig,
eventDispatcher),
queueSize,
false);
// set virtual channel for events triggered by worker
writerPool.setEventSource(eventChannel);
xlinkPool.setEventSource(eventChannel);
databaseWorkerPool.setEventSource(eventChannel);
// start worker pools with a single worker
writerPool.prestartCoreWorker();
xlinkPool.prestartCoreWorker();
databaseWorkerPool.prestartCoreWorker();
// ok, preparation done, start database query
QueryExecuter queryExecuter = null;
try {
queryExecuter = new QueryExecuter(wfsRequest,
queryExpressions,
writerFactory,
databaseWorkerPool,
writerPool,
connectionPool,
jaxbBuilder,
exportFilter,
wfsConfig,
exporterConfig);
} catch (JAXBException e) {
throw new WFSException(WFSExceptionCode.INTERNAL_SERVER_ERROR, "Failed to initialize internal query executer.", e);
} catch (DatatypeConfigurationException e) {
throw new WFSException(WFSExceptionCode.INTERNAL_SERVER_ERROR, "Failed to initialize internal query executer.", e);
}
// execute database query
queryExecuter.executeQuery();
// database query executed. shutdown pools.
try {
databaseWorkerPool.shutdownAndWait();
xlinkPool.shutdownAndWait();
writerPool.shutdownAndWait();
} catch (InterruptedException e) {
throw new WFSException(WFSExceptionCode.INTERNAL_SERVER_ERROR, "Failed to shutdown worker pools.", e);
}
// close SAX writer. this also closes the servlet output stream.
try {
saxWriter.close();
} catch (SAXException e) {
throw new WFSException(WFSExceptionCode.INTERNAL_SERVER_ERROR, "Failed to close the SAX writer.", e);
}
// abort if a worker pool has thrown an error
if (wfsException != null)
throw wfsException;
} finally {
// clean up...
if (databaseWorkerPool != null && !databaseWorkerPool.isTerminated())
databaseWorkerPool.shutdownNow();
if (xlinkPool != null && !xlinkPool.isTerminated())
xlinkPool.shutdownNow();
if (writerPool != null && !writerPool.isTerminated())
writerPool.shutdownNow();
if (eventDispatcher != null) {
try {
eventDispatcher.flushEvents();
} catch (InterruptedException e) {
//
}
}
if (uidCacheManager != null) {
try {
uidCacheManager.shutdownAll();
} catch (SQLException e) {
//
}
}
if (cacheTableManager != null)
((WorkerPool<CacheTableManager>)ObjectRegistry.getInstance().lookup(CacheTableCleanerWorker.class.getName())).addWork(cacheTableManager);
}
}
@Override
public void handleEvent(Event event) throws Exception {
if (event.getChannel() == eventChannel) {
wfsException = new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, ((InterruptEvent)event).getLogMessage(), ((InterruptEvent)event).getCause());
}
}
}