001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://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: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://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.scheduler; 029 030import org.opencms.main.CmsLog; 031import org.opencms.scheduler.CmsScheduledJobInfo; 032import org.opencms.ui.CmsCssIcon; 033import org.opencms.ui.CmsVaadinUtils; 034import org.opencms.ui.Messages; 035import org.opencms.ui.apps.A_CmsWorkplaceApp; 036import org.opencms.ui.apps.CmsAppWorkplaceUi; 037import org.opencms.ui.components.CmsConfirmationDialog; 038import org.opencms.ui.components.CmsResourceInfo; 039import org.opencms.ui.components.OpenCmsTheme; 040import org.opencms.ui.contextmenu.CmsContextMenu; 041import org.opencms.ui.contextmenu.CmsMenuItemVisibilityMode; 042import org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry; 043import org.opencms.util.CmsStringUtil; 044 045import java.util.ArrayList; 046import java.util.Collections; 047import java.util.HashSet; 048import java.util.List; 049import java.util.Locale; 050import java.util.Set; 051 052import org.apache.commons.logging.Log; 053 054import com.vaadin.event.MouseEvents; 055import com.vaadin.shared.MouseEventDetails.MouseButton; 056import com.vaadin.ui.themes.ValoTheme; 057import com.vaadin.v7.data.util.BeanItem; 058import com.vaadin.v7.data.util.BeanItemContainer; 059import com.vaadin.v7.event.ItemClickEvent; 060import com.vaadin.v7.event.ItemClickEvent.ItemClickListener; 061import com.vaadin.v7.ui.Table; 062 063/** 064 * Table used to display scheduled jobs, together with buttons for modifying the jobs.<p> 065 * The columns containing the buttons are implemented as generated columns. 066 */ 067public class CmsJobTable extends Table { 068 069 /** 070 * Property columns of table, including their Messages for header.<p> 071 */ 072 protected enum TableProperty { 073 074 /** 075 * Class name column. 076 */ 077 className("className", org.opencms.workplace.tools.scheduler.Messages.GUI_JOBS_LIST_COL_CLASS_0), 078 079 /** 080 * icon column. 081 */ 082 icon("icon", null), 083 084 /** 085 * last execution date column. 086 */ 087 lastExecution("lastExecution", org.opencms.workplace.tools.scheduler.Messages.GUI_JOBS_LIST_COL_LASTEXE_0), 088 089 /** 090 * Name column. 091 */ 092 name("name", org.opencms.workplace.tools.scheduler.Messages.GUI_JOBS_LIST_COL_NAME_0), 093 094 /** 095 * next execution date column. 096 */ 097 nextExecution("nextExecution", org.opencms.workplace.tools.scheduler.Messages.GUI_JOBS_LIST_COL_NEXTEXE_0); 098 099 /**Message for the header.*/ 100 private String m_header; 101 102 /**Name of column.*/ 103 private String m_name; 104 105 /** 106 * private constructor.<p> 107 * 108 * @param propName name of property 109 * @param header message 110 */ 111 private TableProperty(String propName, String header) { 112 113 m_header = header; 114 m_name = propName; 115 } 116 117 /** 118 * returns property from it's name used for table column ids.<p> 119 * 120 * @param propName to looked up 121 * @return the TableProperty 122 */ 123 static TableProperty get(String propName) { 124 125 for (TableProperty prop : TableProperty.values()) { 126 if (prop.toString().equals(propName)) { 127 return prop; 128 } 129 } 130 return null; 131 } 132 133 /** 134 * Returns all columns with header.<p> 135 * 136 * @return set of TableProperty 137 */ 138 static Set<TableProperty> withHeader() { 139 140 Set<TableProperty> ret = new HashSet<TableProperty>(); 141 142 for (TableProperty prop : TableProperty.values()) { 143 if (prop.getMessageKey() != null) { 144 ret.add(prop); 145 } 146 } 147 return ret; 148 } 149 150 /** 151 * @see java.lang.Enum#toString() 152 */ 153 @Override 154 public String toString() { 155 156 return m_name; 157 } 158 159 /** 160 * Returns the message key.<p> 161 * 162 * @return message 163 */ 164 String getMessageKey() { 165 166 return m_header; 167 } 168 169 } 170 171 /** 172 * Enum representing the actions for which buttons exist in the table rows.<p> 173 */ 174 enum Action { 175 176 /** Enable / disable. */ 177 activation(org.opencms.workplace.tools.scheduler.Messages.GUI_JOBS_LIST_ACTION_MACTIVATE_NAME_0, 178 org.opencms.workplace.tools.scheduler.Messages.GUI_JOBS_LIST_ACTION_MDEACTIVATE_NAME_0), 179 180 /** Create new job from template. */ 181 copy(org.opencms.workplace.tools.scheduler.Messages.GUI_JOBS_LIST_ACTION_COPY_NAME_0, ""), 182 183 /** Deletes the job. */ 184 delete(org.opencms.workplace.tools.scheduler.Messages.GUI_JOBS_LIST_ACTION_DELETE_NAME_0, ""), 185 186 /** Edits the job. */ 187 /** Message constant for key in the resource bundle. */ 188 edit(org.opencms.workplace.tools.scheduler.Messages.GUI_JOBS_LIST_ACTION_EDIT_NAME_0, ""), 189 190 /** Executes the job immediately. */ 191 run(org.opencms.workplace.tools.scheduler.Messages.GUI_JOBS_LIST_ACTION_EXECUTE_NAME_0, ""); 192 193 /** The message key. */ 194 private String m_key; 195 196 /** The message key for activated case.*/ 197 private String m_keyActivated; 198 199 /** 200 * Creates a new action.<p> 201 * 202 * @param key the message key for the action 203 * @param activatedKey an (optional) message key 204 */ 205 private Action(String key, String activatedKey) { 206 207 m_key = key; 208 m_keyActivated = activatedKey; 209 } 210 211 /** 212 * Returns an activated key. 213 * 214 * @return a message key 215 */ 216 String getActivatedMessageKey() { 217 218 return CmsStringUtil.isEmptyOrWhitespaceOnly(m_keyActivated) ? m_key : m_keyActivated; 219 } 220 221 /** 222 * Gets the message key for the action.<p> 223 * 224 * @return the message key 225 */ 226 String getMessageKey() { 227 228 return m_key; 229 } 230 } 231 232 /** 233 * The activate job context menu entry.<p> 234 */ 235 class ActivateEntry implements I_CmsSimpleContextMenuEntry<Set<String>> { 236 237 /** 238 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object) 239 */ 240 public void executeAction(Set<String> data) { 241 242 CmsScheduledJobInfo job = (((Set<CmsJobBean>)getValue()).iterator().next()).getJob(); 243 CmsScheduledJobInfo jobClone = job.clone(); 244 jobClone.setActive(!job.isActive()); 245 246 m_manager.writeElement(jobClone); 247 reloadJobs(); 248 249 } 250 251 /** 252 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale) 253 */ 254 public String getTitle(Locale locale) { 255 256 return CmsVaadinUtils.getMessageText(Action.activation.getMessageKey()); 257 } 258 259 /** 260 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object) 261 */ 262 public CmsMenuItemVisibilityMode getVisibility(Set<String> data) { 263 264 if ((data == null) || (data.size() > 1) || (m_manager.getElement(data.iterator().next()) == null)) { 265 return CmsMenuItemVisibilityMode.VISIBILITY_INVISIBLE; 266 } 267 268 @SuppressWarnings("unchecked") 269 CmsScheduledJobInfo job = (((Set<CmsJobBean>)getValue()).iterator().next()).getJob(); 270 271 return !job.isActive() 272 ? CmsMenuItemVisibilityMode.VISIBILITY_ACTIVE 273 : CmsMenuItemVisibilityMode.VISIBILITY_INVISIBLE; 274 } 275 } 276 277 /** 278 * The copy job context menu entry.<p> 279 */ 280 class CopyEntry implements I_CmsSimpleContextMenuEntry<Set<String>> { 281 282 /** 283 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object) 284 */ 285 public void executeAction(Set<String> data) { 286 287 m_manager.openEditDialog(data.iterator().next(), true); 288 } 289 290 /** 291 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale) 292 */ 293 public String getTitle(Locale locale) { 294 295 return CmsVaadinUtils.getMessageText(Action.copy.getMessageKey()); 296 } 297 298 /** 299 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object) 300 */ 301 public CmsMenuItemVisibilityMode getVisibility(Set<String> data) { 302 303 return (data != null) && (data.size() == 1) && (m_manager.getElement(data.iterator().next()) != null) 304 ? CmsMenuItemVisibilityMode.VISIBILITY_ACTIVE 305 : CmsMenuItemVisibilityMode.VISIBILITY_INVISIBLE; 306 } 307 } 308 309 /** 310 * The activate job context menu entry.<p> 311 */ 312 class DeActivateEntry implements I_CmsSimpleContextMenuEntry<Set<String>> { 313 314 /** 315 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object) 316 */ 317 public void executeAction(Set<String> data) { 318 319 CmsScheduledJobInfo job = (((Set<CmsJobBean>)getValue()).iterator().next()).getJob(); 320 CmsScheduledJobInfo jobClone = job.clone(); 321 jobClone.setActive(!job.isActive()); 322 323 m_manager.writeElement(jobClone); 324 reloadJobs(); 325 326 } 327 328 /** 329 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale) 330 */ 331 public String getTitle(Locale locale) { 332 333 return CmsVaadinUtils.getMessageText(Action.activation.getActivatedMessageKey()); 334 } 335 336 /** 337 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object) 338 */ 339 public CmsMenuItemVisibilityMode getVisibility(Set<String> data) { 340 341 if ((data == null) || (data.size() > 1) || (m_manager.getElement(data.iterator().next()) == null)) { 342 return CmsMenuItemVisibilityMode.VISIBILITY_INVISIBLE; 343 } 344 345 @SuppressWarnings("unchecked") 346 CmsScheduledJobInfo job = (((Set<CmsJobBean>)getValue()).iterator().next()).getJob(); 347 348 return job.isActive() 349 ? CmsMenuItemVisibilityMode.VISIBILITY_ACTIVE 350 : CmsMenuItemVisibilityMode.VISIBILITY_INVISIBLE; 351 } 352 } 353 354 /** 355 * The delete job context menu entry.<p> 356 */ 357 class DeleteEntry implements I_CmsSimpleContextMenuEntry<Set<String>> { 358 359 /** 360 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object) 361 */ 362 @SuppressWarnings("unchecked") 363 public void executeAction(Set<String> data) { 364 365 String jobNames = ""; 366 final List<String> jobIds = new ArrayList<String>(); 367 List<CmsResourceInfo> jobInfos = new ArrayList<CmsResourceInfo>(); 368 for (CmsJobBean job : (Set<CmsJobBean>)getValue()) { 369 jobIds.add(job.getJob().getId()); 370 jobNames += job.getName() + ", "; 371 jobInfos.add(getJobInfo(job.getName(), job.getClassName())); 372 } 373 if (!jobNames.isEmpty()) { 374 jobNames = jobNames.substring(0, jobNames.length() - 2); 375 } 376 377 CmsConfirmationDialog.show( 378 CmsVaadinUtils.getMessageText(Action.delete.getMessageKey()), 379 CmsVaadinUtils.getMessageText(Messages.GUI_SCHEDULER_CONFIRM_DELETE_1, jobNames), 380 new Runnable() { 381 382 public void run() { 383 384 m_manager.deleteElements(jobIds); 385 reloadJobs(); 386 } 387 }).displayResourceInfoDirectly(jobInfos); 388 } 389 390 /** 391 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale) 392 */ 393 public String getTitle(Locale locale) { 394 395 return CmsVaadinUtils.getMessageText(Action.delete.getMessageKey()); 396 } 397 398 /** 399 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object) 400 */ 401 public CmsMenuItemVisibilityMode getVisibility(Set<String> data) { 402 403 return (data != null) && (data.size() > 0) && (m_manager.getElement(data.iterator().next()) != null) 404 ? CmsMenuItemVisibilityMode.VISIBILITY_ACTIVE 405 : CmsMenuItemVisibilityMode.VISIBILITY_INVISIBLE; 406 } 407 } 408 409 /** 410 * The edit job context menu entry.<p> 411 */ 412 class EditEntry implements I_CmsSimpleContextMenuEntry<Set<String>>, I_CmsSimpleContextMenuEntry.I_HasCssStyles { 413 414 /** 415 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object) 416 */ 417 public void executeAction(Set<String> data) { 418 419 m_manager.openEditDialog(data.iterator().next(), false); 420 } 421 422 /** 423 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry.I_HasCssStyles#getStyles() 424 */ 425 public String getStyles() { 426 427 return ValoTheme.LABEL_BOLD; 428 } 429 430 /** 431 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale) 432 */ 433 public String getTitle(Locale locale) { 434 435 return CmsVaadinUtils.getMessageText(Action.edit.getMessageKey()); 436 } 437 438 /** 439 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object) 440 */ 441 public CmsMenuItemVisibilityMode getVisibility(Set<String> data) { 442 443 return (data != null) && (data.size() == 1) && (m_manager.getElement(data.iterator().next()) != null) 444 ? CmsMenuItemVisibilityMode.VISIBILITY_ACTIVE 445 : CmsMenuItemVisibilityMode.VISIBILITY_INVISIBLE; 446 } 447 } 448 449 /** 450 * The delete job context menu entry.<p> 451 */ 452 class RunEntry implements I_CmsSimpleContextMenuEntry<Set<String>> { 453 454 /** 455 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object) 456 */ 457 public void executeAction(Set<String> data) { 458 459 @SuppressWarnings("unchecked") 460 final CmsScheduledJobInfo job = ((Set<CmsJobBean>)getValue()).iterator().next().getJob(); 461 462 CmsConfirmationDialog.show( 463 CmsVaadinUtils.getMessageText(Action.run.getMessageKey()), 464 CmsVaadinUtils.getMessageText(Messages.GUI_SCHEDULER_CONFIRM_EXECUTE_1, job.getJobName()), 465 new Runnable() { 466 467 public void run() { 468 469 m_manager.runJob(job); 470 } 471 }).displayResourceInfoDirectly( 472 Collections.singletonList(getJobInfo(job.getJobName(), job.getClassName()))); 473 } 474 475 /** 476 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale) 477 */ 478 public String getTitle(Locale locale) { 479 480 return CmsVaadinUtils.getMessageText(Action.run.getMessageKey()); 481 } 482 483 /** 484 * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object) 485 */ 486 public CmsMenuItemVisibilityMode getVisibility(Set<String> data) { 487 488 return (data != null) && (data.size() == 1) && (m_manager.getElement(data.iterator().next()) != null) 489 ? CmsMenuItemVisibilityMode.VISIBILITY_ACTIVE 490 : CmsMenuItemVisibilityMode.VISIBILITY_INVISIBLE; 491 } 492 } 493 494 /** Logger instance for this class. */ 495 static final Log LOG = CmsLog.getLog(CmsJobTable.class); 496 497 /** Serial version id. */ 498 private static final long serialVersionUID = 1L; 499 500 /** The job manager instance. */ 501 public CmsJobManagerApp m_manager; 502 503 /** Bean container for the table. */ 504 protected BeanItemContainer<CmsJobBean> m_beanContainer; 505 506 /** The context menu. */ 507 private CmsContextMenu m_menu; 508 509 /** The available menu entries. */ 510 private List<I_CmsSimpleContextMenuEntry<Set<String>>> m_menuEntries; 511 512 /** 513 * Creates a new instance.<p> 514 * 515 * @param manager the job manager instance 516 */ 517 public CmsJobTable(CmsJobManagerApp manager) { 518 519 m_manager = manager; 520 m_beanContainer = new BeanItemContainer<CmsJobBean>(CmsJobBean.class); 521 setContainerDataSource(m_beanContainer); 522 setVisibleColumns( 523 TableProperty.className.toString(), 524 TableProperty.name.toString(), 525 TableProperty.lastExecution.toString(), 526 TableProperty.nextExecution.toString()); 527 setItemIconPropertyId(TableProperty.icon.toString()); 528 setRowHeaderMode(RowHeaderMode.ICON_ONLY); 529 setColumnWidth(null, 40); 530 531 for (TableProperty prop : TableProperty.withHeader()) { 532 setColumnExpandRatio(prop.toString(), 1); 533 setColumnHeader(prop.toString(), CmsVaadinUtils.getMessageText(prop.getMessageKey())); 534 } 535 setSortContainerPropertyId(TableProperty.name.toString()); 536 getVisibleColumns(); 537 setSelectable(true); 538 setMultiSelect(true); 539 addItemClickListener(new ItemClickListener() { 540 541 private static final long serialVersionUID = -4738296706762013443L; 542 543 public void itemClick(ItemClickEvent event) { 544 545 onItemClick(event, event.getItemId(), event.getPropertyId()); 546 } 547 }); 548 549 m_menu = new CmsContextMenu(); 550 m_menu.setAsTableContextMenu(this); 551 552 setCellStyleGenerator(new CellStyleGenerator() { 553 554 private static final long serialVersionUID = 1L; 555 556 public String getStyle(Table source, Object itemId, Object propertyId) { 557 558 if (TableProperty.className.toString().equals(propertyId)) { 559 return " " + OpenCmsTheme.HOVER_COLUMN; 560 } 561 @SuppressWarnings("unchecked") 562 CmsScheduledJobInfo job = ((BeanItem<CmsJobBean>)source.getItem(itemId)).getBean().getJob(); 563 if (TableProperty.name.toString().equals(propertyId) & job.isActive()) { 564 return " " + OpenCmsTheme.IN_NAVIGATION; 565 } 566 return null; 567 } 568 }); 569 570 } 571 572 /** 573 * Returns the resource info box to the given job.<p> 574 * 575 * @param name the job name 576 * @param className the job class 577 * 578 * @return the info box component 579 */ 580 public static CmsResourceInfo getJobInfo(String name, String className) { 581 582 return new CmsResourceInfo(name, className, new CmsCssIcon(OpenCmsTheme.ICON_JOB)); 583 } 584 585 /** 586 * Returns the available menu entries.<p> 587 * 588 * @return the menu entries 589 */ 590 public List<I_CmsSimpleContextMenuEntry<Set<String>>> getMenuEntries() { 591 592 if (m_menuEntries == null) { 593 m_menuEntries = new ArrayList<I_CmsSimpleContextMenuEntry<Set<String>>>(); 594 m_menuEntries.add(new EditEntry()); 595 m_menuEntries.add(new ActivateEntry()); 596 m_menuEntries.add(new DeActivateEntry()); 597 m_menuEntries.add(new CopyEntry()); 598 m_menuEntries.add(new DeleteEntry()); 599 m_menuEntries.add(new RunEntry()); 600 } 601 return m_menuEntries; 602 } 603 604 /** 605 * Reloads the job table data.<p> 606 */ 607 public void reloadJobs() { 608 609 m_beanContainer.removeAllItems(); 610 for (CmsScheduledJobInfo job : m_manager.getAllElements()) { 611 m_beanContainer.addBean(new CmsJobBean(job)); 612 } 613 sort(); 614 refreshRowCache(); 615 616 } 617 618 /** 619 * Sets the menu entries.<p> 620 * 621 * @param newEntries to be set 622 */ 623 public void setMenuEntries(List<I_CmsSimpleContextMenuEntry<Set<String>>> newEntries) { 624 625 m_menuEntries = newEntries; 626 } 627 628 /** 629 * Calls the edit formular to edit a job.<p> 630 * 631 * @param jobId to be edited. 632 */ 633 void editJob(String jobId) { 634 635 String stateEdit = CmsJobManagerApp.PATH_NAME_EDIT; 636 stateEdit = A_CmsWorkplaceApp.addParamToState(stateEdit, CmsJobManagerApp.PARAM_JOB_ID, jobId); 637 CmsAppWorkplaceUi.get().showApp(CmsScheduledJobsAppConfig.APP_ID, stateEdit); 638 } 639 640 /** 641 * Handles the table item clicks, including clicks on images inside of a table item.<p> 642 * 643 * @param event the click event 644 * @param itemId of the clicked row 645 * @param propertyId column id 646 */ 647 @SuppressWarnings("unchecked") 648 void onItemClick(MouseEvents.ClickEvent event, Object itemId, Object propertyId) { 649 650 if (!event.isCtrlKey() && !event.isShiftKey()) { 651 changeValueIfNotMultiSelect(itemId); 652 // don't interfere with multi-selection using control key 653 if (event.getButton().equals(MouseButton.RIGHT) || (propertyId == null)) { 654 Set<String> jobIds = new HashSet<String>(); 655 for (CmsJobBean job : (Set<CmsJobBean>)getValue()) { 656 jobIds.add(job.getJob().getId()); 657 } 658 m_menu.setEntries(getMenuEntries(), jobIds); 659 m_menu.openForTable(event, itemId, propertyId, this); 660 } else if (event.getButton().equals(MouseButton.LEFT) 661 && TableProperty.className.toString().equals(propertyId)) { 662 663 String jobId = ((Set<CmsJobBean>)getValue()).iterator().next().getJob().getId(); 664 m_manager.defaultAction(jobId); 665 } 666 } 667 } 668 669 /** 670 * Checks value of table and sets it new if needed:<p> 671 * if multiselect: new itemId is in current Value? -> no change of value<p> 672 * no multiselect and multiselect, but new item not selected before: set value to new item<p> 673 * 674 * @param itemId if of clicked item 675 */ 676 private void changeValueIfNotMultiSelect(Object itemId) { 677 678 @SuppressWarnings("unchecked") 679 Set<String> value = (Set<String>)getValue(); 680 if (value == null) { 681 select(itemId); 682 } else if (!value.contains(itemId)) { 683 setValue(null); 684 select(itemId); 685 } 686 } 687 688}