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