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: JOOoConvertPluginImpl.java 18651 2007-05-13 20:28:53Z sfermigier $
018 */
019
020package org.nuxeo.ecm.webapp.action;
021
022import static org.jboss.seam.ScopeType.CONVERSATION;
023import static org.jboss.seam.ScopeType.EVENT;
024
025import java.io.Serializable;
026import java.util.ArrayList;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030import java.util.Map.Entry;
031import java.util.Set;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.jboss.seam.annotations.Factory;
036import org.jboss.seam.annotations.In;
037import org.jboss.seam.annotations.Install;
038import org.jboss.seam.annotations.Name;
039import org.jboss.seam.annotations.Observer;
040import org.jboss.seam.annotations.Scope;
041import org.jboss.seam.annotations.intercept.BypassInterceptors;
042import org.nuxeo.ecm.core.api.DocumentModel;
043import org.nuxeo.ecm.platform.types.Type;
044import org.nuxeo.ecm.platform.types.TypeManager;
045import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
046import org.nuxeo.ecm.webapp.helpers.EventNames;
047import org.nuxeo.ecm.webapp.seam.NuxeoSeamHotReloader;
048
049/**
050 * Document type service for document type creation.
051 *
052 * @author eionica@nuxeo.com
053 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
054 */
055@Name("typesTool")
056@Scope(CONVERSATION)
057@Install(precedence = Install.FRAMEWORK)
058public class TypesTool implements Serializable {
059
060    private static final long serialVersionUID = -5037578301250616973L;
061
062    protected static final Log log = LogFactory.getLog(TypesTool.class);
063
064    private static final int COLUMN_SIZE = 4;
065
066    @In(create = true)
067    protected transient TypeManager typeManager;
068
069    @In(create = true)
070    protected NuxeoSeamHotReloader seamReload;
071
072    protected Map<String, List<List<Type>>> typesMap;
073
074    protected Long typesMapTimestamp;
075
076    protected Type selectedType;
077
078    @In(create = true)
079    protected transient NavigationContext navigationContext;
080
081    @Observer(value = { EventNames.CONTENT_ROOT_SELECTION_CHANGED, EventNames.DOCUMENT_SELECTION_CHANGED,
082            EventNames.DOMAIN_SELECTION_CHANGED, EventNames.LOCAL_CONFIGURATION_CHANGED }, create = false)
083    @BypassInterceptors
084    public void resetTypesList() {
085        typesMap = null;
086        typesMapTimestamp = null;
087    }
088
089    /**
090     * Retrieves the list of allowed sub types given a current type.
091     * <p>
092     * This is used at creation time. Current type is retrieved thanks to the document model hold and passed by the
093     * event.
094     */
095    public void populateTypesList() {
096        boolean set = false;
097        DocumentModel model = getCurrentItem();
098        if (model != null) {
099            typesMap = getOrganizedTypeMapForDocumentType(model.getType());
100            set = true;
101        }
102        if (!set) {
103            // set an empty list
104            typesMap = new HashMap<String, List<List<Type>>>();
105        }
106        typesMapTimestamp = typeManager.getLastModified();
107    }
108
109    public Map<String, List<List<Type>>> getOrganizedTypeMapForDocumentType(String type) {
110        Map<String, List<Type>> docTypesMap = typeManager.getTypeMapForDocumentType(type, getConfigurationDocument());
111        docTypesMap = filterTypeMap(docTypesMap);
112        return organizeType(docTypesMap);
113    }
114
115    /**
116     * Returns the Configuration document to be used as the local configuration of the {@code TypeManager}.
117     * <p>
118     * This method can be overridden by Subclasses to define a specific Configuration document.
119     *
120     * @since 5.4.2
121     */
122    protected DocumentModel getConfigurationDocument() {
123        return navigationContext.getCurrentDocument();
124    }
125
126    /**
127     * Method to be overridden by subclasses to filter the type Map.
128     *
129     * @since 5.4.2
130     */
131    protected Map<String, List<Type>> filterTypeMap(Map<String, List<Type>> docTypeMap) {
132        return docTypeMap;
133    }
134
135    /**
136     * Split each @{code List} of {@code Type} in one or more new {@code List}, with maximum 4 {@code Type}s in each new
137     * {@code List} and returns the new computed {@code Map}.
138     */
139    protected Map<String, List<List<Type>>> organizeType(Map<String, List<Type>> types) {
140        Map<String, List<List<Type>>> newTypesMap = new HashMap<String, List<List<Type>>>();
141        Set<Entry<String, List<Type>>> typeEntrySet = types.entrySet();
142        for (Entry<String, List<Type>> set : typeEntrySet) {
143            List<Type> typeList = set.getValue();
144            List<List<Type>> newList = new ArrayList<List<Type>>();
145            int index = 0;
146            newList.add(index, new ArrayList<Type>());
147            for (Type type : typeList) {
148                List<Type> currentList = newList.get(index);
149                if (currentList == null) {
150                    currentList = new ArrayList<Type>();
151                    newList.add(index, currentList);
152                }
153                currentList.add(type);
154                if (currentList.size() % COLUMN_SIZE == 0) {
155                    index++;
156                    newList.add(index, new ArrayList<Type>());
157                }
158            }
159            newTypesMap.put(set.getKey(), newList);
160        }
161        return newTypesMap;
162    }
163
164    public Type getSelectedType() {
165        if (selectedType != null) {
166            log.debug("Returning selected type with id: " + selectedType.getId());
167        }
168        return selectedType;
169    }
170
171    /**
172     * If the selected type is supposed to be automatically injected by Seam through @DataModelSelection callback (i.e.
173     * the user will select the type from a list), this method should be called with <code>null</code> parameter before.
174     */
175    public void setSelectedType(Type type) {
176        if (typesMap == null) {
177            populateTypesList();
178        }
179        selectedType = type;
180    }
181
182    @Factory(value = "typesMap", scope = EVENT)
183    public Map<String, List<List<Type>>> getTypesList() {
184        // XXX : should cache per currentDocument type
185        if (typesMap == null
186                || (seamReload.isDevModeSet() && seamReload.shouldResetCache(typeManager, typesMapTimestamp))) {
187            // cache the list of allowed subtypes
188            populateTypesList();
189        }
190        selectedType = null;
191        return typesMap;
192    }
193
194    public void setTypesList(Map<String, List<List<Type>>> typesList) {
195        this.typesMap = typesList;
196    }
197
198    public Type getType(String typeName) {
199        return typeManager.getType(typeName);
200    }
201
202    public boolean hasType(String typeName) {
203        return typeManager.hasType(typeName);
204    }
205
206    protected DocumentModel getCurrentItem() {
207        return navigationContext.getCurrentDocument();
208    }
209
210}