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.ade.publish.client;
029
030import org.opencms.ade.publish.shared.CmsPublishData;
031import org.opencms.ade.publish.shared.CmsPublishGroupList;
032import org.opencms.ade.publish.shared.CmsPublishListToken;
033import org.opencms.ade.publish.shared.CmsPublishOptions;
034import org.opencms.ade.publish.shared.CmsWorkflow;
035import org.opencms.ade.publish.shared.CmsWorkflowAction;
036import org.opencms.ade.publish.shared.CmsWorkflowActionParams;
037import org.opencms.ade.publish.shared.CmsWorkflowResponse;
038import org.opencms.ade.publish.shared.rpc.I_CmsPublishService;
039import org.opencms.ade.publish.shared.rpc.I_CmsPublishServiceAsync;
040import org.opencms.gwt.client.CmsCoreProvider;
041import org.opencms.gwt.client.CmsEditableDataJSO;
042import org.opencms.gwt.client.rpc.CmsRpcAction;
043import org.opencms.gwt.client.ui.CmsNotification;
044import org.opencms.gwt.client.ui.CmsPopup;
045import org.opencms.gwt.client.ui.CmsPushButton;
046import org.opencms.gwt.client.ui.contenteditor.I_CmsContentEditorHandler;
047import org.opencms.gwt.client.ui.contextmenu.CmsContextMenuHandler;
048import org.opencms.gwt.client.ui.contextmenu.CmsDialogContextMenuHandler;
049import org.opencms.gwt.client.util.CmsDomUtil;
050import org.opencms.gwt.shared.CmsGwtConstants;
051import org.opencms.util.CmsStringUtil;
052import org.opencms.util.CmsUUID;
053
054import java.util.ArrayList;
055import java.util.HashMap;
056import java.util.List;
057import java.util.Map;
058
059import com.google.common.collect.Lists;
060import com.google.gwt.core.client.GWT;
061import com.google.gwt.dom.client.Element;
062import com.google.gwt.event.logical.shared.CloseHandler;
063import com.google.gwt.user.client.Window;
064import com.google.gwt.user.client.rpc.ServiceDefTarget;
065import com.google.gwt.user.client.ui.DeckPanel;
066import com.google.gwt.user.client.ui.PopupPanel;
067
068/**
069 *
070 * Main class for the publish dialog.<p>
071 *
072 * This class is mostly responsible for the control flow and RPC calls of the publish dialog.
073 * It delegates most of the actual GUI work to the {@link CmsPublishSelectPanel} and {@link CmsBrokenLinksPanel} classes.
074 *
075 * @since 8.0.0
076 *
077 */
078public class CmsPublishDialog extends CmsPopup {
079
080    /**
081     * A type which represents the state of a publish action.<p>
082     */
083    public enum State {
084        /** The publish dialog was cancelled. */
085        cancel,
086
087        /** The publish dialog has failed. */
088        failure,
089
090        /** The publish dialog has succeeded. */
091        success;
092    }
093
094    /**
095     * The action for publishing and/or removing resources from the publish list.<p>
096     */
097    private class CmsPublishAction extends CmsRpcAction<CmsWorkflowResponse> {
098
099        /** If true, try to ignore broken links when publishing. */
100        private CmsWorkflowAction m_action;
101
102        /** Creates a new instance of this action.
103         *
104         * @param action the workflow action to execute
105         */
106        public CmsPublishAction(CmsWorkflowAction action) {
107
108            m_action = action;
109        }
110
111        /**
112         * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
113         */
114        @Override
115        public void execute() {
116
117            start(0, true);
118            setLastAction(m_action);
119            CmsWorkflowActionParams actionParams = getWorkflowActionParams();
120            getService().executeAction(m_action, actionParams, this);
121
122        }
123
124        /**
125         * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
126         */
127        @Override
128        protected void onResponse(CmsWorkflowResponse result) {
129
130            stop(false);
131            onReceiveStatus(result);
132
133        }
134    }
135
136    /**
137     * The action for loading the publish list.<p>
138     */
139    private class CmsPublishListAction extends CmsRpcAction<CmsPublishGroupList> {
140
141        /**
142         * Constructor.<p>
143         */
144        protected CmsPublishListAction() {
145
146            // nothing to do
147        }
148
149        /**
150         * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
151         */
152        @Override
153        public void execute() {
154
155            start(0, true);
156            CmsPublishOptions options = getPublishOptions();
157            boolean projectChanged = m_projectChanged;
158            m_projectChanged = false;
159
160            getService().getResourceGroups(getSelectedWorkflow(), options, projectChanged, this);
161        }
162
163        /**
164         * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
165         */
166        @Override
167        protected void onResponse(CmsPublishGroupList result) {
168
169            stop(false);
170            onReceivePublishList(result);
171
172        }
173    }
174
175    /** The dialog width in pixels. */
176    public static final int DIALOG_WIDTH = 766;
177
178    /** The project map used by showPublishDialog. */
179    public static Map<String, String> m_staticProjects;
180
181    /** The index of the "broken links" panel. */
182    public static final int PANEL_BROKEN_LINKS = 1;
183
184    /** The index of the publish selection panel. */
185    public static final int PANEL_SELECT = 0;
186
187    /** The CSS bundle used for this widget. */
188    private static final I_CmsPublishCss CSS = I_CmsPublishLayoutBundle.INSTANCE.publishCss();
189    /** Flag indicating if the CSS has been initialized. */
190    private static boolean CSS_INITIALIZED;
191
192    /** The publish service instance. */
193    private static I_CmsPublishServiceAsync SERVICE;
194
195    /** The panel for selecting the resources to publish or remove from the publish list. */
196    protected CmsPublishSelectPanel m_publishSelectPanel;
197
198    /** Flag to keep track of whether the user just changed the project. */
199    boolean m_projectChanged;
200
201    /** The panel for showing the links that would be broken by publishing. */
202    private CmsBrokenLinksPanel m_brokenLinksPanel;
203
204    /** The content editor handler. */
205    private I_CmsContentEditorHandler m_editorHandler;
206
207    /** Stores a failure message. */
208    private String m_failureMessage;
209
210    /** Stores the last workflow action. */
211    private CmsWorkflowAction m_lastAction;
212
213    /** The context menu handler. */
214    private CmsContextMenuHandler m_menuHandler;
215
216    /** The root panel of this dialog which contains both the selection panel and the panel for displaying broken links. */
217    private DeckPanel m_panel = new DeckPanel();
218
219    /** The current publish list options. */
220    private CmsPublishOptions m_publishOptions;
221
222    /** Stores the state. */
223    private State m_state = State.cancel;
224
225    /** The id of the current workflow. */
226    private String m_workflowId;
227
228    /** The available workflows. */
229    private Map<String, CmsWorkflow> m_workflows;
230
231    /**
232     * Constructs a new publish dialog.<p>
233     *
234     * @param initData the initial data
235     * @param refreshAction the action to perform on a context menu triggered refresh
236     * @param editorHandler the content editor handler
237     */
238    public CmsPublishDialog(
239        CmsPublishData initData,
240        final Runnable refreshAction,
241        I_CmsContentEditorHandler editorHandler) {
242
243        super(800);
244        initCss();
245        setGlassEnabled(true);
246        setPositionFixed();
247        setAutoHideEnabled(false);
248        setModal(true);
249        addStyleName(CSS.publishDialog());
250        m_menuHandler = new CmsDialogContextMenuHandler() {
251
252            @Override
253            public void refreshResource(CmsUUID structureId) {
254
255                CmsPublishDialog.this.hide();
256                refreshAction.run();
257            }
258        };
259        m_editorHandler = editorHandler;
260        m_menuHandler.setEditorHandler(editorHandler);
261        m_workflows = initData.getWorkflows();
262        m_workflowId = initData.getSelectedWorkflowId();
263        m_publishOptions = initData.getOptions();
264        int availableHeight = Window.getClientHeight() - 290;
265        m_publishSelectPanel = new CmsPublishSelectPanel(
266            this,
267            initData.getProjects(),
268            initData.getOptions(),
269            initData.getWorkflows(),
270            initData.getSelectedWorkflowId(),
271            availableHeight);
272        m_brokenLinksPanel = new CmsBrokenLinksPanel(this, availableHeight);
273
274        addDialogClose(null);
275
276        m_panel = new DeckPanel();
277        m_panel.add(m_publishSelectPanel);
278        m_panel.add(m_brokenLinksPanel);
279        setMainContent(m_panel);
280        onReceivePublishList(initData.getGroups());
281    }
282
283    /**
284     * Shows the publish dialog.<p>
285     *
286     * @param result the publish data
287     * @param handler the dialog close handler (may be null)
288     * @param refreshAction the action to execute on a context menu triggered refresh
289     * @param editorHandler the content editor handler (may be null)
290     */
291    public static void showPublishDialog(
292        CmsPublishData result,
293        CloseHandler<PopupPanel> handler,
294        Runnable refreshAction,
295        I_CmsContentEditorHandler editorHandler) {
296
297        CmsPublishDialog publishDialog = new CmsPublishDialog(result, refreshAction, editorHandler);
298        if (handler != null) {
299            publishDialog.addCloseHandler(handler);
300        }
301        publishDialog.centerHorizontally(50);
302        // replace current notification widget by overlay
303        publishDialog.catchNotifications();
304    }
305
306    /**
307     * Convenience method which opens a publish dialog.<p>
308     *
309     * @param handler the close handler
310     * @param params the additional publish dialog parameters
311     * @param refreshAction the action to execute after a context menu triggered refresh
312     * @param editorHandler the content editor handler
313     */
314    public static void showPublishDialog(
315        final HashMap<String, String> params,
316        final CloseHandler<PopupPanel> handler,
317        final Runnable refreshAction,
318        final I_CmsContentEditorHandler editorHandler) {
319
320        List<String> structureIds = Lists.newArrayList();
321        for (CmsEditableDataJSO editableData : CmsDomUtil.getAllEditableDataForPage()) {
322            structureIds.add("" + editableData.getStructureId());
323        }
324
325        List<Element> collectorInfos = CmsDomUtil.getElementsByClass(CmsGwtConstants.CLASS_COLLECTOR_INFO);
326        int j = 1;
327        for (Element elem : collectorInfos) {
328            String infoJson = elem.getAttribute(CmsGwtConstants.ATTR_DATA_COLLECTOR);
329            params.put(CmsPublishOptions.PARAM_COLLECTOR_INFO + "." + j, infoJson);
330            j += 1;
331        }
332        String editableIdList = CmsStringUtil.listAsString(structureIds, ",");
333        params.put(CmsPublishOptions.PARAM_COLLECTOR_ITEMS, editableIdList);
334
335        (new CmsRpcAction<CmsPublishData>() {
336
337            /**
338             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
339             */
340            @Override
341            public void execute() {
342
343                start(0, true);
344                getService().getInitData(params, this);
345            }
346
347            /**
348             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
349             */
350            @Override
351            protected void onResponse(CmsPublishData result) {
352
353                stop(false);
354                showPublishDialog(result, handler, refreshAction, editorHandler);
355            }
356        }).execute();
357    }
358
359    /**
360     * Convenience method which opens a publish dialog.<p>
361     *
362     * @param refreshAction the action to execute after a context menu triggered refresh
363     */
364    public static void showPublishDialog(Runnable refreshAction) {
365
366        showPublishDialog(new HashMap<String, String>(), null, refreshAction, null);
367    }
368
369    /**
370     * Returns the publish service instance.<p>
371     *
372     * @return the publish service instance
373     */
374    protected static I_CmsPublishServiceAsync getService() {
375
376        if (SERVICE == null) {
377            SERVICE = GWT.create(I_CmsPublishService.class);
378            String serviceUrl = CmsCoreProvider.get().link("org.opencms.ade.publish.CmsPublishService.gwt");
379            ((ServiceDefTarget)SERVICE).setServiceEntryPoint(serviceUrl);
380        }
381        return SERVICE;
382    }
383
384    /**
385     * Executes the specified action for the selected resources.<p>
386     *
387     * @param actionKey the workflow action
388     */
389    public void executeAction(CmsWorkflowAction actionKey) {
390
391        (new CmsPublishAction(actionKey)).execute();
392    }
393
394    /**
395     * Gets the context menu handler.<p>
396     *
397     * @return the context menu handler
398     */
399    public CmsContextMenuHandler getContextMenuHandler() {
400
401        return m_menuHandler;
402    }
403
404    /**
405     * Returns the content editor handler.<p>
406     *
407     * @return the content editor handler
408     */
409    public I_CmsContentEditorHandler getEditorHandler() {
410
411        return m_editorHandler;
412    }
413
414    /**
415     * Gets the failure message.<p>
416     *
417     * @return the failure message
418     */
419    public String getFailureMessage() {
420
421        return m_failureMessage;
422    }
423
424    /**
425     * Gets the last workflow action.<p>
426     *
427     * @return the last workflow action
428     */
429    public CmsWorkflowAction getLastAction() {
430
431        return m_lastAction;
432    }
433
434    /**
435     * Returns the current publish options.<p>
436     *
437     * @return a publish options bean
438     */
439    public CmsPublishOptions getPublishOptions() {
440
441        return m_publishOptions;
442    }
443
444    /**
445     * Gets the publish dialog state.<p>
446     *
447     * @return the publish dialog state
448     */
449    public State getState() {
450
451        return m_state;
452    }
453
454    /**
455     * Checks whether the publish dialog has failed.<p>
456     *
457     * @return checks whether the publish dialog has succeeded
458     */
459    public boolean hasFailed() {
460
461        return m_state == State.failure;
462    }
463
464    /**
465     * Checks whether the publish dialog has succeeded.<p>
466     *
467     * @return true if the publish dialog has succeeded
468     */
469    public boolean hasSucceeded() {
470
471        return m_state == State.success;
472    }
473
474    /**
475     * Method which is called when the cancel button is pressed.<p>
476     */
477    public void onCancel() {
478
479        if (m_publishSelectPanel.m_model != null) {
480            final List<CmsUUID> toRemove = m_publishSelectPanel.m_model.getIdsOfAlreadyPublishedResources();
481            if (toRemove.isEmpty()) {
482                hide();
483            } else {
484                CmsRpcAction<CmsWorkflowResponse> action = new CmsRpcAction<CmsWorkflowResponse>() {
485
486                    @Override
487                    public void execute() {
488
489                        start(0, true);
490                        CmsWorkflowActionParams params = getWorkflowActionParams();
491                        getService().executeAction(
492                            new CmsWorkflowAction(CmsWorkflowAction.ACTION_CANCEL, "", true),
493                            params,
494                            this);
495                    }
496
497                    @Override
498                    protected void onResponse(CmsWorkflowResponse result) {
499
500                        stop(false);
501                        hide();
502
503                    }
504                };
505                action.execute();
506            }
507        } else {
508            hide();
509        }
510    }
511
512    /**
513     * Method which is  called when the back button is pressed.<p>
514     */
515    public void onGoBack() {
516
517        setPanel(PANEL_SELECT);
518    }
519
520    /**
521     * Method which is called after the publish list has been received from the server.<p>
522     *
523     * @param groups the groups of the publish list
524     */
525    public void onReceivePublishList(CmsPublishGroupList groups) {
526
527        m_publishSelectPanel.setGroupList(groups, true, groups.getOverrideWorkflowId());
528        setPanel(PANEL_SELECT);
529        if (!isVisible()) {
530            center();
531        }
532    }
533
534    /**
535     * Method which is called after the status from a publish action has arrived.<p>
536     *
537     * @param brokenResources the list of broken resources
538     */
539    public void onReceiveStatus(CmsWorkflowResponse brokenResources) {
540
541        if (brokenResources.isSuccess()) {
542            succeed();
543            hide();
544            CmsNotification.get().send(
545                CmsNotification.Type.NORMAL,
546                org.opencms.gwt.client.Messages.get().key(org.opencms.gwt.client.Messages.GUI_DONE_0));
547
548        } else {
549            m_failureMessage = brokenResources.getMessage();
550            m_state = State.failure;
551            m_brokenLinksPanel.setEntries(brokenResources.getResources(), brokenResources.getAvailableActions());
552            setPanel(PANEL_BROKEN_LINKS);
553        }
554    }
555
556    /**
557     * Sets the include related resources option.<p>
558     *
559     * @param includeRelated the include related option
560     */
561    public void setIncludeRelated(boolean includeRelated) {
562
563        m_publishOptions.setIncludeRelated(includeRelated);
564    }
565
566    /**
567     * Sets the include sibling resources option.<p>
568     *
569     * @param includeSiblings the include siblings option
570     */
571    public void setIncludeSiblings(boolean includeSiblings) {
572
573        m_publishOptions.setIncludeSiblings(includeSiblings);
574    }
575
576    /**
577     * Changes the currently active panel.<p>
578     *
579     * @param panelId the number of the panel to show
580     */
581    public void setPanel(int panelId) {
582
583        m_panel.showWidget(panelId);
584        removeAllButtons();
585        if (panelId == PANEL_SELECT) {
586            for (CmsPushButton button : m_publishSelectPanel.getButtons()) {
587                addButton(button);
588            }
589            m_publishSelectPanel.updateDialogTitle();
590        } else if (panelId == PANEL_BROKEN_LINKS) {
591            for (CmsPushButton button : m_brokenLinksPanel.getButtons()) {
592                addButton(button);
593            }
594            m_brokenLinksPanel.updateTitle();
595        }
596    }
597
598    /**
599     * This is called when the user just changed the project.<p>
600     */
601    public void setProjectChanged() {
602
603        m_projectChanged = true;
604    }
605
606    /**
607     * Sets the selected project id.<p>
608     *
609     * @param projectId the project id
610     */
611    public void setProjectId(CmsUUID projectId) {
612
613        m_publishOptions.setProjectId(projectId);
614    }
615
616    /**
617     * Sets the selected workflow id.<p>
618     *
619     * @param workflowId the workflow id
620     */
621    public void setWorkflowId(String workflowId) {
622
623        m_workflowId = workflowId;
624        if (!m_workflows.containsKey(workflowId)) {
625            m_workflowId = "WORKFLOW_PUBLISH";
626        }
627    }
628
629    /**
630     * Sets the publish dialog state to 'success'.<p>
631     */
632    public void succeed() {
633
634        m_state = State.success;
635        CmsCoreProvider.get().fireEvent(new CmsPublishEvent());
636    }
637
638    /**
639     * Method which is called when the publish options are changed.<p>
640     */
641    public void updateResourceList() {
642
643        (new CmsPublishListAction()).execute();
644    }
645
646    /**
647     * Returns the selected workflow.<p>
648     *
649     * @return the selected workflow
650     */
651    protected CmsWorkflow getSelectedWorkflow() {
652
653        return m_workflows.get(m_workflowId);
654    }
655
656    /**
657     * Gets the workflow action parameters to which the workflow action should be applied.<p>
658     *
659     * @return the workflow action parameters
660     */
661    protected CmsWorkflowActionParams getWorkflowActionParams() {
662
663        if (m_publishSelectPanel.isShowResources()) {
664            List<CmsUUID> resourcesToPublish = new ArrayList<CmsUUID>(m_publishSelectPanel.getResourcesToPublish());
665            List<CmsUUID> resourcesToRemove = new ArrayList<CmsUUID>(m_publishSelectPanel.getResourcesToRemove());
666            CmsWorkflowActionParams actionParams = new CmsWorkflowActionParams(resourcesToPublish, resourcesToRemove);
667            return actionParams;
668        } else {
669            CmsPublishOptions options = getPublishOptions();
670            CmsWorkflow workflow = getSelectedWorkflow();
671            return new CmsWorkflowActionParams(new CmsPublishListToken(workflow, options));
672
673        }
674    }
675
676    /**
677     * Sets the last workflow action.<p>
678     *
679     * @param action a workflow action
680     */
681    protected void setLastAction(CmsWorkflowAction action) {
682
683        m_lastAction = action;
684    }
685
686    /**
687     * Ensures all style sheets are loaded.<p>
688     */
689    private void initCss() {
690
691        if (!CSS_INITIALIZED) {
692            I_CmsPublishLayoutBundle.INSTANCE.publishCss().ensureInjected();
693            CSS_INITIALIZED = true;
694        }
695    }
696
697}