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