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.userdata;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsUser;
032import org.opencms.i18n.CmsEncoder;
033import org.opencms.jsp.userdata.I_CmsUserDataDomain;
034import org.opencms.jsp.userdata.Messages;
035import org.opencms.main.CmsException;
036import org.opencms.main.CmsLog;
037import org.opencms.main.OpenCms;
038import org.opencms.security.I_CmsPrincipal;
039import org.opencms.ui.A_CmsUI;
040import org.opencms.ui.CmsVaadinUtils;
041import org.opencms.ui.components.CmsBasicDialog;
042import org.opencms.ui.components.CmsBasicDialog.DialogWidth;
043import org.opencms.ui.components.CmsErrorDialog;
044import org.opencms.ui.components.OpenCmsTheme;
045import org.opencms.ui.components.editablegroup.CmsEditableGroup;
046import org.opencms.ui.dialogs.permissions.CmsPrincipalSelect;
047import org.opencms.ui.dialogs.permissions.CmsPrincipalSelect.WidgetType;
048import org.opencms.ui.dialogs.permissions.CmsPrincipalSelectDialog;
049import org.opencms.ui.report.CmsReportOverlay;
050import org.opencms.util.CmsFileUtil;
051import org.opencms.util.CmsStringUtil;
052
053import java.io.ByteArrayInputStream;
054import java.io.InputStream;
055import java.nio.charset.StandardCharsets;
056import java.util.ArrayList;
057import java.util.HashMap;
058import java.util.List;
059import java.util.concurrent.atomic.AtomicReference;
060import java.util.stream.Collectors;
061
062import org.apache.commons.logging.Log;
063
064import org.jsoup.Jsoup;
065import org.jsoup.nodes.Document;
066
067import com.vaadin.icons.VaadinIcons;
068import com.vaadin.server.FileDownloader;
069import com.vaadin.server.FontAwesome;
070import com.vaadin.server.Page;
071import com.vaadin.server.StreamResource;
072import com.vaadin.server.UserError;
073import com.vaadin.shared.ui.ContentMode;
074import com.vaadin.ui.Button;
075import com.vaadin.ui.FormLayout;
076import com.vaadin.ui.Label;
077import com.vaadin.ui.Notification;
078import com.vaadin.ui.TextField;
079import com.vaadin.ui.VerticalLayout;
080import com.vaadin.ui.Window;
081
082/**
083 * The GUI form for the user data app.
084 *
085 * <p>Generates a user data report either for a selected OpenCms user, or for an email address.
086 * The report is constructed by the I_CmsUserDataDomain plugins configured in opencms-system.xml.
087 */
088public class CmsUserDataAppPanel extends VerticalLayout {
089
090    /**
091     * Helper class recording the status of a user data search operation.
092     */
093    private class Status {
094
095        /** The m changed. */
096        private volatile boolean m_changed;
097
098        /** The m exception. */
099        private volatile Exception m_exception;
100
101        /**
102         * Gets the exception.
103         *
104         * @return the exception
105         */
106        public Exception getException() {
107
108            return m_exception;
109        }
110
111        /**
112         * Checks if is changed.
113         *
114         * @return true, if is changed
115         */
116        public boolean isChanged() {
117
118            return m_changed;
119        }
120
121        /**
122         * Sets the changed status.
123         *
124         * @param changed the new changed status
125         */
126        public void setChanged(boolean changed) {
127
128            m_changed = changed;
129        }
130
131        /**
132         * Sets the exception.
133         *
134         * @param exception the new exception
135         */
136        public void setException(Exception exception) {
137
138            m_exception = exception;
139        }
140
141    }
142
143    /** The serial version id. */
144    private static final long serialVersionUID = 1L;
145
146    /** The logger instance for this class. */
147    private static final Log LOG = CmsLog.getLog(CmsUserDataAppPanel.class);
148
149    /** CSS class for the container for the user data HTML. */
150    public static final String O_USERDATA_CONTAINER = "o-userdata-container";
151
152    /** The field for entering the email address. */
153    protected TextField m_email;
154
155    /** Contains the (dynamic) additional text filter fields. */
156    protected FormLayout m_filters;
157
158    /** The button for generating the report based on the email address. */
159    protected Button m_searchByEmail;
160
161    /** Manages the dynamic text filter fields. */
162    protected CmsEditableGroup m_filterGroup;
163
164    /** The widget containing the report/results. */
165    protected VerticalLayout m_resultsContainer;
166
167    /** The label for the results. */
168    protected Label m_resultsLabel;
169
170    /** The button used to download the report. */
171    protected Button m_download;
172
173    /** The button used to select an OpenCms user for whom to generate the report. */
174    protected Button m_pickUserButton;
175
176    /** The field containing the name of the user for whom to generate the report. */
177    protected TextField m_user;
178
179    /** The button which generates the report for the selected OpenCms user. */
180    protected Button m_searchByUser;
181
182    /** The report as a string (the content of the download). */
183    protected String m_result;
184
185    /** The m report overlay. */
186    protected AtomicReference<CmsReportOverlay> m_reportOverlay = new AtomicReference<>();
187
188    /**
189     * Creates a new instance.
190     */
191    public CmsUserDataAppPanel() {
192
193        CmsVaadinUtils.readAndLocalizeDesign(this, CmsVaadinUtils.getWpMessagesForCurrentLocale(), new HashMap<>());
194        m_resultsLabel.setContentMode(ContentMode.HTML);
195        m_resultsLabel.setWidth("100%");
196        m_resultsLabel.addStyleName(O_USERDATA_CONTAINER);
197        m_resultsContainer.setVisible(false);
198        m_filterGroup = new CmsEditableGroup(m_filters, () -> {
199            TextField filterField = new TextField();
200            return filterField;
201        }, CmsVaadinUtils.getMessageText(org.opencms.ui.apps.Messages.GUI_USERDATA_TEXT_FILTER_ADD_0));
202        m_filterGroup.setRowCaption(
203            CmsVaadinUtils.getMessageText(org.opencms.ui.apps.Messages.GUI_USERDATA_TEXT_FILTER_0));
204        m_filterGroup.addRow(new TextField());
205        FileDownloader downloader = new FileDownloader(new StreamResource(() -> {
206            String result = CmsUserDataAppPanel.this.getResult();
207            byte[] resultBytes = result.getBytes(StandardCharsets.UTF_8);
208            return new ByteArrayInputStream(resultBytes);
209        }, "userdata.html"));
210        downloader.extend(m_download);
211        m_download.setIcon(VaadinIcons.DOWNLOAD);
212        m_pickUserButton.setCaption("");
213        m_pickUserButton.setIcon(FontAwesome.USER);
214        m_pickUserButton.addStyleName(OpenCmsTheme.BUTTON_ICON);
215        m_pickUserButton.addClickListener(evt -> {
216            final Window window = CmsBasicDialog.prepareWindow(DialogWidth.max);
217
218            CmsPrincipalSelectDialog dialog = new CmsPrincipalSelectDialog(
219                principal -> selectUser(principal),
220                "",
221                window,
222                WidgetType.userwidget,
223                true,
224                CmsPrincipalSelect.PrincipalType.user);
225            dialog.setOuComboBoxEnabled(true);
226            window.setCaption(CmsVaadinUtils.getMessageText(org.opencms.ui.apps.Messages.GUI_USERDATA_SELECT_USER_0));
227            window.setContent(dialog);
228            A_CmsUI.get().addWindow(window);
229        });
230
231        m_searchByEmail.addClickListener(event -> {
232
233            hideResult();
234            List<String> filterStrings = new ArrayList<>();
235            m_filterGroup.getRows().stream().forEach(row -> {
236                if (row.getComponent() instanceof TextField) {
237                    TextField textField = (TextField)row.getComponent();
238                    String value = textField.getValue();
239                    if (!CmsStringUtil.isEmptyOrWhitespaceOnly(value)) {
240                        value = value.trim();
241                        filterStrings.add(value);
242                    }
243                }
244            });
245
246            String email = m_email.getValue().trim();
247            CmsObject cms = getCmsObjectForReport();
248            try {
249                Document doc = Jsoup.parseBodyFragment("");
250                doc.body().addClass(O_USERDATA_CONTAINER);
251                doc.head().append("<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />");
252                try (InputStream stream = getClass().getClassLoader().getResourceAsStream(
253                    "VAADIN/themes/opencms/userdata.css")) {
254                    String style = new String(CmsFileUtil.readFully(stream, false), StandardCharsets.UTF_8);
255                    doc.head().append("<style>\n" + style + "\n</style>\n");
256                }
257                @SuppressWarnings("synthetic-access")
258                Status status = new Status();
259                List<String> headerSearchTerms = new ArrayList<>();
260                if (!CmsStringUtil.isEmptyOrWhitespaceOnly(email)) {
261                    headerSearchTerms.add(email);
262                }
263                headerSearchTerms.addAll(filterStrings);
264                String headerSearchTermsString = headerSearchTerms.stream().map(s -> "\"" + s + "\"").collect(
265                    Collectors.joining(", "));
266                doc.body().appendElement("h1").attr("class", "udr-header").text(
267                    Messages.get().getBundle(cms.getRequestContext().getLocale()).key(
268                        Messages.GUI_USER_INFORMATION_FOR_1,
269                        headerSearchTermsString));
270
271                CmsUserDataReportThread thread = new CmsUserDataReportThread(cms, report -> {
272                    try {
273                        boolean changed = OpenCms.getUserDataRequestManager().getInfoForEmail(
274                            cms,
275                            I_CmsUserDataDomain.Mode.workplace,
276                            email,
277                            filterStrings,
278                            doc.body(),
279                            report);
280                        status.setChanged(changed);
281                    } catch (CmsException e) {
282                        LOG.error(e.getLocalizedMessage(), e);
283                        status.setException(e);
284                    }
285                });
286                if (m_reportOverlay.get() != null) {
287                    removeComponent(m_reportOverlay.get());
288                    m_reportOverlay.set(null);
289                }
290                m_reportOverlay.set(new CmsReportOverlay(thread));
291                addComponent(m_reportOverlay.get());
292                m_reportOverlay.get().addReportFinishedHandler(() -> {
293                    if (status.getException() != null) {
294                        CmsErrorDialog.showErrorDialog(status.getException());
295                    } else if (status.isChanged()) {
296                        showResult(doc);
297                    } else {
298                        showResult(null);
299                    }
300                });
301                thread.start();
302            } catch (Exception e) {
303                CmsErrorDialog.showErrorDialog(e);
304            }
305        });
306
307        m_searchByUser.addClickListener(evt -> {
308            hideResult();
309            m_user.setComponentError(null);
310            String user = m_user.getValue().trim();
311            CmsObject cms = getCmsObjectForReport();
312            CmsUser userObj = null;
313            try {
314                userObj = cms.readUser(user);
315            } catch (Exception e) {
316                LOG.info(e.getLocalizedMessage(), e);
317                m_user.setComponentError(
318                    new UserError(
319                        CmsVaadinUtils.getMessageText(org.opencms.ui.apps.Messages.GUI_USERDATA_USER_NOT_FOUND_0)));
320                return;
321            }
322            final CmsUser finalUser = userObj;
323            try {
324                Document doc = Jsoup.parseBodyFragment("");
325                @SuppressWarnings("synthetic-access")
326                Status status = new Status();
327                doc.body().appendElement("h1").attr("class", "udr-header").text(
328                    Messages.get().getBundle(cms.getRequestContext().getLocale()).key(
329                        Messages.GUI_USER_INFORMATION_FOR_1,
330                        user));
331
332                CmsUserDataReportThread thread = new CmsUserDataReportThread(cms, report -> {
333                    try {
334                        boolean changed = OpenCms.getUserDataRequestManager().getInfoForUser(
335                            cms,
336                            I_CmsUserDataDomain.Mode.workplace,
337                            finalUser,
338                            doc.body(),
339                            report);
340                        status.setChanged(changed);
341                    } catch (CmsException e) {
342                        LOG.error(e.getLocalizedMessage(), e);
343                        status.setException(e);
344                    }
345                });
346                if (m_reportOverlay.get() != null) {
347                    removeComponent(m_reportOverlay.get());
348                    m_reportOverlay.set(null);
349                }
350                m_reportOverlay.set(new CmsReportOverlay(thread));
351                addComponent(m_reportOverlay.get());
352                m_reportOverlay.get().addReportFinishedHandler(() -> {
353                    if (status.getException() != null) {
354                        CmsErrorDialog.showErrorDialog(status.getException());
355                    } else if (status.isChanged()) {
356                        showResult(doc);
357                    } else {
358                        showResult(null);
359                    }
360                });
361                thread.start();
362            } catch (Exception e) {
363                CmsErrorDialog.showErrorDialog(e);
364            }
365        });
366    }
367
368    /**
369     * Prepares the CmsObject to use for the report.
370     *
371     * <p>The CmsObject's locale needs to be set to the current locale, because the plugins
372     * used to generate the report do not use a separate locale parameter.
373     *
374     * @return the CmsObject to use for generating the report
375     */
376    protected CmsObject getCmsObjectForReport() {
377
378        CmsObject cms = A_CmsUI.getCmsObject();
379        try {
380            cms = OpenCms.initCmsObject(cms);
381            cms.getRequestContext().setLocale(OpenCms.getWorkplaceManager().getWorkplaceLocale(cms));
382        } catch (Exception e) {
383            LOG.error(e.getLocalizedMessage(), e);
384        }
385        return cms;
386    }
387
388    /**
389     * Gets the report for the download.
390     *
391     * @return the report as HTML text
392     */
393    protected String getResult() {
394
395        return m_result;
396    }
397
398    /**
399     * Hides the previous search results.
400     */
401    private void hideResult() {
402
403        m_resultsLabel.setValue("");
404        m_resultsContainer.setVisible(false);
405    }
406
407    /**
408     * Called when an OpenCms user is selected.
409     *
410     * @param principal the selected user
411     */
412    private void selectUser(I_CmsPrincipal principal) {
413
414        CmsUser user = (CmsUser)principal;
415        m_user.setComponentError(null);
416        m_user.setValue(user.getName());
417    }
418
419    /**
420     * Shows the report.
421     *
422     * <p>If null is given an argument, reports previously shown are hidden.
423     *
424     * @param doc the report (or null, to hide previous results)
425     */
426    private void showResult(Document doc) {
427
428        if (doc != null) {
429            m_resultsContainer.setVisible(true);
430            m_resultsLabel.setValue(doc.body().html());
431            m_result = "<!DOCTYPE html>\n" + doc.toString();
432        } else {
433            hideResult();
434            String notfound = CmsVaadinUtils.getMessageText(org.opencms.ui.apps.Messages.GUI_USERDATA_NOT_FOUND_0);
435            Notification notification = new Notification(
436                "",
437                "<p>" + CmsEncoder.escapeXml(notfound) + "</p>",
438                Notification.Type.WARNING_MESSAGE,
439                true);
440            notification.setDelayMsec(-1);
441            notification.show(Page.getCurrent());
442
443        }
444    }
445
446}