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.sessions;
029
030import org.opencms.file.CmsUser;
031import org.opencms.main.CmsException;
032import org.opencms.main.CmsLog;
033import org.opencms.main.CmsSessionInfo;
034import org.opencms.main.OpenCms;
035import org.opencms.security.CmsOrganizationalUnit;
036import org.opencms.site.CmsSite;
037import org.opencms.ui.A_CmsUI;
038import org.opencms.ui.CmsCssIcon;
039import org.opencms.ui.CmsVaadinUtils;
040import org.opencms.ui.apps.Messages;
041import org.opencms.ui.components.CmsBasicDialog;
042import org.opencms.ui.components.CmsResourceIcon;
043import org.opencms.ui.components.OpenCmsTheme;
044import org.opencms.ui.contextmenu.CmsContextMenu;
045import org.opencms.ui.contextmenu.CmsMenuItemVisibilityMode;
046import org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry;
047import org.opencms.util.CmsStringUtil;
048
049import java.util.ArrayList;
050import java.util.List;
051import java.util.Locale;
052import java.util.Set;
053
054import org.apache.commons.logging.Log;
055
056import com.vaadin.v7.data.Item;
057import com.vaadin.v7.data.util.IndexedContainer;
058import com.vaadin.v7.data.util.filter.Or;
059import com.vaadin.v7.data.util.filter.SimpleStringFilter;
060import com.vaadin.v7.event.ItemClickEvent;
061import com.vaadin.v7.event.ItemClickEvent.ItemClickListener;
062import com.vaadin.event.MouseEvents;
063import com.vaadin.shared.MouseEventDetails.MouseButton;
064import com.vaadin.v7.shared.ui.label.ContentMode;
065import com.vaadin.ui.Component;
066import com.vaadin.v7.ui.Label;
067import com.vaadin.v7.ui.Table;
068import com.vaadin.ui.Window;
069import com.vaadin.ui.themes.ValoTheme;
070
071/**
072 * Class for the table to show all current sessions.<p>
073 */
074public class CmsSessionsTable extends Table {
075
076    /**
077     * The menu entry to switch to the explorer of concerning site.<p>
078     */
079    class KillEntry implements I_CmsSimpleContextMenuEntry<Set<String>> {
080
081        /**
082         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
083         */
084        public void executeAction(Set<String> data) {
085
086            showKillDialog(
087                data,
088                CmsVaadinUtils.getMessageText(
089                    Messages.GUI_MESSAGES_DESTROY_SESSIONS_1,
090                    CmsSessionsApp.getUserNames(data, CmsVaadinUtils.getMessageText(Messages.GUI_MESSAGES_AND_0))),
091                CmsSessionsTable.this);
092
093        }
094
095        /**
096         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
097         */
098        public String getTitle(Locale locale) {
099
100            return CmsVaadinUtils.getMessageText(Messages.GUI_MESSAGES_DESTROY_SESSION_0);
101        }
102
103        /**
104         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
105         */
106        public CmsMenuItemVisibilityMode getVisibility(Set<String> data) {
107
108            if ((data.size() == 1) & data.iterator().next().equals(m_mySessionId)) {
109                return CmsMenuItemVisibilityMode.VISIBILITY_INACTIVE;
110            }
111
112            return CmsMenuItemVisibilityMode.VISIBILITY_ACTIVE;
113        }
114
115    }
116
117    /**
118     * The menu entry to switch to the explorer of concerning site.<p>
119     */
120    class SendBroadcastEntry implements I_CmsSimpleContextMenuEntry<Set<String>> {
121
122        /**
123         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
124         */
125        public void executeAction(Set<String> data) {
126
127            CmsSessionsApp.showSendBroadcastDialog(
128                data,
129                CmsVaadinUtils.getMessageText(
130                    Messages.GUI_MESSAGES_BROADCAST_SESSIONS_1,
131                    CmsSessionsApp.getUserNames(data, CmsVaadinUtils.getMessageText(Messages.GUI_MESSAGES_AND_0))),
132                CmsSessionsTable.this);
133
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_MESSAGES_BROADCAST_SEND_0);
142        }
143
144        /**
145         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
146         */
147        public CmsMenuItemVisibilityMode getVisibility(Set<String> data) {
148
149            return CmsMenuItemVisibilityMode.VISIBILITY_ACTIVE;
150        }
151
152    }
153
154    /**
155     * All table properties.<p>
156     */
157    enum TableProperty {
158
159        /**Date of release column.*/
160        DateCreated(Messages.GUI_MESSAGES_BROADCAST_COLS_CREATION_0, String.class, "", false),
161        /**Icon.*/
162        Icon(null, Label.class, null, false),
163
164        /**Icon column.*/
165        IS_ACTIVE(Messages.GUI_MESSAGES_BROADCAST_COLS_STATUS_0, Long.class, new Long(0L), false),
166
167        /**Is Broadcast send but not displayed.*/
168        IS_WAITING(null, Boolean.class, new Boolean(false), false),
169
170        /**Date of expiration column. */
171        OrgUnit(Messages.GUI_MESSAGES_BROADCAST_COLS_ORGUNIT_0, String.class, "", false),
172
173        /**Last modified column. */
174        Project(Messages.GUI_MESSAGES_BROADCAST_COLS_PROJECT_0, String.class, "", false),
175
176        /**Path column.*/
177        Site(Messages.GUI_MESSAGES_BROADCAST_COLS_SITE_0, String.class, "", false),
178
179        /**Broken links column. */
180        UserName(Messages.GUI_MESSAGES_BROADCAST_COLS_USER_0, String.class, "", false);
181
182        /**Indicates if column is collapsable.*/
183        private boolean m_collapsable;
184
185        /**Default value for column.*/
186        private Object m_defaultValue;
187
188        /**Header Message key.*/
189        private String m_headerMessage;
190
191        /**Type of column property.*/
192        private Class<?> m_type;
193
194        /**
195         * constructor.
196         *
197         * @param headerMessage key
198         * @param type to property
199         * @param defaultValue of column
200         * @param collapsable should this column be collapsable?
201         */
202        TableProperty(String headerMessage, Class<?> type, Object defaultValue, boolean collapsable) {
203
204            m_headerMessage = headerMessage;
205            m_type = type;
206            m_defaultValue = defaultValue;
207            m_collapsable = collapsable;
208        }
209
210        /**
211         * Returns list of all properties with non-empty header.<p>
212         *
213         * @return list of properties
214         */
215        static List<TableProperty> withHeader() {
216
217            List<TableProperty> props = new ArrayList<TableProperty>();
218
219            for (TableProperty prop : TableProperty.values()) {
220                if (prop.m_headerMessage != null) {
221                    props.add(prop);
222                }
223            }
224            return props;
225        }
226
227        /**
228         * Returns the default value of property.<p>
229         *
230         * @return object
231         */
232        Object getDefaultValue() {
233
234            return m_defaultValue;
235        }
236
237        /**
238         * Returns localized header.<p>
239         *
240         * @return string for header
241         */
242        String getLocalizedMessage() {
243
244            if (m_headerMessage == null) {
245                return "";
246            }
247            return CmsVaadinUtils.getMessageText(m_headerMessage);
248        }
249
250        /**
251         * Returns tye of value for given property.<p>
252         *
253         * @return type
254         */
255        Class<?> getType() {
256
257            return m_type;
258        }
259
260        /**
261         * Indicates if column is collapsable.<p>
262         *
263         * @return boolean, true = is collapsable
264         */
265        boolean isCollapsable() {
266
267            return m_collapsable;
268        }
269
270    }
271
272    /**
273     * User entry.<p>
274     */
275    class UserEntry implements I_CmsSimpleContextMenuEntry<Set<String>>, I_CmsSimpleContextMenuEntry.I_HasCssStyles {
276
277        /**
278         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
279         */
280        public void executeAction(Set<String> context) {
281
282            showUserInfoWindow(context.iterator().next());
283
284        }
285
286        /**
287         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry.I_HasCssStyles#getStyles()
288         */
289        public String getStyles() {
290
291            return ValoTheme.LABEL_BOLD;
292        }
293
294        /**
295         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
296         */
297        public String getTitle(Locale locale) {
298
299            return CmsVaadinUtils.getMessageText(Messages.GUI_MESSAGES_SHOW_USER_0);
300        }
301
302        /**
303         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
304         */
305        public CmsMenuItemVisibilityMode getVisibility(Set<String> data) {
306
307            return (data != null) && (data.size() == 1)
308            ? CmsMenuItemVisibilityMode.VISIBILITY_ACTIVE
309            : CmsMenuItemVisibilityMode.VISIBILITY_INVISIBLE;
310        }
311
312    }
313
314    /** The logger for this class. */
315    static Log LOG = CmsLog.getLog(CmsSessionsTable.class.getName());
316
317    /**Time limit (in milliseconds) since when a user is inactive.*/
318    public static final long INACTIVE_LIMIT = 3 * 60 * 1000; //3 minute
319
320    /**vaadin serial id.*/
321    private static final long serialVersionUID = 4136423899776482696L;
322
323    /**Session id of user who uses the tool.*/
324    protected String m_mySessionId;
325
326    /**Container holding table data.*/
327    private IndexedContainer m_container;
328
329    /** The context menu. */
330    private CmsContextMenu m_menu;
331
332    /** The available menu entries. */
333    private List<I_CmsSimpleContextMenuEntry<Set<String>>> m_menuEntries;
334
335    /**
336     * public constructor.<p>
337     */
338    public CmsSessionsTable() {
339
340        try {
341
342            m_mySessionId = OpenCms.getSessionManager().getSessionInfo(
343                CmsVaadinUtils.getRequest()).getSessionId().getStringValue();
344
345            ini();
346
347            setColumnWidth(TableProperty.IS_ACTIVE, 80);
348
349            addGeneratedColumn(TableProperty.Icon, new ColumnGenerator() {
350
351                private static final long serialVersionUID = 1431421875590401227L;
352
353                public Object generateCell(Table source, Object itemId, Object columnId) {
354
355                    CmsCssIcon icon = new CmsCssIcon(OpenCmsTheme.ICON_SESSION);
356                    if (((Boolean)source.getItem(itemId).getItemProperty(
357                        TableProperty.IS_WAITING).getValue()).booleanValue()) {
358                        icon.setOverlay(OpenCmsTheme.STATE_CHANGED + " " + CmsResourceIcon.ICON_CLASS_CHANGED);
359                    }
360                    return new Label(icon.getHtmlWithOverlay(), ContentMode.HTML);
361                }
362
363            });
364
365            setCellStyleGenerator(new CellStyleGenerator() {
366
367                private static final long serialVersionUID = 1L;
368
369                public String getStyle(Table source, Object itemId, Object propertyId) {
370
371                    String furtherClass = "";
372                    if (m_mySessionId.equals(itemId)) {
373                        furtherClass = " " + OpenCmsTheme.IN_NAVIGATION;
374                    }
375
376                    if (TableProperty.UserName.equals(propertyId)) {
377                        return " " + OpenCmsTheme.HOVER_COLUMN + furtherClass;
378                    }
379
380                    if (((Boolean)source.getItem(itemId).getItemProperty(
381                        TableProperty.IS_WAITING).getValue()).booleanValue() & (propertyId == null)) {
382                        return " " + OpenCmsTheme.STATE_CHANGED;
383                    }
384
385                    if (TableProperty.IS_ACTIVE.equals(propertyId)) {
386                        return CmsUserInfoDialog.getStatusStyleForItem(
387                            (Long)source.getItem(itemId).getItemProperty(TableProperty.IS_ACTIVE).getValue());
388
389                    }
390
391                    return null;
392                }
393            });
394            addItemClickListener(new ItemClickListener() {
395
396                private static final long serialVersionUID = 7957778390938304845L;
397
398                public void itemClick(ItemClickEvent event) {
399
400                    onItemClick(event, event.getItemId(), event.getPropertyId());
401                }
402
403            });
404
405            addGeneratedColumn(TableProperty.IS_ACTIVE, new ColumnGenerator() {
406
407                private static final long serialVersionUID = -6781906011584975559L;
408
409                public Object generateCell(Table source, Object itemId, Object columnId) {
410
411                    return CmsUserInfoDialog.getStatusForItem(
412                        (Long)source.getItem(itemId).getItemProperty(TableProperty.IS_ACTIVE).getValue());
413
414                }
415
416            });
417
418            setItemDescriptionGenerator(new ItemDescriptionGenerator() {
419
420                private static final long serialVersionUID = 7367011213487089661L;
421
422                public String generateDescription(Component source, Object itemId, Object propertyId) {
423
424                    if (TableProperty.IS_ACTIVE.equals(propertyId)) {
425
426                        String[] ret = CmsSessionInfo.getHourMinuteSecondTimeString(
427                            ((Long)((Table)source).getItem(itemId).getItemProperty(propertyId).getValue()).longValue());
428
429                        if (Integer.parseInt(ret[1]) == 1) {
430                            return CmsVaadinUtils.getMessageText(Messages.GUI_MESSAGES_LAST_ACTIVITY_ONE_MINUTE_0);
431                        }
432                        if (Integer.parseInt(ret[1]) == 0) {
433                            return CmsVaadinUtils.getMessageText(Messages.GUI_MESSAGES_LAST_ACTIVITY_LESS_ONE_MINUTE_0);
434                        }
435                        return CmsVaadinUtils.getMessageText(
436                            Messages.GUI_MESSAGES_LAST_ACTIVITY_MINUTES_1,
437                            new Integer(ret[1]));
438                    }
439                    return null;
440                }
441            });
442
443        } catch (CmsException e) {
444            LOG.error("Unable to read sessions", e);
445        }
446
447    }
448
449    /**
450     * Runnable called when a window should be closed.<p>
451     * Reinitializes the table.<p>
452     *
453     * @param window to be closed
454     * @param table to be updated
455     * @return a runnable
456     */
457    protected static Runnable getCloseRunnable(final Window window, final CmsSessionsTable table) {
458
459        return new Runnable() {
460
461            public void run() {
462
463                window.close();
464                try {
465                    table.ini();
466                } catch (CmsException e) {
467                    LOG.error("Error on reading session information", e);
468                }
469
470            }
471
472        };
473    }
474
475    /**
476     *Shows the dialog to destroy given sessions.<p>
477     *
478     * @param ids to kill session
479     * @param caption of the window
480     * @param table to be updated
481     */
482    protected static void showKillDialog(Set<String> ids, String caption, final CmsSessionsTable table) {
483
484        final Window window = CmsBasicDialog.prepareWindow();
485        window.setCaption(caption);
486        window.setContent(new CmsKillSessionDialog(ids, getCloseRunnable(window, table)));
487        A_CmsUI.get().addWindow(window);
488    }
489
490    /**
491     * Filters the table according to given search string.<p>
492     *
493     * @param search string to be looked for.
494     */
495    public void filterTable(String search) {
496
497        m_container.removeAllContainerFilters();
498        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(search)) {
499            m_container.addContainerFilter(
500                new Or(
501                    new SimpleStringFilter(TableProperty.UserName, search, true, false),
502                    new SimpleStringFilter(TableProperty.Site, search, true, false),
503                    new SimpleStringFilter(TableProperty.Project, search, true, false)));
504        }
505        if ((getValue() != null) & !((Set<String>)getValue()).isEmpty()) {
506            setCurrentPageFirstItemId(((Set<String>)getValue()).iterator().next());
507        }
508    }
509
510    /**
511     * Initializes the table.<p>
512     *
513     * @throws CmsException when something goes wrong
514     */
515    protected void ini() throws CmsException {
516
517        if (m_container == null) {
518            m_container = new IndexedContainer();
519            setContainerDataSource(m_container);
520        } else {
521            m_container.removeAllItems();
522        }
523        for (TableProperty prop : TableProperty.values()) {
524            m_container.addContainerProperty(prop, prop.getType(), prop.getDefaultValue());
525            setColumnHeader(prop, prop.getLocalizedMessage());
526        }
527
528        setColumnWidth(TableProperty.Icon, 40);
529        setSelectable(true);
530        setMultiSelect(true);
531
532        m_menu = new CmsContextMenu();
533        m_menu.setAsTableContextMenu(this);
534
535        List<CmsSessionInfo> sessionInfos = OpenCms.getSessionManager().getSessionInfos();
536        List<CmsOrganizationalUnit> manageableOus = OpenCms.getRoleManager().getManageableOrgUnits(
537            A_CmsUI.getCmsObject(),
538            "",
539            true,
540            false);
541        for (CmsSessionInfo session : sessionInfos) {
542            CmsUser user = A_CmsUI.getCmsObject().readUser(session.getUserId());
543            CmsOrganizationalUnit userOu = OpenCms.getOrgUnitManager().readOrganizationalUnit(
544                A_CmsUI.getCmsObject(),
545                user.getOuFqn());
546            if (!(manageableOus.contains(userOu) && !user.isWebuser())) {
547                continue;
548            }
549            //            CmsListItem item = getList().newItem(sessionInfo.getSessionId().toString());
550            Item item = m_container.addItem(session.getSessionId().getStringValue());
551            item.getItemProperty(TableProperty.UserName).setValue(user.getName());
552            item.getItemProperty(TableProperty.DateCreated).setValue(
553                session.getAgeOfSession() + " " + CmsVaadinUtils.getMessageText(Messages.GUI_MESSAGES_HOUR_0));
554            item.getItemProperty(TableProperty.IS_ACTIVE).setValue(
555                new Long(System.currentTimeMillis() - session.getTimeUpdated()));
556            item.getItemProperty(TableProperty.OrgUnit).setValue(userOu.getName());
557            item.getItemProperty(TableProperty.Project).setValue(
558                A_CmsUI.getCmsObject().readProject(session.getProject()).getName());
559            CmsSite site = OpenCms.getSiteManager().getSiteForSiteRoot(session.getSiteRoot());
560            String siteTitle = site == null
561            ? CmsVaadinUtils.getMessageText(org.opencms.ade.galleries.Messages.GUI_ROOT_SITE_0)
562            : site.getTitle();
563            item.getItemProperty(TableProperty.Site).setValue(siteTitle);
564
565            item.getItemProperty(TableProperty.IS_WAITING).setValue(
566                new Boolean(!session.getBroadcastQueue().isEmpty()));
567
568        }
569
570        setVisibleColumns(
571            TableProperty.Icon,
572            TableProperty.IS_ACTIVE,
573            TableProperty.UserName,
574            TableProperty.DateCreated,
575            TableProperty.Site,
576            TableProperty.Project);
577
578    }
579
580    /**
581     * Shows window with user information.<p>
582     *
583     * @param data sessionid to be shown user off
584     */
585    protected void showUserInfoWindow(String data) {
586
587        CmsUserInfoDialog.showUserInfo(OpenCms.getSessionManager().getSessionInfo(data));
588    }
589
590    /**
591     * Returns the available menu entries.<p>
592     *
593     * @return the menu entries
594     */
595    List<I_CmsSimpleContextMenuEntry<Set<String>>> getMenuEntries() {
596
597        if (m_menuEntries == null) {
598            m_menuEntries = new ArrayList<I_CmsSimpleContextMenuEntry<Set<String>>>();
599            m_menuEntries.add(new UserEntry());
600            m_menuEntries.add(new SendBroadcastEntry());
601            m_menuEntries.add(new KillEntry());
602        }
603        return m_menuEntries;
604    }
605
606    /**
607     * Handles the table item clicks, including clicks on images inside of a table item.<p>
608     *
609     * @param event the click event
610     * @param itemId of the clicked row
611     * @param propertyId column id
612     */
613    void onItemClick(MouseEvents.ClickEvent event, Object itemId, Object propertyId) {
614
615        if (!event.isCtrlKey() && !event.isShiftKey()) {
616
617            changeValueIfNotMultiSelect(itemId);
618
619            // don't interfere with multi-selection using control key
620            if (event.getButton().equals(MouseButton.RIGHT) || (TableProperty.Icon.equals(propertyId))) {
621
622                m_menu.setEntries(getMenuEntries(), (Set<String>)getValue());
623                m_menu.openForTable(event, itemId, propertyId, this);
624            } else if (event.getButton().equals(MouseButton.LEFT) && TableProperty.UserName.equals(propertyId)) {
625                showUserInfoWindow(((Set<String>)getValue()).iterator().next());
626            }
627        }
628    }
629
630    /**
631     * Checks value of table and sets it new if needed:<p>
632     * if multiselect: new itemId is in current Value? -> no change of value<p>
633     * no multiselect and multiselect, but new item not selected before: set value to new item<p>
634     *
635     * @param itemId if of clicked item
636     */
637    private void changeValueIfNotMultiSelect(Object itemId) {
638
639        @SuppressWarnings("unchecked")
640        Set<String> value = (Set<String>)getValue();
641        if (value == null) {
642            select(itemId);
643        } else if (!value.contains(itemId)) {
644            setValue(null);
645            select(itemId);
646        }
647    }
648
649}