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