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.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.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.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                    m_fieldName.setComponentError(null);
272                    CmsObject cms = A_CmsUI.getCmsObject();
273                    String projectName = CmsStringUtil.joinPaths(m_fieldOU.getValue(), m_fieldName.getValue());
274                    try {
275                        cms.readProject(projectName);
276                        throw new InvalidValueException(CmsVaadinUtils.getMessageText(Messages.GUI_PROJECTS_PROJECT_WITH_SAME_NAME_EXISTS_0));
277
278                    } catch (CmsException e) {
279                        // ignore
280                    }
281                }
282            });
283        }
284        m_fieldOU.addValueChangeListener(new ValueChangeListener() {
285
286            private static final long serialVersionUID = 1L;
287
288            public void valueChange(ValueChangeEvent event) {
289
290                if (m_project == null) {
291                    // the 'project exists' validation is attached to m_fieldName, but is also dependent on the value of the OU field,
292                    // so we trigger it here
293                    try {
294                        m_fieldName.setComponentError(null);
295                        m_fieldName.validate();
296                    } catch (InvalidValueException e) {
297                        m_fieldName.setComponentError(new UserError(e.getMessage()));
298                    }
299                }
300                validateResourceFields();
301            }
302        });
303
304        m_addResource.addClickListener(new ClickListener() {
305
306            private static final long serialVersionUID = 1L;
307
308            public void buttonClick(ClickEvent event) {
309
310                addResourceField(null);
311            }
312        });
313        m_ok.addClickListener(new ClickListener() {
314
315            private static final long serialVersionUID = 1L;
316
317            public void buttonClick(ClickEvent event) {
318
319                submit();
320            }
321        });
322        m_cancel.addClickListener(new ClickListener() {
323
324            private static final long serialVersionUID = 1L;
325
326            public void buttonClick(ClickEvent event) {
327
328                cancel();
329            }
330        });
331        m_fields = new Field<?>[] {m_fieldName, m_fieldDescription, m_fieldManager, m_fieldUser, m_fieldOU};
332    }
333
334    /**
335     * Adds a new resource field.<p>
336     *
337     * @param value the value to set
338     */
339    void addResourceField(String value) {
340
341        CmsPathSelectField field = new CmsPathSelectField();
342        field.setUseRootPaths(true);
343        if (value != null) {
344            field.setValue(value);
345        }
346        field.addValidator(m_resourceValidator);
347        CmsRemovableFormRow<CmsPathSelectField> row = new CmsRemovableFormRow<CmsPathSelectField>(
348            field,
349            CmsVaadinUtils.getMessageText(Messages.GUI_PROJECTS_REMOVE_RESOURCE_0));
350        row.setCaption(CmsVaadinUtils.getMessageText(Messages.GUI_PROJECTS_RESOURCE_0));
351        row.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_PROJECTS_RESOURCE_HELP_0));
352        m_resources.addComponent(row);
353    }
354
355    /**
356     * Cancels project edit.<p>
357     */
358    void cancel() {
359
360        m_window.close();
361    }
362
363    /**
364     * Submits the form.<p>
365     */
366    void submit() {
367
368        if (isValid()) {
369            if (m_project == null) {
370                createProject();
371            } else {
372                saveProject();
373            }
374            m_table.loadProjects();
375            m_window.close();
376        }
377    }
378
379    /**
380     * Validates the resource fields.<p>
381     */
382    @SuppressWarnings("unchecked")
383    void validateResourceFields() {
384
385        for (Component c : m_resources) {
386            if (c instanceof CmsRemovableFormRow<?>) {
387                ((CmsRemovableFormRow<CmsPathSelectField>)c).getInput().validate();
388            }
389        }
390    }
391
392    /**
393     * Creates a new project.<p>
394     */
395    private void createProject() {
396
397        CmsObject cms = A_CmsUI.getCmsObject();
398        try {
399            String name = "/";
400            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_fieldOU.getValue())) {
401                name = CmsStringUtil.joinPaths(name, m_fieldOU.getValue());
402            }
403            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_fieldName.getValue())) {
404                name = CmsStringUtil.joinPaths(name, m_fieldName.getValue());
405            } else {
406                name = CmsStringUtil.joinPaths(name, "/");
407            }
408
409            m_project = cms.createProject(
410                name,
411                m_fieldDescription.getValue(),
412                m_fieldUser.getValue(),
413                m_fieldManager.getValue(),
414                m_fieldDeleteAfterPublish.getValue().booleanValue()
415                ? CmsProject.PROJECT_TYPE_TEMPORARY
416                : CmsProject.PROJECT_TYPE_NORMAL);
417            updateProjectResources();
418
419        } catch (Throwable t) {
420            CmsErrorDialog.showErrorDialog(t);
421        }
422    }
423
424    /**
425     * Returns the selected resource paths.<p>
426     *
427     * @return the resource paths
428     */
429    @SuppressWarnings("unchecked")
430    private Set<String> getResourcePaths() {
431
432        Set<String> resources = new HashSet<String>();
433        for (Component c : m_resources) {
434            if (c instanceof CmsRemovableFormRow<?>) {
435                String value = ((CmsRemovableFormRow<CmsPathSelectField>)c).getInput().getValue();
436                if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(value)) {
437                    resources.add(value);
438                }
439            }
440        }
441        return resources;
442    }
443
444    /**
445     * Validates the form fields.<p>
446     *
447     * @return <code>true</code> in case all fields are valid
448     */
449    @SuppressWarnings("unchecked")
450    private boolean isValid() {
451
452        for (Field<?> field : m_fields) {
453            if (!field.isValid()) {
454                field.focus();
455
456                return false;
457            }
458        }
459        for (Component c : m_resources) {
460            if (c instanceof CmsRemovableFormRow<?>) {
461                if (!((CmsRemovableFormRow<CmsPathSelectField>)c).getInput().isValid()) {
462                    ((CmsRemovableFormRow<CmsPathSelectField>)c).getInput().focus();
463                    return false;
464                }
465            }
466        }
467        return true;
468    }
469
470    /**
471     * Saves an existing project.<p>
472     */
473    private void saveProject() {
474
475        CmsObject cms = A_CmsUI.getCmsObject();
476        try {
477            m_project.setDescription(m_fieldDescription.getValue());
478            m_project.setGroupId(cms.readGroup(m_fieldUser.getValue()).getId());
479            m_project.setManagerGroupId(cms.readGroup(m_fieldManager.getValue()).getId());
480            m_project.setDeleteAfterPublishing(m_fieldDeleteAfterPublish.getValue().booleanValue());
481            cms.writeProject(m_project);
482            updateProjectResources();
483        } catch (Throwable t) {
484            CmsErrorDialog.showErrorDialog(t);
485        }
486    }
487
488    /**
489     * Updates the project resources.<p>
490     *
491     * @throws CmsException in case writing the project fails
492     */
493    private void updateProjectResources() throws CmsException {
494
495        CmsObject cms = A_CmsUI.getCmsObject();
496        Set<String> resourceRootPaths = getResourcePaths();
497        // write the edited project resources
498        CmsProject currentProject = cms.getRequestContext().getCurrentProject();
499        // change the current project
500        cms.getRequestContext().setCurrentProject(m_project);
501        // store the current site root
502        String currentSite = cms.getRequestContext().getSiteRoot();
503        // copy the resources to the current project
504        try {
505            // switch to the root site
506            cms.getRequestContext().setSiteRoot("");
507            // remove deleted resources
508            for (String resName : cms.readProjectResources(m_project)) {
509                if (!resourceRootPaths.contains(resName)) {
510                    cms.removeResourceFromProject(resName);
511                }
512            }
513            // read project resources again!
514            List<String> currentResNames = cms.readProjectResources(m_project);
515            // copy missing resources
516            for (String resName : resourceRootPaths) {
517                if (!currentResNames.contains(resName)) {
518                    cms.copyResourceToProject(resName);
519                }
520            }
521        } finally {
522            // switch back to current site and project
523            cms.getRequestContext().setSiteRoot(currentSite);
524            cms.getRequestContext().setCurrentProject(currentProject);
525        }
526    }
527}