001/*
002 *  Copyright 2010 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.workflow;
017
018import java.util.List;
019import java.util.Map;
020import java.util.Optional;
021import java.util.stream.Collectors;
022
023import org.apache.avalon.framework.activity.Initializable;
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.commons.collections.ListUtils;
027import org.apache.commons.lang3.ArrayUtils;
028
029import org.ametys.cms.contenttype.ContentType;
030import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
031import org.ametys.cms.contenttype.ContentValidator;
032import org.ametys.cms.repository.Content;
033import org.ametys.core.util.I18nUtils;
034import org.ametys.plugins.repository.AmetysRepositoryException;
035import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder;
036import org.ametys.plugins.repository.data.holder.group.ModelAwareComposite;
037import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeater;
038import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeaterEntry;
039import org.ametys.plugins.repository.model.CompositeDefinition;
040import org.ametys.plugins.repository.model.RepeaterDefinition;
041import org.ametys.plugins.workflow.EnhancedCondition;
042import org.ametys.runtime.i18n.I18nizableText;
043import org.ametys.runtime.model.ElementDefinition;
044import org.ametys.runtime.model.ModelHelper;
045import org.ametys.runtime.model.ModelItem;
046import org.ametys.runtime.parameter.Errors;
047
048import com.opensymphony.module.propertyset.PropertySet;
049import com.opensymphony.workflow.WorkflowException;
050
051/**
052 * OSWorkflow condition to check all content metadata are valid
053 */
054public class ValidateContentCondition extends AbstractContentWorkflowComponent implements EnhancedCondition, Initializable
055{
056    private ContentTypeExtensionPoint _cTypeEP;
057    private I18nUtils _i18nUtils;
058    
059    @Override
060    public void initialize() throws Exception
061    {
062        _cTypeEP = (ContentTypeExtensionPoint) _manager.lookup(ContentTypeExtensionPoint.ROLE);
063    }
064    
065    @Override
066    public void service(ServiceManager smanager) throws ServiceException
067    {
068        super.service(smanager);
069        _i18nUtils = (I18nUtils) smanager.lookup(I18nUtils.ROLE);
070    }
071    
072    @Override
073    public boolean passesCondition(Map transientVars, Map args, PropertySet ps) throws WorkflowException
074    {
075        List<String> conditionFailures = getConditionFailures(transientVars);
076        try
077        {
078            Content content = getContent(transientVars);
079            
080            List<ModelItem> modelItems = ModelHelper.getModelItems(content.getModel())
081                                                .stream()
082                                                .filter(modelItem -> !(modelItem instanceof ElementDefinition) || ((ElementDefinition) modelItem).isEditable())
083                                                .collect(Collectors.toList());
084            
085            if (!_validateValues(modelItems, Optional.of(content), "", content, conditionFailures))
086            {
087                return false;
088            }
089            
090            String[] allContentTypes = ArrayUtils.addAll(content.getTypes(), content.getMixinTypes());
091            for (String cTypeId : allContentTypes)
092            {
093                ContentType contentType = _cTypeEP.getExtension(cTypeId);
094                
095                for (ContentValidator contentValidator : contentType.getGlobalValidators())
096                {
097                    Errors errors = new Errors();
098                    contentValidator.validate(content, errors);
099                    if (errors.hasErrors())
100                    {
101                        List<String> translatedErrors = errors.getErrors().stream().map(error -> _i18nUtils.translate(error, "en")).collect(Collectors.toList());
102                        
103                        conditionFailures.add(String.format("Validate condition failed for content '%s' on global validator '%s' with errors %s", content.getId(), contentValidator.getClass().getName(), translatedErrors));
104                        return false;
105                    }
106                }
107            }
108            
109            return true;
110        }
111        catch (AmetysRepositoryException e)
112        {
113            conditionFailures.add("Validate metadata condition failed (" + e.getMessage() + ")");
114            _logger.error("Cannot check condition ("  + e.getMessage() + "). Assuming it is false.", e);
115            return false; 
116        }
117    }
118    
119    private boolean _validateValues(List<ModelItem> modelItems, Optional<? extends ModelAwareDataHolder> dataHolder, String dataPath, Content content, List<String> conditionFailures)
120    {
121        for (ModelItem modelItem : modelItems)
122        {
123            String name = modelItem.getName();
124            if (modelItem instanceof ElementDefinition)
125            {
126                // simple element
127                ElementDefinition definition = (ElementDefinition) modelItem;
128                Object value = dataHolder.map(holder -> holder.getValue(name)).orElse(null);
129                
130                if (!ModelHelper.validateValue(definition, value).isEmpty())
131                {
132                    conditionFailures.add(String.format("Validate attribute condition failed for content %s on attribute %s with value %s", content.getId(), dataPath + name, String.valueOf(value)));
133                    return false;
134                }
135            }
136            else if (modelItem instanceof CompositeDefinition)
137            {
138                // composite
139                Optional<ModelAwareComposite> composite = dataHolder.map(holder -> holder.getComposite(name));
140                if (!_validateValues(((CompositeDefinition) modelItem).getChildren(), composite, dataPath + name + "/", content, conditionFailures))
141                {
142                    return false;
143                }
144            }
145            else if (modelItem instanceof RepeaterDefinition)
146            {
147                // repeater
148                RepeaterDefinition definition = (RepeaterDefinition) modelItem;
149                Optional<ModelAwareRepeater> repeater = dataHolder.map(holder -> holder.getRepeater(name));
150                
151                Optional<List<? extends ModelAwareRepeaterEntry>> entries = repeater.map(ModelAwareRepeater::getEntries);
152                
153                int repeaterSize = repeater.map(ModelAwareRepeater::getSize).orElse(0);
154                int minSize = definition.getMinSize();
155                int maxSize = definition.getMaxSize();
156                
157                if (repeaterSize < minSize
158                    || maxSize > 0 && repeaterSize > maxSize)
159                {
160                    conditionFailures.add(String.format("Validate repeater size condition failed for content %s on repeater %s with size %s", content.getId(), dataPath + name, repeaterSize));
161                    return false;
162                }
163                
164                if (entries.isPresent())
165                {
166                    for (ModelAwareRepeaterEntry entry : entries.get())
167                    {
168                        if (!_validateValues(definition.getChildren(), Optional.of(entry), dataPath + name + "[" + (entry.getPosition())  + "]/", content, conditionFailures))
169                        {
170                            return false;
171                        }
172                    }
173                }
174            }
175        }
176        
177        return true;
178    }
179
180    @Override
181    public List<ConditionArgument> getArguments()
182    {
183        return ListUtils.EMPTY_LIST;
184    }
185
186    @Override
187    public I18nizableText getDescription(Map<String, String> argumentsValues)
188    {
189        return new I18nizableText("plugin.cms", "PLUGINS_CMS_VALIDATE_CONTENT_CONDITION_DESCRIPTION");
190    }
191}