All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geomajas.internal.configuration.ConfigurationDtoPostProcessor Maven / Gradle / Ivy

The newest version!
/*
 * This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
 *
 * Copyright 2008-2016 Geosparc nv, http://www.geosparc.com/, Belgium.
 *
 * The program is available in open source according to the GNU Affero
 * General Public License. All contributions in this program are covered
 * by the Geomajas Contributors License Agreement. For full licensing
 * details, see LICENSE.txt in the project root.
 */
package org.geomajas.internal.configuration;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;

import org.geomajas.configuration.AbstractAttributeInfo;
import org.geomajas.configuration.AssociationAttributeInfo;
import org.geomajas.configuration.FeatureInfo;
import org.geomajas.configuration.FeatureStyleInfo;
import org.geomajas.configuration.LayerInfo;
import org.geomajas.configuration.NamedStyleInfo;
import org.geomajas.configuration.RasterLayerInfo;
import org.geomajas.configuration.VectorLayerInfo;
import org.geomajas.configuration.client.ClientApplicationInfo;
import org.geomajas.configuration.client.ClientLayerInfo;
import org.geomajas.configuration.client.ClientLayerTreeInfo;
import org.geomajas.configuration.client.ClientLayerTreeNodeInfo;
import org.geomajas.configuration.client.ClientMapInfo;
import org.geomajas.configuration.client.ClientVectorLayerInfo;
import org.geomajas.configuration.client.ScaleInfo;
import org.geomajas.geometry.Bbox;
import org.geomajas.geometry.Coordinate;
import org.geomajas.geometry.Crs;
import org.geomajas.geometry.CrsTransform;
import org.geomajas.global.ExceptionCode;
import org.geomajas.global.GeomajasException;
import org.geomajas.layer.Layer;
import org.geomajas.layer.LayerException;
import org.geomajas.layer.LayerType;
import org.geomajas.layer.RasterLayer;
import org.geomajas.layer.VectorLayer;
import org.geomajas.service.DtoConverterService;
import org.geomajas.service.GeoService;
import org.geomajas.service.StyleConverterService;
import org.geomajas.sld.NamedLayerInfo;
import org.geomajas.sld.StyledLayerDescriptorInfo;
import org.geomajas.sld.UserLayerInfo;
import org.geomajas.sld.UserStyleInfo;
import org.geotools.geometry.DirectPosition2D;
import org.geotools.referencing.GeodeticCalculator;
import org.jibx.runtime.BindingDirectory;
import org.jibx.runtime.IBindingFactory;
import org.jibx.runtime.IUnmarshallingContext;
import org.jibx.runtime.JiBXException;
import org.opengis.referencing.operation.TransformException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

import com.vividsolutions.jts.geom.Envelope;

/**
 * Post-processes configuration DTOs. Generally responsible for any behaviour that would violate the DTO contract
 * (especially for GWT) if it would be added to the configuration objects themselves, such as hooking up client
 * configurations to their server layers.
 * 
 * @author Jan De Moerloose
 */
@Component
public class ConfigurationDtoPostProcessor {

	private static final double METER_PER_INCH = 0.0254;

	private final Logger log = LoggerFactory.getLogger(ConfigurationDtoPostProcessor.class);

	@Autowired
	private DtoConverterService converterService;

	@Autowired
	private GeoService geoService;

	@Autowired
	private StyleConverterService styleConverterService;

	@Autowired(required = false)
	protected Map applicationMap = new LinkedHashMap();

	@Autowired(required = false)
	protected Map namedStyleMap = new LinkedHashMap();

	@Autowired(required = false)
	protected Map> layerMap = new LinkedHashMap>();

	@Autowired(required = false)
	protected Map rasterLayerMap = new LinkedHashMap();

	@Autowired(required = false)
	protected Map vectorLayerMap = new LinkedHashMap();

	@Autowired(required = true)
	private ApplicationContext applicationContext;

	public ConfigurationDtoPostProcessor() {

	}

	@PostConstruct
	protected void processConfiguration() throws BeansException {
		try {
			for (RasterLayer layer : rasterLayerMap.values()) {
				postProcess(layer);
			}
			for (VectorLayer layer : vectorLayerMap.values()) {
				postProcess(layer);
			}
			for (ClientApplicationInfo application : applicationMap.values()) {
				postProcess(application);
			}
			for (NamedStyleInfo style : namedStyleMap.values()) {
				postProcess(style);
			}
		} catch (LayerException e) {
			throw new BeanInitializationException("Invalid configuration", e);
		}
	}

