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.util.ArrayList;
019import java.util.Arrays;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Locale;
023import java.util.Map;
024import java.util.stream.Collectors;
025
026import org.apache.avalon.framework.component.Component;
027import org.apache.avalon.framework.configuration.Configuration;
028import org.apache.avalon.framework.configuration.ConfigurationException;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.avalon.framework.service.Serviceable;
032import org.apache.cocoon.xml.AttributesImpl;
033import org.apache.cocoon.xml.XMLUtils;
034import org.apache.commons.lang3.StringUtils;
035import org.xml.sax.ContentHandler;
036import org.xml.sax.SAXException;
037
038import org.ametys.cms.contenttype.ContentTypesHelper;
039import org.ametys.cms.data.ContentValue;
040import org.ametys.cms.repository.Content;
041import org.ametys.core.model.type.AbstractElementType;
042import org.ametys.core.util.DateUtils;
043import org.ametys.plugins.repository.AmetysObjectResolver;
044import org.ametys.runtime.model.exception.BadItemTypeException;
045
046/**
047 * Abstract class for content type of elements
048 */
049public abstract class AbstractContentElementType extends AbstractElementType<ContentValue> implements Component, Serviceable
050{
051    /** Ametys object resolver */
052    protected AmetysObjectResolver _resolver;
053    
054    /** Helper for content types */
055    protected ContentTypesHelper _contentTypesHelper;
056    
057    public void service(ServiceManager manager) throws ServiceException
058    {
059        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
060        _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
061    }
062
063    public ContentValue castValue(String value) throws BadItemTypeException
064    {
065        return new ContentValue(_resolver, value);
066    }
067    
068    @Override
069    public String toString(ContentValue value)
070    {
071        return value.getContentId();
072    }
073    
074    @Override
075    public void valueToSAX(ContentHandler contentHandler, String tagName, Object value, Locale locale) throws SAXException
076    {
077        if (value != null)
078        {
079            if (value instanceof String)
080            {
081                Content content = _resolver.resolveById((String) value);
082                _singleContentToSAX(contentHandler, tagName, content, null);
083            }
084            else if (value instanceof String[])
085            {
086                for (String contentId : (String[]) value)
087                {
088                    Content content = _resolver.resolveById(contentId);
089                    _singleContentToSAX(contentHandler, tagName, content, null);
090                }
091            }
092            else if (value instanceof Content)
093            {
094                _singleContentToSAX(contentHandler, tagName, (Content) value, null);
095            }
096            else if (value instanceof Content[])
097            {
098                for (Content content : (Content[]) value)
099                {
100                    _singleContentToSAX(contentHandler, tagName, content, null);
101                }
102            }
103            else if (value instanceof ContentValue)
104            {
105                _singleContentToSAX(contentHandler, tagName, ((ContentValue) value).getContent(), null);
106            }
107            else if (value instanceof ContentValue[])
108            {
109                for (ContentValue contentWrapper : (ContentValue[]) value)
110                {
111                    _singleContentToSAX(contentHandler, tagName, contentWrapper.getContent(), null);
112                }
113            }
114            else
115            {
116                throw new IllegalArgumentException("Try to sax the non content value '" + value + "' in tag name '" + tagName + "'");
117            }
118        }
119    }
120    
121    private void _singleContentToSAX(ContentHandler contentHandler, String tagName, Content content, Locale locale) throws SAXException
122    {
123        AttributesImpl attrs = new AttributesImpl();
124        attrs.addCDATAAttribute("id", content.getId());
125        attrs.addCDATAAttribute("name", content.getName());
126        attrs.addCDATAAttribute("title", content.getTitle(locale));
127        if (content.getLanguage() != null)
128        {
129            attrs.addCDATAAttribute("language", content.getLanguage());
130        }
131        attrs.addCDATAAttribute("createdAt", DateUtils.dateToString(content.getCreationDate()));
132        attrs.addCDATAAttribute("creator", content.getCreator().getLogin());
133        attrs.addCDATAAttribute("lastModifiedAt", DateUtils.dateToString(content.getLastModified()));
134        
135        XMLUtils.createElement(contentHandler, tagName, attrs);
136    }
137    
138    @Override
139    public ContentValue parseConfiguration(Configuration configuration) throws ConfigurationException
140    {
141        if (configuration != null)
142        {
143            String contentId = configuration.getAttribute("id", null);
144            if (StringUtils.isNotEmpty(contentId))
145            {
146                return new ContentValue(_resolver, contentId);
147            }
148        }
149        
150        return null;
151    }
152    
153    @Override
154    public Object valueToJSONForClient(Object value)
155    {
156        if (value == null)
157        {
158            return value;
159        }
160        else if (value instanceof String)
161        {
162            return _content2Json((String) value, null);
163        }
164        else if (value instanceof String[])
165        {
166            return Arrays.asList((String[]) value).stream()
167                                                  .map(contentId -> _content2Json(contentId, null))
168                                                  .collect(Collectors.toList());
169        }
170        else if (value instanceof Content)
171        {
172            return _content2Json((Content) value, null);
173        }
174        else if (value instanceof Content[])
175        {
176            return Arrays.asList((Content[]) value).stream()
177                                                   .map(content -> _content2Json(content, null))
178                                                   .collect(Collectors.toList());
179        }
180        else if (value instanceof ContentValue)
181        {
182            return _content2Json(((ContentValue) value).getContent(), null);
183        }
184        else if (value instanceof ContentValue[])
185        {
186            return Arrays.asList((ContentValue[]) value).stream()
187                                                          .map(contentWrapper -> _content2Json(contentWrapper.getContent(), null))
188                                                          .collect(Collectors.toList());
189        }
190        else
191        {
192            throw new IllegalArgumentException("Try to convert the non content value '" + value + "' to JSON");
193        }
194    }
195
196    private Map<String, Object> _content2Json(String contentId, Locale locale)
197    {
198        Content content = _resolver.resolveById(contentId);
199        return _content2Json(content, locale);
200    }
201    
202    private Map<String, Object> _content2Json(Content content, Locale locale)
203    {
204        Map<String, Object> contentProperties = new HashMap<>();
205
206        contentProperties.put("id", content.getId());
207        contentProperties.put("title", content.getTitle(locale));
208        contentProperties.put("iconGlyph", StringUtils.defaultString(_contentTypesHelper.getIconGlyph(content)));
209        contentProperties.put("iconDecorator", StringUtils.defaultString(_contentTypesHelper.getIconDecorator(content)));
210        contentProperties.put("smallIcon", StringUtils.defaultString(_contentTypesHelper.getSmallIcon(content)));
211        contentProperties.put("mediumIcon", StringUtils.defaultString(_contentTypesHelper.getMediumIcon(content)));
212        contentProperties.put("largeIcon", StringUtils.defaultString(_contentTypesHelper.getLargeIcon(content)));
213        contentProperties.put("contentTypes", content.getTypes());
214        contentProperties.put("mixins", content.getMixinTypes());
215        
216        return contentProperties;
217    }
218
219    public Object fromJSONForClient(Object json)
220    {
221        if (json == null)
222        {
223            return json;
224        }
225        else if (json instanceof String)
226        {
227            return castValue((String) json);
228        }
229        else if (json instanceof List)
230        {
231            @SuppressWarnings("unchecked")
232            List<String> jsonList = (List<String>) json;
233            List<ContentValue> contentList = new ArrayList<>();
234            for (String singleJSON : jsonList)
235            {
236                contentList.add(castValue(singleJSON));
237            }
238            return contentList.toArray(new ContentValue[contentList.size()]);
239        }
240        else
241        {
242            throw new IllegalArgumentException("Try to convert the non content time JSON object '" + json + "' into a content");
243        }
244    }
245    
246    @Override
247    public boolean isCompatible(Object value)
248    {
249        return super.isCompatible(value)
250            || (value instanceof String && _hasAmetysObjectsForIdentifiers((String) value)) || (value instanceof String[] && _hasAmetysObjectsForIdentifiers((String[]) value))
251            || value instanceof Content || value instanceof Content[];
252    }
253    
254    public boolean isSimple()
255    {
256        return false;
257    }
258    
259    /**
260     * Check if ametys objects exist with the given identifiers
261     * @param identifiers the identifiers
262     * @return <code>true</code> if ametys object exist for each identifier, <code>false</code> otherwise
263     */
264    protected boolean _hasAmetysObjectsForIdentifiers(String... identifiers)
265    {
266        for (String identifier : identifiers)
267        {
268            // If there is at list one identifier that does not match an ametys object => return false
269            if (identifier.indexOf("://") < 0 || !_resolver.hasAmetysObjectForId(identifier))
270            {
271                return false;
272            }
273        }
274        
275        // All identifiers match an ametys object
276        return true;
277    }
278}