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