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.plugins.repository.data.type;
017
018import java.lang.reflect.Array;
019
020import org.ametys.plugins.repository.RepositoryConstants;
021import org.ametys.plugins.repository.data.UnknownDataException;
022import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData;
023import org.ametys.plugins.repository.data.repositorydata.RepositoryData;
024import org.ametys.runtime.model.exception.BadItemTypeException;
025
026/**
027 * Interface for complex types of elements stored in the repository
028 * The complex types ares those which need nodes to be stored
029 * @param <T> Type of the element value
030 */
031public interface ComplexRepositoryElementType<T> extends RepositoryElementType<T>
032{
033    /** Data name for storing the type identifier of an element with multiple values*/
034    public static String TYPE_ID_DATA_NAME = "typeId";
035    
036    @SuppressWarnings("unchecked")
037    public default Object read(RepositoryData parentData, String name) throws BadItemTypeException
038    {
039        if (!parentData.hasValue(name))
040        {
041            return null;
042        }
043        
044        if (!isCompatible(parentData, name))
045        {
046            throw new BadItemTypeException("Try to get " + getId() + " value from the non '" + getId() + "' data '" + name + "' on '" + parentData + "'");
047        }
048        
049        if (parentData.isMultiple(name))
050        {
051            RepositoryData multipleParentData = parentData.getRepositoryData(name);
052            
053            return multipleParentData.getDataNames()
054                                     .stream()
055                                     .map(singleDataName -> multipleParentData.getRepositoryData(singleDataName))
056                                     .map(singleData -> readSingleValue(singleData))
057                                     .toArray(size -> (T[]) Array.newInstance(getManagedClass(), size));
058        }
059        else
060        {
061            RepositoryData data = parentData.getRepositoryData(name);
062            return !isSingleValueEmpty(data) ? readSingleValue(data) : null;
063        }
064    }
065    
066    public default boolean hasNonEmptyValue(RepositoryData parentData, String name) throws BadItemTypeException
067    {
068        if (!parentData.hasValue(name))
069        {
070            return false;
071        }
072        
073        if (!isCompatible(parentData, name))
074        {
075            throw new BadItemTypeException("Try to check " + getId() + " value from the non '" + getId() + "' data '" + name + "' on '" + parentData + "'");
076        }
077        
078        if (parentData.isMultiple(name))
079        {
080            RepositoryData multipleParentData = parentData.getRepositoryData(name);
081            
082            return !multipleParentData.getDataNames().isEmpty();
083        }
084        else
085        {
086            RepositoryData data = parentData.getRepositoryData(name);
087            return !isSingleValueEmpty(data);
088        }
089    }
090    
091    /**
092     * Checks if the single value is empty
093     * @param singleValueData repository data containing the value to check
094     * @return <code>true</code> if the value is empty, <code>false</code> otherwise
095     */
096    public boolean isSingleValueEmpty(RepositoryData singleValueData);
097    
098    /**
099     * Read the single value in the given repository data
100     * @param singleValueData repository data containing the value
101     * @return the value. Can return null if the given data does not contain the necessary elements
102     */
103    public T readSingleValue(RepositoryData singleValueData);
104    
105    @SuppressWarnings("unchecked")
106    public default void write(ModifiableRepositoryData parentData, String name, Object value) throws BadItemTypeException
107    {
108        if (value == null)
109        {
110            if (parentData.hasValue(name) && parentData.isMultiple(name))
111            {
112                parentData.removeValue(name);
113                ModifiableRepositoryData multipleParentData = parentData.addRepositoryData(name, RepositoryConstants.MULTIPLE_ITEM_NODETYPE);
114                // Add the type identifier in a property to know what is the type of the data even if there is no value
115                multipleParentData.setValue(TYPE_ID_DATA_NAME, getId(), RepositoryConstants.NAMESPACE_PREFIX_INTERNAL);
116            }
117            else
118            {
119                emptySingleValue(parentData, name);
120            }
121        }
122        else if (getManagedClass().isInstance(value))
123        {
124            if (parentData.hasValue(name) && removeValueBeforeWritingIt())
125            {
126                parentData.removeValue(name);
127            }
128            
129            writeSingleValue(parentData, name, (T) value);
130        }
131        else if (getManagedClassArray().isInstance(value))
132        {
133            ModifiableRepositoryData multipleParentData;
134            
135            if (parentData.hasValue(name) && !removeValueBeforeWritingIt())
136            {
137                multipleParentData = parentData.getRepositoryData(name);
138            }
139            else
140            {
141                if (parentData.hasValue(name))
142                {
143                    parentData.removeValue(name);
144                }
145                
146                multipleParentData = parentData.addRepositoryData(name, RepositoryConstants.MULTIPLE_ITEM_NODETYPE);
147                // Add the type identifier in a property to know what the type of the data even if there is no value
148                multipleParentData.setValue(TYPE_ID_DATA_NAME, getId(), RepositoryConstants.NAMESPACE_PREFIX_INTERNAL);
149            }
150            
151            // Loop on all given values to replace the existing ones
152            for (int i = 0; i < ((T[]) value).length; i++)
153            {
154                T singleValue = ((T[]) value)[i];
155                if (singleValue == null)
156                {
157                    throw new IllegalArgumentException("Try to set a null value into the multiple " + getId() + " data '" + name + "' on '" + parentData + "'");
158                }
159                else
160                {
161                    writeSingleValue(multipleParentData, String.valueOf(i + 1), singleValue);
162                }
163            }
164            
165            // Remove existing entries from the last one to the last of given values
166            for (int i = multipleParentData.getDataNames().size(); i > ((T[]) value).length; i--)
167            {
168                multipleParentData.removeValue(String.valueOf(i));
169            }
170        }
171        else
172        {
173            StringBuilder message = new StringBuilder().append("Try to set the non ").append(getId()).append(" value '").append(value);
174            message.append("' to the ").append(getId()).append(" data '").append(name).append("' on '").append(parentData).append("'");
175            throw new BadItemTypeException(message.toString());
176        }
177    }
178    
179    /**
180     * Determines if the existing value has to be removed before writing the new one
181     * @return <code>true</code> if the previous value has to be removed, <code>false</code> otherwise
182     */
183    public default boolean removeValueBeforeWritingIt()
184    {
185        return true;
186    }
187    
188    /**
189     * Empties the single value into the given repositoryData
190     * This method is called by the {@link #write(ModifiableRepositoryData, String, Object)} method, when the given value is <code>null</code>
191     * @param parentData repository where to empty the single value.
192     * @param name the name of the element to empty
193     */
194    public default void emptySingleValue(ModifiableRepositoryData parentData, String name)
195    {
196        if (removeValueBeforeWritingIt())
197        {
198            if (parentData.hasValue(name))
199            {
200                parentData.removeValue(name);
201            }
202            
203            parentData.addRepositoryData(name, getRepositoryDataType());
204        }
205    }
206    
207    /**
208     * Write the single value into the given repository data
209     * This method is called by the {@link #write(ModifiableRepositoryData, String, Object)} method, once for each value if the value is an array
210     * @param parentData repository where to store the single value.
211     * @param name the name of the element to write
212     * @param value the single value to write. Can be null. In this case, an empty data must be created
213     */
214    public void writeSingleValue(ModifiableRepositoryData parentData, String name, T value);
215    
216    public default boolean isCompatible(RepositoryData parentData, String name) throws UnknownDataException
217    {
218        if (parentData.hasValue(name + EMPTY_METADATA_SUFFIX, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL))
219        {
220            return true;
221        }
222        
223        if (getRepositoryDataType().equals(parentData.getType(name)))
224        {
225            return true;
226        }
227
228        if (RepositoryConstants.MULTIPLE_ITEM_NODETYPE.equals(parentData.getType(name)))
229        {
230            RepositoryData multipleItemNode = parentData.getRepositoryData(name);
231            return getId().equals(multipleItemNode.getString(TYPE_ID_DATA_NAME, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL));
232        }
233            
234        return false;
235    }
236}