001/*
002 *  Copyright 2010 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.plugins.repository.workspace;
017
018import java.io.File;
019import java.io.FileInputStream;
020import java.io.InputStream;
021import java.text.ParseException;
022import java.text.SimpleDateFormat;
023import java.util.Arrays;
024import java.util.Collections;
025import java.util.Date;
026import java.util.HashMap;
027import java.util.Map;
028
029import org.apache.avalon.framework.parameters.ParameterException;
030import org.apache.avalon.framework.parameters.Parameters;
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.apache.cocoon.ProcessingException;
034import org.apache.cocoon.acting.ServiceableAction;
035import org.apache.cocoon.environment.ObjectModelHelper;
036import org.apache.cocoon.environment.Redirector;
037import org.apache.cocoon.environment.Request;
038import org.apache.cocoon.environment.SourceResolver;
039import org.apache.cocoon.servlet.multipart.PartOnDisk;
040import org.apache.commons.lang3.exception.ExceptionUtils;
041
042import org.ametys.core.cocoon.JSonReader;
043import org.ametys.core.util.LambdaUtils;
044import org.ametys.plugins.repository.AmetysObject;
045import org.ametys.plugins.repository.AmetysObjectResolver;
046import org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType;
047import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata;
048import org.ametys.plugins.repository.metadata.ModifiableFile;
049import org.ametys.plugins.repository.metadata.ModifiableFolder;
050import org.ametys.plugins.repository.metadata.ModifiableMetadataAwareAmetysObject;
051import org.ametys.plugins.repository.metadata.ModifiableRichText;
052import org.ametys.runtime.i18n.I18nizableTextParameter;
053import org.ametys.runtime.i18n.I18nizableText;
054
055/**
056 * Set value for a metadata
057 */
058public class SetMetadataAction extends ServiceableAction
059{
060    static SimpleDateFormat _DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
061    private AmetysObjectResolver _resolver;
062    
063    @Override
064    public void service(ServiceManager serviceManager) throws ServiceException
065    {
066        super.service(serviceManager);
067        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
068    }
069    
070    public Map act(Redirector redirector, SourceResolver sourceResolver, Map objectModel, String source, Parameters parameters) throws Exception
071    {
072        Request request = ObjectModelHelper.getRequest(objectModel);
073        
074        String id = null; 
075        String name = null;
076        String compositePath = null;
077        String richtextPath = null;
078        boolean multiple;
079        
080        String type = null;
081        String fileFieldId = null;
082    
083        try
084        {
085            id = parameters.getParameter("id");
086            name = parameters.getParameter("name");
087            compositePath = parameters.getParameter("cPath");
088            richtextPath = parameters.getParameter("rtPath");
089            type = parameters.getParameter("type");
090            fileFieldId = parameters.getParameter("filefieldid");
091            multiple = parameters.getParameterAsBoolean("multiple", false);
092        }
093        catch (ParameterException e)
094        {
095            throw new ProcessingException("Missing at least one parameter", e);
096        }
097        
098        if (getLogger().isInfoEnabled())
099        {
100            getLogger().info("Setting metadata '" + name + "' of the AmetysObject of id '" + id + "'");
101        }
102        
103        AmetysObject ao = null;
104        
105        if (id.equals("/"))
106        {
107            ao = _resolver.resolveByPath("/");
108        }
109        else
110        {
111            ao = _resolver.resolveById(id);
112        }
113
114        String[] values = request.getParameterValues("value");
115        
116        Map<String, Object> result = new HashMap<>();
117        
118        try
119        {
120            ModifiableCompositeMetadata holder = _getHolderInComposite((ModifiableMetadataAwareAmetysObject) ao, compositePath);
121            
122            //changing value of a metadata in a composite
123            if (richtextPath.isEmpty())
124            {
125                _setMetadataOutsideRT(holder, type, name, values, multiple);          
126            }
127            //changing a value inside a richtext
128            else if (richtextPath.split("/").length == 1)
129            {
130                _setMetadataInsideRT(request, holder, name, values[0], fileFieldId, richtextPath);        
131            }
132            else //TODO this part is not tested !!
133            {
134                _setMetadataInsideRTDataFolder(request, holder, name, values[0], fileFieldId, richtextPath);
135            }
136    
137            ((ModifiableMetadataAwareAmetysObject) ao).saveChanges();
138            
139            result.put("success", true);
140        }
141        catch (Exception e)
142        {
143            getLogger().error("Unable to set value " + values[0] + " for metadata '" + name + "'", e);
144            result.put("success", false);
145            
146            Throwable rootCause = ExceptionUtils.getRootCause(e);
147            
148            if (rootCause instanceof ParseException)
149            {
150                Map<String, I18nizableTextParameter> params = Collections.singletonMap("error", new I18nizableText(e.getMessage()));
151                result.put("message", new I18nizableText("plugin.repositoryapp", "PLUGINS_REPOSITORYAPP_CREATE_PROP_ERROR_DATE", params));
152            }
153            else
154            {
155                result.put("message", e.toString());
156            }
157        }
158
159        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
160        return EMPTY_MAP;
161    }
162    
163    private ModifiableCompositeMetadata _getHolderInComposite(ModifiableMetadataAwareAmetysObject ao, String compositePath)
164    {
165        ModifiableCompositeMetadata holder = ao.getMetadataHolder();
166
167        // deep search in composite metadata
168        if (!compositePath.isEmpty())
169        {
170            String[] tokens = compositePath.split("/");        
171            for (int i = 0; i < tokens.length; i++)
172            {
173                holder = holder.getCompositeMetadata(tokens[i]);
174            }
175        }
176        
177        return holder;
178    }
179    
180    private void _setMetadataOutsideRT(ModifiableCompositeMetadata holder, String type, String name, String[] values, boolean multiple) throws Exception
181    {
182        //switch according to type
183        //convert value to correct type if not string and set new value          
184        MetadataType mdType = MetadataType.valueOf(type.toUpperCase());
185        switch (mdType)
186        {
187            case DATE:
188                if (multiple)
189                {
190                    Date[] dateValues = Arrays.stream(values).map(LambdaUtils.wrap(_DATE_FORMAT::parse)).toArray(Date[]::new);
191                    holder.setMetadata(name, dateValues);
192                }
193                else
194                {
195                    Date dateVal = _DATE_FORMAT.parse(values[0]);
196                    holder.setMetadata(name, dateVal);
197                }
198                break;
199            case STRING:
200                if (multiple)
201                {
202                    holder.setMetadata(name, values);
203                }
204                else
205                {
206                    holder.setMetadata(name, values[0]);
207                }
208                break;
209            case LONG:
210                if (multiple)
211                {
212                    long[] longValues = Arrays.stream(values).mapToLong(Long::parseLong).toArray();
213                    holder.setMetadata(name, longValues);
214                }
215                else
216                {
217                    Long longVal = Long.parseLong(values[0]);
218                    holder.setMetadata(name, longVal);
219                }
220                break;
221            case DOUBLE:
222                if (multiple)
223                {
224                    double[] doubleValues = Arrays.stream(values).mapToDouble(Double::parseDouble).toArray();
225                    holder.setMetadata(name, doubleValues);
226                }
227                else
228                {
229                    Double doubleVal = Double.parseDouble(values[0]);
230                    holder.setMetadata(name, doubleVal);
231                }
232                
233                break;
234            case BOOLEAN:
235                Boolean booleanVal = Boolean.parseBoolean(values[0]);
236                holder.setMetadata(name, booleanVal);
237                break;
238            case COMPOSITE:
239
240                break;
241            case BINARY:
242
243                break;
244            case RICHTEXT:
245
246                break;
247            case OBJECT_COLLECTION:
248                // TODO?
249                break;
250            default:
251                break;
252        }
253    }
254    
255    private void _setMetadataInsideRT(Request request, ModifiableCompositeMetadata holder, String name, String value, String fileFieldId, String richtextPath) throws Exception
256    {
257        String[] rtTokens = richtextPath.split("/");
258        ModifiableRichText rt = holder.getRichText(rtTokens[0]);
259        
260        // update correct property
261        if (name.equals("mime-type"))
262        {
263            rt.setMimeType(value);
264        }
265        else if (name.equals("last-modified-date"))
266        {
267            Date dateVal = _DATE_FORMAT.parse(value);
268            rt.setLastModified(dateVal);
269        }
270        else if (name.equals("encoding"))
271        {
272            rt.setEncoding(value);
273        }
274        else if (name.equals("data"))
275        {
276            PartOnDisk uploadedFilePart = (PartOnDisk) request.get(fileFieldId);
277            File uploadedFile = (uploadedFilePart != null) ? uploadedFilePart.getFile() : null;
278            
279            if (uploadedFile != null)
280            {
281                InputStream is = new FileInputStream(uploadedFile);
282                rt.setInputStream(is);
283            }
284        }
285        else if (name.equals("length"))
286        {
287            //not possible to change length
288        }
289    }
290    
291    private void _setMetadataInsideRTDataFolder(Request request, ModifiableCompositeMetadata holder, String name, String value, String fileFieldId, String richtextPath) throws Exception
292    {
293        String[] rtTokens = richtextPath.split("/");
294        ModifiableRichText rt = holder.getRichText(rtTokens[0]);
295        ModifiableFolder folder = rt.getAdditionalDataFolder();
296        
297        for (int i = 2; i < rtTokens.length - 1; i++)
298        {
299            folder = folder.getFolder(rtTokens[i]);
300        }
301        
302        ModifiableFile file = folder.getFile(rtTokens[rtTokens.length - 1]);
303        
304        // update correct property
305        if (name.equals("mime-type"))
306        {
307            file.getResource().setMimeType(value);
308        }
309        else if (name.equals("last-modified-date"))
310        {
311            Date dateVal = _DATE_FORMAT.parse(value);
312            file.getResource().setLastModified(dateVal);
313        }
314        else if (name.equals("encoding"))
315        {
316            file.getResource().setEncoding(value);
317        }
318        else if (name.equals("data"))
319        {
320            PartOnDisk uploadedFilePart = (PartOnDisk) request.get(fileFieldId);
321            File uploadedFile = (uploadedFilePart != null) ? uploadedFilePart.getFile() : null;
322            InputStream is = new FileInputStream(uploadedFile);
323            file.getResource().setInputStream(is);
324        }
325        else if (name.equals("length"))
326        {
327            //not possible to change length
328        }
329    }
330}