001/* 002 * Copyright 2018 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.holder.impl; 017 018import java.util.Collection; 019import java.util.Locale; 020 021import org.apache.commons.lang3.StringUtils; 022import org.apache.commons.lang3.tuple.ImmutablePair; 023import org.apache.commons.lang3.tuple.Pair; 024import org.xml.sax.ContentHandler; 025import org.xml.sax.SAXException; 026 027import org.ametys.plugins.repository.data.holder.DataHolder; 028import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 029import org.ametys.plugins.repository.data.holder.ModelLessDataHolder; 030import org.ametys.plugins.repository.data.holder.ModifiableDataHolder; 031import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder; 032import org.ametys.plugins.repository.data.holder.ModifiableModelLessDataHolder; 033import org.ametys.plugins.repository.data.holder.group.Composite; 034import org.ametys.plugins.repository.data.holder.group.ModifiableComposite; 035import org.ametys.plugins.repository.data.holder.group.ModifiableRepeater; 036import org.ametys.plugins.repository.data.holder.group.Repeater; 037import org.ametys.plugins.repository.data.holder.group.RepeaterEntry; 038import org.ametys.plugins.repository.metadata.MultilingualString; 039import org.ametys.runtime.model.ModelItem; 040import org.ametys.runtime.model.ModelItemContainer; 041import org.ametys.runtime.model.ModelItemGroup; 042import org.ametys.runtime.model.exception.BadItemTypeException; 043import org.ametys.runtime.model.exception.UndefinedItemPathException; 044import org.ametys.runtime.model.type.ModelItemType; 045 046/** 047 * Helper for implementations of data holder 048 */ 049public final class DataHolderHelper 050{ 051 private DataHolderHelper() 052 { 053 // Empty constructor 054 } 055 056 /** 057 * Checks if there is a model item at the given relative path 058 * @param path path of the model item. This path is relative to the given containers. No matter if it is a definition or data path (with repeater entry positions) 059 * @param itemContainers model item containers where to search if there is a model item 060 * @return <code>true</code> if there is model item at this path, <code>false</code> otherwise 061 */ 062 public static boolean hasModelItem(String path, Collection<? extends ModelItemContainer> itemContainers) 063 { 064 try 065 { 066 getModelItem(path, itemContainers); 067 return true; 068 } 069 catch (UndefinedItemPathException e) 070 { 071 return false; 072 } 073 } 074 075 /** 076 * Retrieves the model item at the given relative path 077 * @param path path of the model item to retrieve. This path is relative to the given containers. No matter if it is a definition or data path (with repeater entry positions) 078 * @param itemContainers model item containers where to search the model item 079 * @return the model item 080 * @throws IllegalArgumentException if the given path is null or empty 081 * @throws UndefinedItemPathException if there is no item defined at the given path in given item containers 082 */ 083 public static ModelItem getModelItem(String path, Collection<? extends ModelItemContainer> itemContainers) throws IllegalArgumentException, UndefinedItemPathException 084 { 085 if (StringUtils.isEmpty(path)) 086 { 087 throw new IllegalArgumentException("Unable to retrieve the model item at the given path. This path is empty."); 088 } 089 090 String definitionPath = getDefinitionPathFromDataPath(path); 091 092 ModelItem item = null; 093 for (ModelItemContainer container : itemContainers) 094 { 095 if (container.hasModelItem(definitionPath)) 096 { 097 item = container.getModelItem(definitionPath); 098 break; 099 } 100 } 101 102 if (item != null) 103 { 104 return item; 105 } 106 else 107 { 108 String absolutePath = path; 109 if (itemContainers.size() == 1) 110 { 111 ModelItemContainer container = itemContainers.iterator().next(); 112 if (container instanceof ModelItemGroup) 113 { 114 absolutePath = ((ModelItemGroup) container).getPath() + ModelItem.ITEM_PATH_SEPARATOR + path; 115 } 116 } 117 118 throw new UndefinedItemPathException("Unable to retrieve the model item at path '" + absolutePath + "'. This path is not defined by the model."); 119 } 120 } 121 122 /** 123 * Retrieves the given dataPath as a definition path (without the repeaterEntry positions) 124 * @param dataPath the data path 125 * @return the definition path 126 */ 127 public static String getDefinitionPathFromDataPath(String dataPath) 128 { 129 return dataPath.replaceAll("\\[[0-9]+\\]", ""); 130 } 131 132 /** 133 * Retrieves the repeater entry represented by the given path segment 134 * @param dataHolder data holder that contains the repeater entry. The data holder must be the direct parent of the repeater 135 * @param repeaterName the name of the repeater 136 * @param entryPosition the position of the entry 137 * @return the repeater entry 138 * @throws BadItemTypeException if the value stored in the repository for the given repeater name is not a repeater 139 */ 140 public static RepeaterEntry getRepeaterEntry(DataHolder dataHolder, String repeaterName, int entryPosition) throws BadItemTypeException 141 { 142 Repeater repeater = dataHolder.getRepeater(repeaterName); 143 if (repeater.hasEntry(entryPosition)) 144 { 145 return repeater.getEntry(entryPosition); 146 } 147 else 148 { 149 return null; 150 } 151 } 152 153 /** 154 * Test if the path is a repeater entry path (for example entries[1]) 155 * @param path the path representing the repeater entry 156 * @return true if the pathSegment is a repeater entry path 157 */ 158 public static boolean isRepeaterEntryPath(String path) 159 { 160 return path.matches(".*(\\[[0-9]+\\])$"); 161 } 162 163 /** 164 * Retrieves the pair of repeater name and entry position of the given path segment 165 * Return <code>null</code> if the given path does not represent a repeater entry 166 * @param pathSegment the path segment representing the repeater entry 167 * @return the pair of repeater name and entry position 168 */ 169 public static Pair<String, Integer> getRepeaterNameAndEntryPosition(String pathSegment) 170 { 171 if (isRepeaterEntryPath(pathSegment)) 172 { 173 String repeaterName = pathSegment.substring(0, pathSegment.indexOf("[")); 174 String entryPositionAsString = pathSegment.substring(pathSegment.indexOf("[") + 1, pathSegment.length() - 1); 175 return new ImmutablePair<>(repeaterName, Integer.parseInt(entryPositionAsString)); 176 } 177 else 178 { 179 return null; 180 } 181 } 182 183 /** 184 * Copies the source {@link DataHolder} to the given {@link ModifiableDataHolder} destination. 185 * @param source the source {@link DataHolder} 186 * @param destination the {@link ModifiableDataHolder} destination 187 */ 188 public static void copyDataHolder(DataHolder source, ModifiableDataHolder destination) 189 { 190 for (String name : source.getDataNames()) 191 { 192 Object value = source instanceof ModelAwareDataHolder ? ((ModelAwareDataHolder) source).getValue(name) : ((ModelLessDataHolder) source).getValue(name); 193 194 if (value instanceof Composite) 195 { 196 ModifiableComposite compositeDestination = destination.getComposite(name, true); 197 ((Composite) value).copyTo(compositeDestination); 198 } 199 else if (value instanceof Repeater) 200 { 201 ModifiableRepeater repeaterDestination = destination.getRepeater(name, true); 202 ((Repeater) value).copyTo(repeaterDestination); 203 } 204 else if (destination instanceof ModifiableModelAwareDataHolder) 205 { 206 ((ModifiableModelAwareDataHolder) destination).setValue(name, value); 207 } 208 else 209 { 210 ((ModifiableModelLessDataHolder) destination).setValue(name, value); 211 } 212 } 213 } 214 215 /** 216 * Generates SAX events for the given {@link DataHolder} 217 * @param dataHolder the {@link DataHolder} to SAX 218 * @param contentHandler the {@link ContentHandler} that will receive the SAX events 219 * @param locale The locale to use for localized data, such as {@link MultilingualString}. Can be <code>null</code> to generate SAX events for all existing {@link Locale}s. 220 * @throws SAXException if an error occurs during the SAX events generation 221 */ 222 public static void dataHolderToSAX(DataHolder dataHolder, ContentHandler contentHandler, Locale locale) throws SAXException 223 { 224 for (String name : dataHolder.getDataNames()) 225 { 226 ModelItemType type = dataHolder instanceof ModelAwareDataHolder ? ((ModelAwareDataHolder) dataHolder).getType(name) : ((ModelLessDataHolder) dataHolder).getType(name); 227 Object value = dataHolder instanceof ModelAwareDataHolder ? ((ModelAwareDataHolder) dataHolder).getValue(name) : ((ModelLessDataHolder) dataHolder).getValueOfType(name, type.getId()); 228 type.valueToSAX(contentHandler, name, value, locale); 229 } 230 } 231 232 /** 233 * Generates SAX events for the data at the given data path in the given {@link DataHolder} 234 * Do not generate any event if there is no values at the given path 235 * @param dataHolder the {@link DataHolder} containing the value to SAX 236 * @param contentHandler the {@link ContentHandler} that will receive the SAX events 237 * @param dataPath the path of the data to SAX 238 * @param tagName the tag name of the SAX event to generate. If <code>null</code> the name of the data is used 239 * @param locale The locale to use for localized data, such as {@link MultilingualString}. Can be <code>null</code> to generate SAX events for all existing {@link Locale}s. 240 * @throws SAXException if an error occurs during the SAX events generation 241 */ 242 public static void dataHolderValueToSAX(DataHolder dataHolder, ContentHandler contentHandler, String dataPath, String tagName, Locale locale) throws SAXException 243 { 244 // Get the value to SAX 245 if (dataHolder.hasValue(dataPath)) 246 { 247 ModelItemType type = dataHolder instanceof ModelAwareDataHolder ? ((ModelAwareDataHolder) dataHolder).getType(dataPath) : ((ModelLessDataHolder) dataHolder).getType(dataPath); 248 Object value = dataHolder instanceof ModelAwareDataHolder ? ((ModelAwareDataHolder) dataHolder).getValue(dataPath) : ((ModelLessDataHolder) dataHolder).getValueOfType(dataPath, type.getId()); 249 250 // Get the tag where to SAX the value 251 String finalTagName = tagName; 252 if (StringUtils.isEmpty(finalTagName)) 253 { 254 // If no specific tag name is given, use the data name 255 if (dataPath.contains(ModelItem.ITEM_PATH_SEPARATOR)) 256 { 257 finalTagName = dataPath.substring(dataPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR)); 258 } 259 else 260 { 261 finalTagName = dataPath; 262 } 263 } 264 265 // Generates SAX events 266 type.valueToSAX(contentHandler, finalTagName, value, locale); 267 } 268 } 269}