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