001/* 002 * Copyright 2017 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.content.compare; 017 018import java.io.IOException; 019import java.util.Arrays; 020import java.util.Date; 021import java.util.List; 022import java.util.Locale; 023import java.util.Objects; 024 025import org.apache.avalon.framework.component.Component; 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.avalon.framework.service.Serviceable; 029import org.apache.commons.io.IOUtils; 030import org.apache.commons.lang3.ArrayUtils; 031 032import org.ametys.cms.contenttype.AbstractMetadataSetElement; 033import org.ametys.cms.contenttype.ContentConstants; 034import org.ametys.cms.contenttype.ContentType; 035import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 036import org.ametys.cms.contenttype.ContentTypesHelper; 037import org.ametys.cms.contenttype.Fieldset; 038import org.ametys.cms.contenttype.MetadataDefinition; 039import org.ametys.cms.contenttype.MetadataDefinitionHolder; 040import org.ametys.cms.contenttype.MetadataDefinitionReference; 041import org.ametys.cms.contenttype.MetadataSet; 042import org.ametys.cms.contenttype.MetadataType; 043import org.ametys.cms.contenttype.RepeaterDefinition; 044import org.ametys.cms.repository.Content; 045import org.ametys.core.user.UserIdentity; 046import org.ametys.plugins.repository.AmetysRepositoryException; 047import org.ametys.plugins.repository.metadata.BinaryMetadata; 048import org.ametys.plugins.repository.metadata.CompositeMetadata; 049import org.ametys.plugins.repository.metadata.MultilingualString; 050import org.ametys.plugins.repository.metadata.RichText; 051import org.ametys.runtime.i18n.I18nizableText; 052import org.ametys.runtime.plugin.component.AbstractLogEnabled; 053 054/** 055 * Object used to compare two contents 056 * 057 */ 058public class ContentComparator extends AbstractLogEnabled implements Component, Serviceable 059{ 060 /** The Avalon role */ 061 public static final String ROLE = ContentComparator.class.getName(); 062 063 private ContentTypesHelper _contentTypesHelper; 064 private ContentTypeExtensionPoint _contentTypeExtensionPoint; 065 066 public void service(ServiceManager manager) throws ServiceException 067 { 068 this._contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 069 this._contentTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 070 } 071 072 /** 073 * Compare 2 contents, for all their common metadatas (contents with different contentTypes are rejected) 074 * @param content1 1st content 075 * @param content2 new content 076 * @return {@link ContentComparatorResult} 077 * @throws AmetysRepositoryException repository exception 078 * @throws IOException IO exception 079 */ 080 public ContentComparatorResult compare(Content content1, Content content2) throws AmetysRepositoryException, IOException 081 { 082 return compare(content1, content2, null); 083 } 084 085 /** 086 * Compare 2 contents, for all their common metadatas 087 * @param content1 1st content 088 * @param content2 new content 089 * @param strictCompare true to reject contents with different content types 090 * @return {@link ContentComparatorResult} 091 * @throws AmetysRepositoryException repository exception 092 * @throws IOException IO exception 093 */ 094 public ContentComparatorResult compare(Content content1, Content content2, boolean strictCompare) throws AmetysRepositoryException, IOException 095 { 096 return compare(content1, content2, null, strictCompare); 097 } 098 099 /** 100 * Compare 2 contents, filtering with a metadataSet (contents with different contentTypes are rejected) 101 * @param content1 1st content 102 * @param content2 new content 103 * @param metadataSetName name of the metadataSet 104 * @return {@link ContentComparatorResult} 105 * @throws AmetysRepositoryException repository exception 106 * @throws IOException IO exception 107 */ 108 public ContentComparatorResult compare(Content content1, Content content2, String metadataSetName) throws AmetysRepositoryException, IOException 109 { 110 return compare(content1, content2, metadataSetName, true); 111 } 112 113 /** 114 * Compare 2 contents, filtering with a metadataSet 115 * @param content1 1st content 116 * @param content2 new content 117 * @param metadataSetName name of the metadataSet 118 * @param strictCompare true to reject contents with different content types 119 * @return {@link ContentComparatorResult} 120 * @throws AmetysRepositoryException repository exception 121 * @throws IOException IO exception 122 */ 123 public ContentComparatorResult compare(Content content1, Content content2, String metadataSetName, boolean strictCompare) throws AmetysRepositoryException, IOException 124 { 125 return _compare(content1, content2, metadataSetName, strictCompare); 126 } 127 128 private ContentComparatorResult _compare(Content content1, Content content2, String metadataSetName, boolean strictCompare) throws AmetysRepositoryException, IOException 129 { 130 //if strictCompare, we check if the types are strictly equals 131 List<String> types1 = Arrays.asList(content1.getTypes()); 132 List<String> types2 = Arrays.asList(content2.getTypes()); 133 if (strictCompare && (types1.size() != types2.size() || !types1.containsAll(types2) || !types2.containsAll(types1))) 134 { 135 ContentComparatorResult result = new ContentComparatorResult(content1, content2); 136 result.setNotEquals(); 137 return result; 138 } 139 else 140 { 141 MetadataSet metadataSet = _generateMetadataSet(metadataSetName, content1); 142 ContentComparatorResult result = new ContentComparatorResult(content1, content2, metadataSetName, metadataSet); 143 if (metadataSet != null) 144 { 145 for (AbstractMetadataSetElement abstractMetadataSetElement : metadataSet.getElements()) 146 { 147 _compareMetadatas(result, content1, content1.getMetadataHolder(), content2, content2.getMetadataHolder(), "", abstractMetadataSetElement, ""); 148 } 149 } 150 else 151 { 152 result.setNotEquals(); 153 } 154 155 return result; 156 } 157 } 158 159 /** 160 * Generate a metadataSet using a name (can be null) and a content 161 * @param metadataSetName name of the metadataSet. If null, a __generated__ metadataSet will be created using all metadatas 162 * @param content content where the metadataSet should be retreived 163 * @return MetadataSet from the name, or generated 164 */ 165 private MetadataSet _generateMetadataSet(String metadataSetName, Content content) 166 { 167 if (metadataSetName != null) 168 { 169 return _contentTypesHelper.getMetadataSetForView(metadataSetName, content.getTypes(), content.getMixinTypes()); 170 } 171 else 172 { 173 MetadataSet metadataSet; 174 metadataSet = new MetadataSet(); 175 metadataSet.setName("__generated__"); 176 metadataSet.setLabel(new I18nizableText("Live edition metadataset")); 177 metadataSet.setDescription(new I18nizableText("Live edition metadataset")); 178 metadataSet.setSmallIcon(null); 179 metadataSet.setMediumIcon(null); 180 metadataSet.setLargeIcon(null); 181 metadataSet.setEdition(true); 182 metadataSet.setInternal(true); 183 184 String[] types = ArrayUtils.addAll(content.getTypes(), content.getMixinTypes()); 185 for (String contentTypeId1 : types) 186 { 187 ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId1); 188 _fillMetadataSet(metadataSet, contentType); 189 } 190 191 return metadataSet; 192 } 193 } 194 195 /** 196 * Recursive method to generate a metadataSet 197 * @param metadataSet MetadataSet where definitions will be added 198 * @param rootDefinition holder of the definitions to look for 199 */ 200 private void _fillMetadataSet(AbstractMetadataSetElement metadataSet, MetadataDefinitionHolder rootDefinition) 201 { 202 for (String metadataName : rootDefinition.getMetadataNames()) 203 { 204 MetadataDefinitionReference metaDefRef; 205 //if the metadataSet already contains the definition, no need to create a new one 206 if (metadataSet.hasMetadataDefinitionReference(metadataName)) 207 { 208 metaDefRef = metadataSet.getMetadataDefinitionReference(metadataName); 209 } 210 else 211 { 212 metaDefRef = new MetadataDefinitionReference(metadataName, "__generated__"); 213 metadataSet.addElement(metaDefRef); 214 } 215 216 MetadataDefinition metadataDefinition = rootDefinition.getMetadataDefinition(metadataName); 217 //if the definition is a composite, we need to go deeper 218 if (metadataDefinition.getType() == MetadataType.COMPOSITE) 219 { 220 _fillMetadataSet(metaDefRef, metadataDefinition); 221 } 222 } 223 224 } 225 226 /** 227 * Compare 2 metadata and add the differences in result 228 * @param result global result where differences will be stocked 229 * @param content1 1st content 230 * @param content1CompositeMetadata compositeMetadata fot content1 231 * @param content2 2nd content 232 * @param content2CompositeMetadata compositeMetadata fot content2 233 * @param contentPath path of it's content (inside of composite for example) 234 * @param abstractMetadataSetElement element that will be compared 235 * @param metadataDefinitionPath path in the metadataDefinition (different from contentPath because of repeaters) 236 * @throws AmetysRepositoryException repository exception 237 * @throws IOException IO Exception (comparison of InputStream mainly) 238 */ 239 private void _compareMetadatas(ContentComparatorResult result, Content content1, CompositeMetadata content1CompositeMetadata, Content content2, CompositeMetadata content2CompositeMetadata, String contentPath, AbstractMetadataSetElement abstractMetadataSetElement, String metadataDefinitionPath) throws AmetysRepositoryException, IOException 240 { 241 //Si définition, on compare, si fieldset, on re-boucle 242 if (abstractMetadataSetElement instanceof MetadataDefinitionReference) 243 { 244 MetadataDefinitionReference metadataDefinitionReference = (MetadataDefinitionReference) abstractMetadataSetElement; 245 String metadataName = metadataDefinitionReference.getMetadataName(); 246 MetadataDefinition metadataDefinition1 = _getMetadataDefinition(metadataDefinitionPath + metadataName, content1); 247 MetadataDefinition metadataDefinition2 = _getMetadataDefinition(metadataDefinitionPath + metadataName, content2); 248 if (metadataDefinition1 != null && metadataDefinition2 != null && metadataDefinition1.getReferenceContentType().equals(metadataDefinition2.getReferenceContentType())) 249 { 250 if (_checkMetadataPresence(result, content1CompositeMetadata, content2CompositeMetadata, contentPath, metadataName, true)) 251 { 252 //Si la définition dit que c'est du composite ou du repeater, la il faut aller voir les enfants, et donc faire avancer le path 253 //Sinon, on compare 254 if (metadataDefinition1.getType() == MetadataType.COMPOSITE) 255 { 256 _compareCompositeMetadata(result, metadataDefinition1, metadataDefinitionReference, content1, content1CompositeMetadata, content2, content2CompositeMetadata, contentPath, metadataDefinitionPath); 257 } 258 else 259 { 260 _compareMetadataContent(result, content1CompositeMetadata, content2CompositeMetadata, metadataDefinition1, metadataName, contentPath); 261 } 262 } 263 } 264 else 265 { 266 //TODO voir si on ignore, ou si on dit que c'est différent 267 //TODO voir ce qu'il se passe si d'un côté il y a la définition d'un côté et pas de l'autre 268 } 269 270 } 271 else if (abstractMetadataSetElement instanceof Fieldset) 272 { 273 for (AbstractMetadataSetElement metadata : abstractMetadataSetElement.getElements()) 274 { 275 _compareMetadatas(result, content1, content1CompositeMetadata, content2, content2CompositeMetadata, contentPath, metadata, metadataDefinitionPath); 276 } 277 } 278 } 279 280 /** 281 * Compare 2 composite metadatas (repeater or composite) 282 * @param result global result where differences will be stocked 283 * @param metadataDefinition1 definition of the metadata (from content1) 284 * @param metadataDefinitionReference metadata definition reference 285 * @param content1 1st content 286 * @param content1CompositeMetadata 1st composite metadata 287 * @param content2 2nd content 288 * @param content2CompositeMetadata 2nd composite metadata 289 * @param contentPath path of it's content (inside of composite for example) 290 * @param metadataDefinitionPath path in the metadataDefinition (different from contentPath because of repeaters) 291 * @throws AmetysRepositoryException repository exception 292 * @throws IOException IO Exception 293 */ 294 private void _compareCompositeMetadata(ContentComparatorResult result, MetadataDefinition metadataDefinition1, MetadataDefinitionReference metadataDefinitionReference, Content content1, CompositeMetadata content1CompositeMetadata, Content content2, CompositeMetadata content2CompositeMetadata, String contentPath, String metadataDefinitionPath) throws AmetysRepositoryException, IOException 295 { 296 String metadataName = metadataDefinitionReference.getMetadataName(); 297 if (metadataDefinition1 instanceof RepeaterDefinition) 298 { 299 CompositeMetadata compositeMetadata1 = content1CompositeMetadata.getCompositeMetadata(metadataName); 300 CompositeMetadata compositeMetadata2 = content2CompositeMetadata.getCompositeMetadata(metadataName); 301 String[] metadataNames1 = compositeMetadata1.getMetadataNames(); 302 String[] metadataNames2 = compositeMetadata2.getMetadataNames(); 303 if (metadataNames1.length > metadataNames2.length) 304 { 305 for (int i = metadataNames2.length + 1; i <= metadataNames1.length; i++) 306 { 307 result.setNotEquals(); 308 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName + ContentConstants.METADATA_PATH_SEPARATOR + Integer.toString(i), ChangeType.REMOVED); 309 result.addChange(change); 310 } 311 } 312 else if (metadataNames1.length < metadataNames2.length) 313 { 314 for (int i = metadataNames1.length + 1; i <= metadataNames2.length; i++) 315 { 316 result.setNotEquals(); 317 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName + ContentConstants.METADATA_PATH_SEPARATOR + Integer.toString(i), ChangeType.ADDED); 318 result.addChange(change); 319 } 320 } 321 //Si il y en a plus d'un côté ou de l'autre, on compare quand même ceux qui sont en commun 322 for (int i = 1; i <= Math.min(metadataNames1.length, metadataNames2.length); i++) 323 { 324 String nodeName = Integer.toString(i); 325 if (_checkMetadataPresence(result, compositeMetadata1, compositeMetadata2, contentPath + metadataName, nodeName, true)) 326 { 327 CompositeMetadata subCompositeMetadata1 = compositeMetadata1.getCompositeMetadata(nodeName); 328 CompositeMetadata subCompositeMetadata2 = compositeMetadata2.getCompositeMetadata(nodeName); 329 //Maintenant on boucle sur les métadonnées du noeud 330 for (AbstractMetadataSetElement metadata : metadataDefinitionReference.getElements()) 331 { 332 _compareMetadatas(result, content1, subCompositeMetadata1, content2, subCompositeMetadata2, 333 contentPath + metadataName + ContentConstants.METADATA_PATH_SEPARATOR + nodeName + ContentConstants.METADATA_PATH_SEPARATOR, 334 metadata, metadataDefinitionPath + metadataName + ContentConstants.METADATA_PATH_SEPARATOR); 335 } 336 } 337 } 338 } 339 else 340 { 341 //Composite 342 for (AbstractMetadataSetElement metadata : metadataDefinitionReference.getElements()) 343 { 344 _compareMetadatas(result, content1, content1CompositeMetadata.getCompositeMetadata(metadataName), 345 content2, content2CompositeMetadata.getCompositeMetadata(metadataName), 346 contentPath + metadataName + ContentConstants.METADATA_PATH_SEPARATOR, 347 metadata, 348 metadataDefinitionPath + metadataName + ContentConstants.METADATA_PATH_SEPARATOR); 349 } 350 } 351 } 352 353 /** 354 * Get the definition of a metadata in a content 355 * @param metadataPath path of the metadata 356 * @param content content 357 * @return MetadataDefinition 358 */ 359 private MetadataDefinition _getMetadataDefinition(String metadataPath, Content content) 360 { 361 return _contentTypesHelper.getMetadataDefinition(metadataPath, content); 362 } 363 364 /** 365 * Compare 2 non-composite metadata 366 * @param result global result where differences will be stocked 367 * @param content1CompositeMetadata compositeMetadata fot content1 368 * @param content2CompositeMetadata compositeMetadata fot content2 369 * @param metadataDefinition metadata definition 370 * @param metadataName Metadata Name 371 * @param contentPath path of it's content (inside of composite for example) 372 * @throws AmetysRepositoryException repository exception 373 * @throws IOException IO Exception 374 */ 375 private void _compareMetadataContent(ContentComparatorResult result, CompositeMetadata content1CompositeMetadata, CompositeMetadata content2CompositeMetadata, MetadataDefinition metadataDefinition, String metadataName, String contentPath) throws AmetysRepositoryException, IOException 376 { 377 if (metadataDefinition.isMultiple()) 378 { 379 _compareMultipleMetadata(result, content1CompositeMetadata, content2CompositeMetadata, metadataDefinition, metadataName, contentPath); 380 } 381 else 382 { 383 _compareSingleMetadata(result, content1CompositeMetadata, content2CompositeMetadata, metadataDefinition, metadataName, contentPath); 384 } 385 } 386 387 /** 388 * Compare 2 non-composite non-multivalued metadata 389 * @param result global result where differences will be stocked 390 * @param content1CompositeMetadata compositeMetadata fot content1 391 * @param content2CompositeMetadata compositeMetadata fot content2 392 * @param metadataDefinition path in the metadataDefinition (different from contentPath because of repeaters) 393 * @param metadataName Metadata Name 394 * @param contentPath path of it's content (inside of composite for example) 395 * @throws AmetysRepositoryException repository exception 396 * @throws IOException IO Exception 397 */ 398 private void _compareSingleMetadata(ContentComparatorResult result, CompositeMetadata content1CompositeMetadata, CompositeMetadata content2CompositeMetadata, MetadataDefinition metadataDefinition, String metadataName, String contentPath) throws AmetysRepositoryException, IOException 399 { 400 switch (metadataDefinition.getType()) 401 { 402 case SUB_CONTENT: 403 // On ne compare pas 404 break; 405 case COMPOSITE: 406 // Impossible 407 break; 408 case FILE: 409 _compareFileMetadata(result, content1CompositeMetadata, content2CompositeMetadata, metadataName, contentPath); 410 break; 411 case BINARY: 412 _compareBinaryMetadata(result, content1CompositeMetadata, content2CompositeMetadata, metadataName, contentPath); 413 break; 414 case BOOLEAN: 415 _compareBooleanMetadata(result, content1CompositeMetadata, content2CompositeMetadata, metadataName, contentPath); 416 break; 417 case CONTENT: 418 _compareContentMetadata(result, content1CompositeMetadata, content2CompositeMetadata, metadataName, contentPath); 419 break; 420 case REFERENCE: 421 _compareReferenceMetadata(result, content1CompositeMetadata, content2CompositeMetadata, metadataName, contentPath); 422 break; 423 case USER: 424 _compareUserMetadata(result, content1CompositeMetadata, content2CompositeMetadata, metadataName, contentPath); 425 break; 426 case DATE: 427 case DATETIME: 428 _compareDateMetadata(result, content1CompositeMetadata, content2CompositeMetadata, metadataName, contentPath); 429 break; 430 case DOUBLE: 431 _compareDoubleMetadata(result, content1CompositeMetadata, content2CompositeMetadata, metadataName, contentPath); 432 break; 433 case LONG: 434 _compareLongMetadata(result, content1CompositeMetadata, content2CompositeMetadata, metadataName, contentPath); 435 break; 436 case GEOCODE: 437 _compareGeoCodeMetadata(result, content1CompositeMetadata, content2CompositeMetadata, metadataName, contentPath); 438 break; 439 case RICH_TEXT: 440 _compareRichTextMetadata(result, content1CompositeMetadata, content2CompositeMetadata, metadataName, contentPath); 441 break; 442 case MULTILINGUAL_STRING: 443 _compareMultilingualStringMetadata(result, content1CompositeMetadata, content2CompositeMetadata, metadataName, contentPath); 444 break; 445 case STRING: 446 _compareStringMetadata(result, content1CompositeMetadata, content2CompositeMetadata, metadataName, contentPath); 447 break; 448 default: 449 getLogger().warn("Metadata type '" + metadataDefinition.getType() + " is not supported for content comparison"); 450 break; 451 452 } 453 } 454 455 /** 456 * Compare 2 non-composite multivalued metadata 457 * @param result global result where differences will be stocked 458 * @param content1CompositeMetadata compositeMetadata fot content1 459 * @param content2CompositeMetadata compositeMetadata fot content2 460 * @param metadataDefinition path in the metadataDefinition (different from contentPath because of repeaters) 461 * @param metadataName Metadata Name 462 * @param contentPath path of it's content (inside of composite for example) 463 * @throws AmetysRepositoryException repository exception 464 */ 465 private void _compareMultipleMetadata(ContentComparatorResult result, CompositeMetadata content1CompositeMetadata, CompositeMetadata content2CompositeMetadata, MetadataDefinition metadataDefinition, String metadataName, String contentPath) 466 { 467 switch (metadataDefinition.getType()) 468 { 469 case SUB_CONTENT: 470 //On ne compare pas 471 break; 472 case COMPOSITE: 473 //Impossible 474 break; 475 case FILE: 476 // Pas de liste 477 break; 478 case BINARY: 479 case RICH_TEXT: 480 // Pas de liste 481 break; 482 case BOOLEAN: 483 _compareList(result, content1CompositeMetadata.getBooleanArray(metadataName), content2CompositeMetadata.getBooleanArray(metadataName), metadataName, contentPath); 484 break; 485 case CONTENT: 486 case STRING: 487 _compareList(result, content1CompositeMetadata.getStringArray(metadataName), content2CompositeMetadata.getStringArray(metadataName), metadataName, contentPath); 488 break; 489 case REFERENCE: 490 //Pas de liste 491 break; 492 case USER: 493 _compareList(result, content1CompositeMetadata.getUserArray(metadataName), content2CompositeMetadata.getUserArray(metadataName), metadataName, contentPath); 494 break; 495 case DATE: 496 case DATETIME: 497 _compareList(result, content1CompositeMetadata.getDateArray(metadataName), content2CompositeMetadata.getDateArray(metadataName), metadataName, contentPath); 498 break; 499 case DOUBLE: 500 _compareList(result, content1CompositeMetadata.getDoubleArray(metadataName), content2CompositeMetadata.getDoubleArray(metadataName), metadataName, contentPath); 501 break; 502 case LONG: 503 _compareList(result, content1CompositeMetadata.getLongArray(metadataName), content2CompositeMetadata.getLongArray(metadataName), metadataName, contentPath); 504 break; 505 case GEOCODE: 506 // Pas de liste 507 break; 508 default: 509 getLogger().warn("Metadata type '" + metadataDefinition.getType() + " list is not supported for content comparison"); 510 break; 511 512 } 513 } 514 515 /** 516 * see {@link ContentComparator#_compareList(ContentComparatorResult, Object[], Object[], String, String)} 517 * 518 * @param result Result where diff will be added 519 * @param list1 1st list 520 * @param list2 2nd list 521 * @param metadataName Metadata Name 522 * @param contentPath Content Path 523 */ 524 private void _compareList(ContentComparatorResult result, boolean[] list1, boolean[] list2, String metadataName, String contentPath) 525 { 526 Object[] newList1 = {}; 527 Object[] newList2 = {}; 528 for (boolean b : list1) 529 { 530 newList1 = ArrayUtils.add(newList1, new Boolean(b)); 531 } 532 for (boolean b : list2) 533 { 534 newList2 = ArrayUtils.add(newList2, new Boolean(b)); 535 } 536 _compareList(result, newList1, newList2, metadataName, contentPath); 537 } 538 539 /** 540 * see {@link ContentComparator#_compareList(ContentComparatorResult, Object[], Object[], String, String)} 541 * 542 * @param result Result where diff will be added 543 * @param list1 1st list 544 * @param list2 2nd list 545 * @param metadataName Metadata Name 546 * @param contentPath Content Path 547 */ 548 private void _compareList(ContentComparatorResult result, long[] list1, long[] list2, String metadataName, String contentPath) 549 { 550 Object[] newList1 = {}; 551 Object[] newList2 = {}; 552 for (long l : list1) 553 { 554 newList1 = ArrayUtils.add(newList1, new Long(l)); 555 } 556 for (long l : list2) 557 { 558 newList2 = ArrayUtils.add(newList2, new Long(l)); 559 } 560 _compareList(result, newList1, newList2, metadataName, contentPath); 561 } 562 563 /** 564 * see {@link ContentComparator#_compareList(ContentComparatorResult, Object[], Object[], String, String)} 565 * 566 * @param result Result where diff will be added 567 * @param list1 1st list 568 * @param list2 2nd list 569 * @param metadataName Metadata Name 570 * @param contentPath Content Path 571 */ 572 private void _compareList(ContentComparatorResult result, double[] list1, double[] list2, String metadataName, String contentPath) 573 { 574 Object[] newList1 = {}; 575 Object[] newList2 = {}; 576 for (double d : list1) 577 { 578 newList1 = ArrayUtils.add(newList1, new Double(d)); 579 } 580 for (double d : list2) 581 { 582 newList2 = ArrayUtils.add(newList2, new Double(d)); 583 } 584 _compareList(result, newList1, newList2, metadataName, contentPath); 585 } 586 587 /** 588 * Compare 2 lists of any Object 589 * @param <T> Any Object 590 * @param result result in which the differences will be added 591 * @param list1 1st list 592 * @param list2 2nd list 593 * @param metadataName Used only for the difference definition 594 * @param contentPath Used only for the difference definition 595 */ 596 private <T> void _compareList(ContentComparatorResult result, T[] list1, T[] list2, String metadataName, String contentPath) 597 { 598 if (!Objects.deepEquals(list1, list2)) 599 { 600 ChangeType type = ChangeType.MODIFIED; 601 ChangeTypeDetail detail = ChangeTypeDetail.NONE; 602 if (list1.length == list2.length) 603 { 604 T[] diff = removeAll(list1, list2); 605 if (diff.length == 0) 606 { 607 detail = ChangeTypeDetail.ORDER; 608 } 609 } 610 else 611 { 612 if (containsAllOnce(list2, list1)) 613 { 614 detail = ChangeTypeDetail.MORE; 615 } 616 else if (containsAllOnce(list1, list2)) 617 { 618 detail = ChangeTypeDetail.LESS; 619 } 620 } 621 result.setNotEquals(); 622 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName, type, detail); 623 result.addChange(change); 624 } 625 } 626 627 /** 628 * Remove ONCE all elements from list2 in list1 629 * [A, A, B, C], [A, B] will result [A, C] 630 * @param <T> Any Object 631 * @param list1 origin list 632 * @param list2 list of objects to remove 633 * @return a new list with removed objects 634 */ 635 private static <T> T[] removeAll(T[] list1, T[] list2) 636 { 637 T[] result = list1.clone(); 638 for (T element : list2) 639 { 640 result = ArrayUtils.removeElement(result, element); 641 } 642 return result; 643 } 644 645 /** 646 * Check if all elements in list2 are present only 1 time in list1 647 * [A, B, C], [A, A, B, C] will return false 648 * [A, A, B, C], [A, A, A, B, C] will return false 649 * [A, A, B, C], [A, B, C] will return true 650 * @param <T> Any Object 651 * @param list1 complete list 652 * @param list2 elements to check 653 * @return true if all elements in list2 are once in list1 654 */ 655 private static <T> boolean containsAllOnce(T[] list1, T[] list2) 656 { 657 boolean result = true; 658 T[] listClone = list1.clone(); 659 for (T element : list2) 660 { 661 if (!ArrayUtils.contains(listClone, element)) 662 { 663 result = false; 664 break; 665 } 666 else 667 { 668 listClone = ArrayUtils.removeElement(listClone, element); 669 } 670 } 671 return result; 672 } 673 674 /** 675 * Compare 2 string metadata <br> 676 * <p>{@link ChangeType} can be <br> 677 * {@link ChangeType#ADDED} if metadata is present in content2CompositeMetadata and not in content1CompositeMetadata<br> 678 * {@link ChangeType#MODIFIED} if metadata is present in content2CompositeMetadata and in content1CompositeMetadata<br> 679 * {@link ChangeType#REMOVED}if metadata is not present in content2CompositeMetadata and present in content1CompositeMetadata</p> 680 * <p>{@link ChangeTypeDetail} can be <br> 681 * {@link ChangeTypeDetail#NONE} no particular change (ABCD - OHSGDS)<br> 682 * {@link ChangeTypeDetail#LESS_CONTENT_END} (ABCD - ABC)<br> 683 * {@link ChangeTypeDetail#MORE_CONTENT_END} (ABCD - ABCDZZ)<br> 684 * {@link ChangeTypeDetail#LESS_CONTENT_START} (ABCD - BCD)<br> 685 * {@link ChangeTypeDetail#MORE_CONTENT_START} (ABCD - ZZABCD)<br> 686 * {@link ChangeTypeDetail#LESS_CONTENT} (ABCD - BC)<br> 687 * {@link ChangeTypeDetail#MORE_CONTENT} (ABCD - ZZABCDZZ)</p> 688 * 689 * @param result Result where diff will be added 690 * @param content1CompositeMetadata 1st content 691 * @param content2CompositeMetadata 2nd content 692 * @param metadataName metadata Name 693 * @param contentPath Content Path 694 */ 695 private void _compareStringMetadata(ContentComparatorResult result, CompositeMetadata content1CompositeMetadata, CompositeMetadata content2CompositeMetadata, String metadataName, String contentPath) 696 { 697 String str1 = content1CompositeMetadata.getString(metadataName); 698 String str2 = content2CompositeMetadata.getString(metadataName); 699 700 ContentComparatorChange change = _getChange(contentPath + metadataName, str1, str2); 701 if (change != null) 702 { 703 result.setNotEquals(); 704 result.addChange(change); 705 } 706 } 707 708 private ContentComparatorChange _getChange(String path, String value1, String value2) 709 { 710 if (!value1.equals(value2)) 711 { 712 ChangeType type = ChangeType.MODIFIED; 713 ChangeTypeDetail detail = ChangeTypeDetail.NONE; 714 if (value1.isEmpty() && !value2.isEmpty()) 715 { 716 type = ChangeType.ADDED; 717 } 718 else if (!value1.isEmpty() && value2.isEmpty()) 719 { 720 type = ChangeType.REMOVED; 721 } 722 else if (value1.startsWith(value2)) 723 { 724 detail = ChangeTypeDetail.LESS_CONTENT_END; 725 } 726 else if (value2.startsWith(value1)) 727 { 728 detail = ChangeTypeDetail.MORE_CONTENT_END; 729 } 730 else if (value1.endsWith(value2)) 731 { 732 detail = ChangeTypeDetail.LESS_CONTENT_START; 733 } 734 else if (value2.endsWith(value1)) 735 { 736 detail = ChangeTypeDetail.MORE_CONTENT_START; 737 } 738 else if (value1.contains(value2)) 739 { 740 detail = ChangeTypeDetail.LESS_CONTENT; 741 } 742 else if (value2.contains(value1)) 743 { 744 detail = ChangeTypeDetail.MORE_CONTENT; 745 } 746 return new ContentComparatorChange(path, type, detail); 747 } 748 749 // No change 750 return null; 751 } 752 753 /** 754 * Compare 2 RichText metadata <br> 755 * <p>{@link ChangeType} can be <br> 756 * {@link ChangeType#ADDED} if metadata is present in content2CompositeMetadata and not in content1CompositeMetadata<br> 757 * {@link ChangeType#MODIFIED} if metadata is present in content2CompositeMetadata and in content1CompositeMetadata<br> 758 * {@link ChangeType#REMOVED}if metadata is not present in content2CompositeMetadata and present in content1CompositeMetadata</p> 759 * <p>sub-paths will be added for RichText : encoding, lastModified, mimeType and inputStream</p> 760 * 761 * @param result Result where diff will be added 762 * @param content1CompositeMetadata 1st content 763 * @param content2CompositeMetadata 2nd content 764 * @param metadataName metadata Name 765 * @param contentPath Content Path 766 * @throws AmetysRepositoryException repository exception 767 * @throws IOException IO Exception 768 */ 769 private void _compareRichTextMetadata(ContentComparatorResult result, CompositeMetadata content1CompositeMetadata, CompositeMetadata content2CompositeMetadata, String metadataName, String contentPath) throws AmetysRepositoryException, IOException 770 { 771 RichText object1 = content1CompositeMetadata.getRichText(metadataName); 772 RichText object2 = content2CompositeMetadata.getRichText(metadataName); 773 774 if (object1 != null && object2 != null) 775 { 776 _objectCompare(result, object1.getEncoding(), object2.getEncoding(), contentPath, metadataName + ContentConstants.METADATA_PATH_SEPARATOR + "encoding"); 777 _objectCompare(result, object1.getLastModified(), object2.getLastModified(), contentPath, metadataName + ContentConstants.METADATA_PATH_SEPARATOR + "lastModified"); 778 _objectCompare(result, object1.getMimeType(), object2.getMimeType(), contentPath, metadataName + ContentConstants.METADATA_PATH_SEPARATOR + "mimeType"); 779 780 if (!IOUtils.contentEquals(object1.getInputStream(), object2.getInputStream())) 781 { 782 result.setNotEquals(); 783 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName + ContentConstants.METADATA_PATH_SEPARATOR + "inputStream", ChangeType.MODIFIED); 784 result.addChange(change); 785 } 786 //Note : folders are not compared, they are used for pictures and id of the pictures are in the inputstream, so if something changes, the inputstream changes. 787 } 788 else if (object1 != null && object2 == null) 789 { 790 result.setNotEquals(); 791 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName, ChangeType.REMOVED); 792 result.addChange(change); 793 } 794 else if (object1 == null && object2 != null) 795 { 796 result.setNotEquals(); 797 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName, ChangeType.ADDED); 798 result.addChange(change); 799 } 800 } 801 802 /** 803 * Compare 2 GeoCode metadata <br> 804 * <p>{@link ChangeType} can be <br> 805 * {@link ChangeType#ADDED} if metadata is present in content2CompositeMetadata and not in content1CompositeMetadata<br> 806 * {@link ChangeType#MODIFIED} if metadata is present in content2CompositeMetadata and in content1CompositeMetadata<br> 807 * {@link ChangeType#REMOVED}if metadata is not present in content2CompositeMetadata and present in content1CompositeMetadata</p> 808 * <p>sub-paths will be added for GeoCode : latitude and longitude</p> 809 * <p>For each sub-path, {@link ChangeTypeDetail} can be <br> 810 * {@link ChangeTypeDetail#MORE}<br> 811 * {@link ChangeTypeDetail#LESS}</p> 812 * 813 * @param result Result where diff will be added 814 * @param content1CompositeMetadata 1st content 815 * @param content2CompositeMetadata 2nd content 816 * @param metadataName metadata Name 817 * @param contentPath Content Path 818 */ 819 private void _compareGeoCodeMetadata(ContentComparatorResult result, CompositeMetadata content1CompositeMetadata, CompositeMetadata content2CompositeMetadata, String metadataName, String contentPath) 820 { 821 CompositeMetadata compositeMetadata1 = content1CompositeMetadata.getCompositeMetadata(metadataName); 822 CompositeMetadata compositeMetadata2 = content2CompositeMetadata.getCompositeMetadata(metadataName); 823 824 if (_checkMetadataPresence(result, compositeMetadata1, compositeMetadata2, contentPath + metadataName + ContentConstants.METADATA_PATH_SEPARATOR, "latitude", true)) 825 { 826 double latitude1 = compositeMetadata1.getDouble("latitude"); 827 double latitude2 = compositeMetadata2.getDouble("latitude"); 828 if (latitude1 != latitude2) 829 { 830 ChangeTypeDetail detail = ChangeTypeDetail.NONE; 831 if (latitude2 > latitude1) 832 { 833 detail = ChangeTypeDetail.MORE; 834 } 835 else 836 { 837 detail = ChangeTypeDetail.LESS; 838 } 839 result.setNotEquals(); 840 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName + ContentConstants.METADATA_PATH_SEPARATOR + "latitude", ChangeType.MODIFIED, detail); 841 result.addChange(change); 842 } 843 } 844 if (_checkMetadataPresence(result, compositeMetadata1, compositeMetadata2, contentPath + metadataName + ContentConstants.METADATA_PATH_SEPARATOR, "longitude", true)) 845 { 846 double longitude1 = compositeMetadata1.getDouble("longitude"); 847 double longitude2 = compositeMetadata2.getDouble("longitude"); 848 if (longitude1 != longitude2) 849 { 850 ChangeTypeDetail detail = ChangeTypeDetail.NONE; 851 if (longitude2 > longitude1) 852 { 853 detail = ChangeTypeDetail.MORE; 854 } 855 else 856 { 857 detail = ChangeTypeDetail.LESS; 858 } 859 result.setNotEquals(); 860 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName + ContentConstants.METADATA_PATH_SEPARATOR + "longitude", ChangeType.MODIFIED, detail); 861 result.addChange(change); 862 } 863 } 864 } 865 866 private void _compareMultilingualStringMetadata(ContentComparatorResult result, CompositeMetadata content1CompositeMetadata, CompositeMetadata content2CompositeMetadata, String metadataName, String contentPath) 867 { 868 MultilingualString multilingualString1 = content1CompositeMetadata.getMultilingualString(metadataName); 869 MultilingualString multilingualString2 = content2CompositeMetadata.getMultilingualString(metadataName); 870 871 for (Locale locale : multilingualString1.getLocales()) 872 { 873 String value1 = multilingualString1.getValue(locale); 874 String value2 = multilingualString2.hasLocale(locale) ? multilingualString2.getValue(locale) : ""; 875 876 ContentComparatorChange change = _getChange(contentPath + metadataName + ContentConstants.METADATA_PATH_SEPARATOR + locale.toString(), value1, value2); 877 if (change != null) 878 { 879 result.setNotEquals(); 880 result.addChange(change); 881 } 882 } 883 884 for (Locale locale : multilingualString2.getLocales()) 885 { 886 if (!multilingualString1.hasLocale(locale)) 887 { 888 result.setNotEquals(); 889 result.addChange(new ContentComparatorChange(contentPath + metadataName + ContentConstants.METADATA_PATH_SEPARATOR + locale.toString(), ChangeType.ADDED, ChangeTypeDetail.NONE)); 890 } 891 } 892 } 893 894 /** 895 * Compare 2 Long metadata <br> 896 * <p>{@link ChangeType} can be <br> 897 * {@link ChangeType#ADDED} if metadata is present in content2CompositeMetadata and not in content1CompositeMetadata<br> 898 * {@link ChangeType#MODIFIED} if metadata is present in content2CompositeMetadata and in content1CompositeMetadata<br> 899 * {@link ChangeType#REMOVED}if metadata is not present in content2CompositeMetadata and present in content1CompositeMetadata</p> 900 * <p>{@link ChangeTypeDetail} can be <br> 901 * {@link ChangeTypeDetail#MORE}<br> 902 * {@link ChangeTypeDetail#LESS}</p> 903 * 904 * @param result Result where diff will be added 905 * @param content1CompositeMetadata 1st content 906 * @param content2CompositeMetadata 2nd content 907 * @param metadataName metadata Name 908 * @param contentPath Content Path 909 */ 910 private void _compareLongMetadata(ContentComparatorResult result, CompositeMetadata content1CompositeMetadata, CompositeMetadata content2CompositeMetadata, String metadataName, String contentPath) 911 { 912 long long1 = content1CompositeMetadata.getLong(metadataName); 913 long long2 = content2CompositeMetadata.getLong(metadataName); 914 if (long1 != long2) 915 { 916 ChangeTypeDetail detail = ChangeTypeDetail.NONE; 917 if (long2 > long1) 918 { 919 detail = ChangeTypeDetail.MORE; 920 } 921 else 922 { 923 detail = ChangeTypeDetail.LESS; 924 } 925 result.setNotEquals(); 926 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName, ChangeType.MODIFIED, detail); 927 result.addChange(change); 928 } 929 } 930 931 /** 932 * Compare 2 Double metadata <br> 933 * <p>{@link ChangeType} can be <br> 934 * {@link ChangeType#ADDED} if metadata is present in content2CompositeMetadata and not in content1CompositeMetadata<br> 935 * {@link ChangeType#MODIFIED} if metadata is present in content2CompositeMetadata and in content1CompositeMetadata<br> 936 * {@link ChangeType#REMOVED}if metadata is not present in content2CompositeMetadata and present in content1CompositeMetadata</p> 937 * <p>{@link ChangeTypeDetail} can be <br> 938 * {@link ChangeTypeDetail#MORE}<br> 939 * {@link ChangeTypeDetail#LESS}</p> 940 * 941 * @param result Result where diff will be added 942 * @param content1CompositeMetadata 1st content 943 * @param content2CompositeMetadata 2nd content 944 * @param metadataName metadata Name 945 * @param contentPath Content Path 946 */ 947 private void _compareDoubleMetadata(ContentComparatorResult result, CompositeMetadata content1CompositeMetadata, CompositeMetadata content2CompositeMetadata, String metadataName, String contentPath) 948 { 949 double double1 = content1CompositeMetadata.getDouble(metadataName); 950 double double2 = content2CompositeMetadata.getDouble(metadataName); 951 if (double1 != double2) 952 { 953 ChangeTypeDetail detail = ChangeTypeDetail.NONE; 954 if (double2 > double1) 955 { 956 detail = ChangeTypeDetail.MORE; 957 } 958 else 959 { 960 detail = ChangeTypeDetail.LESS; 961 } 962 result.setNotEquals(); 963 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName, ChangeType.MODIFIED, detail); 964 result.addChange(change); 965 } 966 } 967 968 /** 969 * Compare 2 Date metadata <br> 970 * <p>{@link ChangeType} can be <br> 971 * {@link ChangeType#ADDED} if metadata is present in content2CompositeMetadata and not in content1CompositeMetadata<br> 972 * {@link ChangeType#MODIFIED} if metadata is present in content2CompositeMetadata and in content1CompositeMetadata<br> 973 * {@link ChangeType#REMOVED}if metadata is not present in content2CompositeMetadata and present in content1CompositeMetadata</p> 974 * <p>{@link ChangeTypeDetail} can be <br> 975 * {@link ChangeTypeDetail#AFTER}<br> 976 * {@link ChangeTypeDetail#BEFORE}</p> 977 * 978 * @param result Result where diff will be added 979 * @param content1CompositeMetadata 1st content 980 * @param content2CompositeMetadata 2nd content 981 * @param metadataName metadata Name 982 * @param contentPath Content Path 983 */ 984 private void _compareDateMetadata(ContentComparatorResult result, CompositeMetadata content1CompositeMetadata, CompositeMetadata content2CompositeMetadata, String metadataName, String contentPath) 985 { 986 Date date1 = content1CompositeMetadata.getDate(metadataName); 987 Date date2 = content2CompositeMetadata.getDate(metadataName); 988 if (!date1.equals(date2)) 989 { 990 ChangeTypeDetail detail = ChangeTypeDetail.NONE; 991 if (date2.before(date1)) 992 { 993 detail = ChangeTypeDetail.BEFORE; 994 } 995 else 996 { 997 detail = ChangeTypeDetail.AFTER; 998 } 999 result.setNotEquals(); 1000 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName, ChangeType.MODIFIED, detail); 1001 result.addChange(change); 1002 } 1003 } 1004 1005 /** 1006 * Compare 2 Content metadata <br> 1007 * <p>{@link ChangeType} can be <br> 1008 * {@link ChangeType#ADDED} if metadata is present in content2CompositeMetadata and not in content1CompositeMetadata<br> 1009 * {@link ChangeType#MODIFIED} if metadata is present in content2CompositeMetadata and in content1CompositeMetadata<br> 1010 * {@link ChangeType#REMOVED}if metadata is not present in content2CompositeMetadata and present in content1CompositeMetadata</p> 1011 * 1012 * @param result Result where diff will be added 1013 * @param content1CompositeMetadata 1st content 1014 * @param content2CompositeMetadata 2nd content 1015 * @param metadataName metadata Name 1016 * @param contentPath Content Path 1017 */ 1018 private void _compareContentMetadata(ContentComparatorResult result, CompositeMetadata content1CompositeMetadata, CompositeMetadata content2CompositeMetadata, String metadataName, String contentPath) 1019 { 1020 String obj1 = content1CompositeMetadata.getString(metadataName); 1021 String obj2 = content2CompositeMetadata.getString(metadataName); 1022 _objectCompare(result, obj1, obj2, contentPath, metadataName); 1023 } 1024 1025 /** 1026 * Compare 2 Reference metadata <br> 1027 * <p>{@link ChangeType} can be <br> 1028 * {@link ChangeType#ADDED} if metadata is present in content2CompositeMetadata and not in content1CompositeMetadata<br> 1029 * {@link ChangeType#MODIFIED} if metadata is present in content2CompositeMetadata and in content1CompositeMetadata<br> 1030 * {@link ChangeType#REMOVED}if metadata is not present in content2CompositeMetadata and present in content1CompositeMetadata</p> 1031 * <p>sub-paths will be added for Reference : type and value</p> 1032 * 1033 * @param result Result where diff will be added 1034 * @param content1CompositeMetadata 1st content 1035 * @param content2CompositeMetadata 2nd content 1036 * @param metadataName metadata Name 1037 * @param contentPath Content Path 1038 */ 1039 private void _compareReferenceMetadata(ContentComparatorResult result, CompositeMetadata content1CompositeMetadata, CompositeMetadata content2CompositeMetadata, String metadataName, String contentPath) 1040 { 1041 CompositeMetadata compositeMetadata1 = content1CompositeMetadata.getCompositeMetadata(metadataName); 1042 CompositeMetadata compositeMetadata2 = content2CompositeMetadata.getCompositeMetadata(metadataName); 1043 1044 if (_checkMetadataPresence(result, compositeMetadata1, compositeMetadata2, contentPath + ContentConstants.METADATA_PATH_SEPARATOR + metadataName, "type", true)) 1045 { 1046 String type1 = compositeMetadata1.getString("type"); 1047 String type2 = compositeMetadata2.getString("type"); 1048 _objectCompare(result, type1, type2, contentPath + metadataName + ContentConstants.METADATA_PATH_SEPARATOR, "type"); 1049 } 1050 1051 if (_checkMetadataPresence(result, compositeMetadata1, compositeMetadata2, contentPath + ContentConstants.METADATA_PATH_SEPARATOR + metadataName, "value", true)) 1052 { 1053 String value1 = compositeMetadata1.getString("value"); 1054 String value2 = compositeMetadata2.getString("value"); 1055 _objectCompare(result, value1, value2, contentPath + metadataName + ContentConstants.METADATA_PATH_SEPARATOR, "value"); 1056 } 1057 } 1058 1059 /** 1060 * Compare 2 User metadata <br> 1061 * <p>{@link ChangeType} can be <br> 1062 * {@link ChangeType#ADDED} if metadata is present in content2CompositeMetadata and not in content1CompositeMetadata<br> 1063 * {@link ChangeType#MODIFIED} if metadata is present in content2CompositeMetadata and in content1CompositeMetadata<br> 1064 * {@link ChangeType#REMOVED}if metadata is not present in content2CompositeMetadata and present in content1CompositeMetadata</p> 1065 * 1066 * @param result Result where diff will be added 1067 * @param content1CompositeMetadata 1st content 1068 * @param content2CompositeMetadata 2nd content 1069 * @param metadataName metadata Name 1070 * @param contentPath Content Path 1071 */ 1072 private void _compareUserMetadata(ContentComparatorResult result, CompositeMetadata content1CompositeMetadata, CompositeMetadata content2CompositeMetadata, String metadataName, String contentPath) 1073 { 1074 UserIdentity obj1 = content1CompositeMetadata.getUser(metadataName); 1075 UserIdentity obj2 = content2CompositeMetadata.getUser(metadataName); 1076 _objectCompare(result, obj1, obj2, contentPath, metadataName); 1077 } 1078 1079 /** 1080 * Compare 2 File metadata <br> 1081 * If both files do not have the same type, a {@link ChangeTypeDetail#TYPE} will be added to result<br> 1082 * see {@link ContentComparator#_compareBinaryMetadata(ContentComparatorResult, CompositeMetadata, CompositeMetadata, String, String)} if objects are binary<br> 1083 * see {@link ContentComparator#_objectCompare(ContentComparatorResult, Object, Object, String, String)} if objects are references<br> 1084 * 1085 * <p>{@link ChangeType} can be <br> 1086 * {@link ChangeType#ADDED} if metadata is present in content2CompositeMetadata and not in content1CompositeMetadata<br> 1087 * {@link ChangeType#MODIFIED} if metadata is present in content2CompositeMetadata and in content1CompositeMetadata<br> 1088 * {@link ChangeType#REMOVED}if metadata is not present in content2CompositeMetadata and present in content1CompositeMetadata</p> 1089 * 1090 * @param result Result where diff will be added 1091 * @param content1CompositeMetadata 1st content 1092 * @param content2CompositeMetadata 2nd content 1093 * @param metadataName metadata Name 1094 * @param contentPath Content Path 1095 * @throws AmetysRepositoryException repository exception 1096 * @throws IOException IO Exception 1097 */ 1098 private void _compareFileMetadata(ContentComparatorResult result, CompositeMetadata content1CompositeMetadata, CompositeMetadata content2CompositeMetadata, String metadataName, String contentPath) throws AmetysRepositoryException, IOException 1099 { 1100 if (content1CompositeMetadata.getType(metadataName).equals(content2CompositeMetadata.getType(metadataName))) 1101 { 1102 if (CompositeMetadata.MetadataType.BINARY.equals(content1CompositeMetadata.getType(metadataName))) 1103 { 1104 _compareBinaryMetadata(result, content1CompositeMetadata, content2CompositeMetadata, metadataName, contentPath); 1105 } 1106 else 1107 { 1108 _objectCompare(result, content1CompositeMetadata.getString(metadataName), content2CompositeMetadata.getString(metadataName), contentPath, metadataName); 1109 } 1110 } 1111 else 1112 { 1113 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName, ChangeType.MODIFIED, ChangeTypeDetail.TYPE); 1114 result.setNotEquals(); 1115 result.addChange(change); 1116 } 1117 } 1118 1119 /** 1120 * Compare 2 Binary metadata <br> 1121 * <p>{@link ChangeType} can be <br> 1122 * {@link ChangeType#ADDED} if metadata is present in content2CompositeMetadata and not in content1CompositeMetadata<br> 1123 * {@link ChangeType#MODIFIED} if metadata is present in content2CompositeMetadata and in content1CompositeMetadata<br> 1124 * {@link ChangeType#REMOVED}if metadata is not present in content2CompositeMetadata and present in content1CompositeMetadata</p> 1125 * <p>sub-paths will be added for Binary : encoding, fileName, lastModified, mimeType and inputStream</p> 1126 * 1127 * @param result Result where diff will be added 1128 * @param content1CompositeMetadata 1st content 1129 * @param content2CompositeMetadata 2nd content 1130 * @param metadataName metadata Name 1131 * @param contentPath Content Path 1132 * @throws AmetysRepositoryException repository exception 1133 * @throws IOException IO Exception 1134 */ 1135 private void _compareBinaryMetadata(ContentComparatorResult result, CompositeMetadata content1CompositeMetadata, CompositeMetadata content2CompositeMetadata, String metadataName, String contentPath) throws AmetysRepositoryException, IOException 1136 { 1137 BinaryMetadata object1 = content1CompositeMetadata.getBinaryMetadata(metadataName); 1138 BinaryMetadata object2 = content2CompositeMetadata.getBinaryMetadata(metadataName); 1139 1140 if (object1 != null && object2 != null) 1141 { 1142 _objectCompare(result, object1.getEncoding(), object2.getEncoding(), contentPath, metadataName + ContentConstants.METADATA_PATH_SEPARATOR + "encoding"); 1143 _objectCompare(result, object1.getFilename(), object2.getFilename(), contentPath, metadataName + ContentConstants.METADATA_PATH_SEPARATOR + "fileName"); 1144 _objectCompare(result, object1.getLastModified(), object2.getLastModified(), contentPath, metadataName + ContentConstants.METADATA_PATH_SEPARATOR + "lastModified"); 1145 _objectCompare(result, object1.getMimeType(), object2.getMimeType(), contentPath, metadataName + ContentConstants.METADATA_PATH_SEPARATOR + "mimeType"); 1146 1147 if (!IOUtils.contentEquals(object1.getInputStream(), object2.getInputStream())) 1148 { 1149 result.setNotEquals(); 1150 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName + ContentConstants.METADATA_PATH_SEPARATOR + "inputStream", ChangeType.MODIFIED); 1151 result.addChange(change); 1152 } 1153 } 1154 else if (object1 != null && object2 == null) 1155 { 1156 result.setNotEquals(); 1157 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName, ChangeType.REMOVED); 1158 result.addChange(change); 1159 } 1160 else if (object1 == null && object2 != null) 1161 { 1162 result.setNotEquals(); 1163 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName, ChangeType.ADDED); 1164 result.addChange(change); 1165 } 1166 1167 1168 } 1169 1170 /** 1171 * Compare 2 Boolean metadata <br> 1172 * <p>{@link ChangeType} can be <br> 1173 * {@link ChangeType#ADDED} if metadata is present in content2CompositeMetadata and not in content1CompositeMetadata<br> 1174 * {@link ChangeType#MODIFIED} if metadata is present in content2CompositeMetadata and in content1CompositeMetadata<br> 1175 * {@link ChangeType#REMOVED}if metadata is not present in content2CompositeMetadata and present in content1CompositeMetadata</p> 1176 * 1177 * @param result Result where diff will be added 1178 * @param content1CompositeMetadata 1st content 1179 * @param content2CompositeMetadata 2nd content 1180 * @param metadataName metadata Name 1181 * @param contentPath Content Path 1182 */ 1183 private void _compareBooleanMetadata(ContentComparatorResult result, CompositeMetadata content1CompositeMetadata, CompositeMetadata content2CompositeMetadata, String metadataName, String contentPath) 1184 { 1185 Boolean obj1 = content1CompositeMetadata.getBoolean(metadataName); 1186 Boolean obj2 = content2CompositeMetadata.getBoolean(metadataName); 1187 _objectCompare(result, obj1, obj2, contentPath, metadataName); 1188 } 1189 1190 /** 1191 * Compare 2 Objects<br> 1192 * <p>{@link ChangeType} can be <br> 1193 * {@link ChangeType#ADDED} if metadata is present in content2CompositeMetadata and not in content1CompositeMetadata<br> 1194 * {@link ChangeType#MODIFIED} if metadata is present in content2CompositeMetadata and in content1CompositeMetadata<br> 1195 * {@link ChangeType#REMOVED}if metadata is not present in content2CompositeMetadata and present in content1CompositeMetadata</p> 1196 * 1197 * @param result Result where diff will be added 1198 * @param object1 1st object 1199 * @param object2 2nd object 1200 * @param contentPath path (used to log) 1201 * @param metadataName metadata Name 1202 */ 1203 private void _objectCompare(ContentComparatorResult result, Object object1, Object object2, String contentPath, String metadataName) 1204 { 1205 //normalement pas besoin de tester la nullité, ça a du être vérifié plus tôt déjà (par checkMetadataPresence) 1206 if (object1 != null && object2 != null) 1207 { 1208 if (!object1.equals(object2)) 1209 { 1210 result.setNotEquals(); 1211 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName, ChangeType.MODIFIED); 1212 result.addChange(change); 1213 } 1214 } 1215 else if (object1 != null && object2 == null) 1216 { 1217 result.setNotEquals(); 1218 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName, ChangeType.REMOVED); 1219 result.addChange(change); 1220 } 1221 else if (object1 == null && object2 != null) 1222 { 1223 result.setNotEquals(); 1224 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName, ChangeType.ADDED); 1225 result.addChange(change); 1226 } 1227 } 1228 1229 /** 1230 * Check if a metadata is present in both contents and add a change if requested 1231 * @param result The result 1232 * @param content1CompositeMetadata 1st composite 1233 * @param content2CompositeMetadata 2nd composite 1234 * @param contentPath path (used to log) 1235 * @param metadataName name of the metadata 1236 * @param logChange true to add a change in the list (if one have it and the other don't) 1237 * @return true if both contents have the node 1238 */ 1239 private boolean _checkMetadataPresence(ContentComparatorResult result, CompositeMetadata content1CompositeMetadata, CompositeMetadata content2CompositeMetadata, String contentPath, String metadataName, boolean logChange) 1240 { 1241 if (content1CompositeMetadata.hasMetadata(metadataName) && content2CompositeMetadata.hasMetadata(metadataName)) 1242 { 1243 return true; 1244 } 1245 else if (logChange && content1CompositeMetadata.hasMetadata(metadataName) && !content2CompositeMetadata.hasMetadata(metadataName)) 1246 { 1247 result.setNotEquals(); 1248 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName, ChangeType.REMOVED); 1249 result.addChange(change); 1250 } 1251 else if (logChange && !content1CompositeMetadata.hasMetadata(metadataName) && content2CompositeMetadata.hasMetadata(metadataName)) 1252 { 1253 result.setNotEquals(); 1254 ContentComparatorChange change = new ContentComparatorChange(contentPath + metadataName, ChangeType.ADDED); 1255 result.addChange(change); 1256 } 1257 return false; 1258 } 1259 1260 /** 1261 * General type of change 1262 */ 1263 public enum ChangeType 1264 { 1265 /** 1266 * The metadata is added in the new content 1267 */ 1268 ADDED, 1269 /** 1270 * The metadata is removed in the new content 1271 */ 1272 REMOVED, 1273 /** 1274 * The metadata is modified in the new content 1275 */ 1276 MODIFIED 1277 } 1278 /** 1279 * More precise change info 1280 */ 1281 public enum ChangeTypeDetail 1282 { 1283 /** 1284 * No particular detail 1285 */ 1286 NONE, 1287 /** 1288 * New (int/bool/long) is bigger than old 1289 */ 1290 MORE, 1291 /** 1292 * New (int/bool/long) is smaller than old 1293 */ 1294 LESS, 1295 /** 1296 * Same data, but order changed 1297 */ 1298 ORDER, 1299 /** 1300 * New (date) is before old 1301 */ 1302 BEFORE, 1303 /** 1304 * New (date) is after old 1305 */ 1306 AFTER, 1307 /** 1308 * More content in new field 1309 */ 1310 MORE_CONTENT, 1311 /** 1312 * More content at the start of the new field 1313 */ 1314 MORE_CONTENT_START, 1315 /** 1316 * More content at the end of the new field 1317 */ 1318 MORE_CONTENT_END, 1319 /** 1320 * Less content in the new field 1321 */ 1322 LESS_CONTENT, 1323 /** 1324 * Less content at the start of the new field 1325 */ 1326 LESS_CONTENT_START, 1327 /** 1328 * Less content at the end of the new field 1329 */ 1330 LESS_CONTENT_END, 1331 /** 1332 * MetadataType changed (reference file to binary file for example) 1333 */ 1334 TYPE 1335 } 1336}