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