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.cms.data.holder.group.impl; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.Comparator; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Optional; 026import java.util.SortedSet; 027import java.util.TreeSet; 028 029import org.apache.cocoon.xml.AttributesImpl; 030import org.apache.cocoon.xml.XMLUtils; 031import org.apache.commons.lang3.StringUtils; 032import org.apache.solr.common.SolrInputDocument; 033import org.xml.sax.Attributes; 034import org.xml.sax.ContentHandler; 035import org.xml.sax.SAXException; 036 037import org.ametys.cms.content.indexing.solr.SolrFieldNames; 038import org.ametys.cms.data.holder.IndexableDataHolder; 039import org.ametys.cms.data.holder.group.IndexableRepeater; 040import org.ametys.cms.data.holder.group.IndexableRepeaterEntry; 041import org.ametys.cms.data.holder.impl.IndexableDataHolderHelper; 042import org.ametys.plugins.repository.data.holder.DataHolder; 043import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeaterEntry; 044import org.ametys.plugins.repository.data.holder.group.ModifiableRepeater; 045import org.ametys.plugins.repository.data.holder.group.ModifiableRepeaterEntry; 046import org.ametys.plugins.repository.data.holder.group.Repeater; 047import org.ametys.plugins.repository.data.holder.group.RepeaterEntry; 048import org.ametys.plugins.repository.data.repositorydata.RepositoryData; 049import org.ametys.plugins.repository.model.RepeaterDefinition; 050import org.ametys.runtime.model.ModelViewItemGroup; 051import org.ametys.runtime.model.ViewItemAccessor; 052import org.ametys.runtime.model.exception.BadItemTypeException; 053import org.ametys.runtime.model.exception.NotUniqueTypeException; 054import org.ametys.runtime.model.exception.UndefinedItemPathException; 055import org.ametys.runtime.model.exception.UnknownTypeException; 056import org.ametys.runtime.model.type.DataContext; 057 058/** 059 * Class for model aware repeaters 060 */ 061public class DefaultModelAwareRepeater implements IndexableRepeater 062{ 063 /** Definition of this repeater */ 064 protected RepeaterDefinition _definition; 065 066 /** Parent of the current {@link Repeater} */ 067 protected IndexableDataHolder _parent; 068 069 /** Root {@link DataHolder} */ 070 protected IndexableDataHolder _root; 071 072 /** Repository data to use to store entries in the repository */ 073 protected RepositoryData _repositoryData; 074 075 /** 076 * Creates a model aware repeater 077 * @param repositoryData the repository data of the repeater 078 * @param parent the parent of the created {@link Repeater} 079 * @param root the root {@link DataHolder} 080 * @param definition the definition of the repeater 081 */ 082 public DefaultModelAwareRepeater(RepositoryData repositoryData, IndexableDataHolder parent, IndexableDataHolder root, RepeaterDefinition definition) 083 { 084 _repositoryData = repositoryData; 085 _definition = definition; 086 _parent = parent; 087 _root = root; 088 } 089 090 @Override 091 public List<? extends IndexableRepeaterEntry> getEntries() 092 { 093 SortedSet<IndexableRepeaterEntry> entries = new TreeSet<>(new Comparator<ModelAwareRepeaterEntry>() 094 { 095 public int compare(ModelAwareRepeaterEntry entry1, ModelAwareRepeaterEntry entry2) 096 { 097 return Integer.compare(entry1.getPosition(), entry2.getPosition()); 098 } 099 }); 100 101 for (String entryName : _repositoryData.getDataNames()) 102 { 103 IndexableRepeaterEntry entry = getEntry(Integer.parseInt(entryName)); 104 entries.add(entry); 105 } 106 107 return Collections.unmodifiableList(new ArrayList<>(entries)); 108 } 109 110 public IndexableRepeaterEntry getEntry(int position) 111 { 112 if (1 <= position && position <= getSize()) 113 { 114 RepositoryData entryRepositoryData = _repositoryData.getRepositoryData(String.valueOf(position)); 115 return new DefaultModelAwareRepeaterEntry(entryRepositoryData, this, _definition); 116 } 117 else if (-getSize() < position && position <= 0) 118 { 119 // Find the positive equivalent position and call the getEntry method with this position 120 return getEntry(getSize() + position); 121 } 122 else 123 { 124 return null; 125 } 126 } 127 128 public int getSize() 129 { 130 return _repositoryData.getDataNames().size(); 131 } 132 133 public boolean hasEntry(int position) 134 { 135 if (1 <= position) 136 { 137 return _repositoryData.hasValue(String.valueOf(position)); 138 } 139 else 140 { 141 return _repositoryData.hasValue(String.valueOf(getSize() + position)); 142 } 143 } 144 145 /** 146 * Retrieves the repeater's model 147 * @return the repeater's model 148 */ 149 public RepeaterDefinition getModel() 150 { 151 return _definition; 152 } 153 154 public void dataToSAX(ContentHandler contentHandler, String dataPath, DataContext context) throws SAXException 155 { 156 for (ModelAwareRepeaterEntry entry : getEntries()) 157 { 158 XMLUtils.startElement(contentHandler, "entry", _getEntryAttributes(entry)); 159 entry.dataToSAX(contentHandler, dataPath, context); 160 XMLUtils.endElement(contentHandler, "entry"); 161 } 162 } 163 164 public void dataToSAX(ContentHandler contentHandler, DataContext context) throws SAXException, BadItemTypeException 165 { 166 ModelViewItemGroup viewItemGroup = ModelViewItemGroup.of(_definition); 167 dataToSAX(contentHandler, viewItemGroup, context); 168 } 169 170 public void dataToSAX(ContentHandler contentHandler, ViewItemAccessor viewItemAccessor, DataContext context) throws SAXException, BadItemTypeException 171 { 172 for (ModelAwareRepeaterEntry entry : getEntries()) 173 { 174 XMLUtils.startElement(contentHandler, "entry", _getEntryAttributes(entry)); 175 entry.dataToSAX(contentHandler, viewItemAccessor, context); 176 XMLUtils.endElement(contentHandler, "entry"); 177 } 178 } 179 180 private Attributes _getEntryAttributes(ModelAwareRepeaterEntry entry) 181 { 182 AttributesImpl entryAttrs = new AttributesImpl(); 183 String entryName = Integer.toString(entry.getPosition()); 184 entryAttrs.addCDATAAttribute("name", entryName); 185 return entryAttrs; 186 } 187 188 public Map<String, Object> dataToJSON(String dataPath, DataContext context) throws IOException 189 { 190 return _dataToJSON(Optional.of(dataPath), Optional.empty(), context); 191 } 192 193 public Map<String, Object> dataToJSON(DataContext context) throws BadItemTypeException 194 { 195 ModelViewItemGroup viewItemGroup = ModelViewItemGroup.of(_definition); 196 return dataToJSON(viewItemGroup, context); 197 } 198 199 public Map<String, Object> dataToJSON(ViewItemAccessor viewItemAccessor, DataContext context) throws BadItemTypeException 200 { 201 return _dataToJSON(Optional.empty(), Optional.of(viewItemAccessor), context); 202 } 203 204 @SuppressWarnings("unchecked") 205 private Map<String, Object> _dataToJSON(Optional<String> dataPath, Optional<ViewItemAccessor> viewItemAccessor, DataContext context) throws BadItemTypeException 206 { 207 List<Map<String, Object>> entriesValues = new ArrayList<>(); 208 for (ModelAwareRepeaterEntry entry : getEntries()) 209 { 210 DataContext entryContext = context.cloneContext(); 211 if (StringUtils.isNotEmpty(context.getDataPath())) 212 { 213 entryContext.addSuffixToLastSegment("[" + entry.getPosition() + "]"); 214 } 215 216 Map<String, Object> entryValues = null; 217 if (dataPath.isPresent()) 218 { 219 entryValues = (Map<String, Object>) entry.dataToJSON(dataPath.get(), entryContext); 220 } 221 else if (viewItemAccessor.isPresent()) 222 { 223 entryValues = entry.dataToJSON(viewItemAccessor.get(), entryContext); 224 } 225 226 entriesValues.add(entryValues); 227 } 228 229 Map<String, Object> result = new HashMap<>(); 230 result.put("entryCount", getSize()); 231 result.put("entries", entriesValues); 232 return result; 233 } 234 235 /** 236 * Generates SAX events for the comments of the data in the given view in the current {@link DataHolder} 237 * @param contentHandler the {@link ContentHandler} that will receive the SAX events 238 * @param viewItemAccessor the {@link ViewItemAccessor} referencing the items for which generate SAX events 239 * @throws SAXException if an error occurs during the SAX events generation 240 */ 241 public void commentsToSAX(ContentHandler contentHandler, ViewItemAccessor viewItemAccessor) throws SAXException 242 { 243 for (ModelAwareRepeaterEntry entry : getEntries()) 244 { 245 entry.commentsToSAX(contentHandler, viewItemAccessor); 246 } 247 } 248 249 public void copyTo(ModifiableRepeater repeater) throws UndefinedItemPathException, BadItemTypeException, UnknownTypeException, NotUniqueTypeException 250 { 251 for (RepeaterEntry entry : getEntries()) 252 { 253 ModifiableRepeaterEntry entryDestination = repeater.addEntry(entry.getPosition()); 254 entry.copyTo(entryDestination); 255 } 256 } 257 258 public List<SolrInputDocument> indexData(SolrInputDocument document, SolrInputDocument rootDocument, String solrFieldPrefix, DataContext context) throws BadItemTypeException 259 { 260 List<SolrInputDocument> additionalDocuments = new ArrayList<>(); 261 String solrFieldName = solrFieldPrefix + context.getDataPathLastSegment(); 262 263 for (IndexableRepeaterEntry entry : getEntries()) 264 { 265 // Update the context with entry position 266 DataContext newContext = context.cloneContext() 267 .addSuffixToLastSegment("[" + entry.getPosition() + "]"); 268 269 // Creates a new Solr document for each entry 270 String repeaterEntryDocId = document.getField("id").getFirstValue().toString() + "/" + solrFieldName + "/" + entry.getPosition(); 271 SolrInputDocument repeaterEntryDoc = new SolrInputDocument(); 272 repeaterEntryDoc.addField("id", repeaterEntryDocId); 273 repeaterEntryDoc.addField(SolrFieldNames.DOCUMENT_TYPE, SolrFieldNames.TYPE_REPEATER); 274 repeaterEntryDoc.addField(SolrFieldNames.REPEATER_ENTRY_POSITION, entry.getPosition()); 275 276 // Add the created document to additional documents 277 additionalDocuments.add(repeaterEntryDoc); 278 document.addField(solrFieldName + "_s_dv", repeaterEntryDocId); 279 280 additionalDocuments.addAll(IndexableDataHolderHelper.indexData(entry, repeaterEntryDoc, rootDocument, StringUtils.EMPTY, newContext)); 281 } 282 283 return additionalDocuments; 284 } 285 286 public RepositoryData getRepositoryData() 287 { 288 return _repositoryData; 289 } 290 291 public IndexableDataHolder getParentDataHolder() 292 { 293 return _parent; 294 } 295 296 public IndexableDataHolder getRootDataHolder() 297 { 298 return _root; 299 } 300}