001/*
002 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Nuxeo - initial API and implementation
016 *
017 * $Id$
018 */
019package org.nuxeo.ecm.webapp.versioning;
020
021import static org.jboss.seam.ScopeType.CONVERSATION;
022import static org.jboss.seam.ScopeType.EVENT;
023import static org.jboss.seam.annotations.Install.FRAMEWORK;
024
025import java.io.Serializable;
026import java.util.Collection;
027import java.util.Collections;
028import java.util.LinkedHashMap;
029import java.util.List;
030import java.util.Locale;
031import java.util.Map;
032import java.util.Map.Entry;
033
034import javax.faces.application.FacesMessage;
035import javax.faces.component.UIComponent;
036import javax.faces.context.FacesContext;
037import javax.faces.validator.ValidatorException;
038
039import org.apache.commons.logging.Log;
040import org.apache.commons.logging.LogFactory;
041import org.jboss.seam.annotations.Factory;
042import org.jboss.seam.annotations.In;
043import org.jboss.seam.annotations.Install;
044import org.jboss.seam.annotations.Name;
045import org.jboss.seam.annotations.Observer;
046import org.jboss.seam.annotations.Scope;
047import org.jboss.seam.annotations.intercept.BypassInterceptors;
048import org.nuxeo.common.utils.i18n.I18NUtils;
049import org.nuxeo.ecm.core.api.CoreSession;
050import org.nuxeo.ecm.core.api.DocumentModel;
051import org.nuxeo.ecm.core.api.VersionModel;
052import org.nuxeo.ecm.core.api.VersioningOption;
053import org.nuxeo.ecm.core.api.facet.VersioningDocument;
054import org.nuxeo.ecm.core.versioning.VersioningService;
055import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
056import org.nuxeo.ecm.platform.versioning.api.VersionIncEditOptions;
057import org.nuxeo.ecm.platform.versioning.api.VersioningActions;
058import org.nuxeo.ecm.platform.versioning.api.VersioningManager;
059import org.nuxeo.ecm.webapp.helpers.EventNames;
060import org.nuxeo.ecm.webapp.helpers.ResourcesAccessor;
061
062/**
063 * Web action bean for document versioning. Used also by other seam components through injection.
064 *
065 * @author Dragos Mihalache
066 */
067@Name("documentVersioning")
068@Scope(CONVERSATION)
069@Install(precedence = FRAMEWORK)
070public class DocumentVersioningBean implements DocumentVersioning, Serializable {
071
072    private static final long serialVersionUID = 75409841629876L;
073
074    private static final Log log = LogFactory.getLog(DocumentVersioningBean.class);
075
076    @In(create = true)
077    protected transient ResourcesAccessor resourcesAccessor;
078
079    @In(create = true, required = false)
080    protected transient CoreSession documentManager;
081
082    /**
083     * @deprecated since 5.7.3: versioning options map does not need to be kept in cache anymore here
084     */
085    @Deprecated
086    protected Map<String, String> availableVersioningOptionsMap;
087
088    @In(create = true)
089    protected transient NavigationContext navigationContext;
090
091    @In(create = true)
092    private transient VersioningManager versioningManager;
093
094    /**
095     * field used for deciding whether or not to display versioning controls section (in document editing)
096     */
097    private Boolean rendered;
098
099    private VersioningActions selectedOption;
100
101    @Override
102    public Collection<VersionModel> getItemVersioningHistory(DocumentModel document) {
103        List<VersionModel> versions = Collections.emptyList();
104        versions = documentManager.getVersionsForDocument(document.getRef());
105        for (VersionModel model : versions) {
106            DocumentModel ver = documentManager.getDocumentWithVersion(document.getRef(), model);
107            if (ver != null) {
108                model.setDescription(ver.getAdapter(VersioningDocument.class).getVersionLabel());
109            }
110        }
111        return versions;
112    }
113
114    @Override
115    public Collection<VersionModel> getCurrentItemVersioningHistory() {
116        return getItemVersioningHistory(navigationContext.getCurrentDocument());
117    }
118
119    @Factory(autoCreate = true, value = "currentDocumentVersionInfo", scope = EVENT)
120    public VersionInfo getCurrentDocumentVersionInfo() {
121        DocumentModel doc = navigationContext.getCurrentDocument();
122        if (doc == null) {
123            return null;
124        }
125        String versionLabel = versioningManager.getVersionLabel(doc);
126        boolean available = versionLabel != null && versionLabel.length() != 0;
127        return new VersionInfo(versionLabel, available);
128    }
129
130    @Observer(value = { EventNames.DOCUMENT_SELECTION_CHANGED, EventNames.DOCUMENT_CHANGED }, create = false)
131    @BypassInterceptors
132    public void resetVersioningOption() {
133        availableVersioningOptionsMap = null;
134        selectedOption = null;
135        rendered = null;
136    }
137
138    @Deprecated
139    @Override
140    public Map<String, String> getAvailableVersioningOptionsMap() {
141        // FIXME: should cache the map and invalidate it correctly as it refers
142        // to current document
143        DocumentModel doc = navigationContext.getCurrentDocument();
144        LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
145        for (Entry<String, String> en : getVersioningOptionsMap(doc).entrySet()) {
146            // reverse keys with values for the jsf controls
147            map.put(en.getValue(), en.getKey());
148        }
149        availableVersioningOptionsMap = map;
150        return availableVersioningOptionsMap;
151    }
152
153    @Override
154    public Map<String, String> getVersioningOptionsMap(DocumentModel doc) {
155        Map<String, String> map = new LinkedHashMap<String, String>();
156        VersionIncEditOptions options = getAvailableVersioningOptions(doc);
157        if (options != null) {
158            for (VersioningActions option : options.getOptions()) {
159                String label = "label.versioning.option." + option.toString();
160                if (resourcesAccessor != null) {
161                    label = resourcesAccessor.getMessages().get(label);
162                }
163                map.put(option.name(), label);
164            }
165        }
166        return map;
167    }
168
169    /**
170     * @deprecated since 5.7.3: options are resolved from the layout value instead now that a generic widget definition
171     *             handles it
172     */
173    @Deprecated
174    private VersionIncEditOptions getCurrentAvailableVersioningOptions() {
175        return getAvailableVersioningOptions(navigationContext.getCurrentDocument());
176    }
177
178    public VersionIncEditOptions getAvailableVersioningOptions(DocumentModel doc) {
179        return versioningManager.getVersionIncEditOptions(doc);
180    }
181
182    @Override
183    public String getVersionLabel(DocumentModel doc) {
184        return versioningManager.getVersionLabel(doc);
185    }
186
187    @Deprecated
188    @Override
189    public String getVersioningOptionInstanceId() {
190        if (selectedOption == null) {
191            // FIXME: should cache the versioning options and invalidate them
192            // correctly as it refers to current document
193            VersionIncEditOptions options = getCurrentAvailableVersioningOptions();
194            if (options != null) {
195                selectedOption = options.getDefaultVersioningAction();
196            }
197            if (selectedOption == null) {
198                selectedOption = VersioningActions.ACTION_NO_INCREMENT;
199            }
200        }
201        return selectedOption.name();
202    }
203
204    @Deprecated
205    @Override
206    public void setVersioningOptionInstanceId(String optionId) {
207        setVersioningOptionInstanceId(navigationContext.getCurrentDocument(), optionId);
208    }
209
210    @Deprecated
211    @Override
212    public void setVersioningOptionInstanceId(DocumentModel docModel, String optionId) {
213        if (optionId != null) {
214            selectedOption = VersioningActions.valueOf(optionId);
215            setVersioningOptionInstanceId(docModel, selectedOption);
216        } else {
217            // component is present but no option has been selected
218            // should not reach here...
219        }
220    }
221
222    @Deprecated
223    @Override
224    public void setVersioningOptionInstanceId(DocumentModel docModel, VersioningActions option) {
225        // add version inc option to document context so it will be
226        // taken into consideration on the server side
227        VersioningOption vo;
228        if (option == VersioningActions.ACTION_INCREMENT_MAJOR) {
229            vo = VersioningOption.MAJOR;
230        } else if (option == VersioningActions.ACTION_INCREMENT_MINOR) {
231            vo = VersioningOption.MINOR;
232        } else if (option == VersioningActions.ACTION_NO_INCREMENT) {
233            vo = VersioningOption.NONE;
234        } else {
235            vo = null;
236        }
237        docModel.putContextData(VersioningService.VERSIONING_OPTION, vo);
238    }
239
240    @Override
241    public void validateOptionSelection(FacesContext context, UIComponent component, Object value) {
242        if (value != null) {
243            // ok
244            return;
245        }
246        String bundleName = context.getApplication().getMessageBundle();
247        Locale locale = context.getViewRoot().getLocale();
248        String msg = I18NUtils.getMessageString(bundleName, "error.versioning.none_selected", null, locale);
249        FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg);
250
251        throw new ValidatorException(message);
252    }
253
254    @Deprecated
255    @Override
256    @Factory(value = "renderVersioningOptionsForCurrentDocument", scope = EVENT)
257    public boolean factoryForRenderVersioningOption() {
258        return getRendered();
259    }
260
261    /**
262     * @deprecated since 5.7.3: rendered clause is now evaluated on the widget definition instead
263     */
264    @Deprecated
265    public boolean getRendered() {
266        if (rendered == null) {
267            rendered = Boolean.FALSE;
268            if (navigationContext.getCurrentDocument() != null) {
269                Map<String, String> options = getAvailableVersioningOptionsMap();
270                // do not display the versioning options if there is only one
271                // choice
272                if (options != null && options.size() > 1) {
273                    rendered = Boolean.TRUE;
274                }
275            }
276        }
277        return rendered.booleanValue();
278    }
279
280    /**
281     * @deprecated since 5.7.3: rendered clause is now evaluated on the widget definition instead
282     */
283    @Deprecated
284    public void setRendered(Boolean rendered) {
285        this.rendered = rendered;
286    }
287
288}