001/*
002 *  Copyright 2022 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.script;
017
018import java.lang.reflect.Array;
019import java.util.List;
020
021import javax.jcr.Node;
022import javax.jcr.Property;
023import javax.jcr.PropertyType;
024import javax.jcr.RepositoryException;
025import javax.jcr.Value;
026
027import org.apache.commons.lang3.ArrayUtils;
028import org.apache.commons.lang3.StringUtils;
029
030import org.ametys.plugins.repository.data.holder.DataHolder;
031import org.ametys.plugins.repository.data.holder.ModifiableDataHolder;
032import org.ametys.runtime.model.exception.BadItemTypeException;
033import org.ametys.runtime.model.type.ElementType;
034import org.ametys.runtime.model.type.ModelItemType;
035
036/**
037 * Helper methods to manipulate some objects in the script tool. Arrays and lists are not managed the same way depending of JS engine used in the JVM.
038 */
039public final class RepositoryScriptHelper
040{
041    private RepositoryScriptHelper()
042    {
043        // Utility class
044    }
045    
046    /**
047     * Helper to set ambiguous object values from a node property
048     * @param node the node
049     * @param name the property name
050     * @param values the property values as String[] or Value[]
051     * @throws RepositoryException if an error occurred
052     */
053    public static void setProperty(Node node, String name, Object values) throws RepositoryException
054    {
055        Object javaValues = values;
056        if (values instanceof List)
057        {
058            List<?> list = (List<?>) values;
059            if (list.isEmpty())
060            {
061                javaValues = new Value[0];
062            }
063            else if (list.get(0) instanceof String)
064            {
065                javaValues = list.toArray(new String[list.size()]);
066            }
067            else if (list.get(0) instanceof Value)
068            {
069                javaValues = list.toArray(new Value[list.size()]);
070            }
071        }
072        _setProperty(node, name, javaValues);
073    }
074    
075    private static void _setProperty(Node node, String name, Object values) throws RepositoryException
076    {
077        if (values instanceof String[])
078        {
079            node.setProperty(name, (String[]) values);
080        }
081        else if (values instanceof Value[])
082        {
083            node.setProperty(name, (Value[]) values);
084        }
085        else if (values instanceof String)
086        {
087            node.setProperty(name, (String) values);
088        }
089        else if (values instanceof Boolean)
090        {
091            node.setProperty(name, (Boolean) values);
092        }
093        else if (values instanceof Long)
094        {
095            node.setProperty(name, (Long) values);
096        }
097        else if (values instanceof Integer)
098        {
099            node.setProperty(name, ((Integer) values).longValue());
100        }
101        else if (values instanceof Double)
102        {
103            node.setProperty(name, (Double) values);
104        }
105        else if (values instanceof Float)
106        {
107            node.setProperty(name, ((Float) values).doubleValue());
108        }
109        else
110        {
111            Class< ? > clazz = values.getClass();
112            throw new IllegalArgumentException("argument values (" + values + ") is of unsupported type by RepositoryScriptHelper#setProperty: '" + clazz + "'");
113        }
114    }
115    
116    /**
117     * Helper to convert a single-valued property to a multi-valued property.
118     * This helper checks that property exists and that it is not already multiple.
119     * @param node the node holding the property
120     * @param propertyName the property's name
121     * @return true if changes was made
122     * @throws RepositoryException if an error occurred
123     */
124    public static boolean convertSingleToMultipleProperty (Node node, String propertyName) throws RepositoryException
125    {
126        boolean needSave = false;
127        if (node.hasProperty(propertyName))
128        {
129            Property property = node.getProperty(propertyName);
130            if (!property.getDefinition().isMultiple())
131            {
132                Value value = property.getValue();
133                if (value.getType() == PropertyType.STRING)
134                {
135                    String valueAsStr = value.getString();
136                    property.remove();
137                    
138                    if (!StringUtils.isEmpty(valueAsStr))
139                    {
140                        String[] strArray = value.getString().split(",");
141                        node.setProperty(propertyName, strArray);
142                    }
143                    else
144                    {
145                        node.setProperty(propertyName, new String[0]);
146                    }
147                }
148                else
149                {
150                    Value[] values = ArrayUtils.toArray(value);
151                    node.setProperty(propertyName, values);
152                }
153                
154                needSave = true;
155            }
156        }
157        
158        return needSave;
159    }
160    
161    /**
162     * Set the value of a {@link ModifiableDataHolder}.
163     * If a multiple value is provided as a {@link List} instead of an array, this method will try to convert it into
164     * an array based on the available {@link ElementType} of the {@link DataHolder}.
165     * 
166     * @param dataHolder the data holder to update
167     * @param name the data name to update
168     * @param value the value
169     * @param type the type of the data
170     */
171    @SuppressWarnings("unchecked")
172    public static void setValueOfType(ModifiableDataHolder dataHolder, String name, Object value, String type)
173    {
174        if (value instanceof List list)
175        {
176            ModelItemType modelItemType = dataHolder.getModelItemTypeExtensionPoint().getExtension(type);
177            if (modelItemType == null)
178            {
179                throw new BadItemTypeException("Type '" + type + "'does not exist for the given DataHolder (" + dataHolder.getRepositoryData().toString() + "). Impossible to use setValue with it.");
180            }
181            else if (modelItemType instanceof ElementType elementType)
182            {
183                Class managedClass = elementType.getManagedClass();
184                Object[] array =  (Object[]) Array.newInstance(managedClass, list.size());
185                dataHolder.setValue(name, list.toArray(array), type);
186            }
187            else
188            {
189                throw new BadItemTypeException("Type '" + type + "'is not associated with an ElementType. Impossible to use setValue with it.");
190            }
191        }
192        else
193        {
194            dataHolder.setValue(name, value, type);
195        }
196    }
197    
198    /**
199     * Set the value of a {@link ModifiableDataHolder}.
200     * If a multiple value is provided as a {@link List} instead of an array, this method will try to convert it into
201     * an array based on the managed class of the type of data at path name.
202     * 
203     * @param dataHolder the data holder to update
204     * @param name the data name to update
205     * @param value the value
206     */
207    @SuppressWarnings("unchecked")
208    public static void setValue(ModifiableDataHolder dataHolder, String name, Object value)
209    {
210        if (value instanceof List list)
211        {
212            ModelItemType itemType = dataHolder.getType(name);
213            if (itemType instanceof ElementType elementType)
214            {
215                Class managedClass = elementType.getManagedClass();
216                Object[] array =  (Object[]) Array.newInstance(managedClass, list.size());
217                dataHolder.setValue(name, list.toArray(array));
218            }
219            else
220            {
221                throw new BadItemTypeException("Item at path '" + name + "' is not associated with an ElementType. Impossible to use setValue with it.");
222            }
223        }
224        else
225        {
226            dataHolder.setValue(name, value);
227        }
228    }
229    
230}