001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.ui.apps.user; 029 030import org.opencms.file.CmsObject; 031import org.opencms.file.CmsResource; 032import org.opencms.main.CmsException; 033import org.opencms.main.CmsLog; 034import org.opencms.main.OpenCms; 035import org.opencms.security.CmsOrganizationalUnit; 036import org.opencms.security.CmsRole; 037import org.opencms.security.CmsRoleViolationException; 038import org.opencms.ui.A_CmsUI; 039import org.opencms.ui.CmsCssIcon; 040import org.opencms.ui.CmsVaadinUtils; 041import org.opencms.ui.apps.Messages; 042import org.opencms.ui.components.CmsBasicDialog; 043import org.opencms.ui.components.CmsBasicDialog.DialogWidth; 044import org.opencms.ui.components.OpenCmsTheme; 045import org.opencms.ui.contextmenu.CmsContextMenu; 046import org.opencms.ui.contextmenu.CmsMenuItemVisibilityMode; 047import org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry; 048import org.opencms.util.CmsStringUtil; 049 050import java.util.ArrayList; 051import java.util.Collections; 052import java.util.List; 053import java.util.Locale; 054import java.util.Set; 055 056import org.apache.commons.logging.Log; 057 058import com.vaadin.server.Resource; 059import com.vaadin.shared.MouseEventDetails.MouseButton; 060import com.vaadin.ui.Window; 061import com.vaadin.ui.themes.ValoTheme; 062import com.vaadin.v7.data.Item; 063import com.vaadin.v7.data.util.IndexedContainer; 064import com.vaadin.v7.data.util.filter.Or; 065import com.vaadin.v7.data.util.filter.SimpleStringFilter; 066import com.vaadin.v7.event.ItemClickEvent; 067import com.vaadin.v7.event.ItemClickEvent.ItemClickListener; 068import com.vaadin.v7.ui.Table; 069import com.vaadin.v7.ui.VerticalLayout; 070 071/** 072 * Class to show ous in table for account management.<p> 073 */ 074public class CmsOUTable extends Table implements I_CmsFilterableTable { 075 076 /** 077 * Menu Entry for Delete option.<p> 078 */ 079 class EntryDelete implements I_CmsSimpleContextMenuEntry<Set<String>> { 080 081 /** 082 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object) 083 */ 084 public void executeAction(final Set<String> context) { 085 086 Window window = CmsBasicDialog.prepareWindow(); 087 CmsDeleteOUDialog dialog = new CmsDeleteOUDialog(m_cms, context.iterator().next(), window, m_app); 088 window.setContent(dialog); 089 window.setCaption(CmsVaadinUtils.getMessageText(Messages.GUI_USERMANAGEMENT_OU_DELETE_0)); 090 A_CmsUI.get().addWindow(window); 091 } 092 093 /** 094 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale) 095 */ 096 public String getTitle(Locale locale) { 097 098 return CmsVaadinUtils.getMessageText(Messages.GUI_USERMANAGEMENT_OU_DELETE_0); 099 } 100 101 /** 102 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object) 103 */ 104 public CmsMenuItemVisibilityMode getVisibility(Set<String> context) { 105 106 if (getItem(context.iterator().next()).getItemProperty(TableProperty.Type).getValue().equals( 107 CmsOuTreeType.OU)) { 108 if (isAdmin()) { 109 return CmsMenuItemVisibilityMode.VISIBILITY_ACTIVE; 110 } 111 return CmsMenuItemVisibilityMode.VISIBILITY_INACTIVE; 112 } 113 114 return CmsMenuItemVisibilityMode.VISIBILITY_INVISIBLE; 115 116 } 117 } 118 119 /** 120 * Edit menu entry.<p> 121 */ 122 class EntryEdit implements I_CmsSimpleContextMenuEntry<Set<String>> { 123 124 /** 125 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object) 126 */ 127 public void executeAction(Set<String> context) { 128 129 Window window = CmsBasicDialog.prepareWindow(); 130 window.setCaption(CmsVaadinUtils.getMessageText(Messages.GUI_USERMANAGEMENT_OU_EDIT_WINDOW_CAPTION_0)); 131 window.setContent(new CmsOUEditDialog(m_cms, context.iterator().next(), window, m_app)); 132 133 A_CmsUI.get().addWindow(window); 134 } 135 136 /** 137 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale) 138 */ 139 public String getTitle(Locale locale) { 140 141 return CmsVaadinUtils.getMessageText(Messages.GUI_USERMANAGEMENT_OU_EDIT_0); 142 } 143 144 /** 145 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object) 146 */ 147 public CmsMenuItemVisibilityMode getVisibility(Set<String> context) { 148 149 if (getItem(context.iterator().next()).getItemProperty(TableProperty.Type).getValue().equals( 150 CmsOuTreeType.OU)) { 151 if (isAdmin()) { 152 return CmsMenuItemVisibilityMode.VISIBILITY_ACTIVE; 153 } 154 return CmsMenuItemVisibilityMode.VISIBILITY_INACTIVE; 155 } 156 157 return CmsMenuItemVisibilityMode.VISIBILITY_INVISIBLE; 158 159 } 160 161 } 162 163 /** 164 * Entry for new user in the context menu.<p> 165 */ 166 class EntryImportExportUser implements I_CmsSimpleContextMenuEntry<Set<String>> { 167 168 /** 169 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object) 170 */ 171 public void executeAction(Set<String> context) { 172 173 openImportExportDialog(m_cms, context.iterator().next()); 174 } 175 176 177 178 /** 179 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale) 180 */ 181 public String getTitle(Locale locale) { 182 183 return CmsVaadinUtils.getMessageText(Messages.GUI_USERMANAGEMENT_USER_IMEXPORT_CONTEXTMENUNAME_0); 184 } 185 186 /** 187 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object) 188 */ 189 public CmsMenuItemVisibilityMode getVisibility(Set<String> context) { 190 191 if (getItem(context.iterator().next()).getItemProperty(TableProperty.Type).getValue().equals( 192 CmsOuTreeType.OU)) { 193 return CmsMenuItemVisibilityMode.VISIBILITY_ACTIVE; 194 } 195 return CmsMenuItemVisibilityMode.VISIBILITY_INVISIBLE; 196 } 197 198 } 199 200 /** 201 * New ou menu entry.<p> 202 */ 203 class EntryNewGroup implements I_CmsSimpleContextMenuEntry<Set<String>> { 204 205 /** 206 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object) 207 */ 208 public void executeAction(Set<String> context) { 209 210 Window window = CmsBasicDialog.prepareWindow(); 211 window.setCaption(CmsVaadinUtils.getMessageText(Messages.GUI_USERMANAGEMENT_ADD_GROUP_0)); 212 window.setContent(new CmsGroupEditDialog(m_cms, window, m_parentOu, m_app)); 213 214 A_CmsUI.get().addWindow(window); 215 } 216 217 /** 218 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale) 219 */ 220 public String getTitle(Locale locale) { 221 222 return CmsVaadinUtils.getMessageText(Messages.GUI_USERMANAGEMENT_ADD_GROUP_0); 223 } 224 225 /** 226 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object) 227 */ 228 public CmsMenuItemVisibilityMode getVisibility(Set<String> context) { 229 230 Object typeObj = getItem(context.iterator().next()).getItemProperty(TableProperty.Type).getValue(); 231 if (((I_CmsOuTreeType)typeObj).isGroup()) { 232 return CmsMenuItemVisibilityMode.VISIBILITY_ACTIVE; 233 234 } 235 return CmsMenuItemVisibilityMode.VISIBILITY_INVISIBLE; 236 } 237 238 } 239 240 /** 241 * Entry for new user in the context menu.<p> 242 */ 243 class EntryNewUser implements I_CmsSimpleContextMenuEntry<Set<String>> { 244 245 /** 246 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object) 247 */ 248 public void executeAction(Set<String> context) { 249 250 Window window = CmsBasicDialog.prepareWindow(); 251 window.setCaption(CmsVaadinUtils.getMessageText(Messages.GUI_USERMANAGEMENT_ADD_USER_0)); 252 window.setContent(new CmsUserEditDialog(m_cms, window, m_parentOu, m_app)); 253 254 A_CmsUI.get().addWindow(window); 255 } 256 257 /** 258 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale) 259 */ 260 public String getTitle(Locale locale) { 261 262 return CmsVaadinUtils.getMessageText(Messages.GUI_USERMANAGEMENT_ADD_USER_0); 263 } 264 265 /** 266 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object) 267 */ 268 public CmsMenuItemVisibilityMode getVisibility(Set<String> context) { 269 270 Object typeObj = getItem(context.iterator().next()).getItemProperty(TableProperty.Type).getValue(); 271 if (((I_CmsOuTreeType)typeObj).isUser()) { 272 return CmsMenuItemVisibilityMode.VISIBILITY_ACTIVE; 273 } 274 return CmsMenuItemVisibilityMode.VISIBILITY_INVISIBLE; 275 } 276 277 } 278 279 /** 280 * Entry to open item in context menu.<p> 281 */ 282 class EntryOpen implements I_CmsSimpleContextMenuEntry<Set<String>>, I_CmsSimpleContextMenuEntry.I_HasCssStyles { 283 284 /** 285 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object) 286 */ 287 public void executeAction(Set<String> context) { 288 289 String itemId = context.iterator().next(); 290 updateApp(itemId); 291 292 } 293 294 /** 295 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry.I_HasCssStyles#getStyles() 296 */ 297 public String getStyles() { 298 299 return ValoTheme.LABEL_BOLD; 300 } 301 302 /** 303 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale) 304 */ 305 public String getTitle(Locale locale) { 306 307 return CmsVaadinUtils.getMessageText(Messages.GUI_USERMANAGEMENT_OPEN_0); 308 } 309 310 /** 311 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object) 312 */ 313 public CmsMenuItemVisibilityMode getVisibility(Set<String> context) { 314 315 if (m_app != null) { 316 return CmsMenuItemVisibilityMode.VISIBILITY_ACTIVE; 317 } else { 318 return CmsMenuItemVisibilityMode.VISIBILITY_INVISIBLE; 319 } 320 } 321 322 } 323 324 /** 325 * Table properties.<p> 326 */ 327 enum TableProperty { 328 /** Description property.*/ 329 Description(Messages.GUI_USERMANAGEMENT_OU_DESCRIPTION_0, String.class, ""), 330 /**Icon property. */ 331 Icon(null, Resource.class, new CmsCssIcon(OpenCmsTheme.ICON_OU)), 332 /** Name property. */ 333 Name(Messages.GUI_USERMANAGEMENT_OU_NAME_0, String.class, ""), 334 /** Resources property.*/ 335 Ressources(Messages.GUI_USERMANAGEMENT_OU_EDIT_PANEL2_0, String.class, ""), 336 /** Type property. */ 337 Type(null, I_CmsOuTreeType.class, CmsOuTreeType.OU); 338 339 /**Default value for column.*/ 340 private Object m_defaultValue; 341 342 /**Header Message key.*/ 343 private String m_headerMessage; 344 345 /**Type of column property.*/ 346 private Class<?> m_type; 347 348 /** 349 * constructor.<p> 350 * 351 * @param name Name 352 * @param type type 353 * @param defaultValue value 354 */ 355 TableProperty(String name, Class<?> type, Object defaultValue) { 356 357 m_headerMessage = name; 358 m_type = type; 359 m_defaultValue = defaultValue; 360 } 361 362 /** 363 * The default value.<p> 364 * 365 * @return the default value object 366 */ 367 Object getDefault() { 368 369 return m_defaultValue; 370 } 371 372 /** 373 * Gets the name of the property.<p> 374 * 375 * @return a name 376 */ 377 String getName() { 378 379 return m_headerMessage == null ? "" : CmsVaadinUtils.getMessageText(m_headerMessage); 380 381 } 382 383 /** 384 * Gets the type of property.<p> 385 * 386 * @return the type 387 */ 388 Class<?> getType() { 389 390 return m_type; 391 } 392 393 } 394 395 /** Log instance for this class. */ 396 static final Log LOG = CmsLog.getLog(CmsOUTable.class); 397 398 /**vaadin serial id. */ 399 private static final long serialVersionUID = -1080519790145391678L; 400 401 /**Calling app. */ 402 protected CmsAccountsApp m_app; 403 404 /**Parent ou. */ 405 protected String m_parentOu; 406 407 /**CmsObject. */ 408 CmsObject m_cms; 409 410 /** The context menu. */ 411 CmsContextMenu m_menu; 412 413 /**Indexed container. */ 414 private IndexedContainer m_container; 415 416 /** The available menu entries. */ 417 private List<I_CmsSimpleContextMenuEntry<Set<String>>> m_menuEntries; 418 419 /** 420 * public constructor.<p> 421 * 422 * @param ou ou 423 * @param app calling app 424 */ 425 public CmsOUTable(String ou, CmsAccountsApp app) { 426 427 m_app = app; 428 init(ou); 429 } 430 431 /** 432 * Opens the import/export dialog for a specific OU. 433 * 434 * @param cms the CMS context 435 * @param ou the OU 436 */ 437 public static void openImportExportDialog(CmsObject cms, String ou) { 438 439 boolean includeTechnicalFields = false; 440 try { 441 OpenCms.getRoleManager().checkRole(cms, CmsRole.ADMINISTRATOR); 442 includeTechnicalFields = true; 443 } catch (CmsRoleViolationException e) { 444 // ok 445 } 446 Window window = CmsBasicDialog.prepareWindow(DialogWidth.wide); 447 window.setCaption(CmsVaadinUtils.getMessageText(Messages.GUI_USERMANAGEMENT_USER_IMEXPORT_DIALOGNAME_0)); 448 window.setContent( 449 CmsImportExportUserDialog.getExportUserDialogForOU( 450 ou, 451 window, 452 includeTechnicalFields)); 453 454 A_CmsUI.get().addWindow(window); 455 } 456 457 /** 458 * @see org.opencms.ui.apps.user.I_CmsFilterableTable#filter(java.lang.String) 459 */ 460 public void filter(String data) { 461 462 m_container.removeAllContainerFilters(); 463 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(data)) { 464 m_container.addContainerFilter( 465 new Or( 466 new SimpleStringFilter(TableProperty.Name, data, true, false), 467 new SimpleStringFilter(TableProperty.Description, data, true, false))); 468 } 469 470 } 471 472 /** 473 * @see org.opencms.ui.apps.user.I_CmsFilterableTable#getEmptyLayout() 474 */ 475 public VerticalLayout getEmptyLayout() { 476 477 VerticalLayout layout = new VerticalLayout(); 478 layout.setVisible(false); 479 return layout; 480 } 481 482 /** 483 * Checks if user has role to edit ou.<p> 484 * 485 * @return true if admin 486 */ 487 protected boolean isAdmin() { 488 489 return OpenCms.getRoleManager().hasRole(m_cms, CmsRole.ADMINISTRATOR.forOrgUnit(m_parentOu)); 490 } 491 492 /** 493 * Updates app.<p> 494 * 495 * @param itemId of current item 496 */ 497 protected void updateApp(String itemId) { 498 499 I_CmsOuTreeType foundType = null; 500 for (I_CmsOuTreeType type : m_app.getTreeTypeProvider().getTreeTypes()) { 501 if (itemId.equals(type.getId())) { 502 foundType = type; 503 break; 504 } 505 } 506 if (foundType != null) { 507 m_app.update(m_parentOu, foundType, null); 508 return; 509 } 510 m_app.update(itemId, CmsOuTreeType.OU, null, ""); 511 } 512 513 /** 514 * Returns the available menu entries.<p> 515 * 516 * @return the menu entries 517 */ 518 List<I_CmsSimpleContextMenuEntry<Set<String>>> getMenuEntries() { 519 520 if (m_menuEntries == null) { 521 m_menuEntries = new ArrayList<I_CmsSimpleContextMenuEntry<Set<String>>>(); 522 m_menuEntries.add(new EntryOpen()); 523 m_menuEntries.add(new EntryEdit()); 524 m_menuEntries.add(new EntryNewGroup()); 525 m_menuEntries.add(new EntryNewUser()); 526 m_menuEntries.add(new EntryImportExportUser()); 527 m_menuEntries.add(new EntryDelete()); 528 } 529 return m_menuEntries; 530 } 531 532 /** 533 * Adds ou to table.<p> 534 * 535 * @param ou to be added 536 */ 537 private void addOuToTable(CmsOrganizationalUnit ou) { 538 539 if (m_app.isParentOfManagableOU(ou.getName())) { 540 Item item = m_container.addItem(ou.getName()); 541 if (item != null) { 542 item.getItemProperty(TableProperty.Name).setValue(ou.getName()); 543 item.getItemProperty(TableProperty.Description).setValue(ou.getDisplayName(A_CmsUI.get().getLocale())); 544 if (ou.hasFlagWebuser()) { 545 item.getItemProperty(TableProperty.Icon).setValue(new CmsCssIcon(OpenCmsTheme.ICON_OU_WEB)); 546 } 547 } 548 } 549 } 550 551 /** 552 * initializes table.<p> 553 * 554 * @param parentOu ou name 555 */ 556 private void init(String parentOu) { 557 558 m_parentOu = parentOu; 559 m_menu = new CmsContextMenu(); 560 m_menu.setAsTableContextMenu(this); 561 562 m_container = new IndexedContainer(); 563 564 setContainerDataSource(m_container); 565 566 for (TableProperty prop : TableProperty.values()) { 567 m_container.addContainerProperty(prop, prop.getType(), prop.getDefault()); 568 setColumnHeader(prop, prop.getName()); 569 } 570 setContainerDataSource(m_container); 571 setItemIconPropertyId(TableProperty.Icon); 572 setRowHeaderMode(RowHeaderMode.ICON_ONLY); 573 574 setColumnWidth(null, 40); 575 setSelectable(true); 576 577 addGeneratedColumn(TableProperty.Ressources, new ColumnGenerator() { 578 579 private static final long serialVersionUID = 4624734503799549261L; 580 581 public Object generateCell(Table source, Object itemId, Object columnId) { 582 583 String out = ""; 584 try { 585 boolean isOu = true; 586 for (I_CmsOuTreeType type : m_app.getTreeTypeProvider().getTreeTypes()) { 587 if (type.getId().equals(itemId)) { 588 isOu = false; 589 } 590 } 591 if (isOu) { 592 List<CmsResource> resources = OpenCms.getOrgUnitManager().getResourcesForOrganizationalUnit( 593 m_cms, 594 (String)itemId); 595 if (!resources.isEmpty()) { 596 out = resources.get(0).getRootPath(); 597 int i = 1; 598 while ((resources.size() > i) & (out.length() < 50)) { 599 out += ", " + resources.get(i).getRootPath(); 600 } 601 if (resources.size() > i) { 602 out += " ..."; 603 } 604 } 605 } 606 } catch (CmsException e) { 607 LOG.error("unable to read resources.", e); 608 } 609 return out; 610 } 611 }); 612 613 setVisibleColumns(TableProperty.Name, TableProperty.Description, TableProperty.Ressources); 614 615 try { 616 m_cms = OpenCms.initCmsObject(A_CmsUI.getCmsObject()); 617 m_cms.getRequestContext().setSiteRoot(""); 618 } catch (CmsException e) { 619 m_cms = A_CmsUI.getCmsObject(); 620 } 621 try { 622 if (m_app.isOUManagable(m_parentOu)) { 623 624 for (I_CmsOuTreeType treeType : m_app.getTreeTypeProvider().getTreeTypes()) { 625 if (treeType.showInOuTable() && treeType.isValidForOu(m_cms, m_parentOu)) { 626 Item item = m_container.addItem(treeType.getId()); 627 item.getItemProperty(TableProperty.Name).setValue(treeType.getName()); 628 item.getItemProperty(TableProperty.Icon).setValue(treeType.getIcon()); 629 item.getItemProperty(TableProperty.Type).setValue(treeType); 630 } 631 } 632 } 633 List<CmsOrganizationalUnit> webOus = new ArrayList<CmsOrganizationalUnit>(); 634 for (CmsOrganizationalUnit ou : OpenCms.getOrgUnitManager().getOrganizationalUnits( 635 m_cms, 636 parentOu, 637 false)) { 638 639 if (ou.hasFlagWebuser()) { 640 webOus.add(ou); 641 } else { 642 addOuToTable(ou); 643 } 644 } 645 for (CmsOrganizationalUnit ou : webOus) { 646 addOuToTable(ou); 647 } 648 } catch (CmsException e) { 649 LOG.error("Unable to read ous", e); 650 } 651 652 addItemClickListener(new ItemClickListener() { 653 654 private static final long serialVersionUID = 4807195510202231174L; 655 656 public void itemClick(ItemClickEvent event) { 657 658 setValue(null); 659 select(event.getItemId()); 660 if (event.getButton().equals(MouseButton.RIGHT) || (event.getPropertyId() == null)) { 661 m_menu.setEntries(getMenuEntries(), Collections.singleton((String)getValue())); 662 m_menu.openForTable(event, event.getItemId(), event.getPropertyId(), CmsOUTable.this); 663 return; 664 } 665 if (event.getButton().equals(MouseButton.LEFT) && event.getPropertyId().equals(TableProperty.Name)) { 666 updateApp((String)getValue()); 667 } 668 669 } 670 671 }); 672 setCellStyleGenerator(new CellStyleGenerator() { 673 674 private static final long serialVersionUID = 1L; 675 676 public String getStyle(Table source, Object itemId, Object propertyId) { 677 678 if (TableProperty.Name.equals(propertyId)) { 679 return " " + OpenCmsTheme.HOVER_COLUMN; 680 } 681 682 return ""; 683 } 684 }); 685 } 686}