001/* 002 * Copyright 2018 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.cms.data.type; 017 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022import java.util.Optional; 023import java.util.stream.Stream; 024 025import org.apache.cocoon.xml.AttributesImpl; 026import org.apache.cocoon.xml.XMLUtils; 027import org.apache.commons.lang3.StringUtils; 028import org.apache.commons.lang3.tuple.Triple; 029import org.w3c.dom.Element; 030import org.xml.sax.ContentHandler; 031import org.xml.sax.SAXException; 032 033import org.ametys.cms.data.Geocode; 034import org.ametys.core.model.type.AbstractElementType; 035import org.ametys.core.model.type.ModelItemTypeHelper; 036import org.ametys.runtime.model.ViewItem; 037import org.ametys.runtime.model.compare.DataChangeType; 038import org.ametys.runtime.model.compare.DataChangeTypeDetail; 039import org.ametys.runtime.model.exception.BadItemTypeException; 040import org.ametys.runtime.model.type.DataContext; 041 042/** 043 * Abstract class for geocode type of elements 044 */ 045public abstract class AbstractGeocodeElementType extends AbstractElementType<Geocode> 046{ 047 /** The separator between the latitude and the longitude for the string representation of a geocode */ 048 protected static final String __SEPARATOR = ","; 049 050 @Override 051 public Geocode convertValue(Object value) throws BadItemTypeException 052 { 053 if (value instanceof String) 054 { 055 String strValue = (String) value; 056 if (StringUtils.isEmpty(strValue)) 057 { 058 return null; 059 } 060 061 if (StringUtils.countMatches(strValue, __SEPARATOR) == 1) 062 { 063 try 064 { 065 Double latitude = Double.valueOf(StringUtils.substringBeforeLast(strValue, __SEPARATOR)); 066 Double longitude = Double.valueOf(StringUtils.substringAfterLast(strValue, __SEPARATOR)); 067 return new Geocode(latitude, longitude); 068 } 069 catch (NumberFormatException e) 070 { 071 throw new BadItemTypeException("Unable to cast '" + value + "' to a geocode", e); 072 } 073 } 074 } 075 076 return super.convertValue(value); 077 } 078 079 @Override 080 public String toString(Geocode value) 081 { 082 if (value != null) 083 { 084 return String.valueOf(value.getLatitude()) + __SEPARATOR + String.valueOf(value.getLongitude()); 085 } 086 else 087 { 088 return null; 089 } 090 } 091 092 @SuppressWarnings("unchecked") 093 public Object fromJSONForClient(Object json, DataContext context) 094 { 095 if (json == null) 096 { 097 return json; 098 } 099 else if (json instanceof Map) 100 { 101 return _json2Geocode((Map<String, Object>) json); 102 } 103 else if (json instanceof List) 104 { 105 List<Geocode> geocodes = new ArrayList<>(); 106 for (Map<String, Object> singleJson : (List<Map<String, Object>>) json) 107 { 108 geocodes.add(_json2Geocode(singleJson)); 109 } 110 return geocodes.toArray(new Geocode[geocodes.size()]); 111 } 112 else 113 { 114 throw new IllegalArgumentException("Try to convert the non Geocode JSON object '" + json + "' into a geocode"); 115 } 116 } 117 118 private Geocode _json2Geocode(Map<String, Object> json) 119 { 120 // If the geocode is empty, return null 121 return Optional.ofNullable(json) 122 .filter(geocode -> !geocode.isEmpty() 123 && geocode.get(Geocode.LATITUDE_IDENTIFIER) != null && geocode.get(Geocode.LATITUDE_IDENTIFIER) instanceof Number 124 && geocode.get(Geocode.LONGITUDE_IDENTIFIER) != null && geocode.get(Geocode.LONGITUDE_IDENTIFIER) instanceof Number) 125 .map(geocode -> new Geocode(((Number) geocode.get(Geocode.LATITUDE_IDENTIFIER)).doubleValue(), ((Number) geocode.get(Geocode.LONGITUDE_IDENTIFIER)).doubleValue())) 126 .orElse(null); 127 } 128 129 @Override 130 protected Object _singleValueToJSON(Geocode value, DataContext context) 131 { 132 Map<String, Object> geocodeInfos = new HashMap<>(); 133 134 geocodeInfos.put(Geocode.LATITUDE_IDENTIFIER, value.getLatitude()); 135 geocodeInfos.put(Geocode.LONGITUDE_IDENTIFIER, value.getLongitude()); 136 137 return geocodeInfos; 138 } 139 140 @Override 141 protected Geocode _singleValueFromXML(Element element, Optional<Object> additionalData) 142 { 143 Optional<Double> latitude = _coordinateFromXML(element, Geocode.LATITUDE_IDENTIFIER); 144 Optional<Double> longitude = _coordinateFromXML(element, Geocode.LONGITUDE_IDENTIFIER); 145 146 if (latitude.isPresent() && longitude.isPresent()) 147 { 148 return new Geocode(latitude.get(), longitude.get()); 149 } 150 else 151 { 152 return null; 153 } 154 } 155 156 private Optional<Double> _coordinateFromXML(Element element, String coordinateIdentifier) 157 { 158 String coordinateAsString = element.getAttribute(coordinateIdentifier); 159 return Optional.ofNullable(coordinateAsString) 160 .filter(StringUtils::isNotEmpty) 161 .map(Double::valueOf); 162 } 163 164 @Override 165 protected void _singleValueToSAX(ContentHandler contentHandler, String tagName, Geocode geocode, Optional<ViewItem> viewItem, DataContext context, AttributesImpl attributes) throws SAXException 166 { 167 AttributesImpl localAttributes = new AttributesImpl(attributes); 168 localAttributes.addCDATAAttribute(Geocode.LATITUDE_IDENTIFIER, geocode.getLatitude().toString()); 169 localAttributes.addCDATAAttribute(Geocode.LONGITUDE_IDENTIFIER, geocode.getLongitude().toString()); 170 XMLUtils.createElement(contentHandler, tagName, localAttributes); 171 } 172 173 @Override 174 protected Stream<Triple<DataChangeType, DataChangeTypeDetail, String>> _compareMultipleValues(Geocode[] value1, Geocode[] value2) 175 { 176 throw new UnsupportedOperationException("Unable to compare multiple values of type '" + getId() + "'"); 177 } 178 179 @Override 180 protected Stream<Triple<DataChangeType, DataChangeTypeDetail, String>> _compareSingleValues(Geocode value1, Geocode value2) 181 { 182 if (ModelItemTypeHelper.areSingleObjectsBothNotNullAndDifferents(value1, value2)) 183 { 184 return Stream.of 185 ( 186 ModelItemTypeHelper.compareSingleNumbers(value1.getLatitude(), value2.getLatitude(), Geocode.LATITUDE_IDENTIFIER), 187 ModelItemTypeHelper.compareSingleNumbers(value1.getLongitude(), value2.getLongitude(), Geocode.LONGITUDE_IDENTIFIER) 188 ) 189 .filter(Optional::isPresent) 190 .map(Optional::get); 191 } 192 else 193 { 194 return Stream.of(ModelItemTypeHelper.compareSingleObjects(value1, value2, StringUtils.EMPTY)) 195 .filter(Optional::isPresent) 196 .map(Optional::get); 197 } 198 } 199 200 public boolean isSimple() 201 { 202 return true; 203 } 204 205 @Override 206 protected boolean _useJSONForEdition() 207 { 208 return true; 209 } 210}