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.Collection; 021import java.util.Collections; 022import java.util.Comparator; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.List; 026import java.util.Map; 027import java.util.Optional; 028import java.util.Set; 029import java.util.SortedSet; 030import java.util.TreeSet; 031 032import org.apache.cocoon.xml.AttributesImpl; 033import org.apache.cocoon.xml.XMLUtils; 034import org.apache.commons.lang3.StringUtils; 035import org.apache.solr.common.SolrInputDocument; 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038import org.xml.sax.Attributes; 039import org.xml.sax.ContentHandler; 040import org.xml.sax.SAXException; 041 042import org.ametys.cms.content.indexing.solr.SolrFieldNames; 043import org.ametys.cms.data.holder.IndexableDataHolder; 044import org.ametys.cms.data.holder.group.IndexableRepeater; 045import org.ametys.cms.data.holder.group.IndexableRepeaterEntry; 046import org.ametys.cms.data.holder.impl.IndexableDataHolderHelper; 047import org.ametys.cms.data.type.indexing.IndexableDataContext; 048import org.ametys.plugins.repository.data.holder.DataHolder; 049import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 050import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeaterEntry; 051import org.ametys.plugins.repository.data.holder.group.ModifiableRepeater; 052import org.ametys.plugins.repository.data.holder.group.ModifiableRepeaterEntry; 053import org.ametys.plugins.repository.data.holder.group.Repeater; 054import org.ametys.plugins.repository.data.holder.group.RepeaterEntry; 055import org.ametys.plugins.repository.data.holder.values.SynchronizableRepeater; 056import org.ametys.plugins.repository.data.holder.values.SynchronizationContext; 057import org.ametys.plugins.repository.data.repositorydata.RepositoryData; 058import org.ametys.plugins.repository.model.RepeaterDefinition; 059import org.ametys.runtime.model.ModelItem; 060import org.ametys.runtime.model.ModelViewItem; 061import org.ametys.runtime.model.ModelViewItemGroup; 062import org.ametys.runtime.model.ViewHelper; 063import org.ametys.runtime.model.ViewItemAccessor; 064import org.ametys.runtime.model.exception.BadItemTypeException; 065import org.ametys.runtime.model.exception.NotUniqueTypeException; 066import org.ametys.runtime.model.exception.UndefinedItemPathException; 067import org.ametys.runtime.model.exception.UnknownTypeException; 068import org.ametys.runtime.model.type.DataContext; 069 070/** 071 * Class for model aware repeaters 072 */ 073public class DefaultModelAwareRepeater implements IndexableRepeater 074{ 075 private static final Logger __LOGGER = LoggerFactory.getLogger(ModelAwareDataHolder.class); 076 077 /** Definition of this repeater */ 078 protected RepeaterDefinition _definition; 079 080 /** Parent of the current {@link Repeater} */ 081 protected IndexableDataHolder _parent; 082 083 /** Root {@link DataHolder} */ 084 protected IndexableDataHolder _root; 085 086 /** Repository data to use to store entries in the repository */ 087 protected RepositoryData _repositoryData; 088 089 /** 090 * Creates a model aware repeater 091 * @param repositoryData the repository data of the repeater 092 * @param definition the definition of the repeater 093 * @param parent the parent of the created {@link Repeater} 094 * @param root the root {@link DataHolder} 095 */ 096 public DefaultModelAwareRepeater(RepositoryData repositoryData, RepeaterDefinition definition, IndexableDataHolder parent, IndexableDataHolder root) 097 { 098 _repositoryData = repositoryData; 099 _definition = definition; 100 _parent = parent; 101 _root = root; 102 } 103 104 @Override 105 public List<? extends IndexableRepeaterEntry> getEntries() 106 { 107 SortedSet<IndexableRepeaterEntry> entries = new TreeSet<>(new Comparator<ModelAwareRepeaterEntry>() 108 { 109 public int compare(ModelAwareRepeaterEntry entry1, ModelAwareRepeaterEntry entry2) 110 { 111 return Integer.compare(entry1.getPosition(), entry2.getPosition()); 112 } 113 }); 114 115 for (String entryName : _repositoryData.getDataNames()) 116 { 117 IndexableRepeaterEntry entry = getEntry(Integer.parseInt(entryName)); 118 entries.add(entry); 119 } 120 121 return Collections.unmodifiableList(new ArrayList<>(entries)); 122 } 123 124 public IndexableRepeaterEntry getEntry(int position) 125 { 126 if (1 <= position && position <= getSize()) 127 { 128 RepositoryData entryRepositoryData = _repositoryData.getRepositoryData(String.valueOf(position)); 129 return new DefaultModelAwareRepeaterEntry(entryRepositoryData, _definition, this); 130 } 131 else if (-getSize() < position && position <= 0) 132 { 133 // Find the positive equivalent position and call the getEntry method with this position 134 return getEntry(getSize() + position); 135 } 136 else 137 { 138 return null; 139 } 140 } 141 142 public int getSize() 143 { 144 return _repositoryData.getDataNames().size(); 145 } 146 147 public boolean hasEntry(int position) 148 { 149 if (1 <= position) 150 { 151 return _repositoryData.hasValue(String.valueOf(position)); 152 } 153 else 154 { 155 return _repositoryData.hasValue(String.valueOf(getSize() + position)); 156 } 157 } 158 159 /** 160 * Retrieves the repeater's model 161 * @return the repeater's model 162 */ 163 public RepeaterDefinition getModel() 164 { 165 return _definition; 166 } 167 168 public void dataToSAX(ContentHandler contentHandler, String dataPath, DataContext context) throws SAXException 169 { 170 for (ModelAwareRepeaterEntry entry : getEntries()) 171 { 172 XMLUtils.startElement(contentHandler, "entry", _getEntryAttributes(entry)); 173 entry.dataToSAX(contentHandler, dataPath, context); 174 XMLUtils.endElement(contentHandler, "entry"); 175 } 176 } 177 178 public void dataToSAX(ContentHandler contentHandler, DataContext context) throws SAXException, BadItemTypeException 179 { 180 ModelViewItemGroup viewItemGroup = ModelViewItemGroup.of(_definition); 181 dataToSAX(contentHandler, viewItemGroup, context); 182 } 183 184 public void dataToSAX(ContentHandler contentHandler, ViewItemAccessor viewItemAccessor, DataContext context) throws SAXException, BadItemTypeException 185 { 186 for (ModelAwareRepeaterEntry entry : getEntries()) 187 { 188 XMLUtils.startElement(contentHandler, "entry", _getEntryAttributes(entry)); 189 entry.dataToSAX(contentHandler, viewItemAccessor, context); 190 XMLUtils.endElement(contentHandler, "entry"); 191 } 192 } 193 194 public void dataToSAXForEdition(ContentHandler contentHandler, ViewItemAccessor viewItemAccessor, DataContext context) throws SAXException, BadItemTypeException 195 { 196 for (ModelAwareRepeaterEntry entry : getEntries()) 197 { 198 XMLUtils.startElement(contentHandler, "entry", _getEntryAttributes(entry)); 199 entry.dataToSAXForEdition(contentHandler, viewItemAccessor, context); 200 XMLUtils.endElement(contentHandler, "entry"); 201 } 202 } 203 204 private Attributes _getEntryAttributes(ModelAwareRepeaterEntry entry) 205 { 206 AttributesImpl entryAttrs = new AttributesImpl(); 207 String entryName = Integer.toString(entry.getPosition()); 208 entryAttrs.addCDATAAttribute("name", entryName); 209 return entryAttrs; 210 } 211 212 public Map<String, Object> dataToJSON(String dataPath, DataContext context) throws IOException 213 { 214 return _dataToJSON(Optional.of(dataPath), Optional.empty(), context, false); 215 } 216 217 public Map<String, Object> dataToJSON(DataContext context) throws BadItemTypeException 218 { 219 ModelViewItemGroup viewItemGroup = ModelViewItemGroup.of(_definition); 220 return dataToJSON(viewItemGroup, context); 221 } 222 223 public Map<String, Object> dataToJSON(ViewItemAccessor viewItemAccessor, DataContext context) throws BadItemTypeException 224 { 225 return _dataToJSON(Optional.empty(), Optional.of(viewItemAccessor), context, false); 226 } 227 228 public Map<String, Object> dataToJSONForEdition(ViewItemAccessor viewItemAccessor, DataContext context) throws BadItemTypeException 229 { 230 return _dataToJSON(Optional.empty(), Optional.of(viewItemAccessor), context, true); 231 } 232 233 @SuppressWarnings("unchecked") 234 private Map<String, Object> _dataToJSON(Optional<String> dataPath, Optional<ViewItemAccessor> viewItemAccessor, DataContext context, boolean isEdition) throws BadItemTypeException 235 { 236 List<Map<String, Object>> entriesValues = new ArrayList<>(); 237 for (ModelAwareRepeaterEntry entry : getEntries()) 238 { 239 DataContext entryContext = context.cloneContext(); 240 if (StringUtils.isNotEmpty(context.getDataPath())) 241 { 242 entryContext.addSuffixToLastSegment("[" + entry.getPosition() + "]"); 243 } 244 245 Map<String, Object> entryValues = null; 246 if (dataPath.isPresent()) 247 { 248 entryValues = (Map<String, Object>) entry.dataToJSON(dataPath.get(), entryContext); 249 } 250 else if (viewItemAccessor.isPresent()) 251 { 252 entryValues = isEdition 253 ? entry.dataToJSONForEdition(viewItemAccessor.get(), entryContext) 254 : entry.dataToJSON(viewItemAccessor.get(), entryContext); 255 } 256 257 entriesValues.add(entryValues); 258 } 259 260 Map<String, Object> result = new HashMap<>(); 261 result.put("entryCount", getSize()); 262 result.put("entries", entriesValues); 263 result.put("label", _definition.getLabel()); 264 265 Optional.ofNullable(_definition.getHeaderLabel()) 266 .ifPresent(headerLabel -> result.put("header-label", headerLabel)); 267 268 return result; 269 } 270 271 /** 272 * Generates SAX events for the comments of the data in the given view in the current {@link DataHolder} 273 * @param contentHandler the {@link ContentHandler} that will receive the SAX events 274 * @param viewItemAccessor the {@link ViewItemAccessor} referencing the items for which generate SAX events 275 * @throws SAXException if an error occurs during the SAX events generation 276 */ 277 public void commentsToSAX(ContentHandler contentHandler, ViewItemAccessor viewItemAccessor) throws SAXException 278 { 279 for (ModelAwareRepeaterEntry entry : getEntries()) 280 { 281 entry.commentsToSAX(contentHandler, viewItemAccessor); 282 } 283 } 284 285 public void copyTo(ModifiableRepeater repeater, DataContext context) throws UndefinedItemPathException, BadItemTypeException, UnknownTypeException, NotUniqueTypeException 286 { 287 for (RepeaterEntry entry : getEntries()) 288 { 289 DataContext entryContext = context.cloneContext(); 290 if (StringUtils.isNotEmpty(context.getDataPath())) 291 { 292 entryContext.addSuffixToLastSegment("[" + entry.getPosition() + "]"); 293 } 294 295 ModifiableRepeaterEntry entryDestination = repeater.addEntry(entry.getPosition()); 296 entry.copyTo(entryDestination, entryContext); 297 } 298 } 299 300 public List<SolrInputDocument> indexData(SolrInputDocument document, SolrInputDocument rootDocument, String solrFieldPrefix, IndexableDataContext context) throws BadItemTypeException 301 { 302 List<SolrInputDocument> additionalDocuments = new ArrayList<>(); 303 String solrFieldName = solrFieldPrefix + context.getDataPathLastSegment(); 304 305 for (IndexableRepeaterEntry entry : getEntries()) 306 { 307 // Update the context with entry position 308 IndexableDataContext newContext = context.cloneContext() 309 .addSuffixToLastSegment("[" + entry.getPosition() + "]"); 310 311 SolrInputDocument repeaterEntryDoc = new SolrInputDocument(); 312 313 if (!context.indexForFullTextField()) 314 { 315 // Creates a new Solr document for each entry 316 String repeaterEntryDocId = document.getField("id").getFirstValue().toString() + "/" + solrFieldName + "/" + entry.getPosition(); 317 repeaterEntryDoc.addField("id", repeaterEntryDocId); 318 repeaterEntryDoc.addField(SolrFieldNames.DOCUMENT_TYPE, SolrFieldNames.TYPE_REPEATER); 319 repeaterEntryDoc.addField(SolrFieldNames.REPEATER_ENTRY_POSITION, entry.getPosition()); 320 321 document.addField(solrFieldName + "_s_dv", repeaterEntryDocId); 322 } 323 324 // Add the created document to additional documents 325 additionalDocuments.add(repeaterEntryDoc); 326 327 ViewItemAccessor viewItemAccessor = context.getViewItem() 328 .map(ViewItemAccessor.class::cast) 329 .orElse(ViewHelper.createViewItemAccessor(entry.getModel())); 330 additionalDocuments.addAll(IndexableDataHolderHelper.indexData(entry, viewItemAccessor, repeaterEntryDoc, rootDocument, StringUtils.EMPTY, newContext)); 331 } 332 333 return additionalDocuments; 334 } 335 336 public boolean hasDifferences(ViewItemAccessor viewItemAccessor, SynchronizableRepeater repeaterValues, SynchronizationContext context) throws UndefinedItemPathException, BadItemTypeException 337 { 338 return SynchronizableRepeater.Mode.APPEND.equals(repeaterValues.getMode()) 339 ? _hasDifferencesInAppendMode(viewItemAccessor, repeaterValues, context) 340 : SynchronizableRepeater.Mode.REPLACE.equals(repeaterValues.getMode()) 341 ? _hasDifferencesInReplaceMode(viewItemAccessor, repeaterValues, context) 342 : _hasDifferencesInReplaceAllMode(viewItemAccessor, repeaterValues, context); 343 } 344 345 /** 346 * Check if there are differences between the given values and the repeater's entries if {@link SynchronizableRepeater#getMode()} is APPEND 347 * @param viewItemAccessor The {@link ViewItemAccessor} for all items to check 348 * @param repeaterValues the values of the repeater to check 349 * @param context the context of the synchronization 350 * @return <code>true</code> if there are differences, <code>false</code> otherwise 351 * @throws UndefinedItemPathException if an entry's key refers to a data that is not defined by the model 352 * @throws BadItemTypeException if the type defined by the model of one of the entry's key doesn't match the corresponding value 353 */ 354 protected boolean _hasDifferencesInAppendMode(ViewItemAccessor viewItemAccessor, SynchronizableRepeater repeaterValues, SynchronizationContext context) throws UndefinedItemPathException, BadItemTypeException 355 { 356 boolean hasEntriesToAppend = !repeaterValues.getRemovedEntries().isEmpty() || !repeaterValues.getEntries().isEmpty(); 357 358 if (__LOGGER.isDebugEnabled()) 359 { 360 String viewItemPath = viewItemAccessor instanceof ModelViewItem modelViewItem 361 ? ViewHelper.getModelViewItemPath(modelViewItem) 362 : StringUtils.EMPTY; 363 if (hasEntriesToAppend) 364 { 365 __LOGGER.debug("#hasDifferences[{}] differences detected: some entries will be appended", viewItemPath); 366 } 367 else 368 { 369 __LOGGER.debug("#hasDifferences[{}] no difference detected.", viewItemPath); 370 } 371 } 372 373 return hasEntriesToAppend; 374 } 375 376 /** 377 * Check if there are differences between the given values and the repeater's entries if {@link SynchronizableRepeater#getMode()} is REPLACE 378 * @param viewItemAccessor The {@link ViewItemAccessor} for all items to check 379 * @param repeaterValues the values of the repeater to check 380 * @param context the context of the synchronization 381 * @return <code>true</code> if there are differences, <code>false</code> otherwise 382 * @throws UndefinedItemPathException if an entry's key refers to a data that is not defined by the model 383 * @throws BadItemTypeException if the type defined by the model of one of the entry's key doesn't match the corresponding value 384 */ 385 protected boolean _hasDifferencesInReplaceMode(ViewItemAccessor viewItemAccessor, SynchronizableRepeater repeaterValues, SynchronizationContext context) throws UndefinedItemPathException, BadItemTypeException 386 { 387 List<Integer> positions = repeaterValues.getReplacePositions(); 388 List<Map<String, Object>> entriesValues = repeaterValues.getEntries(); 389 390 for (int i = 0; i < positions.size(); i++) 391 { 392 int position = positions.get(i); 393 ModelAwareRepeaterEntry repeaterEntry = getEntry(position); 394 if (repeaterEntry.hasDifferences(viewItemAccessor, entriesValues.get(i), context)) 395 { 396 return true; 397 } 398 } 399 400 // No differences has been found in entries 401 if (__LOGGER.isDebugEnabled()) 402 { 403 String viewItemPath = viewItemAccessor instanceof ModelViewItem modelViewItem 404 ? ViewHelper.getModelViewItemPath(modelViewItem) 405 : StringUtils.EMPTY; 406 __LOGGER.debug("#hasDifferences[{}] no difference detected.", viewItemPath); 407 } 408 return false; 409 } 410 411 /** 412 * Check if there are differences between the given values and the repeater's entries if {@link SynchronizableRepeater#getMode()} is REPLACE_ALL 413 * @param viewItemAccessor The {@link ViewItemAccessor} for all items to check 414 * @param repeaterValues the values of the repeater to check 415 * @param context the context of the synchronization 416 * @return <code>true</code> if there are differences, <code>false</code> otherwise 417 * @throws UndefinedItemPathException if an entry's key refers to a data that is not defined by the model 418 * @throws BadItemTypeException if the type defined by the model of one of the entry's key doesn't match the corresponding value 419 */ 420 protected boolean _hasDifferencesInReplaceAllMode(ViewItemAccessor viewItemAccessor, SynchronizableRepeater repeaterValues, SynchronizationContext context) throws UndefinedItemPathException, BadItemTypeException 421 { 422 List<Map<String, Object>> entriesValues = repeaterValues.getEntries(); 423 if (hasToMoveEntries(repeaterValues.getPositionsMapping(), entriesValues.size())) 424 { 425 if (__LOGGER.isDebugEnabled()) 426 { 427 String viewItemPath = viewItemAccessor instanceof ModelViewItem modelViewItem 428 ? ViewHelper.getModelViewItemPath(modelViewItem) 429 : StringUtils.EMPTY; 430 __LOGGER.debug("#hasDifferences[{}] differences detected: some entries will be moved", viewItemPath); 431 } 432 return true; 433 } 434 435 for (ModelAwareRepeaterEntry repeaterEntry : getEntries()) 436 { 437 int entryIndex = repeaterEntry.getPosition() - 1; 438 Map<String, Object> entryValues = entriesValues.get(entryIndex); 439 if (repeaterEntry.hasDifferences(viewItemAccessor, entryValues, context)) 440 { 441 return true; 442 } 443 } 444 445 // No differences has been found in entries 446 if (__LOGGER.isDebugEnabled()) 447 { 448 String viewItemPath = viewItemAccessor instanceof ModelViewItem modelViewItem 449 ? ViewHelper.getModelViewItemPath(modelViewItem) 450 : StringUtils.EMPTY; 451 __LOGGER.debug("#hasDifferences[{}] no difference detected.", viewItemPath); 452 } 453 return false; 454 } 455 456 public Collection<ModelItem> getDifferences(ViewItemAccessor viewItemAccessor, SynchronizableRepeater repeaterValues, SynchronizationContext context) throws UndefinedItemPathException, BadItemTypeException 457 { 458 return SynchronizableRepeater.Mode.APPEND.equals(repeaterValues.getMode()) 459 ? _getDifferencesInAppendMode(viewItemAccessor, repeaterValues, context) 460 : SynchronizableRepeater.Mode.REPLACE.equals(repeaterValues.getMode()) 461 ? _getDifferencesInReplaceMode(viewItemAccessor, repeaterValues, context) 462 : _getDifferencesInReplaceAllMode(viewItemAccessor, repeaterValues, context); 463 } 464 465 /** 466 * Get the collection of model items where there are differences between the given values and the repeater's entries if {@link SynchronizableRepeater#getMode()} is APPEND 467 * @param viewItemAccessor The {@link ViewItemAccessor} for all items to check 468 * @param repeaterValues the values of the repeater to check 469 * @param context the context of the synchronization 470 * @return a collection of model items with differences 471 * @throws UndefinedItemPathException if a key in the given Map refers to a data that is not defined by the model 472 * @throws BadItemTypeException if the type defined by the model of one of the Map's key doesn't match the corresponding value 473 */ 474 protected Collection<ModelItem> _getDifferencesInAppendMode(ViewItemAccessor viewItemAccessor, SynchronizableRepeater repeaterValues, SynchronizationContext context) throws UndefinedItemPathException, BadItemTypeException 475 { 476 Set<ModelItem> modelItems = new HashSet<>(); 477 478 if (!repeaterValues.getRemovedEntries().isEmpty() || !repeaterValues.getEntries().isEmpty()) 479 { 480 modelItems.addAll(ViewHelper.getModelItems(viewItemAccessor)); 481 } 482 483 return modelItems; 484 } 485 486 /** 487 * Get the collection of model items where there are differences between the given values and the repeater's entries if {@link SynchronizableRepeater#getMode()} is REPLACE 488 * @param viewItemAccessor The {@link ViewItemAccessor} for all items to check 489 * @param repeaterValues the values of the repeater to check 490 * @param context the context of the synchronization 491 * @return a collection of model items with differences 492 * @throws UndefinedItemPathException if a key in the given Map refers to a data that is not defined by the model 493 * @throws BadItemTypeException if the type defined by the model of one of the Map's key doesn't match the corresponding value 494 */ 495 protected Collection<ModelItem> _getDifferencesInReplaceMode(ViewItemAccessor viewItemAccessor, SynchronizableRepeater repeaterValues, SynchronizationContext context) throws UndefinedItemPathException, BadItemTypeException 496 { 497 Set<ModelItem> modelItems = new HashSet<>(); 498 499 List<Integer> positions = repeaterValues.getReplacePositions(); 500 List<Map<String, Object>> entriesValues = repeaterValues.getEntries(); 501 502 for (int i = 0; i < positions.size(); i++) 503 { 504 int position = positions.get(i); 505 ModelAwareRepeaterEntry repeaterEntry = getEntry(position); 506 modelItems.addAll(repeaterEntry.getDifferences(viewItemAccessor, entriesValues.get(i), context)); 507 } 508 509 return modelItems; 510 } 511 512 /** 513 * Get the collection of model items where there are differences between the given values and the repeater's entries if {@link SynchronizableRepeater#getMode()} is REPLACE_ALL 514 * @param viewItemAccessor The {@link ViewItemAccessor} for all items to check 515 * @param repeaterValues the values of the repeater to check 516 * @param context the context of the synchronization 517 * @return a collection of model items with differences 518 * @throws UndefinedItemPathException if a key in the given Map refers to a data that is not defined by the model 519 * @throws BadItemTypeException if the type defined by the model of one of the Map's key doesn't match the corresponding value 520 */ 521 protected Collection<ModelItem> _getDifferencesInReplaceAllMode(ViewItemAccessor viewItemAccessor, SynchronizableRepeater repeaterValues, SynchronizationContext context) throws UndefinedItemPathException, BadItemTypeException 522 { 523 Set<ModelItem> modelItems = new HashSet<>(); 524 525 List<Map<String, Object>> entriesValues = repeaterValues.getEntries(); 526 if (hasToMoveEntries(repeaterValues.getPositionsMapping(), entriesValues.size())) 527 { 528 // Entries moving so everything would be updated 529 modelItems.addAll(ViewHelper.getModelItems(viewItemAccessor)); 530 } 531 else 532 { 533 for (ModelAwareRepeaterEntry repeaterEntry : getEntries()) 534 { 535 int entryIndex = repeaterEntry.getPosition() - 1; 536 Map<String, Object> entryValues = entriesValues.get(entryIndex); 537 modelItems.addAll(repeaterEntry.getDifferences(viewItemAccessor, entryValues, context)); 538 } 539 } 540 541 return modelItems; 542 } 543 544 public boolean hasToMoveEntries(Map<Integer, Integer> positionsMapping, int targetSize) 545 { 546 int initialSize = getSize(); 547 548 if (targetSize != initialSize) 549 { 550 return true; 551 } 552 553 for (Map.Entry<Integer, Integer> mapping : positionsMapping.entrySet()) 554 { 555 if (!mapping.getKey().equals(mapping.getValue())) 556 { 557 return true; 558 } 559 } 560 561 return false; 562 } 563 564 public RepositoryData getRepositoryData() 565 { 566 return _repositoryData; 567 } 568 569 public IndexableDataHolder getParentDataHolder() 570 { 571 return _parent; 572 } 573 574 public IndexableDataHolder getRootDataHolder() 575 { 576 return _root; 577 } 578}