1
22
23 package com.liferay.portlet.imagegallery.service.impl;
24
25 import com.liferay.portal.PortalException;
26 import com.liferay.portal.SystemException;
27 import com.liferay.portal.kernel.image.ImageProcessor;
28 import com.liferay.portal.kernel.image.ImageProcessorUtil;
29 import com.liferay.portal.kernel.log.Log;
30 import com.liferay.portal.kernel.log.LogFactoryUtil;
31 import com.liferay.portal.kernel.search.SearchEngineUtil;
32 import com.liferay.portal.kernel.search.SearchException;
33 import com.liferay.portal.kernel.util.ByteArrayMaker;
34 import com.liferay.portal.kernel.util.FileUtil;
35 import com.liferay.portal.kernel.util.GetterUtil;
36 import com.liferay.portal.kernel.util.OrderByComparator;
37 import com.liferay.portal.kernel.util.StringPool;
38 import com.liferay.portal.kernel.util.StringUtil;
39 import com.liferay.portal.kernel.util.Validator;
40 import com.liferay.portal.model.Image;
41 import com.liferay.portal.model.ResourceConstants;
42 import com.liferay.portal.model.User;
43 import com.liferay.portal.service.ServiceContext;
44 import com.liferay.portal.util.PropsKeys;
45 import com.liferay.portal.util.PropsUtil;
46 import com.liferay.portal.util.PropsValues;
47 import com.liferay.portlet.imagegallery.DuplicateImageNameException;
48 import com.liferay.portlet.imagegallery.ImageNameException;
49 import com.liferay.portlet.imagegallery.ImageSizeException;
50 import com.liferay.portlet.imagegallery.NoSuchImageException;
51 import com.liferay.portlet.imagegallery.model.IGFolder;
52 import com.liferay.portlet.imagegallery.model.IGImage;
53 import com.liferay.portlet.imagegallery.model.impl.IGImageImpl;
54 import com.liferay.portlet.imagegallery.service.base.IGImageLocalServiceBaseImpl;
55 import com.liferay.portlet.imagegallery.util.Indexer;
56
57 import com.sun.media.jai.codec.ImageCodec;
58 import com.sun.media.jai.codec.ImageEncoder;
59
60 import java.awt.image.RenderedImage;
61
62 import java.io.File;
63 import java.io.IOException;
64 import java.io.InputStream;
65
66 import java.util.Date;
67 import java.util.List;
68
69 import javax.imageio.ImageIO;
70
71
78 public class IGImageLocalServiceImpl extends IGImageLocalServiceBaseImpl {
79
80 public IGImage addImage(
81 long userId, long folderId, String name, String description,
82 File file, String contentType, ServiceContext serviceContext)
83 throws PortalException, SystemException {
84
85 return addImage(
86 null, userId, folderId, name, description, file, contentType,
87 serviceContext);
88 }
89
90 public IGImage addImage(
91 long userId, long folderId, String name, String description,
92 String fileName, byte[] bytes, String contentType,
93 ServiceContext serviceContext)
94 throws PortalException, SystemException {
95
96 return addImage(
97 null, userId, folderId, name, description, fileName, bytes,
98 contentType, serviceContext);
99 }
100
101 public IGImage addImage(
102 long userId, long folderId, String name, String description,
103 String fileName, InputStream is, String contentType,
104 ServiceContext serviceContext)
105 throws PortalException, SystemException {
106
107 return addImage(
108 null, userId, folderId, name, description, fileName, is,
109 contentType, serviceContext);
110 }
111
112 public IGImage addImage(
113 String uuid, long userId, long folderId, String name,
114 String description, File file, String contentType,
115 ServiceContext serviceContext)
116 throws PortalException, SystemException {
117
118 try {
119 String fileName = file.getName();
120 byte[] bytes = FileUtil.getBytes(file);
121
122 return addImage(
123 uuid, userId, folderId, name, description, fileName, bytes,
124 contentType, serviceContext);
125 }
126 catch (IOException ioe) {
127 throw new SystemException(ioe);
128 }
129 }
130
131 public IGImage addImage(
132 String uuid, long userId, long folderId, String name,
133 String description, String fileName, byte[] bytes,
134 String contentType, ServiceContext serviceContext)
135 throws PortalException, SystemException {
136
137 try {
138
139
141 String extension = FileUtil.getExtension(fileName);
142
143 if (Validator.isNotNull(name) &&
144 StringUtil.endsWith(name, extension)) {
145
146 name = FileUtil.stripExtension(name);
147 }
148
149 String nameWithExtension = name + StringPool.PERIOD + extension;
150
151 validate(folderId, nameWithExtension, fileName, bytes);
152
153 User user = userPersistence.findByPrimaryKey(userId);
154 IGFolder folder = igFolderPersistence.findByPrimaryKey(folderId);
155 RenderedImage renderedImage = ImageProcessorUtil.read(
156 bytes).getRenderedImage();
157 Date now = new Date();
158
159 long imageId = counterLocalService.increment();
160
161 if (Validator.isNull(name)) {
162 name = String.valueOf(imageId);
163 }
164
165 IGImage image = igImagePersistence.create(imageId);
166
167 image.setUuid(uuid);
168 image.setCompanyId(user.getCompanyId());
169 image.setUserId(user.getUserId());
170 image.setCreateDate(now);
171 image.setModifiedDate(now);
172 image.setFolderId(folderId);
173 image.setName(name);
174 image.setDescription(description);
175 image.setSmallImageId(counterLocalService.increment());
176 image.setLargeImageId(counterLocalService.increment());
177
178 if (PropsValues.IG_IMAGE_CUSTOM_1_MAX_DIMENSION > 0) {
179 image.setCustom1ImageId(counterLocalService.increment());
180 }
181
182 if (PropsValues.IG_IMAGE_CUSTOM_2_MAX_DIMENSION > 0) {
183 image.setCustom2ImageId(counterLocalService.increment());
184 }
185
186 igImagePersistence.update(image, false);
187
188
190 saveImages(
191 image.getLargeImageId(), renderedImage, image.getSmallImageId(),
192 image.getCustom1ImageId(), image.getCustom2ImageId(), bytes,
193 contentType);
194
195
197 if (serviceContext.getAddCommunityPermissions() ||
198 serviceContext.getAddGuestPermissions()) {
199
200 addImageResources(
201 folder, image, serviceContext.getAddCommunityPermissions(),
202 serviceContext.getAddGuestPermissions());
203 }
204 else {
205 addImageResources(
206 folder, image, serviceContext.getCommunityPermissions(),
207 serviceContext.getGuestPermissions());
208 }
209
210
212 updateTagsAsset(userId, image, serviceContext.getTagsEntries());
213
214
216 try {
217 Indexer.addImage(
218 image.getCompanyId(), folder.getGroupId(), folderId,
219 imageId, name, description, image.getModifiedDate(),
220 serviceContext.getTagsEntries(), image.getExpandoBridge());
221 }
222 catch (SearchException se) {
223 _log.error("Indexing " + imageId, se);
224 }
225
226 return image;
227 }
228 catch (IOException ioe) {
229 throw new ImageSizeException(ioe);
230 }
231 }
232
233 public IGImage addImage(
234 String uuid, long userId, long folderId, String name,
235 String description, String fileName, InputStream is,
236 String contentType, ServiceContext serviceContext)
237 throws PortalException, SystemException {
238
239 try {
240 byte[] bytes = FileUtil.getBytes(is);
241
242 return addImage(
243 uuid, userId, folderId, name, description, fileName, bytes,
244 contentType, serviceContext);
245 }
246 catch (IOException ioe) {
247 throw new SystemException(ioe);
248 }
249 }
250
251 public void addImageResources(
252 long folderId, long imageId, boolean addCommunityPermissions,
253 boolean addGuestPermissions)
254 throws PortalException, SystemException {
255
256 IGFolder folder = igFolderPersistence.findByPrimaryKey(folderId);
257 IGImage image = igImagePersistence.findByPrimaryKey(imageId);
258
259 addImageResources(
260 folder, image, addCommunityPermissions, addGuestPermissions);
261 }
262
263 public void addImageResources(
264 IGFolder folder, IGImage image, boolean addCommunityPermissions,
265 boolean addGuestPermissions)
266 throws PortalException, SystemException {
267
268 resourceLocalService.addResources(
269 image.getCompanyId(), folder.getGroupId(), image.getUserId(),
270 IGImage.class.getName(), image.getImageId(), false,
271 addCommunityPermissions, addGuestPermissions);
272 }
273
274 public void addImageResources(
275 long folderId, long imageId, String[] communityPermissions,
276 String[] guestPermissions)
277 throws PortalException, SystemException {
278
279 IGFolder folder = igFolderPersistence.findByPrimaryKey(folderId);
280 IGImage image = igImagePersistence.findByPrimaryKey(imageId);
281
282 addImageResources(
283 folder, image, communityPermissions, guestPermissions);
284 }
285
286 public void addImageResources(
287 IGFolder folder, IGImage image, String[] communityPermissions,
288 String[] guestPermissions)
289 throws PortalException, SystemException {
290
291 resourceLocalService.addModelResources(
292 image.getCompanyId(), folder.getGroupId(), image.getUserId(),
293 IGImage.class.getName(), image.getImageId(), communityPermissions,
294 guestPermissions);
295 }
296
297 public void deleteImage(long imageId)
298 throws PortalException, SystemException {
299
300 IGImage image = igImagePersistence.findByPrimaryKey(imageId);
301
302 deleteImage(image);
303 }
304
305 public void deleteImage(IGImage image)
306 throws PortalException, SystemException {
307
308
310 try {
311 Indexer.deleteImage(image.getCompanyId(), image.getImageId());
312 }
313 catch (SearchException se) {
314 _log.error("Deleting index " + image.getImageId(), se);
315 }
316
317
319 tagsAssetLocalService.deleteAsset(
320 IGImage.class.getName(), image.getImageId());
321
322
324 resourceLocalService.deleteResource(
325 image.getCompanyId(), IGImage.class.getName(),
326 ResourceConstants.SCOPE_INDIVIDUAL, image.getImageId());
327
328
330 imageLocalService.deleteImage(image.getSmallImageId());
331 imageLocalService.deleteImage(image.getLargeImageId());
332
333
335 igImagePersistence.remove(image);
336 }
337
338 public void deleteImages(long folderId)
339 throws PortalException, SystemException {
340
341 List<IGImage> images = igImagePersistence.findByFolderId(folderId);
342
343 for (IGImage image : images) {
344 deleteImage(image);
345 }
346 }
347
348 public int getFoldersImagesCount(List<Long> folderIds)
349 throws SystemException {
350
351 return igImageFinder.countByFolderIds(folderIds);
352 }
353
354 public List<IGImage> getGroupImages(long groupId, int start, int end)
355 throws SystemException {
356
357 return igImageFinder.findByGroupId(groupId, start, end);
358 }
359
360 public List<IGImage> getGroupImages(
361 long groupId, long userId, int start, int end)
362 throws SystemException {
363
364 if (userId <= 0) {
365 return igImageFinder.findByGroupId(groupId, start, end);
366 }
367 else {
368 return igImageFinder.findByG_U(groupId, userId, start, end);
369 }
370 }
371
372 public int getGroupImagesCount(long groupId) throws SystemException {
373 return igImageFinder.countByGroupId(groupId);
374 }
375
376 public int getGroupImagesCount(long groupId, long userId)
377 throws SystemException {
378
379 if (userId <= 0) {
380 return igImageFinder.countByGroupId(groupId);
381 }
382 else {
383 return igImageFinder.countByG_U(groupId, userId);
384 }
385 }
386
387 public IGImage getImage(long imageId)
388 throws PortalException, SystemException {
389
390 return igImagePersistence.findByPrimaryKey(imageId);
391 }
392
393 public IGImage getImageByCustom1ImageId(long custom1ImageId)
394 throws PortalException, SystemException {
395
396 return igImagePersistence.findByCustom1ImageId(custom1ImageId);
397 }
398
399 public IGImage getImageByCustom2ImageId(long custom2ImageId)
400 throws PortalException, SystemException {
401
402 return igImagePersistence.findByCustom2ImageId(custom2ImageId);
403 }
404
405 public IGImage getImageByFolderIdAndNameWithExtension(
406 long folderId, String nameWithExtension)
407 throws PortalException, SystemException {
408
409 String name = FileUtil.stripExtension(nameWithExtension);
410
411 List<IGImage> images = igImagePersistence.findByF_N(folderId, name);
412
413 if ((images.size() <= 0) && Validator.isNumber(name)) {
414 long imageId = GetterUtil.getLong(name);
415
416 IGImage image = igImagePersistence.fetchByPrimaryKey(imageId);
417
418 if (image != null) {
419 images.add(image);
420 }
421 }
422
423 for (IGImage image : images) {
424 if (nameWithExtension.equals(image.getNameWithExtension())) {
425 return image;
426 }
427 }
428
429 throw new NoSuchImageException();
430 }
431
432 public IGImage getImageByLargeImageId(long largeImageId)
433 throws PortalException, SystemException {
434
435 return igImagePersistence.findByLargeImageId(largeImageId);
436 }
437
438 public IGImage getImageBySmallImageId(long smallImageId)
439 throws PortalException, SystemException {
440
441 return igImagePersistence.findBySmallImageId(smallImageId);
442 }
443
444 public IGImage getImageByUuidAndGroupId(String uuid, long groupId)
445 throws PortalException, SystemException {
446
447 return igImageFinder.findByUuid_G(uuid, groupId);
448 }
449
450 public List<IGImage> getImages(long folderId) throws SystemException {
451 return igImagePersistence.findByFolderId(folderId);
452 }
453
454 public List<IGImage> getImages(long folderId, int start, int end)
455 throws SystemException {
456
457 return igImagePersistence.findByFolderId(folderId, start, end);
458 }
459
460 public List<IGImage> getImages(
461 long folderId, int start, int end, OrderByComparator obc)
462 throws SystemException {
463
464 return igImagePersistence.findByFolderId(folderId, start, end, obc);
465 }
466
467 public int getImagesCount(long folderId) throws SystemException {
468 return igImagePersistence.countByFolderId(folderId);
469 }
470
471 public List<IGImage> getNoAssetImages() throws SystemException {
472 return igImageFinder.findByNoAssets();
473 }
474
475 public void reIndex(long imageId) throws SystemException {
476 if (SearchEngineUtil.isIndexReadOnly()) {
477 return;
478 }
479
480 IGImage image = igImagePersistence.fetchByPrimaryKey(imageId);
481
482 if (image == null) {
483 return;
484 }
485
486 IGFolder folder = igFolderPersistence.fetchByPrimaryKey(
487 image.getFolderId());
488
489 long companyId = folder.getCompanyId();
490 long groupId = folder.getGroupId();
491 long folderId = folder.getFolderId();
492 String name = image.getName();
493 String description = image.getDescription();
494 Date modifiedDate = image.getModifiedDate();
495
496 String[] tagsEntries = tagsEntryLocalService.getEntryNames(
497 IGImage.class.getName(), imageId);
498
499 try {
500 Indexer.updateImage(
501 companyId, groupId, folderId, imageId, name,
502 description, modifiedDate, tagsEntries,
503 image.getExpandoBridge());
504 }
505 catch (SearchException se) {
506 _log.error("Reindexing " + imageId, se);
507 }
508 }
509
510 public IGImage updateImage(
511 long userId, long imageId, long folderId, String name,
512 String description, byte[] bytes, String contentType,
513 ServiceContext serviceContext)
514 throws PortalException, SystemException {
515
516 try {
517
518
520 IGImage image = igImagePersistence.findByPrimaryKey(imageId);
521
522 IGFolder folder = getFolder(image, folderId);
523
524 RenderedImage renderedImage = null;
525
526 if (bytes != null) {
527 renderedImage = ImageProcessorUtil.read(
528 bytes).getRenderedImage();
529
530 validate(bytes);
531 }
532
533 if (Validator.isNotNull(name) && !name.equals(image.getName())) {
534 String nameWithExtension = IGImageImpl.getNameWithExtension(
535 name, image.getImageType());
536
537 validate(folderId, nameWithExtension);
538 }
539 else {
540 name = image.getName();
541 }
542
543 image.setModifiedDate(new Date());
544 image.setFolderId(folder.getFolderId());
545 image.setName(name);
546 image.setDescription(description);
547
548 igImagePersistence.update(image, false);
549
550
552 if (renderedImage != null) {
553 saveImages(
554 image.getLargeImageId(), renderedImage,
555 image.getSmallImageId(), image.getCustom1ImageId(),
556 image.getCustom2ImageId(), bytes, contentType);
557 }
558
559
561 String[] tagsEntries = serviceContext.getTagsEntries();
562
563 updateTagsAsset(userId, image, tagsEntries);
564
565
567 try {
568 Indexer.updateImage(
569 image.getCompanyId(), folder.getGroupId(),
570 folder.getFolderId(), imageId, name, description,
571 image.getModifiedDate(), tagsEntries,
572 image.getExpandoBridge());
573 }
574 catch (SearchException se) {
575 _log.error("Indexing " + imageId, se);
576 }
577
578 return image;
579 }
580 catch (IOException ioe) {
581 throw new ImageSizeException(ioe);
582 }
583 }
584
585 public IGImage updateImage(
586 long userId, long imageId, long folderId, String name,
587 String description, File file, String contentType,
588 ServiceContext serviceContext)
589 throws PortalException, SystemException {
590
591 try {
592 byte[] bytes = null;
593
594 if ((file != null) && file.exists()) {
595 bytes = FileUtil.getBytes(file);
596 }
597
598 return updateImage(
599 userId, imageId, folderId, name, description, bytes,
600 contentType, serviceContext);
601 }
602 catch (IOException ioe) {
603 throw new SystemException(ioe);
604 }
605 }
606
607 public IGImage updateImage(
608 long userId, long imageId, long folderId, String name,
609 String description, InputStream is, String contentType,
610 ServiceContext serviceContext)
611 throws PortalException, SystemException {
612
613 try {
614 byte[] bytes = null;
615
616 if (is != null) {
617 bytes = FileUtil.getBytes(is);
618 }
619
620 return updateImage(
621 userId, imageId, folderId, name, description, bytes,
622 contentType, serviceContext);
623 }
624 catch (IOException ioe) {
625 throw new SystemException(ioe);
626 }
627 }
628
629 public void updateTagsAsset(
630 long userId, IGImage image, String[] tagsEntries)
631 throws PortalException, SystemException {
632
633 Image largeImage = imageLocalService.getImage(image.getLargeImageId());
634
635 if (largeImage == null) {
636 return;
637 }
638
639 tagsAssetLocalService.updateAsset(
640 userId, image.getFolder().getGroupId(), IGImage.class.getName(),
641 image.getImageId(), null, tagsEntries, true, null, null, null, null,
642 largeImage.getType(), null, image.getDescription(), null, null,
643 largeImage.getHeight(), largeImage.getWidth(), null, false);
644 }
645
646 protected IGFolder getFolder(IGImage image, long folderId)
647 throws PortalException, SystemException {
648
649 if (image.getFolderId() != folderId) {
650 IGFolder oldFolder = igFolderPersistence.findByPrimaryKey(
651 image.getFolderId());
652
653 IGFolder newFolder = igFolderPersistence.fetchByPrimaryKey(
654 folderId);
655
656 if ((newFolder == null) ||
657 (oldFolder.getGroupId() != newFolder.getGroupId())) {
658
659 folderId = image.getFolderId();
660 }
661 }
662
663 return igFolderPersistence.findByPrimaryKey(folderId);
664 }
665
666 protected void saveImages(
667 long largeImageId, RenderedImage renderedImage, long smallImageId,
668 long custom1ImageId, long custom2ImageId, byte[] bytes,
669 String contentType)
670 throws SystemException {
671
672 try {
673
674
676 imageLocalService.updateImage(largeImageId, bytes);
677
678
680 saveScaledImage(
681 renderedImage, smallImageId, contentType,
682 PropsValues.IG_IMAGE_THUMBNAIL_MAX_DIMENSION);
683
684 if (custom1ImageId > 0) {
685 saveScaledImage(
686 renderedImage, custom1ImageId, contentType,
687 PropsValues.IG_IMAGE_CUSTOM_1_MAX_DIMENSION);
688 }
689
690 if (custom2ImageId > 0) {
691 saveScaledImage(
692 renderedImage, custom2ImageId, contentType,
693 PropsValues.IG_IMAGE_CUSTOM_2_MAX_DIMENSION);
694 }
695 }
696 catch (IOException ioe) {
697 throw new SystemException(ioe);
698 }
699 }
700
701 protected void saveScaledImage(
702 RenderedImage renderedImage, long imageId, String contentType,
703 int dimension)
704 throws IOException, SystemException {
705
706 RenderedImage thumbnail = ImageProcessorUtil.scale(
707 renderedImage, dimension, dimension);
708
709 ByteArrayMaker bam = new ByteArrayMaker();
710
711 if (contentType.indexOf("bmp") != -1) {
712 ImageEncoder encoder = ImageCodec.createImageEncoder(
713 "BMP", bam, null);
714
715 encoder.encode(thumbnail);
716 }
717 else if (contentType.indexOf("gif") != -1) {
718 ImageProcessorUtil.encodeGIF(thumbnail, bam);
719 }
720 else if (contentType.indexOf("jpg") != -1 ||
721 contentType.indexOf("jpeg") != -1) {
722
723 ImageIO.write(thumbnail, "jpeg", bam);
724 }
725 else if (contentType.indexOf("png") != -1) {
726 ImageIO.write(thumbnail, "png", bam);
727 }
728 else if (contentType.indexOf("tif") != -1) {
729 ImageEncoder encoder = ImageCodec.createImageEncoder(
730 "TIFF", bam, null);
731
732 encoder.encode(thumbnail);
733 }
734
735 imageLocalService.updateImage(imageId, bam.toByteArray());
736 }
737
738 protected void validate(byte[] bytes) throws ImageSizeException {
739 if ((PropsValues.IG_IMAGE_MAX_SIZE > 0) &&
740 ((bytes == null) ||
741 (bytes.length > PropsValues.IG_IMAGE_MAX_SIZE))) {
742
743 throw new ImageSizeException();
744 }
745 }
746
747 protected void validate(long folderId, String nameWithExtension)
748 throws PortalException, SystemException {
749
750 if ((nameWithExtension.indexOf("\\\\") != -1) ||
751 (nameWithExtension.indexOf("//") != -1) ||
752 (nameWithExtension.indexOf(":") != -1) ||
753 (nameWithExtension.indexOf("*") != -1) ||
754 (nameWithExtension.indexOf("?") != -1) ||
755 (nameWithExtension.indexOf("\"") != -1) ||
756 (nameWithExtension.indexOf("<") != -1) ||
757 (nameWithExtension.indexOf(">") != -1) ||
758 (nameWithExtension.indexOf("|") != -1) ||
759 (nameWithExtension.indexOf("&") != -1) ||
760 (nameWithExtension.indexOf("[") != -1) ||
761 (nameWithExtension.indexOf("]") != -1) ||
762 (nameWithExtension.indexOf("'") != -1)) {
763
764 throw new ImageNameException();
765 }
766
767 boolean validImageExtension = false;
768
769 String[] imageExtensions =
770 PropsUtil.getArray(PropsKeys.IG_IMAGE_EXTENSIONS);
771
772 for (int i = 0; i < imageExtensions.length; i++) {
773 if (StringPool.STAR.equals(imageExtensions[i]) ||
774 StringUtil.endsWith(nameWithExtension, imageExtensions[i])) {
775
776 validImageExtension = true;
777
778 break;
779 }
780 }
781
782 if (!validImageExtension) {
783 throw new ImageNameException();
784 }
785
786 String name = FileUtil.stripExtension(nameWithExtension);
787 String imageType = FileUtil.getExtension(nameWithExtension);
788
789 List<IGImage> images = igImagePersistence.findByF_N(folderId, name);
790
791 if (imageType.equals("jpeg")) {
792 imageType = ImageProcessor.TYPE_JPEG;
793 }
794 else if (imageType.equals("tif")) {
795 imageType = ImageProcessor.TYPE_TIFF;
796 }
797
798 for (IGImage image : images) {
799 if (imageType.equals(image.getImageType())) {
800 throw new DuplicateImageNameException();
801 }
802 }
803 }
804
805 protected void validate(
806 long folderId, String nameWithExtension, String fileName,
807 byte[] bytes)
808 throws PortalException, SystemException {
809
810 if (Validator.isNotNull(fileName)) {
811 String extension = FileUtil.getExtension(fileName);
812
813 if (Validator.isNull(nameWithExtension)) {
814 nameWithExtension = fileName;
815 }
816 else if (!StringUtil.endsWith(nameWithExtension, extension)) {
817 throw new ImageNameException();
818 }
819 }
820
821 validate(folderId, nameWithExtension);
822 validate(bytes);
823 }
824
825 private static Log _log =
826 LogFactoryUtil.getLog(IGImageLocalServiceImpl.class);
827
828 }