001// -------------------------------------------------------------------------------- 002// Copyright 2002-2024 Echo Three, LLC 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// -------------------------------------------------------------------------------- 016 017package com.echothree.model.control.item.server.logic; 018 019import com.echothree.model.control.core.common.EntityAttributeTypes; 020import com.echothree.model.control.core.common.MimeTypeUsageTypes; 021import com.echothree.model.control.item.common.exception.InvalidItemDescriptionTypeException; 022import com.echothree.model.control.item.common.exception.UnknownItemDescriptionTypeNameException; 023import com.echothree.model.control.item.server.control.ItemControl; 024import com.echothree.model.control.party.server.control.PartyControl; 025import com.echothree.model.data.core.server.entity.MimeType; 026import com.echothree.model.data.core.server.entity.MimeTypeUsageType; 027import com.echothree.model.data.item.server.entity.Item; 028import com.echothree.model.data.item.server.entity.ItemBlobDescription; 029import com.echothree.model.data.item.server.entity.ItemDescription; 030import com.echothree.model.data.item.server.entity.ItemDescriptionDetail; 031import com.echothree.model.data.item.server.entity.ItemDescriptionType; 032import com.echothree.model.data.item.server.entity.ItemDescriptionTypeDetail; 033import com.echothree.model.data.item.server.entity.ItemImageDescription; 034import com.echothree.model.data.item.server.entity.ItemImageDescriptionType; 035import com.echothree.model.data.item.server.entity.ItemImageType; 036import com.echothree.model.data.item.server.entity.ItemImageTypeDetail; 037import com.echothree.model.data.item.server.entity.ItemStringDescription; 038import com.echothree.model.data.item.server.value.ItemImageDescriptionTypeValue; 039import com.echothree.model.data.item.server.value.ItemImageTypeDetailValue; 040import com.echothree.model.data.party.server.entity.Language; 041import com.echothree.model.data.party.server.entity.Party; 042import com.echothree.util.common.exception.PersistenceDatabaseException; 043import com.echothree.util.common.message.ExecutionErrors; 044import com.echothree.util.common.persistence.BasePK; 045import com.echothree.util.common.persistence.type.ByteArray; 046import com.echothree.util.server.control.BaseLogic; 047import com.echothree.util.server.message.ExecutionErrorAccumulator; 048import com.echothree.util.server.persistence.PersistenceUtils; 049import com.echothree.util.server.persistence.Session; 050import java.awt.Graphics2D; 051import java.awt.RenderingHints; 052import java.awt.Transparency; 053import java.awt.image.BufferedImage; 054import java.io.ByteArrayOutputStream; 055import java.io.IOException; 056import java.util.Iterator; 057import java.util.List; 058import javax.imageio.IIOImage; 059import javax.imageio.ImageIO; 060import javax.imageio.ImageReader; 061import javax.imageio.ImageWriteParam; 062import javax.imageio.ImageWriter; 063import javax.imageio.stream.ImageOutputStream; 064import javax.imageio.stream.MemoryCacheImageInputStream; 065import javax.imageio.stream.MemoryCacheImageOutputStream; 066 067public class ItemDescriptionLogic 068 extends BaseLogic { 069 070 private ItemDescriptionLogic() { 071 super(); 072 } 073 074 private static class ItemDescriptionLogicHolder { 075 static ItemDescriptionLogic instance = new ItemDescriptionLogic(); 076 } 077 078 public static ItemDescriptionLogic getInstance() { 079 return ItemDescriptionLogicHolder.instance; 080 } 081 082 public String getIndexDefaultItemDescriptionTypeName() { 083 var itemControl = Session.getModelController(ItemControl.class); 084 ItemDescriptionType itemDescriptionType = itemControl.getIndexDefaultItemDescriptionType(); 085 086 return itemDescriptionType.getLastDetail().getItemDescriptionTypeName(); 087 } 088 089 public boolean isImage(ItemDescriptionType itemDescriptionType) { 090 MimeTypeUsageType mimeTypeUsageType = itemDescriptionType.getLastDetail().getMimeTypeUsageType(); 091 boolean result = false; 092 093 if(mimeTypeUsageType != null) { 094 result = mimeTypeUsageType.getMimeTypeUsageTypeName().equals(MimeTypeUsageTypes.IMAGE.name()); 095 } 096 097 return result; 098 } 099 100 // Find the first available parent ItemDescription. 101 public ItemDescription getBestParent(ItemControl itemControl, ItemDescriptionType itemDescriptionType, Item item, Language language) { 102 ItemDescription itemDescription = null; 103 ItemDescriptionTypeDetail itemDescriptionTypeDetail = itemDescriptionType.getLastDetail(); 104 105 if(itemDescriptionTypeDetail.getUseParentIfMissing()) { 106 ItemDescriptionType parentItemDescriptionType = itemDescriptionTypeDetail.getParentItemDescriptionType(); 107 108 if(parentItemDescriptionType != null) { 109 ItemDescription parentItemDescription = itemControl.getItemDescription(parentItemDescriptionType, item, language); 110 111 if(parentItemDescription == null) { 112 // If there isn't a parent, or if the parent is scaled, then try the parent's parent 113 itemDescription = getBestParent(itemControl, parentItemDescriptionType, item, language); 114 } else { 115 // If the parent image wasn't scaled, then we'll use that one. 116 itemDescription = parentItemDescription; 117 } 118 } 119 } 120 121 return itemDescription; 122 } 123 124 // Find the first available parent ItemDescription based on the Party's preferred Language. 125 public ItemDescription getBestParent(final ItemDescriptionType itemDescriptionType, final Item item, final Party party) { 126 var itemControl = Session.getModelController(ItemControl.class); 127 var partyControl = Session.getModelController(PartyControl.class); 128 Language language = party == null ? partyControl.getDefaultLanguage() : partyControl.getPreferredLanguage(party); 129 130 return getBestParent(itemControl, itemDescriptionType, item, language); 131 } 132 133 // Find the first available parent ItemDescription based on the Party's preferred Language. 134 public ItemDescription getBestParentUsingNames(final ExecutionErrorAccumulator eea, final String itemDescriptionTypeName, final Item item, 135 final Party party) { 136 var itemControl = Session.getModelController(ItemControl.class); 137 ItemDescriptionType itemDescriptionType = itemControl.getItemDescriptionTypeByName(itemDescriptionTypeName); 138 139 if(itemDescriptionType == null) { 140 handleExecutionError(UnknownItemDescriptionTypeNameException.class, eea, ExecutionErrors.UnknownItemDescriptionTypeName.name(), itemDescriptionTypeName); 141 } 142 143 return (eea == null ? false : eea.hasExecutionErrors()) ? null : getBestParent(itemDescriptionType, item, party); 144 } 145 146 // Find the first available parent ItemDescription based on the Party's preferred Language. 147 public String getBestStringUsingNames(final ExecutionErrorAccumulator eea, final String itemDescriptionTypeName, final Item item, final Party party) { 148 ItemDescription itemDescription = getBestParentUsingNames(eea, itemDescriptionTypeName, item, party); 149 String stringDescription = null; 150 151 if(itemDescription != null) { 152 MimeType mimeType = itemDescription.getLastDetail().getMimeType(); 153 154 if(mimeType == null) { 155 var itemControl = Session.getModelController(ItemControl.class); 156 ItemStringDescription itemStringDescription = itemControl.getItemStringDescription(itemDescription); 157 158 stringDescription = itemStringDescription.getStringDescription(); 159 } else { 160 handleExecutionError(InvalidItemDescriptionTypeException.class, eea, ExecutionErrors.InvalidItemDescriptionType.name(), itemDescriptionTypeName); 161 } 162 } 163 164 return stringDescription; 165 } 166 167 // Find the highest quality parent ItemDescription. 168 public ItemDescription getBestParentImage(ItemControl itemControl, ItemDescriptionType itemDescriptionType, Item item, Language language) { 169 ItemDescription itemDescription = null; 170 ItemDescriptionType parentItemDescriptionType = itemDescriptionType.getLastDetail().getParentItemDescriptionType(); 171 172 if(parentItemDescriptionType != null) { 173 ItemDescription parentItemDescription = itemControl.getItemDescription(parentItemDescriptionType, item, language); 174 175 if(parentItemDescription == null || itemControl.getItemImageDescription(parentItemDescription).getScaledFromParent()) { 176 // If there isn't a parent, or if the parent is scaled, then try the parent's parent 177 itemDescription = getBestParentImage(itemControl, parentItemDescriptionType, item, language); 178 } else { 179 // If the parent image wasn't scaled, then we'll use that one. 180 itemDescription = parentItemDescription; 181 } 182 } 183 184 185 return itemDescription; 186 } 187 188 public ImageReader getImageReader(MimeType mimeType, ItemBlobDescription itemBlobDescription) { 189 MemoryCacheImageInputStream memoryCacheImageInputStream = new MemoryCacheImageInputStream(itemBlobDescription.getBlobDescription().getByteArrayInputStream()); 190 Iterator<ImageReader> imageReaders = ImageIO.getImageReadersByMIMEType(mimeType.getLastDetail().getMimeTypeName()); 191 ImageReader imageReader = imageReaders.hasNext() ? imageReaders.next() : null; 192 193 if(imageReader != null) { 194 imageReader.setInput(memoryCacheImageInputStream); 195 196 try { 197 // If there isn't at least one image, then return null. 198 if(imageReader.getNumImages(true) == 0) { 199 imageReader = null; 200 } 201 } catch (IOException ioe) { 202 // Nothing, height and width stay null. 203 } 204 } 205 206 return imageReader; 207 } 208 209 // Based on: http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html 210 /** 211 * Convenience method that returns a scaled instance of the 212 * provided {@code BufferedImage}. 213 * 214 * @param img the original image to be scaled 215 * @param targetWidth the desired width of the scaled instance, 216 * in pixels 217 * @param targetHeight the desired height of the scaled instance, 218 * in pixels 219 * @param hint one of the rendering hints that corresponds to 220 * {@code RenderingHints.KEY_INTERPOLATION} (e.g. 221 * {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR}, 222 * {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR}, 223 * {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC}) 224 * @param higherQuality if true, this method will use a multi-step 225 * scaling technique that provides higher quality than the usual 226 * one-step technique (only useful in downscaling cases, where 227 * {@code targetWidth} or {@code targetHeight} is 228 * smaller than the original dimensions, and generally only when 229 * the {@code BILINEAR} hint is specified) 230 * @return a scaled version of the original {@code BufferedImage} 231 */ 232 public BufferedImage getScaledInstance(BufferedImage img, int targetWidth, int targetHeight, Object hint, boolean higherQuality) { 233 int type = (img.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; 234 BufferedImage ret = (BufferedImage)img; 235 int w, h; 236 237 if(higherQuality) { 238 // Use multi-step technique: start with original size, then 239 // scale down in multiple passes with drawImage() 240 // until the target size is reached 241 w = img.getWidth(); 242 h = img.getHeight(); 243 } else { 244 // Use one-step technique: scale directly from original 245 // size to target size with a single drawImage() call 246 w = targetWidth; 247 h = targetHeight; 248 } 249 250 do { 251 if(higherQuality && w > targetWidth) { 252 w /= 2; 253 if(w < targetWidth) { 254 w = targetWidth; 255 } 256 } 257 258 if(higherQuality && h > targetHeight) { 259 h /= 2; 260 if(h < targetHeight) { 261 h = targetHeight; 262 } 263 } 264 265 BufferedImage tmp = new BufferedImage(w, h, type); 266 Graphics2D g2 = tmp.createGraphics(); 267 g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint); 268 g2.drawImage(ret, 0, 0, w, h, null); 269 g2.dispose(); 270 271 ret = tmp; 272 } while(w != targetWidth || h != targetHeight); 273 274 return ret; 275 } 276 277 // http://www.nearinfinity.com/blogs/jim_clark/thumbnail_generation_gotchas.html 278 public double scaleToFit(double w1, double h1, double w2, double h2) { 279 double scale = 1.0D; 280 281 if(w1 > h1) { 282 if(w1 > w2) 283 scale = w2 / w1; 284 h1 *= scale; 285 if(h1 > h2) 286 scale *= h2 / h1; 287 } else { 288 if(h1 > h2) 289 scale = h2 / h1; 290 w1 *= scale; 291 if(w1 > w2) 292 scale *= w2 / w1; 293 } 294 295 return scale; 296 } 297 298 public ItemDescription searchForItemDescription(ItemDescriptionType itemDescriptionType, Item item, Language language, BasePK createdBy) { 299 var itemControl = Session.getModelController(ItemControl.class); 300 ItemDescription itemDescription = null; 301 302 if(isImage(itemDescriptionType)) { 303 ItemImageDescriptionType itemImageDescriptionType = itemControl.getItemImageDescriptionType(itemDescriptionType); 304 305 if(itemImageDescriptionType.getScaleFromParent()) { 306 Integer preferredHeight = itemImageDescriptionType.getPreferredHeight(); 307 Integer preferredWidth = itemImageDescriptionType.getPreferredWidth(); 308 309 // preferredHeight and preferredWidth are used as the target sizes for the scaling. Without them, it isn't going to happen. 310 if(preferredHeight != null && preferredWidth != null) { 311 ItemDescription originalItemDescription = getBestParentImage(itemControl, itemDescriptionType, item, language); 312 313 if(originalItemDescription != null) { 314 ItemDescriptionDetail originalItemDescriptionDetail = originalItemDescription.getLastDetail(); 315 ItemImageDescription originalItemImageDescription = itemControl.getItemImageDescription(originalItemDescription); 316 317 // BLOBs only. 318 if(originalItemDescriptionDetail.getMimeType().getLastDetail().getEntityAttributeType().getEntityAttributeTypeName().equals(EntityAttributeTypes.BLOB.name())) { 319 MimeType originalMimeType = originalItemDescriptionDetail.getMimeType(); 320 MimeType preferredMimeType = itemImageDescriptionType.getPreferredMimeType(); 321 Integer quality = itemImageDescriptionType.getQuality(); 322 ItemImageType originalItemImageType = originalItemImageDescription.getItemImageType(); 323 ItemImageTypeDetail originalItemImageTypeDetail = originalItemImageType.getLastDetail(); 324 ItemBlobDescription originalItemBlobDescription = itemControl.getItemBlobDescription(originalItemDescription); 325 326 // ItemImageType settings override any that came from the ItemImageDescriptionType 327 if(originalItemImageTypeDetail.getPreferredMimeType() != null) { 328 preferredMimeType = originalItemImageTypeDetail.getPreferredMimeType(); 329 } 330 331 if(originalItemImageTypeDetail.getQuality() != null) { 332 quality = originalItemImageTypeDetail.getQuality(); 333 } 334 335 // If there's still no preferredMimeType, fall back to the original image's MimeType. 336 if(preferredMimeType == null) { 337 preferredMimeType = originalMimeType; 338 } 339 340 if(quality == null) { 341 quality = 90; // Default quality 342 } 343 344 ImageReader imageReader = getImageReader(originalMimeType, originalItemBlobDescription); 345 if(imageReader != null) { 346 BufferedImage originalBufferedImage = null; 347 348 try { 349 originalBufferedImage = imageReader.read(0); 350 } catch (IOException ioe) { 351 // Ignore, image reading failed, leave originalBufferedImage null. 352 } 353 354 if(originalBufferedImage != null) { 355 int originalHeight = originalBufferedImage.getHeight(); 356 int originalWidth = originalBufferedImage.getWidth(); 357 double scale = scaleToFit(originalWidth, originalHeight, preferredWidth, preferredHeight); 358 String mimeTypeName = preferredMimeType.getLastDetail().getMimeTypeName(); 359 360 BufferedImage scaledBufferedImage = getScaledInstance(originalBufferedImage, (int)Math.round(originalWidth * scale), 361 (int)Math.round(originalHeight * scale), RenderingHints.VALUE_INTERPOLATION_BILINEAR, 362 true); 363 364 Iterator<ImageWriter> imageWriters = ImageIO.getImageWritersByMIMEType(mimeTypeName); 365 ImageWriter imageWriter = imageWriters.hasNext() ? imageWriters.next() : null; 366 367 if(imageWriter != null) { 368 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 369 370 try { 371 float scaledQuality = (float)quality / 100.0f; 372 ImageWriteParam iwp = imageWriter.getDefaultWriteParam(); 373 374 if(iwp.canWriteCompressed()) { 375 String[] compressionTypes = iwp.getCompressionTypes(); 376 377 iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); 378 379 // JPEG doesn't appear to "need" this, but GIF throws an IllegalStateException without it. Pick the first choice. 380 if(compressionTypes.length > 0) { 381 iwp.setCompressionType(compressionTypes[0]); 382 } 383 384 iwp.setCompressionQuality(scaledQuality); 385 } 386 387 ImageOutputStream imageOutputStream = new MemoryCacheImageOutputStream(byteArrayOutputStream); 388 IIOImage img = new IIOImage(scaledBufferedImage, null, null); 389 390 imageWriter.setOutput(imageOutputStream); 391 imageWriter.write(null, img, iwp); 392 imageWriter.dispose(); 393 imageOutputStream.close(); 394 } catch (IOException ioe) { 395 // Exception? Nuke the result, no scaling. 396 byteArrayOutputStream = null; 397 } 398 399 if(byteArrayOutputStream != null) { 400 try { 401 itemDescription = itemControl.createItemDescription(itemDescriptionType, item, language, preferredMimeType, createdBy); 402 itemControl.createItemImageDescription(itemDescription, originalItemImageType, scaledBufferedImage.getHeight(), 403 scaledBufferedImage.getWidth(), Boolean.TRUE, createdBy); 404 itemControl.createItemBlobDescription(itemDescription, new ByteArray(byteArrayOutputStream.toByteArray()), createdBy); 405 } catch(PersistenceDatabaseException pde) { 406 if(PersistenceUtils.getInstance().isIntegrityConstraintViolation(pde)) { 407 itemDescription = itemControl.getItemDescription(itemDescriptionType, item, language); 408 409 if(itemDescription != null) { 410 pde = null; 411 } 412 } 413 414 if(pde != null) { 415 throw pde; 416 } 417 } 418 } 419 } 420 } 421 } 422 } 423 } 424 } 425 } else { 426 itemDescription = getBestParent(itemControl, itemDescriptionType, item, language); 427 } 428 } else { 429 itemDescription = getBestParent(itemControl, itemDescriptionType, item, language); 430 } 431 432 return itemDescription; 433 } 434 435 public static class ImageDimensions { 436 437 private Integer height; 438 private Integer width; 439 440 public ImageDimensions(Integer height, Integer width) { 441 this.height = height; 442 this.width = width; 443 } 444 445 public Integer getHeight() { 446 return height; 447 } 448 449 public void setHeight(Integer height) { 450 this.height = height; 451 } 452 453 public Integer getWidth() { 454 return width; 455 } 456 457 public void setWidth(Integer width) { 458 this.width = width; 459 } 460 461 } 462 463 public ImageDimensions getImageDimensions(String mimeTypeName, ByteArray blobDescription) { 464 MemoryCacheImageInputStream memoryCacheImageInputStream = blobDescription.getMemoryCacheImageInputStream(); 465 Iterator<ImageReader> imageReaders = ImageIO.getImageReadersByMIMEType(mimeTypeName); 466 ImageReader imageReader = imageReaders.hasNext() ? imageReaders.next() : null; 467 ImageDimensions result = null; 468 469 if(imageReader != null) { 470 imageReader.setInput(memoryCacheImageInputStream); 471 472 try { 473 if(imageReader.getNumImages(true) > 0) { 474 result = new ImageDimensions(imageReader.getHeight(0), imageReader.getWidth(0)); 475 } 476 } catch (IOException ioe) { 477 // Nothing, result stays null. 478 } 479 } 480 481 return result; 482 } 483 484 public void deleteItemImageDescriptionChildren(ItemDescriptionType itemDescriptionType, Item item, Language language, BasePK deletedBy) { 485 var itemControl = Session.getModelController(ItemControl.class); 486 List<ItemDescriptionType> childItemDescriptionTypes = itemControl.getItemDescriptionTypesByParentItemDescriptionType(itemDescriptionType); 487 488 childItemDescriptionTypes.forEach((childItemDescriptionType) -> { 489 ItemDescription childItemDescription = itemControl.getItemDescriptionForUpdate(childItemDescriptionType, item, language); 490 boolean childWasScaled = true; 491 if(childItemDescription != null) { 492 ItemImageDescription childItemImageDescription = itemControl.getItemImageDescription(childItemDescription); 493 494 childWasScaled = childItemImageDescription.getScaledFromParent(); 495 496 if(childWasScaled) { 497 itemControl.deleteItemDescription(childItemDescription, deletedBy); 498 } 499 } 500 501 // If no child description existed, or if it was scaled, then go through and make sure there are no scaled children under it. 502 if (childWasScaled) { 503 deleteItemImageDescriptionChildren(childItemDescriptionType, item, language, deletedBy); 504 } 505 }); 506 } 507 508 public void deleteItemImageDescriptionChildren(ItemDescription itemDescription, BasePK deletedBy) { 509 ItemDescriptionDetail itemDescriptionDetail = itemDescription.getLastDetail(); 510 511 deleteItemImageDescriptionChildren(itemDescriptionDetail.getItemDescriptionType(), itemDescriptionDetail.getItem(), itemDescriptionDetail.getLanguage(), deletedBy); 512 } 513 514 public void deleteItemDescription(ItemDescription itemDescription, BasePK deletedBy) { 515 var itemControl = Session.getModelController(ItemControl.class); 516 MimeTypeUsageType mimeTypeUsageType = itemDescription.getLastDetail().getItemDescriptionType().getLastDetail().getMimeTypeUsageType(); 517 518 itemControl.deleteItemDescription(itemDescription, deletedBy); 519 520 if(mimeTypeUsageType != null && mimeTypeUsageType.getMimeTypeUsageTypeName().equals(MimeTypeUsageTypes.IMAGE.name())) { 521 ItemImageDescription itemImageDescription = itemControl.getItemImageDescription(itemDescription); 522 523 if(!itemImageDescription.getScaledFromParent()) { 524 ItemDescriptionLogic.getInstance().deleteItemImageDescriptionChildren(itemDescription, deletedBy); 525 } 526 } 527 } 528 529 public void updateItemImageDescriptionTypeFromValue(ItemImageDescriptionTypeValue itemImageDescriptionTypeValue, BasePK updatedBy) { 530 var itemControl = Session.getModelController(ItemControl.class); 531 532 itemControl.updateItemImageDescriptionTypeFromValue(itemImageDescriptionTypeValue, updatedBy); 533 534 if(itemImageDescriptionTypeValue.getPreferredHeightHasBeenModified() || itemImageDescriptionTypeValue.getPreferredWidthHasBeenModified() 535 || itemImageDescriptionTypeValue.getPreferredMimeTypePKHasBeenModified() || itemImageDescriptionTypeValue.getQualityHasBeenModified() 536 || itemImageDescriptionTypeValue.getScaleFromParent()) { 537 itemControl.deleteItemDescriptions(itemControl.getScaledItemDescriptionsByItemDescriptionTypePKForUpdate(itemImageDescriptionTypeValue.getItemDescriptionTypePK()), updatedBy); 538 } 539 } 540 541 public void updateItemImageTypeFromValue(ItemImageTypeDetailValue itemImageTypeDetailValue, BasePK updatedBy) { 542 var itemControl = Session.getModelController(ItemControl.class); 543 544 itemControl.updateItemImageTypeFromValue(itemImageTypeDetailValue, updatedBy); 545 546 if(itemImageTypeDetailValue.getPreferredMimeTypePKHasBeenModified() || itemImageTypeDetailValue.getQualityHasBeenModified()) { 547 itemControl.deleteItemDescriptions(itemControl.getScaledItemDescriptionsByItemImageTypePKForUpdate(itemImageTypeDetailValue.getItemImageTypePK()), updatedBy); 548 } 549 } 550 551}