001/*
002 *  Copyright 2017 Anyware Services
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 */
016package org.ametys.cms.search.cocoon;
017
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.Collection;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024
025import org.apache.avalon.framework.configuration.Configuration;
026import org.apache.avalon.framework.configuration.ConfigurationException;
027import org.apache.avalon.framework.configuration.DefaultConfiguration;
028import org.apache.avalon.framework.context.Context;
029import org.apache.avalon.framework.context.ContextException;
030import org.apache.avalon.framework.context.Contextualizable;
031import org.apache.avalon.framework.parameters.Parameters;
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.cocoon.ProcessingException;
035import org.apache.cocoon.acting.ServiceableAction;
036import org.apache.cocoon.components.LifecycleHelper;
037import org.apache.cocoon.environment.ObjectModelHelper;
038import org.apache.cocoon.environment.Redirector;
039import org.apache.cocoon.environment.Request;
040import org.apache.cocoon.environment.SourceResolver;
041import org.apache.cocoon.util.log.SLF4JLoggerAdapter;
042import org.slf4j.Logger;
043import org.slf4j.LoggerFactory;
044
045import org.ametys.cms.contenttype.ContentTypesHelper;
046import org.ametys.cms.data.type.ModelItemTypeConstants;
047import org.ametys.cms.repository.Content;
048import org.ametys.cms.search.ui.model.SearchUIModelHelper;
049import org.ametys.cms.search.ui.model.impl.MetadataSearchUIColumn;
050import org.ametys.core.cocoon.JSonReader;
051import org.ametys.core.util.ServerCommHelper;
052import org.ametys.plugins.repository.AmetysObjectResolver;
053import org.ametys.plugins.repository.model.CompositeDefinition;
054import org.ametys.plugins.repository.model.RepeaterDefinition;
055import org.ametys.runtime.model.ElementDefinition;
056import org.ametys.runtime.model.ModelItem;
057
058/**
059 * SAX the columns of a repeater for the search tool.
060 */
061public class ModelRepeaterColumnsAction extends ServiceableAction implements Contextualizable
062{
063    /** The servercomm helper */
064    protected ServerCommHelper _serverCommHelper;
065    /** The Ametys object resolver */
066    protected AmetysObjectResolver _resolver;
067    /** Content Types helper */
068    protected ContentTypesHelper _contentTypesHelper;
069    /** Search UI Model helper */
070    protected SearchUIModelHelper _searchUIModelHelper;
071    
072    private ServiceManager _smanager;
073    private Context _context;
074    
075    @Override
076    public void service(ServiceManager smanager) throws ServiceException
077    {
078        super.service(smanager);
079        _smanager = smanager;
080        _serverCommHelper = (ServerCommHelper) smanager.lookup(ServerCommHelper.ROLE);
081        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
082        _contentTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE);
083        _searchUIModelHelper = (SearchUIModelHelper) smanager.lookup(SearchUIModelHelper.ROLE);
084    }
085    
086    @Override
087    public void contextualize(Context context) throws ContextException
088    {
089        _context = context;
090    }
091    
092    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
093    {
094        Request request = ObjectModelHelper.getRequest(objectModel);
095        Map<String, Object> results = new HashMap<>();
096
097        Map<String, Object> jsParameters = _serverCommHelper.getJsParameters();
098        
099        String contentId = (String) jsParameters.get("contentId");
100        String attributePath = (String) jsParameters.get("metadataPath");
101        
102        Content content = _resolver.resolveById(contentId);
103        if (content != null && attributePath != null && content.hasDefinition(attributePath))
104        {
105            ModelItem modelItem = content.getDefinition(attributePath);
106            
107            if (modelItem instanceof RepeaterDefinition)
108            {
109                List<Object> columns = new ArrayList<>();
110                for (ModelItem subModelItem : ((RepeaterDefinition) modelItem).getModelItems())
111                {
112                    _addAttributeToResults(columns, subModelItem, subModelItem.getName(), Arrays.asList(content.getTypes()));
113                }
114                results.put("columns", columns);
115            }
116        }
117        
118        results.put("success", true);
119        request.setAttribute(JSonReader.OBJECT_TO_READ, results);
120        return EMPTY_MAP;
121    }
122
123    private void _addAttributeToResults(List<Object> columns, ModelItem modelItem, String attributePath, Collection<String> contentTypes) throws ProcessingException, ConfigurationException
124    {
125        if (modelItem instanceof CompositeDefinition)
126        {
127            for (ModelItem subModelItem : ((CompositeDefinition) modelItem).getModelItems())
128            {
129                _addAttributeToResults(columns, subModelItem, attributePath + "." + subModelItem.getName(), contentTypes);
130            }
131        }
132        else
133        {
134            MetadataSearchUIColumn metadataSearchUIColumn;
135            try
136            {
137                metadataSearchUIColumn = MetadataSearchUIColumn.class.newInstance();
138            }
139            catch (InstantiationException | IllegalAccessException  e)
140            {
141                throw new IllegalArgumentException("Cannot instanciate the class " + MetadataSearchUIColumn.class.getCanonicalName() + ". Check that there is a public constructor with no arguments.");
142            }
143            
144            Logger logger = LoggerFactory.getLogger(MetadataSearchUIColumn.class);
145            try
146            {
147                Configuration columnConfig = _getColumnConfiguration(modelItem, contentTypes);
148                LifecycleHelper.setupComponent(metadataSearchUIColumn, new SLF4JLoggerAdapter(logger), _context, _smanager, columnConfig);
149                
150                metadataSearchUIColumn.setId(attributePath); // Force field path to relative field path
151                metadataSearchUIColumn.setFieldPath(attributePath); // Force field path to relative field path
152                metadataSearchUIColumn.setEditable(_isEditionAllowed(modelItem)); // Edition only depends on final definition
153                metadataSearchUIColumn.setMultiple(modelItem instanceof ElementDefinition && ((ElementDefinition) modelItem).isMultiple()); // Multiple only depends on final definition
154                
155                Map<String, Object> columnInfo = _searchUIModelHelper.getColumnInfo(metadataSearchUIColumn);
156                columns.add(columnInfo);  
157            }
158            catch (Exception e)
159            {
160                throw new ConfigurationException("Unable to initialize search ui column for attribute '" + modelItem.getPath() + "'.", e);
161            }
162            finally 
163            {
164                LifecycleHelper.dispose(metadataSearchUIColumn);
165            }
166        }
167    }
168    
169    private boolean _isEditionAllowed(ModelItem modelItem)
170    {
171        return !ModelItemTypeConstants.RICH_TEXT_ELEMENT_TYPE_ID.equals(modelItem.getType().getId());
172    }
173    
174    private Configuration _getColumnConfiguration(ModelItem modelItem, Collection<String> contentTypes)
175    {
176        DefaultConfiguration columnConfig = new DefaultConfiguration("column");
177        
178        DefaultConfiguration metaConfig = new DefaultConfiguration("metadata");
179        metaConfig.setAttribute("path", modelItem.getPath());
180        columnConfig.addChild(metaConfig);
181        
182        DefaultConfiguration cTypesConfig = new DefaultConfiguration("contentTypes");
183        
184        DefaultConfiguration baseCTypeConfig = new DefaultConfiguration("baseType");
185        baseCTypeConfig.setAttribute("id", modelItem.getModel().getId());
186        cTypesConfig.addChild(baseCTypeConfig);
187        
188        for (String cTypeId : contentTypes)
189        {
190            DefaultConfiguration cTypeConfig = new DefaultConfiguration("type");
191            cTypeConfig.setAttribute("id", cTypeId);
192            cTypesConfig.addChild(cTypeConfig);
193        }
194        
195        columnConfig.addChild(cTypesConfig);
196        
197        return columnConfig;
198    }
199}