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 or all entries already present in the repository data 152 for (int i = 0; i < Math.max(multipleParentData.getDataNames().size(), ((T[]) value).length); i++) 153 { 154 String entryName = String.valueOf(i + 1); 155 156 if (i < ((T[]) value).length) 157 { 158 T singleValue = ((T[]) value)[i]; 159 if (singleValue == null) 160 { 161 throw new IllegalArgumentException("Try to set a null value into the multiple " + getId() + " data '" + name + "' on '" + parentData + "'"); 162 } 163 else 164 { 165 writeSingleValue(multipleParentData, entryName, singleValue); 166 } 167 } 168 else 169 { 170 multipleParentData.removeValue(entryName); 171 } 172 } 173 } 174 else 175 { 176 StringBuilder message = new StringBuilder().append("Try to set the non ").append(getId()).append(" value '").append(value); 177 message.append("' to the ").append(getId()).append(" data '").append(name).append("' on '").append(parentData).append("'"); 178 throw new BadItemTypeException(message.toString()); 179 } 180 } 181 182 /** 183 * Determines if the existing value has to be removed before writing the new one 184 * @return <code>true</code> if the previous value has to be removed, <code>false</code> otherwise 185 */ 186 public default boolean removeValueBeforeWritingIt() 187 { 188 return true; 189 } 190 191 /** 192 * Empties the single value into the given repositoryData 193 * This method is called by the {@link #write(ModifiableRepositoryData, String, Object)} method, when the given value is <code>null</code> 194 * @param parentData repository where to empty the single value. 195 * @param name the name of the element to empty 196 */ 197 public void emptySingleValue(ModifiableRepositoryData parentData, String name); 198 199 /** 200 * Write the single value into the given repository data 201 * This method is called by the {@link #write(ModifiableRepositoryData, String, Object)} method, once for each value if the value is an array 202 * @param parentData repository where to store the single value. 203 * @param name the name of the element to write 204 * @param value the single value to write. Can be null. In this case, an empty data must be created 205 */ 206 public void writeSingleValue(ModifiableRepositoryData parentData, String name, T value); 207 208 public default boolean isCompatible(RepositoryData parentData, String name) throws UnknownDataException 209 { 210 if (parentData.hasValue(name + EMPTY_METADATA_SUFFIX, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL)) 211 { 212 return true; 213 } 214 215 if (getRepositoryDataType().equals(parentData.getType(name))) 216 { 217 return true; 218 } 219 220 if (RepositoryConstants.MULTIPLE_ITEM_NODETYPE.equals(parentData.getType(name))) 221 { 222 RepositoryData multipleItemNode = parentData.getRepositoryData(name); 223 return getId().equals(multipleItemNode.getString(TYPE_ID_DATA_NAME, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL)); 224 } 225 226 return false; 227 } 228}