/* * Constellation - An open source and standard compliant SDI * http://www.constellation-sdi.org * * Copyright 2014 Geomatys. * * 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 org.constellation.provider.configuration; import com.vividsolutions.jts.geom.Geometry; import java.io.IOException; import java.io.StringWriter; import java.util.AbstractMap; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.namespace.QName; import org.apache.sis.storage.DataStore; import org.apache.sis.storage.DataStoreException; import org.apache.sis.util.UnconvertibleObjectException; import org.apache.sis.xml.MarshallerPool; import org.constellation.admin.SpringHelper; import org.constellation.admin.exception.ConstellationException; import org.constellation.api.DataType; import org.constellation.api.ProviderType; import org.constellation.api.StyleType; import org.constellation.business.IDataBusiness; import org.constellation.business.IDatasetBusiness; import org.constellation.business.IProviderBusiness; import org.constellation.business.IStyleBusiness; import org.constellation.configuration.AppProperty; import org.constellation.configuration.Application; import org.constellation.configuration.ConfigurationException; import org.constellation.dto.CoverageMetadataBean; import org.constellation.database.api.jooq.tables.pojos.Dataset; import org.constellation.database.api.jooq.tables.pojos.Style; import org.constellation.database.api.repository.DatasetRepository; import org.constellation.generic.database.GenericDatabaseMarshallerPool; import org.constellation.provider.Data; import org.constellation.provider.DataProvider; import org.constellation.provider.DataProviders; import org.constellation.provider.Provider; import org.constellation.provider.ProviderFactory; import org.constellation.provider.StyleProviders; import org.constellation.util.MetadataMapBuilder; import org.constellation.util.ParamUtilities; import org.constellation.util.SimplyMetadataTreeNode; import org.geotoolkit.coverage.grid.ViewType; import org.geotoolkit.coverage.io.CoverageStoreException; import org.geotoolkit.coverage.io.GridCoverageReader; import org.geotoolkit.data.FeatureStore; import org.geotoolkit.feature.type.AttributeType; import org.geotoolkit.feature.type.ComplexType; import org.geotoolkit.feature.type.FeatureType; import org.geotoolkit.feature.type.GeometryDescriptor; import org.geotoolkit.util.NamesExt; import org.geotoolkit.feature.type.PropertyDescriptor; import org.geotoolkit.feature.type.PropertyType; import org.geotoolkit.image.io.metadata.SpatialMetadata; import org.geotoolkit.image.io.metadata.SpatialMetadataFormat; import org.geotoolkit.storage.coverage.CoverageReference; import org.geotoolkit.storage.coverage.PyramidalCoverageReference; import org.geotoolkit.style.MutableFeatureTypeStyle; import org.geotoolkit.style.MutableRule; import org.geotoolkit.style.MutableStyle; import org.opengis.parameter.ParameterValueGroup; import org.opengis.style.RasterSymbolizer; import org.opengis.style.Symbolizer; import org.opengis.util.GenericName; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.w3c.dom.Node; /** * * @author Guilhem Legal (Geomatys) * @author Johann Sorel (Geomatys) */ public final class DefaultConfigurator implements Configurator { @Autowired private IProviderBusiness providerBusiness; @Autowired private IDataBusiness dataBusiness; @Autowired private IStyleBusiness styleBusiness; @Autowired private DatasetRepository datasetRepository; @Autowired private IDatasetBusiness datasetBusiness; public DefaultConfigurator() { SpringHelper.injectDependencies(this); } @Override public List<Map.Entry<String, ParameterValueGroup>> getProviderConfigurations() throws ConfigurationException { final List<String> ids = providerBusiness.getProviderIds(); final List<Map.Entry<String,ParameterValueGroup>> entries = new ArrayList<>(); for (String id : ids) { try { ParameterValueGroup param = getProviderConfiguration(id); if (param != null) { entries.add(new AbstractMap.SimpleImmutableEntry<>(id, param)); } } catch (ConfigurationException ex) { LOGGER.log(Level.WARNING, "error while getting configuration for provider " + id, ex); } } return entries; } @Override public List<ProviderInformation> getProviderInformations() throws ConfigurationException { final List<org.constellation.database.api.jooq.tables.pojos.Provider> records = providerBusiness.getProviders(); final List<ProviderInformation> entries = new ArrayList<>(); for (org.constellation.database.api.jooq.tables.pojos.Provider record : records) { try { final ParameterValueGroup param = getProviderConfiguration(record.getIdentifier()); if (param != null) { entries.add(new ProviderInformation(record.getIdentifier(), record.getImpl(), param)); } } catch (ConfigurationException ex) { LOGGER.log(Level.WARNING, "error while getting configuration for provider " + record.getIdentifier(), ex); } } return entries; } @Override public ParameterValueGroup getProviderConfiguration(String providerId) throws ConfigurationException { final org.constellation.database.api.jooq.tables.pojos.Provider record = providerBusiness.getProvider(providerId); final String impl = record.getImpl(); ProviderFactory factory = DataProviders.getInstance().getFactory(impl); if(factory==null) factory = StyleProviders.getInstance().getFactory(impl); if(factory==null) return null; try { ParameterValueGroup params = (ParameterValueGroup) ParamUtilities.readParameter(record.getConfig(), factory.getProviderDescriptor()); return params; } catch (IOException | UnconvertibleObjectException ex) { throw new ConfigurationException("Error while reading provider configuration for:" + providerId, ex); } } @Override @Transactional public void addProviderConfiguration(final String providerId, final ParameterValueGroup config, final Integer datasetId) throws ConfigurationException { addProviderConfiguration(providerId, config, datasetId,true); } @Override @Transactional public void addProviderConfiguration(final String providerId, final ParameterValueGroup config, final Integer datasetId, final boolean createDatasetIfNull) throws ConfigurationException { Provider provider = DataProviders.getInstance().getProvider(providerId); if(provider==null){ provider = StyleProviders.getInstance().getProvider(providerId); } final ProviderType type = provider.getProviderType(); final String factoryName = provider.getFactory().getName(); try { final org.constellation.database.api.jooq.tables.pojos.Provider pr = providerBusiness.storeProvider(providerId, null, type, factoryName, config); checkDataUpdate(pr, datasetId, createDatasetIfNull); } catch ( IOException ex) { throw new ConfigurationException(ex); } } private void checkDataUpdate(final org.constellation.database.api.jooq.tables.pojos.Provider pr, final Integer datasetId) throws IOException, ConfigurationException{ checkDataUpdate(pr,datasetId,true); } /** * * @param pr given provider * @param datasetId given dataset identifier to attach to data. * @param createDatasetIfNull flag that indicates if a dataset will be created in case of given datasetId is null. * @throws IOException */ private void checkDataUpdate(final org.constellation.database.api.jooq.tables.pojos.Provider pr, Integer datasetId, final boolean createDatasetIfNull) throws IOException, ConfigurationException{ final List<org.constellation.database.api.jooq.tables.pojos.Data> list = providerBusiness.getDatasFromProviderId(pr.getId()); final String type = pr.getType(); if (type.equals(ProviderType.LAYER.name())) { final DataProvider provider = DataProviders.getInstance().getProvider(pr.getIdentifier()); if (datasetId == null) { final Dataset dataset = datasetRepository.findByIdentifier(pr.getIdentifier()); if (dataset == null) { if(createDatasetIfNull) { datasetId = datasetBusiness.createDataset(pr.getIdentifier(), null, pr.getOwner()).getId(); } }else { datasetId = dataset.getId(); } } // Remove no longer existing layer. final Map<String, String> metadata = new HashMap<>(0); for (final org.constellation.database.api.jooq.tables.pojos.Data data : list) { boolean found = false; for (final Object keyObj : provider.getKeys()) { final GenericName key = (GenericName) keyObj; if (data.getName().equals(key.tip().toString())) { found = true; break; } else if (key.tip().toString().contains(data.getName()) && providerBusiness.getProvider(data.getProvider()).getIdentifier().equalsIgnoreCase(provider.getId())) { //save metadata metadata.put(key.tip().toString(), data.getMetadata()); } } if (!found) { dataBusiness.missingData(new QName(data.getNamespace(), data.getName()), provider.getId()); } } //check if layer analysis is required String propertyValue = Application.getProperty(AppProperty.DATA_AUTO_ANALYSE); boolean doAnalysis = propertyValue == null ? false : Boolean.valueOf(propertyValue); // Add new layer. for (final GenericName key : provider.getKeys()) { final QName name = new QName(NamesExt.getNamespace(key), key.tip().toString()); String ns = name.getNamespaceURI(); if(ns.isEmpty()) ns = null; final GenericName gname = NamesExt.create(ns, name.getLocalPart()); boolean found = false; for (final org.constellation.database.api.jooq.tables.pojos.Data data : list) { if (name.equals(new QName(data.getNamespace(),data.getName()))) { found = true; break; } } if (!found) { // Subtype and included String subType = null; boolean included = true; final DataProvider dp = DataProviders.getInstance().getProvider(provider.getId()); final DataStore store = dp.getMainStore(); if (store instanceof FeatureStore) { final FeatureStore fs = (FeatureStore)store; FeatureType fType = null; try { fType = fs.getFeatureType(gname); } catch (DataStoreException ex) { LOGGER.log(Level.INFO, ex.getLocalizedMessage(), ex); } subType = findGeometryType(fType, null); if(subType==null){ // A feature that does not contain geometry, we hide it included = false; } } // Metadata String metadataXml = null; final String currentMetadata = metadata.get(name.getLocalPart()); if (currentMetadata != null) { metadataXml = currentMetadata; } else { final Data layer = (Data) provider.get(gname); final Object origin = layer.getOrigin(); if (origin instanceof CoverageReference) { final CoverageReference fcr = (CoverageReference) origin; try { int i = fcr.getImageIndex(); final GridCoverageReader reader = fcr.acquireReader(); final SpatialMetadata sm = reader.getCoverageMetadata(i); fcr.recycle(reader); if (sm != null) { final Node coverageRootNode = sm.getAsTree(SpatialMetadataFormat.GEOTK_FORMAT_NAME); MetadataMapBuilder.setCounter(0); final List<SimplyMetadataTreeNode> coverageMetadataList = MetadataMapBuilder.createSpatialMetadataList(coverageRootNode, null, 11, i); final CoverageMetadataBean coverageMetadataBean = new CoverageMetadataBean(coverageMetadataList); final MarshallerPool mp = GenericDatabaseMarshallerPool.getInstance(); final Marshaller marshaller = mp.acquireMarshaller(); final StringWriter sw = new StringWriter(); marshaller.marshal(coverageMetadataBean, sw); mp.recycle(marshaller); metadataXml = sw.toString(); } } catch (CoverageStoreException | JAXBException e) { LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e); } } } // find if data is rendered Boolean rendered = null; if (DataType.COVERAGE.equals(provider.getDataType())) { Data providerData = provider.get(key); Object origin = providerData.getOrigin(); if (origin instanceof PyramidalCoverageReference) { try { ViewType packMode = ((PyramidalCoverageReference) origin).getPackMode(); if (ViewType.RENDERED.equals(packMode)) { rendered = Boolean.TRUE; } else { rendered = Boolean.FALSE; } } catch (DataStoreException e) { LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e); } subType = "pyramid"; } else { rendered = Boolean.FALSE; } } //dataBusiness.create(name, pr.getIdentifier(), provider.getDataType().name(), provider.isSensorAffectable(), visible, subType, metadataXml); //do not save the coverage metadata in database, this metadata is obsolete, the full iso metadata is stored later. org.constellation.database.api.jooq.tables.pojos.Data data = dataBusiness.create( name, pr.getIdentifier(), provider.getDataType().name(), provider.isSensorAffectable(), included, rendered, subType, null); if(datasetId != null) { dataBusiness.updateDataDataSetId(name, pr.getIdentifier(), datasetId); } } } } else { final Provider provider = StyleProviders.getInstance().getProvider(pr.getIdentifier()); final List<Style> styles = providerBusiness.getStylesFromProviderId(pr.getId()); // Remove no longer existing style. for (final Style style : styles) { if (provider.get(style.getName()) == null) { try { styleBusiness.deleteStyle(provider.getId(),style.getName()); } catch (ConfigurationException e) { throw new ConstellationException(e); } } } // Add not registered new data. for (final Object key : provider.getKeys()) { boolean found = false; for (final Style style : styles) { if (key.equals(style.getName())) { found = true; break; } } if (!found) { StyleType styleType = StyleType.VECTOR; MutableStyle style = (MutableStyle) provider.get(key); fts: for (MutableFeatureTypeStyle mutableFeatureTypeStyle : style.featureTypeStyles()) { for (MutableRule mutableRule : mutableFeatureTypeStyle.rules()) { for (Symbolizer symbolizer : mutableRule.symbolizers()) { if (symbolizer instanceof RasterSymbolizer) { styleType = StyleType.COVERAGE; break fts; } } } } styleBusiness.writeStyle((String) key, pr.getId(), styleType, style); } } } } @Override @Transactional public void updateProviderConfiguration(String providerId, ParameterValueGroup config) throws ConfigurationException { final org.constellation.database.api.jooq.tables.pojos.Provider pr = providerBusiness.getProvider(providerId); if (pr != null) { try { final String configString = ParamUtilities.writeParameter(config); pr.setConfig(configString); checkDataUpdate(pr, null, false); } catch (IOException e) { e.printStackTrace(); } } //TODO throw exception ? log message ? create new provider ? do nothing ? } @Override @Transactional public void removeProviderConfiguration(String providerId) throws ConfigurationException { dataBusiness.removeDataFromProvider(providerId); providerBusiness.removeProvider(providerId); } private static String findGeometryType(ComplexType ft, Set<GenericName> visited){ if(ft==null) return null; if(visited==null) visited = new HashSet<>(); if(visited.contains(ft.getName())) return null; visited.add(ft.getName()); if(ft instanceof FeatureType){ GeometryDescriptor gd = ((FeatureType)ft).getGeometryDescriptor(); if(gd!=null){ return gd.getType().getBinding().getSimpleName(); } } for(PropertyDescriptor pd : ft.getDescriptors()){ final PropertyType type = pd.getType(); if(type instanceof ComplexType){ String subType = findGeometryType((ComplexType)type, visited); if(subType!=null){ return subType; } }else if(type instanceof AttributeType){ final Class<?> valueClass = type.getBinding(); if(Geometry.class.isAssignableFrom(valueClass)){ return valueClass.getSimpleName(); } } } return null; } }