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.Map;
020import java.util.Optional;
021import java.util.stream.Stream;
022
023import javax.xml.transform.TransformerException;
024
025import org.apache.avalon.framework.configuration.Configurable;
026import org.apache.avalon.framework.configuration.Configuration;
027import org.apache.avalon.framework.configuration.ConfigurationException;
028import org.apache.avalon.framework.context.ContextException;
029import org.apache.avalon.framework.context.Contextualizable;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.cocoon.Constants;
033import org.apache.cocoon.environment.Context;
034import org.apache.cocoon.xml.AttributesImpl;
035import org.apache.commons.lang3.StringUtils;
036import org.apache.commons.lang3.tuple.Triple;
037import org.w3c.dom.Element;
038import org.xml.sax.ContentHandler;
039import org.xml.sax.SAXException;
040
041import org.ametys.cms.data.Binary;
042import org.ametys.core.model.type.AbstractElementType;
043import org.ametys.core.model.type.ModelItemTypeHelper;
044import org.ametys.core.upload.Upload;
045import org.ametys.core.upload.UploadManager;
046import org.ametys.core.user.CurrentUserProvider;
047import org.ametys.plugins.repository.data.holder.values.UntouchedValue;
048import org.ametys.runtime.model.ViewItem;
049import org.ametys.runtime.model.compare.DataChangeType;
050import org.ametys.runtime.model.compare.DataChangeTypeDetail;
051import org.ametys.runtime.model.type.DataContext;
052
053/**
054 * Abstract class for binary type of elements
055 */
056public abstract class AbstractBinaryElementType extends AbstractElementType<Binary> implements Configurable, Contextualizable
057{
058    /** Constant for non-modified binary during edition */
059    public static final String UNTOUCHED = "untouched";
060    
061    /** Type of the binary */
062    protected String _binaryType;
063    
064    /** {@link UploadManager} */
065    protected UploadManager _uploadManager;
066    
067    /** {@link CurrentUserProvider} */
068    protected CurrentUserProvider _userProvider;
069    
070    /** Coocon's context */
071    protected Context _context;
072    
073    public void contextualize(org.apache.avalon.framework.context.Context context) throws ContextException
074    {
075        _context = (Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT);
076    }
077    
078    @Override
079    public void service(ServiceManager manager) throws ServiceException
080    {
081        super.service(manager);
082        _uploadManager = (UploadManager) manager.lookup(UploadManager.ROLE);
083        _userProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
084    }
085    
086    public void configure(Configuration configuration) throws ConfigurationException
087    {
088        Configuration typeNode = configuration.getChild("type", false);
089        if (typeNode != null)
090        {
091            _binaryType = typeNode.getValue(null);
092        }
093        
094        if (StringUtils.isBlank(_binaryType))
095        {
096            throw new ConfigurationException("The configuration of binary type should contain a node 'binary-type' with the type of the binary ('metadata', 'explorer', ...)", configuration);
097        }
098    }
099
100    @Override
101    public String toString(Binary value)
102    {
103        throw new UnsupportedOperationException("Unable to convert a binary to a string value");
104    }
105
106    public Object fromJSONForClient(Object json)
107    {
108        if (json == null)
109        {
110            return null;
111        }
112        
113        if (json instanceof Map)
114        {
115            Map data = (Map) json;
116            if (data.isEmpty())
117            {
118                return null;
119            }
120            
121            String id = (String) ((Map) json).get("id");
122            if (id.equals(UNTOUCHED))
123            {
124                return new UntouchedValue();
125            }
126            
127            Upload upload = _uploadManager.getUpload(_userProvider.getUser(), id);
128            return ResourceElementTypeHelper.binaryFromUpload(upload);
129        }
130        
131        throw new IllegalArgumentException("Try to convert the non binary JSON object '" + json + "' into a binary");
132    }
133
134    @Override
135    public boolean isCompatible(Object value)
136    {
137        return super.isCompatible(value) || value instanceof UntouchedValue;
138    }
139    
140    @Override
141    protected Object _singleValueToJSON(Binary value, DataContext context)
142    {
143        return ResourceElementTypeHelper.singleBinaryToJSON(value, _binaryType, context);
144    }
145    
146    @Override
147    protected Binary _singleValueFromXML(Element element, Optional<Object> additionalData) throws TransformerException, IOException
148    {
149        Binary binary = new Binary();
150        ResourceElementTypeHelper.resourceFromXML(binary, element, additionalData, _context);
151        return binary;
152    }
153    
154    @Override
155    protected void _singleValueToSAX(ContentHandler contentHandler, String tagName, Binary binary, Optional<ViewItem> viewItem, DataContext context, AttributesImpl attributes) throws SAXException
156    {
157        ResourceElementTypeHelper.singleBinaryToSAX(contentHandler, tagName, binary, _binaryType, context, attributes);
158    }
159    
160    @Override
161    protected Stream<Triple<DataChangeType, DataChangeTypeDetail, String>> _compareMultipleValues(Binary[] value1, Binary[] value2) throws IOException
162    {
163        throw new UnsupportedOperationException("Unable to compare multiple values of type '" + getId() + "'");
164    }
165    
166    @Override
167    protected Stream<Triple<DataChangeType, DataChangeTypeDetail, String>> _compareSingleValues(Binary value1, Binary value2) throws IOException
168    {
169        if (ModelItemTypeHelper.areSingleObjectsBothNotNullAndDifferents(value1, value2))
170        {
171            return ResourceElementTypeHelper.compareSingleBinaries(value1, value2);
172        }
173        else
174        {
175            return Stream.of(ModelItemTypeHelper.compareSingleObjects(value1, value2, StringUtils.EMPTY))
176                    .filter(Optional::isPresent)
177                    .map(Optional::get);
178        }
179    }
180
181    public boolean isSimple()
182    {
183        return false;
184    }
185    
186    @Override
187    protected boolean _useJSONForEdition()
188    {
189        return true;
190    }
191}