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.scheduler;
029
030import org.opencms.file.CmsResourceFilter;
031import org.opencms.main.CmsIllegalArgumentException;
032import org.opencms.main.CmsLog;
033import org.opencms.main.CmsRuntimeException;
034import org.opencms.monitor.CmsMemoryMonitor;
035import org.opencms.notification.CmsContentNotificationJob;
036import org.opencms.relations.CmsExternalLinksValidator;
037import org.opencms.relations.CmsInternalRelationsValidationJob;
038import org.opencms.scheduler.CmsScheduledJobInfo;
039import org.opencms.scheduler.jobs.CmsCreateImageSizeJob;
040import org.opencms.scheduler.jobs.CmsDeleteExpiredResourcesJob;
041import org.opencms.scheduler.jobs.CmsHistoryClearJob;
042import org.opencms.scheduler.jobs.CmsImageCacheCleanupJob;
043import org.opencms.scheduler.jobs.CmsPublishJob;
044import org.opencms.scheduler.jobs.CmsStaticExportJob;
045import org.opencms.scheduler.jobs.CmsUnsubscribeDeletedResourcesJob;
046import org.opencms.search.CmsSearchManager;
047import org.opencms.ui.A_CmsUI;
048import org.opencms.ui.CmsVaadinUtils;
049import org.opencms.ui.Messages;
050import org.opencms.ui.components.CmsBasicDialog;
051import org.opencms.ui.components.OpenCmsTheme;
052import org.opencms.ui.components.fileselect.CmsPathSelectField;
053import org.opencms.ui.util.CmsComboNullToEmptyConverter;
054import org.opencms.ui.util.CmsNullToEmptyConverter;
055import org.opencms.util.CmsStringUtil;
056
057import java.util.Collections;
058import java.util.Map;
059import java.util.SortedMap;
060import java.util.TreeMap;
061
062import org.apache.commons.logging.Log;
063
064import com.vaadin.server.FontAwesome;
065import com.vaadin.ui.Button;
066import com.vaadin.ui.Button.ClickEvent;
067import com.vaadin.ui.Button.ClickListener;
068import com.vaadin.ui.Component;
069import com.vaadin.ui.FormLayout;
070import com.vaadin.ui.themes.ValoTheme;
071import com.vaadin.v7.data.Validator;
072import com.vaadin.v7.data.fieldgroup.BeanFieldGroup;
073import com.vaadin.v7.shared.ui.combobox.FilteringMode;
074import com.vaadin.v7.ui.AbstractField;
075import com.vaadin.v7.ui.CheckBox;
076import com.vaadin.v7.ui.ComboBox;
077import com.vaadin.v7.ui.HorizontalLayout;
078import com.vaadin.v7.ui.TextField;
079
080/**
081 * Form used to edit a scheduled job.<p>
082 */
083public class CmsJobEditView extends CmsBasicDialog {
084
085    /**
086     * Validator for the cron expression field.<p>
087     */
088    public class CronExpressionValidator implements Validator {
089
090        /** Serial version id. */
091        private static final long serialVersionUID = 1L;
092
093        /**
094         * @see com.vaadin.data.Validator#validate(java.lang.Object)
095         */
096        public void validate(Object value) throws InvalidValueException {
097
098            CmsScheduledJobInfo info = new CmsScheduledJobInfo();
099            // Job name may be needed in exception
100            try {
101                info.setJobName(m_fieldJobName.getValue());
102            } catch (CmsRuntimeException e) {
103                throw new InvalidValueException(e.getLocalizedMessage(A_CmsUI.get().getLocale()));
104            }
105            String stringValue = (String)value;
106            try {
107                info.setCronExpression(stringValue);
108            } catch (CmsIllegalArgumentException e) {
109                throw new InvalidValueException(e.getLocalizedMessage(A_CmsUI.get().getLocale()));
110            }
111        }
112    }
113
114    /**
115     * Validator for the Java class name field.<p>
116     */
117    public class JobClassValidator implements Validator {
118
119        /** Serial version id. */
120        private static final long serialVersionUID = 1L;
121
122        /**
123         * @see com.vaadin.data.Validator#validate(java.lang.Object)
124         */
125        public void validate(Object value) throws InvalidValueException {
126
127            CmsScheduledJobInfo info = new CmsScheduledJobInfo();
128            String stringValue = (String)value;
129
130            // Job name may be needed in exception
131            try {
132                info.setJobName(m_fieldJobName.getValue());
133            } catch (CmsRuntimeException e) {
134                throw new InvalidValueException(e.getLocalizedMessage(A_CmsUI.get().getLocale()));
135            }
136            try {
137                info.setClassName(stringValue);
138            } catch (CmsIllegalArgumentException e) {
139                throw new InvalidValueException(e.getLocalizedMessage(A_CmsUI.get().getLocale()));
140            }
141        }
142    }
143
144    /**
145     * Validator for the job name.<p>
146     */
147    public class JobNameValidator implements Validator {
148
149        /** Serial version id. */
150        private static final long serialVersionUID = 1L;
151
152        /**
153         * @see com.vaadin.data.Validator#validate(java.lang.Object)
154         */
155        public void validate(Object value) throws InvalidValueException {
156
157            CmsScheduledJobInfo info = new CmsScheduledJobInfo();
158            String name = (String)value;
159            try {
160                info.setJobName(name);
161            } catch (CmsIllegalArgumentException e) {
162                throw new InvalidValueException(e.getLocalizedMessage(A_CmsUI.get().getLocale()));
163
164            }
165        }
166    }
167
168    /**
169     * TODO this is the same like CmsRemovableFormRow
170     * Widget used to display a line of text and also a remove button to remove the widget.<p>
171     */
172    class ParamLine extends HorizontalLayout {
173
174        /** Serial version id. */
175        private static final long serialVersionUID = 1L;
176
177        /** The text input field. */
178        private TextField m_input;
179
180        /**
181         * Creates a new instance.<p>
182         *
183         * @param content the initial content of the text field
184         */
185        public ParamLine(String content) {
186
187            setWidth("100%");
188            TextField input = new TextField();
189            m_input = input;
190            setCaption(CmsVaadinUtils.getMessageText(Messages.GUI_SCHEDULER_PARAMETER_0));
191            setSpacing(true);
192            input.setValue(content);
193            input.setWidth("100%");
194            addComponent(input);
195            setExpandRatio(input, 1f);
196            Button deleteButton = new Button("");
197            deleteButton.addStyleName(ValoTheme.BUTTON_LINK);
198            deleteButton.setIcon(FontAwesome.TIMES_CIRCLE);
199            deleteButton.addStyleName(OpenCmsTheme.BUTTON_UNPADDED);
200            deleteButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_SCHEDULER_REMOVE_PARAMETER_0));
201            deleteButton.addClickListener(new ClickListener() {
202
203                private static final long serialVersionUID = 1L;
204
205                public void buttonClick(ClickEvent event) {
206
207                    m_paramContainer.removeComponent(ParamLine.this);
208
209                }
210            });
211            addComponent(deleteButton);
212
213        }
214
215        /**
216         * Gets the value of the text field.<p>
217         *
218         * @return the value of the text field
219         */
220        public String getValue() {
221
222            return m_input.getValue();
223        }
224
225    }
226
227    /** Logger instance for this class. */
228    static final Log LOG = CmsLog.getLog(CmsJobEditView.class);
229
230    /** Serial version id. */
231    private static final long serialVersionUID = 1L;
232
233    /** Field for the job name. */
234    TextField m_fieldJobName;
235
236    /** Edited job. */
237    CmsScheduledJobInfo m_job = new CmsScheduledJobInfo();
238
239    /** The job manager instance. */
240    CmsJobManagerApp m_manager;
241
242    /** Form containing the job parameters. */
243    FormLayout m_paramContainer;
244
245    /** Button to add a new parameter. */
246    private Button m_buttonAddParam;
247
248    /** The cancel button. */
249    private Button m_cancel;
250
251    /** Field to activate / deactivate the job. */
252    private CheckBox m_fieldActive;
253
254    /** Field for the cron expression. */
255    private ComboBox m_fieldCron;
256
257    /** Field for the encoding. */
258    private TextField m_fieldEncoding;
259
260    /** Field for the class name. */
261    private ComboBox m_fieldJobClass;
262
263    /** Field for the locale. */
264    private TextField m_fieldLocale;
265
266    /** Field for the project. */
267    private TextField m_fieldProject;
268
269    /** Field for the remote address. */
270    private TextField m_fieldRemoteAddress;
271
272    /** 'Reuse instance' check box. */
273    private CheckBox m_fieldReuseInstance;
274
275    /** Field for the site root. */
276    private CmsPathSelectField m_fieldSiteRoot;
277
278    /** Field for the URI. */
279    private CmsPathSelectField m_fieldUri;
280
281    /** Field for the user name. */
282    private TextField m_fieldUser;
283
284    /** Field group. */
285    private BeanFieldGroup<CmsScheduledJobInfo> m_group;
286
287    /** The ok button. */
288    private Button m_ok;
289
290    /**
291     * Creates a new instance.<p>
292     *
293     * @param manager the job manager instance
294     * @param job the job to be edited
295     */
296    public CmsJobEditView(CmsJobManagerApp manager, CmsScheduledJobInfo job) {
297
298        m_manager = manager;
299        m_job = job;
300        CmsVaadinUtils.readAndLocalizeDesign(this, CmsVaadinUtils.getWpMessagesForCurrentLocale(), null);
301        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(job.getClassName())) {
302            displayResourceInfoDirectly(
303                Collections.singletonList(CmsJobTable.getJobInfo(job.getJobName(), job.getClassName())));
304        }
305        m_ok.addClickListener(new ClickListener() {
306
307            private static final long serialVersionUID = 1L;
308
309            public void buttonClick(ClickEvent event) {
310
311                if (trySaveToBean()) {
312                    m_manager.writeElement(m_job);
313                    m_manager.closeDialogWindow(true);
314                }
315
316            }
317        });
318        m_cancel.addClickListener(new ClickListener() {
319
320            private static final long serialVersionUID = 1L;
321
322            public void buttonClick(ClickEvent event) {
323
324                m_manager.closeDialogWindow(false);
325            }
326        });
327
328        BeanFieldGroup<CmsScheduledJobInfo> group = new BeanFieldGroup<CmsScheduledJobInfo>(CmsScheduledJobInfo.class);
329        group.setItemDataSource(m_job);
330        m_group = group;
331
332        bindField(m_fieldJobName, "jobName");
333        bindField(m_fieldJobClass, "className");
334        bindField(m_fieldCron, "cronExpression");
335        bindField(m_fieldReuseInstance, "reuseInstance");
336        bindField(m_fieldActive, "active");
337        bindField(m_fieldUser, "contextInfo.userName");
338        bindField(m_fieldProject, "contextInfo.projectName");
339        bindField(m_fieldSiteRoot, "contextInfo.siteRoot");
340        bindField(m_fieldUri, "contextInfo.requestedUri");
341        bindField(m_fieldLocale, "contextInfo.localeName");
342        bindField(m_fieldEncoding, "contextInfo.encoding");
343        bindField(m_fieldRemoteAddress, "contextInfo.remoteAddr");
344
345        m_fieldJobName.setConverter(new CmsNullToEmptyConverter());
346        m_fieldJobClass.setConverter(new CmsComboNullToEmptyConverter());
347        m_fieldCron.setConverter(new CmsComboNullToEmptyConverter());
348
349        m_buttonAddParam.addClickListener(new ClickListener() {
350
351            /** Serial version id. */
352            private static final long serialVersionUID = 1L;
353
354            public void buttonClick(ClickEvent event) {
355
356                addParamLine("");
357            }
358        });
359
360        m_fieldJobName.addValidator(new JobNameValidator());
361        m_fieldJobClass.setFilteringMode(FilteringMode.OFF);
362        m_fieldCron.setFilteringMode(FilteringMode.OFF);
363        m_fieldCron.setNewItemsAllowed(true);
364        m_fieldJobClass.setNewItemsAllowed(true);
365        m_fieldJobClass.setPageLength(20);
366        m_fieldJobClass.addValidator(new JobClassValidator());
367        m_fieldCron.addValidator(new CronExpressionValidator());
368        m_fieldJobClass.addItem(
369
370            CmsInternalRelationsValidationJob.class.getName());
371        m_fieldJobClass.addItem(CmsPublishJob.class.getName());
372        m_fieldJobClass.addItem(CmsStaticExportJob.class.getName());
373        m_fieldJobClass.addItem(CmsExternalLinksValidator.class.getName());
374        m_fieldJobClass.addItem(CmsMemoryMonitor.class.getName());
375        m_fieldJobClass.addItem(CmsSearchManager.class.getName());
376        m_fieldJobClass.addItem(CmsContentNotificationJob.class.getName());
377        m_fieldJobClass.addItem(CmsCreateImageSizeJob.class.getName());
378        m_fieldJobClass.addItem(CmsImageCacheCleanupJob.class.getName());
379        m_fieldJobClass.addItem(CmsHistoryClearJob.class.getName());
380        m_fieldJobClass.addItem(CmsDeleteExpiredResourcesJob.class.getName());
381        m_fieldJobClass.addItem(CmsUnsubscribeDeletedResourcesJob.class.getName());
382
383        if (!CmsStringUtil.isEmptyOrWhitespaceOnly(m_job.getClassName())) {
384            m_fieldJobClass.addItem(m_job.getClassName());
385        }
386
387        if (!CmsStringUtil.isEmptyOrWhitespaceOnly(m_job.getCronExpression())) {
388            m_fieldCron.addItem(m_job.getCronExpression());
389        }
390
391        for (String item : new String[] {
392            "0 0 3 * * ?",
393            "0 0/30 * * * ?",
394            "0 30 8 ? * 4",
395            "0 15 18 20 * ?",
396            "0 45 15 ? * 1 2007-2009"}) {
397            m_fieldCron.addItem(item);
398        }
399        m_fieldSiteRoot.setResourceFilter(CmsResourceFilter.DEFAULT_FOLDERS);
400    }
401
402    /**
403     * Initializes the form fields with values from the given job bean.<P>
404     *
405     * @param info the job bean with which to fill the form
406     */
407    public void loadFromBean(CmsScheduledJobInfo info) {
408
409        // all other fields already populated by field group, we still need to handle the parameters
410
411        for (Map.Entry<String, String> entry : info.getParameters().entrySet()) {
412            addParamLine(entry.getKey(), entry.getValue());
413        }
414    }
415
416    /**
417     * Try to save the form values to the edited bean.<p>
418     *
419     * @return true if setting the information was successful
420     */
421    public boolean trySaveToBean() {
422
423        try {
424            m_group.commit();
425        } catch (Exception e) {
426            LOG.info(e.getLocalizedMessage(), e);
427            return false;
428        }
429        m_job.setParameters(readParams());
430        return true;
431    }
432
433    /**
434     * Adds a new parameter input field with the given content.<p>
435     *
436     * @param content the content
437     */
438    void addParamLine(String content) {
439
440        m_paramContainer.addComponent(new ParamLine(content));
441    }
442
443    /**
444     * Binds the given component to the given bean property.<p>
445     *
446     * @param field the component
447     * @param property the bean property
448     */
449    void bindField(AbstractField<?> field, String property) {
450
451        m_group.bind(field, property);
452
453        field.setCaption(CmsVaadinUtils.getMessageText("label." + property));
454        field.setDescription(CmsVaadinUtils.getMessageText("label." + property + ".help"));
455    }
456
457    /**
458     * Reads the job parameters from the parameter input fields.<p>
459     *
460     * @return the job parameters
461     */
462    SortedMap<String, String> readParams() {
463
464        SortedMap<String, String> result = new TreeMap<String, String>();
465
466        for (Component component : m_paramContainer) {
467            if (component instanceof ParamLine) {
468                ParamLine paramLine = (ParamLine)component;
469                String keyAndValue = paramLine.getValue();
470                int eqPos = keyAndValue.indexOf("=");
471                if (eqPos >= 0) {
472                    String key = keyAndValue.substring(0, eqPos);
473                    String value = keyAndValue.substring(eqPos + 1);
474                    result.put(key, value);
475                }
476            }
477
478        }
479        return result;
480    }
481
482    /**
483     * Adds a new widget for entering a job parameter.<p>
484     *
485     * @param key the preselected key
486     * @param value the preselected value
487     */
488    private void addParamLine(String key, String value) {
489
490        addParamLine(key + "=" + value);
491
492    }
493}