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