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.projects;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsProject;
032import org.opencms.file.CmsResource;
033import org.opencms.main.CmsException;
034import org.opencms.main.CmsLog;
035import org.opencms.main.OpenCms;
036import org.opencms.security.CmsOrganizationalUnit;
037import org.opencms.security.CmsRole;
038import org.opencms.ui.A_CmsUI;
039import org.opencms.ui.CmsCssIcon;
040import org.opencms.ui.CmsVaadinUtils;
041import org.opencms.ui.apps.Messages;
042import org.opencms.ui.components.CmsBasicDialog;
043import org.opencms.ui.components.CmsErrorDialog;
044import org.opencms.ui.components.CmsRemovableFormRow;
045import org.opencms.ui.components.CmsResourceInfo;
046import org.opencms.ui.components.OpenCmsTheme;
047import org.opencms.ui.components.fileselect.CmsPathSelectField;
048import org.opencms.ui.dialogs.permissions.CmsPrincipalSelect;
049import org.opencms.ui.dialogs.permissions.CmsPrincipalSelect.WidgetType;
050import org.opencms.util.CmsStringUtil;
051import org.opencms.util.CmsUUID;
052
053import java.util.Collections;
054import java.util.HashSet;
055import java.util.List;
056import java.util.Set;
057
058import org.apache.commons.logging.Log;
059
060import com.vaadin.server.UserError;
061import com.vaadin.ui.Button;
062import com.vaadin.ui.Button.ClickEvent;
063import com.vaadin.ui.Button.ClickListener;
064import com.vaadin.ui.Component;
065import com.vaadin.ui.FormLayout;
066import com.vaadin.ui.UI;
067import com.vaadin.ui.Window;
068import com.vaadin.v7.data.Property.ValueChangeEvent;
069import com.vaadin.v7.data.Property.ValueChangeListener;
070import com.vaadin.v7.data.Validator;
071import com.vaadin.v7.data.Validator.InvalidValueException;
072import com.vaadin.v7.ui.CheckBox;
073import com.vaadin.v7.ui.Field;
074import com.vaadin.v7.ui.TextField;
075
076/**
077 * The edit project form component.<p>
078 */
079public class CmsEditProjectForm extends CmsBasicDialog {
080
081    /**
082     * The OU validator.<p>
083     */
084    protected class OUValidator implements Validator {
085
086        /** The serial version id. */
087        private static final long serialVersionUID = 1L;
088
089        /**
090         * @see com.vaadin.v7.data.Validator#validate(java.lang.Object)
091         */
092        public void validate(Object value) throws InvalidValueException {
093
094            if (m_fieldOU.isEnabled() && CmsStringUtil.isNotEmptyOrWhitespaceOnly((String)value)) {
095                try {
096                    OpenCms.getOrgUnitManager().readOrganizationalUnit(A_CmsUI.getCmsObject(), (String)value);
097                } catch (CmsException e) {
098                    throw new InvalidValueException(e.getLocalizedMessage(UI.getCurrent().getLocale()));
099                }
100            }
101        }
102    }
103
104    /**
105     * The resource field validator. Checks whether the resource is part of the project OU.<p>
106     */
107    protected class ResourceValidator implements Validator {
108
109        /** The serial version id. */
110        private static final long serialVersionUID = 1L;
111
112        /**
113         * @see com.vaadin.v7.data.Validator#validate(java.lang.Object)
114         */
115        public void validate(Object value) throws InvalidValueException {
116
117            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly((String)value)
118                && CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_fieldOU.getValue())) {
119                try {
120                    List<CmsResource> ouRes = OpenCms.getOrgUnitManager().getResourcesForOrganizationalUnit(
121                        A_CmsUI.getCmsObject(),
122                        m_fieldOU.getValue());
123
124                    String resPath = (String)value;
125                    for (CmsResource res : ouRes) {
126                        if (resPath.startsWith(res.getRootPath())) {
127                            return;
128                        }
129                    }
130                    throw new InvalidValueException(
131                        "The resource path "
132                            + value
133                            + " is not part of the project OU '"
134                            + m_fieldOU.getValue()
135                            + "'.");
136                } catch (CmsException e) {
137                    // ignore
138                }
139            }
140        }
141    }
142
143    /** The logger for this class. */
144    private static Log LOG = CmsLog.getLog(CmsEditProjectForm.class.getName());
145
146    /** The serial version id. */
147    private static final long serialVersionUID = 2345799706922671537L;
148
149    /** The OU field. */
150    TextField m_fieldOU;
151
152    /** The add resources button. */
153    private Button m_addResource;
154
155    /** The cancel button. */
156    private Button m_cancel;
157
158    /** The delete after publish check box. */
159    private CheckBox m_fieldDeleteAfterPublish;
160
161    /** The project description field. */
162    private TextField m_fieldDescription;
163
164    /** The manager group field. */
165    private CmsPrincipalSelect m_fieldManager;
166
167    /** The project name field. */
168    private TextField m_fieldName;
169
170    /** The form fileds. */
171    private Field<?>[] m_fields;
172
173    /** The user group field. */
174    private CmsPrincipalSelect m_fieldUser;
175
176    /** The projects table instance. */
177    private CmsProjectsTable m_table;
178
179    /** The OK button. */
180    private Button m_ok;
181
182    /** The edited project. */
183    private CmsProject m_project;
184
185    /** The resources form layout. */
186    private FormLayout m_resources;
187
188    /** The resource field validator. */
189    private ResourceValidator m_resourceValidator;
190
191    /** The window this form is displayed in. */
192    private Window m_window;
193
194    /**
195     * Constructor.<p>
196     * Used to edit existing projects.<p>
197     *
198     * @param table the projects table
199     * @param projectId the project to edit
200     * @param window the window this form is displayed in
201     */
202    public CmsEditProjectForm(CmsProjectsTable table, CmsUUID projectId, Window window) {
203
204        this(table, window);
205        CmsObject cms = A_CmsUI.getCmsObject();
206        try {
207            m_project = cms.readProject(projectId);
208            displayResourceInfoDirectly(
209                Collections.singletonList(
210                    new CmsResourceInfo(
211                        m_project.getName(),
212                        m_project.getDescription(),
213                        new CmsCssIcon(OpenCmsTheme.ICON_PROJECT))));
214            m_fieldName.setValue(m_project.getName());
215            m_fieldName.setEnabled(false);
216            m_fieldDescription.setValue(m_project.getDescription());
217            m_fieldUser.setValue(cms.readGroup(m_project.getGroupId()).getName());
218            m_fieldManager.setValue(cms.readGroup(m_project.getManagerGroupId()).getName());
219            try {
220                CmsOrganizationalUnit ou;
221                ou = OpenCms.getOrgUnitManager().readOrganizationalUnit(cms, m_project.getOuFqn());
222                m_fieldOU.setValue(ou.getDisplayName(UI.getCurrent().getLocale()));
223            } catch (CmsException e) {
224                LOG.error(e.getLocalizedMessage(), e);
225                m_fieldOU.setValue(null);
226            }
227            m_fieldOU.setEnabled(false);
228            for (String resName : cms.readProjectResources(m_project)) {
229                addResourceField(resName);
230            }
231        } catch (CmsException e) {
232            CmsErrorDialog.showErrorDialog(e);
233        }
234
235    }
236
237    /**
238     * Constructor.<p>
239     * Use this to create a new project.<p>
240     *
241     * @param table the projects table
242     * @param window the window this form is displayed in
243     */
244    public CmsEditProjectForm(CmsProjectsTable table, Window window) {
245
246        m_window = window;
247        m_table = table;
248        CmsVaadinUtils.readAndLocalizeDesign(this, CmsVaadinUtils.getWpMessagesForCurrentLocale(), null);
249        m_resourceValidator = new ResourceValidator();
250        m_fieldManager.setWidgetType(WidgetType.groupwidget);
251        m_fieldManager.setRealPrincipalsOnly(true);
252        m_fieldUser.setWidgetType(WidgetType.groupwidget);
253        m_fieldUser.setRealPrincipalsOnly(true);
254
255        try {
256            CmsOrganizationalUnit ou = OpenCms.getRoleManager().getOrgUnitsForRole(
257                A_CmsUI.getCmsObject(),
258                CmsRole.PROJECT_MANAGER,
259                true).get(0);
260            m_fieldOU.setValue(ou.getName());
261        } catch (CmsException e) {
262            LOG.error(e.getLocalizedMessage(), e);
263            m_fieldOU.setValue(null);
264        }
265        m_fieldOU.setImmediate(true);
266        m_fieldOU.addValidator(new OUValidator());
267        if (m_project == null) {
268            m_fieldName.addValidator(new Validator() {
269
270                public void validate(Object value) throws InvalidValueException {
271
272                    m_fieldName.setComponentError(null);
273                    CmsObject cms = A_CmsUI.getCmsObject();
274                    String projectName = CmsStringUtil.joinPaths(m_fieldOU.getValue(), m_fieldName.getValue());
275                    try {
276                        cms.readProject(projectName);
277                        throw new InvalidValueException(
278                            CmsVaadinUtils.getMessageText(Messages.GUI_PROJECTS_PROJECT_WITH_SAME_NAME_EXISTS_0));
279
280                    } catch (CmsException e) {
281                        // ignore
282                    }
283                }
284            });
285        }
286        m_fieldOU.addValueChangeListener(new ValueChangeListener() {
287
288            private static final long serialVersionUID = 1L;
289
290            public void valueChange(ValueChangeEvent event) {
291
292                if (m_project == null) {
293                    // the 'project exists' validation is attached to m_fieldName, but is also dependent on the value of the OU field,
294                    // so we trigger it here
295                    try {
296                        m_fieldName.setComponentError(null);
297                        m_fieldName.validate();
298                    } catch (InvalidValueException e) {
299                        m_fieldName.setComponentError(new UserError(e.getMessage()));
300                    }
301                }
302                validateResourceFields();
303            }
304        });
305
306        m_addResource.addClickListener(new ClickListener() {
307
308            private static final long serialVersionUID = 1L;
309
310            public void buttonClick(ClickEvent event) {
311
312                addResourceField(null);
313            }
314        });
315        m_ok.addClickListener(new ClickListener() {
316
317            private static final long serialVersionUID = 1L;
318
319            public void buttonClick(ClickEvent event) {
320
321                submit();
322            }
323        });
324        m_cancel.addClickListener(new ClickListener() {
325
326            private static final long serialVersionUID = 1L;
327
328            public void buttonClick(ClickEvent event) {
329
330                cancel();
331            }
332        });
333        m_fields = new Field<?>[] {m_fieldName, m_fieldDescription, m_fieldManager, m_fieldUser, m_fieldOU};
334    }
335
336    /**
337     * Adds a new resource field.<p>
338     *
339     * @param value the value to set
340     */
341    void addResourceField(String value) {
342
343        CmsPathSelectField field = new CmsPathSelectField();
344        field.setUseRootPaths(true);
345        if (value != null) {
346            field.setValue(value);
347        }
348        field.addValidator(m_resourceValidator);
349        CmsRemovableFormRow<CmsPathSelectField> row = new CmsRemovableFormRow<CmsPathSelectField>(
350            field,
351            CmsVaadinUtils.getMessageText(Messages.GUI_PROJECTS_REMOVE_RESOURCE_0));
352        row.setCaption(CmsVaadinUtils.getMessageText(Messages.GUI_PROJECTS_RESOURCE_0));
353        row.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_PROJECTS_RESOURCE_HELP_0));
354        m_resources.addComponent(row);
355    }
356
357    /**
358     * Cancels project edit.<p>
359     */
360    void cancel() {
361
362        m_window.close();
363    }
364
365    /**
366     * Submits the form.<p>
367     */
368    void submit() {
369
370        if (isValid()) {
371            if (m_project == null) {
372                createProject();
373            } else {
374                saveProject();
375            }
376            m_table.loadProjects();
377            m_window.close();
378        }
379    }
380
381    /**
382     * Validates the resource fields.<p>
383     */
384    @SuppressWarnings("unchecked")
385    void validateResourceFields() {
386
387        for (Component c : m_resources) {
388            if (c instanceof CmsRemovableFormRow<?>) {
389                ((CmsRemovableFormRow<CmsPathSelectField>)c).getInput().validate();
390            }
391        }
392    }
393
394    /**
395     * Creates a new project.<p>
396     */
397    private void createProject() {
398
399        CmsObject cms = A_CmsUI.getCmsObject();
400        try {
401            String name = "/";
402            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_fieldOU.getValue())) {
403                name = CmsStringUtil.joinPaths(name, m_fieldOU.getValue());
404            }
405            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_fieldName.getValue())) {
406                name = CmsStringUtil.joinPaths(name, m_fieldName.getValue());
407            } else {
408                name = CmsStringUtil.joinPaths(name, "/");
409            }
410
411            m_project = cms.createProject(
412                name,
413                m_fieldDescription.getValue(),
414                m_fieldUser.getValue(),
415                m_fieldManager.getValue(),
416                m_fieldDeleteAfterPublish.getValue().booleanValue()
417                ? CmsProject.PROJECT_TYPE_TEMPORARY
418                : CmsProject.PROJECT_TYPE_NORMAL);
419            updateProjectResources();
420
421        } catch (Throwable t) {
422            CmsErrorDialog.showErrorDialog(t);
423        }
424    }
425
426    /**
427     * Returns the selected resource paths.<p>
428     *
429     * @return the resource paths
430     */
431    @SuppressWarnings("unchecked")
432    private Set<String> getResourcePaths() {
433
434        Set<String> resources = new HashSet<String>();
435        for (Component c : m_resources) {
436            if (c instanceof CmsRemovableFormRow<?>) {
437                String value = ((CmsRemovableFormRow<CmsPathSelectField>)c).getInput().getValue();
438                if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(value)) {
439                    resources.add(value);
440                }
441            }
442        }
443        return resources;
444    }
445
446    /**
447     * Validates the form fields.<p>
448     *
449     * @return <code>true</code> in case all fields are valid
450     */
451    @SuppressWarnings("unchecked")
452    private boolean isValid() {
453
454        for (Field<?> field : m_fields) {
455            if (!field.isValid()) {
456                field.focus();
457
458                return false;
459            }
460        }
461        for (Component c : m_resources) {
462            if (c instanceof CmsRemovableFormRow<?>) {
463                if (!((CmsRemovableFormRow<CmsPathSelectField>)c).getInput().isValid()) {
464                    ((CmsRemovableFormRow<CmsPathSelectField>)c).getInput().focus();
465                    return false;
466                }
467            }
468        }
469        return true;
470    }
471
472    /**
473     * Saves an existing project.<p>
474     */
475    private void saveProject() {
476
477        CmsObject cms = A_CmsUI.getCmsObject();
478        try {
479            m_project.setDescription(m_fieldDescription.getValue());
480            m_project.setGroupId(cms.readGroup(m_fieldUser.getValue()).getId());
481            m_project.setManagerGroupId(cms.readGroup(m_fieldManager.getValue()).getId());
482            m_project.setDeleteAfterPublishing(m_fieldDeleteAfterPublish.getValue().booleanValue());
483            cms.writeProject(m_project);
484            updateProjectResources();
485        } catch (Throwable t) {
486            CmsErrorDialog.showErrorDialog(t);
487        }
488    }
489
490    /**
491     * Updates the project resources.<p>
492     *
493     * @throws CmsException in case writing the project fails
494     */
495    private void updateProjectResources() throws CmsException {
496
497        CmsObject cms = A_CmsUI.getCmsObject();
498        Set<String> resourceRootPaths = getResourcePaths();
499        // write the edited project resources
500        CmsProject currentProject = cms.getRequestContext().getCurrentProject();
501        // change the current project
502        cms.getRequestContext().setCurrentProject(m_project);
503        // store the current site root
504        String currentSite = cms.getRequestContext().getSiteRoot();
505        // copy the resources to the current project
506        try {
507            // switch to the root site
508            cms.getRequestContext().setSiteRoot("");
509            // remove deleted resources
510            for (String resName : cms.readProjectResources(m_project)) {
511                if (!resourceRootPaths.contains(resName)) {
512                    cms.removeResourceFromProject(resName);
513                }
514            }
515            // read project resources again!
516            List<String> currentResNames = cms.readProjectResources(m_project);
517            // copy missing resources
518            for (String resName : resourceRootPaths) {
519                if (!currentResNames.contains(resName)) {
520                    cms.copyResourceToProject(resName);
521                }
522            }
523        } finally {
524            // switch back to current site and project
525            cms.getRequestContext().setSiteRoot(currentSite);
526            cms.getRequestContext().setCurrentProject(currentProject);
527        }
528    }
529}