	private void postProcess(RasterLayer layer) throws LayerException {
		RasterLayerInfo info = layer.getLayerInfo();
		for (ScaleInfo scale : info.getZoomLevels()) {
			// for raster layers we don't accept x:y notation !
			if (scale.isPixelPerUnitBased()) {
				throw new LayerException(ExceptionCode.CONVERSION_PROBLEM, "Raster layer " + layer.getId()
						+ " has zoom level " + scale.getNumerator() + ":" + scale.getDenominator()
						+ " in disallowed x:y notation");
			}
			// add the resolution for deprecated api support
			info.getResolutions().add(1. / scale.getPixelPerUnit());
		}
		if (0 == info.getTileWidth()) {
			throw new LayerException(ExceptionCode.LAYER_CONFIGURATION_PROBLEM, layer.getId(),
					"tileWidth should not be zero.");
		}
		if (0 == info.getTileHeight()) {
			throw new LayerException(ExceptionCode.LAYER_CONFIGURATION_PROBLEM, layer.getId(),
					"tileHeight should not be zero.");
		}
	}

	private void postProcess(VectorLayer layer) throws LayerException {
		VectorLayerInfo info = layer.getLayerInfo();
		if (info != null) {
			// log warning when allowing empty attributes
			if (info.isAllowEmptyGeometries()) {
				log.warn("Empty geometries are allowed for layer {}. This disables all security filtering on areas.",
						layer.getId());
			}

			FeatureInfo featureInfo = info.getFeatureInfo();
			postProcess(layer.getId(), featureInfo, "");

			// convert sld to old styles
			for (NamedStyleInfo namedStyle : info.getNamedStyleInfos()) {
				// check sld location
				if (namedStyle.getSldLocation() != null) {
					Resource resource = applicationContext.getResource(namedStyle.getSldLocation());
					IBindingFactory bindingFactory;
					try {
						bindingFactory = BindingDirectory.getFactory(StyledLayerDescriptorInfo.class);
						IUnmarshallingContext unmarshallingContext = bindingFactory.createUnmarshallingContext();
						StyledLayerDescriptorInfo sld = (StyledLayerDescriptorInfo) unmarshallingContext
								.unmarshalDocument(new InputStreamReader(resource.getInputStream()));
						String layerName = (namedStyle.getSldLayerName() != null ? namedStyle.getSldLayerName() : layer
								.getId());
						String styleName = (namedStyle.getSldStyleName() != null ? namedStyle.getSldStyleName() : layer
								.getId());
						namedStyle.setUserStyle(extractStyle(sld, layerName, styleName));
					} catch (JiBXException e) {
						throw new LayerException(e, ExceptionCode.INVALID_SLD, namedStyle.getSldLocation(),
								layer.getId());
					} catch (IOException e) {
						throw new LayerException(e, ExceptionCode.INVALID_SLD, namedStyle.getSldLocation(),
								layer.getId());
					}
					NamedStyleInfo sldStyle = styleConverterService.convert(namedStyle.getUserStyle(),
							featureInfo);
					namedStyle.setFeatureStyles(sldStyle.getFeatureStyles());
					namedStyle.setLabelStyle(sldStyle.getLabelStyle());
				}
			}
			// check for at least 1 style
			if (info.getNamedStyleInfos().size() == 0) {
				info.getNamedStyleInfos().add(new NamedStyleInfo());
			}
			// apply defaults to all styles
			for (NamedStyleInfo namedStyle : info.getNamedStyleInfos()) {
				namedStyle.applyDefaults();
			}
			
			// check for mixed geometry styles (old school styles without layer type will be converted to 3 styles)
			for (NamedStyleInfo namedStyle : info.getNamedStyleInfos()) {
				List convertedStyles = new ArrayList();
				for (FeatureStyleInfo style : namedStyle.getFeatureStyles()) {
					if (style.getLayerType() == LayerType.RASTER || style.getLayerType() == LayerType.GEOMETRY) {
						throw new LayerException(ExceptionCode.INVALID_FEATURE_STYLE_LAYER_TYPE, style.getLayerType());
					} else if (style.getLayerType() == null) {
						if (layer.getLayerInfo().getLayerType() != LayerType.GEOMETRY) {
							style.setLayerType(layer.getLayerInfo().getLayerType());
							convertedStyles.add(style);
						} else {
							String geometryName = featureInfo.getGeometryType().getName();
							// we have to convert to 3 styles here !
							convertedStyles.add(createPointStyle(style, geometryName));
							convertedStyles.add(createLineStyle(style, geometryName));
							convertedStyles.add(createPolygonStyle(style, geometryName));
						}
					} else {
						convertedStyles.add(style);
					}
				}
				namedStyle.setFeatureStyles(convertedStyles);
			}
			
			// index styles
			for (NamedStyleInfo namedStyle : info.getNamedStyleInfos()) {
				int i = 0;
				for (FeatureStyleInfo style : namedStyle.getFeatureStyles()) {
					style.setIndex(i++);
					style.setStyleId(namedStyle.getName() + "-" + style.getIndex());
				}
			}
			
			// convert old styles to sld
			for (NamedStyleInfo namedStyle : info.getNamedStyleInfos()) {
				if (namedStyle.getUserStyle() == null) {
					UserStyleInfo userStyle = styleConverterService.convert(namedStyle, featureInfo
							.getGeometryType().getName());
					namedStyle.setUserStyle(userStyle);
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	private void postProcess(String layerId, FeatureInfo featureInfo, String path) throws LayerException {
		List attributes = (List) (List) featureInfo.getAttributes();


		// check for invalid attribute names and post-process association attributes' FeatureInfo
		for (AbstractAttributeInfo attributeInfo : attributes) {
			if (attributeInfo instanceof AssociationAttributeInfo) {
				postProcess(layerId, ((AssociationAttributeInfo) attributeInfo).getFeature(),
						path + "/" + attributeInfo.getName());
			}

			if (attributeInfo.getName().contains(".") || attributeInfo.getName().contains("/")) {
				throw new LayerException(ExceptionCode.INVALID_ATTRIBUTE_NAME, attributeInfo.getName(), layerId);
			}
		}

		// check for duplicate attribute names
		checkDuplicateAttributes(layerId, path, attributes);

		featureInfo.setAttributesMap(toMap(attributes));
	}

	private Map toMap(List attributes) {
		Map map = new LinkedHashMap();
		for (AbstractAttributeInfo attributeInfo : attributes) {
			map.put(attributeInfo.getName(), attributeInfo);
		}
		return map;
	}

	@SuppressWarnings("unchecked")
	private void checkDuplicateAttributes(String layerId, String path, List attributes)
			throws LayerException {
		Set names = new HashSet();
		for (AbstractAttributeInfo attribute : attributes) {
			String name = attribute.getName();
			if (names.contains(name)) {
				throw new LayerException(ExceptionCode.DUPLICATE_ATTRIBUTE_NAME, name, layerId, path);
			}
			names.add(name);
		}
	}

	private FeatureStyleInfo createPointStyle(FeatureStyleInfo style, String geometryName) {
		FeatureStyleInfo copy = new FeatureStyleInfo(style);
		copy.setLayerType(LayerType.POINT);
		copy.setFormula("(geometryType(" + geometryName + ") = 'Point') OR (geometryType(" + geometryName
				+ ") = 'MultiPoint')");
		return copy;
	}

	private FeatureStyleInfo createLineStyle(FeatureStyleInfo style, String geometryName) {
		FeatureStyleInfo copy = new FeatureStyleInfo(style);
		copy.setLayerType(LayerType.LINESTRING);
		copy.setFormula("(geometryType(" + geometryName + ") = 'LineString') OR (geometryType(" + geometryName
				+ ") = 'MultiLineString')");
		return copy;
	}

	private FeatureStyleInfo createPolygonStyle(FeatureStyleInfo style, String geometryName) {
		FeatureStyleInfo copy = new FeatureStyleInfo(style);
		copy.setLayerType(LayerType.POLYGON);
		copy.setFormula("(geometryType(" + geometryName + ") = 'Polygon') OR (geometryType(" + geometryName
				+ ") = 'MultiPolygon')");
		return copy;
	}

	private NamedStyleInfo postProcess(NamedStyleInfo client) {
		// index styles/rules
		int i = 0;
		for (FeatureStyleInfo style : client.getFeatureStyles()) {
			style.setIndex(i++);
			style.setStyleId(client.getName() + "-" + style.getIndex());
		}
		return client;
	}


	private UserStyleInfo extractStyle(StyledLayerDescriptorInfo sld, String sldLayerName, String sldStyleName)
			throws LayerException {
		NamedLayerInfo namedLayerInfo = null;
		UserLayerInfo userLayerInfo = null;
		// find first named layer or find by name
		for (StyledLayerDescriptorInfo.ChoiceInfo choice : sld.getChoiceList()) {
			// we only support named layers, pick the right name or the first one
			if (choice.ifNamedLayer()) {
				if (null != sldLayerName && sldLayerName.equals(choice.getNamedLayer().getName())) {
					namedLayerInfo = choice.getNamedLayer();
					break;
				}
				if (null == namedLayerInfo) {
					namedLayerInfo = choice.getNamedLayer();
				}
			} else if (choice.ifUserLayer()) {
				if (sldLayerName != null && sldLayerName.equals(choice.getUserLayer().getName())) {
					userLayerInfo = choice.getUserLayer();
					break;
				}
				if (namedLayerInfo == null) {
					userLayerInfo = choice.getUserLayer();
				}
			}
		}
		if (null == namedLayerInfo && null == userLayerInfo) {
			throw new LayerException(ExceptionCode.INVALID_SLD, sld.getName(), sldLayerName);
		}

		UserStyleInfo userStyleInfo = null;
		if (namedLayerInfo != null) {
			for (NamedLayerInfo.ChoiceInfo choice : namedLayerInfo.getChoiceList()) {
				// we only support user styles, pick the right name or the first
				if (choice.ifUserStyle()) {
					if (null != sldStyleName  && sldStyleName.equals(choice.getUserStyle().getName())) {
						userStyleInfo = choice.getUserStyle();
						break;
					}
					if (userStyleInfo == null) {
						userStyleInfo = choice.getUserStyle();
					}
				}
			}
		} else {
			for (UserStyleInfo userStyle : userLayerInfo.getUserStyleList()) {
				if (null != sldStyleName  && sldStyleName.equals(userStyle.getName())) {
					userStyleInfo = userStyle;
					break;
				}
				if (userStyleInfo == null) {
					userStyleInfo = userStyle;
				}
			}
		}
		if (userStyleInfo == null) {
			throw new LayerException(ExceptionCode.INVALID_SLD, sld.getName(), sldLayerName);
		} else {
			return userStyleInfo;
		}

	}

	private ClientApplicationInfo postProcess(ClientApplicationInfo client) throws LayerException, BeansException {
		// initialize maps
		for (ClientMapInfo map : client.getMaps()) {
			map.setUnitLength(getUnitLength(map.getCrs(), map.getInitialBounds()));
			// result should be m = (m/inch) / (number/inch)
			map.setPixelLength(METER_PER_INCH / client.getScreenDpi());
			log.debug("Map {} has unit length : {}m, pixel length {}m",
					new Object[] {map.getId(), map.getUnitLength(), map.getPixelLength()});
			// calculate scales
			double pixPerUnit = map.getUnitLength() / map.getPixelLength();
			// if resolutions have been defined the old way, calculate the scale configuration
			if (map.getResolutions().size() > 0) {
				map.getScaleConfiguration().getZoomLevels().clear();
				for (Double resolution : map.getResolutions()) {
					if (map.isResolutionsRelative()) {
						map.getScaleConfiguration().getZoomLevels().add(new ScaleInfo(1., resolution));
					} else {
						map.getScaleConfiguration().getZoomLevels().add(new ScaleInfo(1. / resolution));
					}
				}
				map.getResolutions().clear();
			}
			// convert the scales so we have both relative and pix/unit
			boolean relativeScales = true;
			for (ScaleInfo scale : map.getScaleConfiguration().getZoomLevels()) {
				// add the resolution for deprecated api support
				if (!map.isResolutionsRelative()) {
					map.getResolutions().add(1. / scale.getPixelPerUnit());
				} else {
					map.getResolutions().add(scale.getDenominator() / scale.getNumerator());
				}
			}
			for (ClientLayerInfo layer : map.getLayers()) {
				String layerId = layer.getServerLayerId();
				Layer serverLayer = layerMap.get(layerId);
				if (serverLayer == null) {
					throw new LayerException(ExceptionCode.LAYER_NOT_FOUND, layerId);
				}
				LayerInfo layerInfo = serverLayer.getLayerInfo();
				layer.setLayerInfo(layerInfo);
				layer.setMaxExtent(getClientMaxExtent(map.getCrs(), layer.getCrs(), layerInfo.getMaxExtent(), layerId));
				log.debug("Layer {} has scale range : {}, {}", new Object[] {layer.getId(),
						layer.getMinimumScale().getPixelPerUnit(), layer.getMaximumScale().getPixelPerUnit()});
				log.debug("Layer {} has zoom-to-point scale : {}", layer.getId(),
						layer.getZoomToPointScale().getPixelPerUnit());
				if (layer instanceof ClientVectorLayerInfo) {
					postProcess((ClientVectorLayerInfo) layer);
				}
			}
			checkLayerTree(map);
		}
		return client;
	}

	private ClientVectorLayerInfo postProcess(ClientVectorLayerInfo layer) throws LayerException {
		// copy feature info from server if not explicitly defined
		if (layer.getFeatureInfo() == null) {
			VectorLayerInfo serverInfo = (VectorLayerInfo) layer.getLayerInfo();
			layer.setFeatureInfo(serverInfo.getFeatureInfo());
		}

		return layer;
	}

	private double getUnitLength(String mapCrsKey, Bbox mapBounds) throws LayerException {
		try {
			if (null == mapBounds) {
				throw new LayerException(ExceptionCode.MAP_MAX_EXTENT_MISSING);
			}
			Crs crs = geoService.getCrs2(mapCrsKey);
			GeodeticCalculator calculator = new GeodeticCalculator(crs);
			Coordinate center = new Coordinate(0.5 * (mapBounds.getX() + mapBounds.getMaxX()),
					0.5 * (mapBounds.getY() + mapBounds.getMaxY()));
			calculator.setStartingPosition(new DirectPosition2D(crs, center.getX(), center.getY()));
			calculator.setDestinationPosition(new DirectPosition2D(crs, center.getX() + 1, center.getY()));
			return calculator.getOrthodromicDistance();
		} catch (TransformException e) {
			throw new LayerException(e, ExceptionCode.TRANSFORMER_CREATE_LAYER_TO_MAP_FAILED);
		}
	}

	public Bbox getClientMaxExtent(String mapCrsKey, String layerCrsKey, Bbox serverBbox, String layer)
			throws LayerException {
		if (mapCrsKey.equals(layerCrsKey)) {
			return serverBbox;
		}
		try {
			Crs mapCrs = geoService.getCrs2(mapCrsKey);
			Crs layerCrs = geoService.getCrs2(layerCrsKey);
			Envelope serverEnvelope = converterService.toInternal(serverBbox);
			CrsTransform transformer = geoService.getCrsTransform(layerCrs, mapCrs);
			Bbox res = converterService.toDto(geoService.transform(serverEnvelope, transformer));
			if (Double.isNaN(res.getX()) || Double.isNaN(res.getY()) || Double.isNaN(res.getWidth())
					|| Double.isNaN(res.getHeight())) {
				throw new LayerException(ExceptionCode.LAYER_EXTENT_CANNOT_CONVERT, layer, mapCrsKey);
			}
			return res;
		} catch (GeomajasException e) {
			throw new LayerException(e, ExceptionCode.TRANSFORMER_CREATE_LAYER_TO_MAP_FAILED);
		}
	}

	private void checkLayerTree(ClientMapInfo map) throws BeansException {
		// if the map contains a layer tree, verify that the layers are part of the map
		ClientLayerTreeInfo layerTree = map.getLayerTree();
		if (null != layerTree) {
			checkTreeNode(map, layerTree.getTreeNode());
		}
	}

	private void checkTreeNode(ClientMapInfo map, ClientLayerTreeNodeInfo node) throws BeansException {
		for (ClientLayerInfo layer : node.getLayers()) {
			if (!mapContains(map, layer)) {
				throw new BeanInitializationException(
						"A LayerTreeNodeInfo object can only reference layers which are part of the map, layer "
								+ layer.getId() + " is not part of map " + map.getId() + ".");
			}
		}
		for (ClientLayerTreeNodeInfo child : node.getTreeNodes()) {
			checkTreeNode(map, child);
		}
	}

	private boolean mapContains(ClientMapInfo map, ClientLayerInfo layer) {
		String id = layer.getId();
		boolean res = false;
		if (null != id) {
			for (ClientLayerInfo mapLayer : map.getLayers()) {
				res |= id.equals(mapLayer.getId());
			}
		}
		return res;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy