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