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