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}