001/*
002 *  Copyright 2015 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.odf;
017
018import java.io.File;
019import java.io.FileFilter;
020import java.util.HashMap;
021import java.util.Map;
022
023import org.apache.avalon.framework.configuration.Configuration;
024import org.apache.avalon.framework.configuration.ConfigurationException;
025import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
026import org.apache.avalon.framework.context.Context;
027import org.apache.avalon.framework.context.ContextException;
028import org.apache.avalon.framework.context.Contextualizable;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.avalon.framework.service.Serviceable;
032import org.apache.cocoon.Constants;
033import org.apache.cocoon.util.log.SLF4JLoggerAdapter;
034import org.apache.commons.lang3.StringUtils;
035
036import org.ametys.cms.content.indexing.solr.observation.ObserverHelper;
037import org.ametys.cms.contenttype.ContentType;
038import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
039import org.ametys.cms.repository.ContentQueryHelper;
040import org.ametys.cms.repository.ContentTypeExpression;
041import org.ametys.cms.repository.ModifiableContent;
042import org.ametys.cms.repository.WorkflowAwareContent;
043import org.ametys.cms.workflow.AbstractContentWorkflowComponent;
044import org.ametys.cms.workflow.ContentWorkflowHelper;
045import org.ametys.cms.workflow.CreateContentFunction;
046import org.ametys.core.engine.BackgroundEngineHelper;
047import org.ametys.core.util.I18nUtils;
048import org.ametys.odf.enumeration.OdfReferenceTableEntry;
049import org.ametys.plugins.repository.AmetysObject;
050import org.ametys.plugins.repository.AmetysObjectIterable;
051import org.ametys.plugins.repository.AmetysObjectIterator;
052import org.ametys.plugins.repository.AmetysObjectResolver;
053import org.ametys.plugins.repository.query.expression.AndExpression;
054import org.ametys.plugins.repository.query.expression.Expression.Operator;
055import org.ametys.plugins.repository.query.expression.StringExpression;
056import org.ametys.runtime.config.Config;
057import org.ametys.runtime.i18n.I18nizableText;
058import org.ametys.runtime.plugin.Init;
059import org.ametys.runtime.plugin.component.AbstractLogEnabled;
060
061import com.opensymphony.workflow.WorkflowException;
062
063/**
064 * This init class populates reference tables contents base on WEB-INF/params/odf files
065 *
066 */
067public class PopulateOdfTableRef extends AbstractLogEnabled implements Init, Serviceable, Contextualizable
068{
069    private AmetysObjectResolver _resolver;
070    private org.apache.cocoon.environment.Context _cocoonContext;
071    private I18nUtils _i18nUtils;
072    private ContentTypeExtensionPoint _cTypeExtensionPoint;
073    private ServiceManager _manager;
074    private ContentWorkflowHelper _workflowHelper;
075
076    @Override
077    public void service(ServiceManager manager) throws ServiceException
078    {
079        _manager = manager;
080        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
081        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
082        _cTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
083        _workflowHelper = (ContentWorkflowHelper) manager.lookup(ContentWorkflowHelper.ROLE);
084    }
085    
086    public void contextualize(Context context) throws ContextException
087    {
088        _cocoonContext = (org.apache.cocoon.environment.Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT);
089    }
090    
091    public void init() throws Exception
092    {
093        File file = new File(_cocoonContext.getRealPath("/WEB-INF/param/odf"));
094        
095        if (file.exists() && file.isDirectory())
096        {
097            Map<String, Object> environmentInformation = null;
098            try
099            {
100                // Enter in a background environment as i18nUtils can not be used outside a request
101                // because of XMLBundleResource uses SourceUtil.toSAX(..) to parse its values.
102                environmentInformation = BackgroundEngineHelper.createAndEnterEngineEnvironment(_manager, _cocoonContext, new SLF4JLoggerAdapter(getLogger()));
103                
104                ObserverHelper.suspendObservationForIndexation();
105                
106                File[] xmlFiles = file.listFiles(new FileFilter()
107                {
108                    public boolean accept(File child)
109                    {
110                        return child.isFile() && child.getName().endsWith(".xml");
111                    }
112                });
113                
114                for (File xmlFile : xmlFiles)
115                {
116                    int count = 0;
117                    String fileName = xmlFile.getName();
118                    
119                    String cTypeId = _transform2ContentTypeId (fileName);
120                    
121                    // Check that the simple content type exists
122                    if (_cTypeExtensionPoint.hasExtension(cTypeId))
123                    {
124                        Configuration configuration = new DefaultConfigurationBuilder().buildFromFile(xmlFile);
125                        for (Configuration itemConfig : configuration.getChildren("item"))
126                        {
127                            boolean hasSubEntries = itemConfig.getChild("subitem", false) != null;
128                            count += _createEntryIfNotExists(cTypeId + (hasSubEntries ? "Category" : ""), itemConfig, null);
129                        }
130                        
131                        getLogger().info("Create {} entries for content type '{}'", count, cTypeId);
132                    }
133                    else
134                    {
135                        getLogger().info("Content type '{}' is not a valid content type.", cTypeId);
136                    }
137                }
138            }
139            finally
140            {
141                // Leave the environment.
142                if (environmentInformation != null)
143                {
144                    BackgroundEngineHelper.leaveEngineEnvironment(environmentInformation);
145                }
146                
147                ObserverHelper.restartObservationForIndexation();
148            }
149        }
150    }
151    
152    private String _transform2ContentTypeId (String filename)
153    {
154        int index = filename.lastIndexOf(".");
155        String id = filename.substring(0, index);
156        
157        StringBuilder sb = new StringBuilder();
158        sb.append("odf-enumeration.");
159        
160        for (String segment : id.split("_"))
161        {
162            sb.append(StringUtils.capitalize(segment));
163        }
164        
165        return sb.toString();
166    }
167    
168    private int _createEntryIfNotExists (String cTypeId, Configuration itemConfig, String parentCategoryId) throws WorkflowException, ConfigurationException
169    {
170        int count = 0;
171        
172        String code = itemConfig.getAttribute("code");
173        String i18nKey = itemConfig.getAttribute("i18n-key");
174        String cdmValue = itemConfig.getAttribute("cdmValue", null);
175        
176        ContentTypeExpression cTypeExpr = new ContentTypeExpression(Operator.EQ, cTypeId);
177        StringExpression codeExpr = new StringExpression(OdfReferenceTableEntry.CODE, Operator.EQ, code);
178        
179        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, codeExpr));
180        AmetysObjectIterable<AmetysObject> contents = _resolver.query(xpathQuery);
181        AmetysObjectIterator<AmetysObject> it = contents.iterator();
182
183        ContentType contentType = _cTypeExtensionPoint.getExtension(cTypeId);
184        
185        ModifiableContent entry = null;
186        if (!it.hasNext())
187        {
188            String titleFR = _i18nUtils.translate(new I18nizableText("application", i18nKey), "fr");
189            
190            if (StringUtils.isEmpty(titleFR))
191            {
192                getLogger().error("The reference table item with the code '{}' of type '{}' can't be migrated because it's I18N key '{}' is not defined for french.", code, cTypeId, i18nKey);
193            }
194            else
195            {
196                String titleEN = _i18nUtils.translate(new I18nizableText("application", i18nKey), "en");
197                
198                Map<String, String> titleVariants = new HashMap<>();
199                titleVariants.put("fr", titleFR);
200                if (titleEN != null)
201                {
202                    titleVariants.put("en", titleEN);
203                }
204                
205                Map<String, Object> inputs = new HashMap<>();
206                if (!contentType.isMultilingual())
207                {
208                    inputs.put(CreateContentFunction.CONTENT_LANGUAGE_KEY, Config.getInstance().getValue("odf.programs.lang")); 
209                }
210                Map<String, Object> result = _workflowHelper.createContent("reference-table", 1, titleFR, titleVariants, new String[] {cTypeId}, new String[0], null, null, inputs);
211                
212                entry = (ModifiableContent) result.get(AbstractContentWorkflowComponent.CONTENT_KEY);
213                if (entry.hasDefinition(OdfReferenceTableEntry.CODE))
214                {
215                    entry.setValue(OdfReferenceTableEntry.CODE, code);
216                }
217                
218                if (StringUtils.isNotEmpty(cdmValue) && entry.hasDefinition(OdfReferenceTableEntry.CDM_VALUE))
219                {
220                    entry.setValue(OdfReferenceTableEntry.CDM_VALUE, cdmValue);
221                }
222                
223                if (parentCategoryId != null && entry.hasDefinition("category"))
224                {
225                    entry.setValue("category", parentCategoryId);
226                }
227                entry.saveChanges();
228                _workflowHelper.doAction((WorkflowAwareContent) entry, 22);
229                
230                count++;
231            }
232        }
233        else
234        {
235            entry = (ModifiableContent) it.next();
236        }
237        
238        if (entry != null)
239        {
240            for (Configuration childConfig : itemConfig.getChildren("subitem"))
241            {
242                String subCType = cTypeId.endsWith("Category") ? StringUtils.substringBefore(cTypeId, "Category") : cTypeId;
243                if (_cTypeExtensionPoint.hasExtension(subCType))
244                {
245                    count += _createEntryIfNotExists(subCType, childConfig, entry.getId());
246                }
247                else
248                {
249                    getLogger().error("Unknow content type of id {}", subCType);
250                }
251            }
252        }
253        
254        return count;
255        
256    }
257
258}