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.commons.lang3.ArrayUtils;
025
026import org.ametys.cms.contenttype.ContentType;
027import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
028import org.ametys.cms.contenttype.ContentValidator;
029import org.ametys.cms.repository.Content;
030import org.ametys.plugins.repository.AmetysRepositoryException;
031import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder;
032import org.ametys.plugins.repository.data.holder.group.impl.ModelAwareComposite;
033import org.ametys.plugins.repository.data.holder.group.impl.ModelAwareRepeater;
034import org.ametys.plugins.repository.data.holder.group.impl.ModelAwareRepeaterEntry;
035import org.ametys.plugins.repository.model.CompositeDefinition;
036import org.ametys.plugins.repository.model.RepeaterDefinition;
037import org.ametys.runtime.model.ElementDefinition;
038import org.ametys.runtime.model.ModelHelper;
039import org.ametys.runtime.model.ModelItem;
040import org.ametys.runtime.parameter.Errors;
041
042import com.opensymphony.module.propertyset.PropertySet;
043import com.opensymphony.workflow.Condition;
044import com.opensymphony.workflow.WorkflowException;
045
046/**
047 * OSWorkflow condition to check all content metadata are valid
048 */
049public class ValidateContentCondition extends AbstractContentWorkflowComponent implements Condition, Initializable
050{
051    private ContentTypeExtensionPoint _cTypeEP;
052    
053    @Override
054    public void initialize() throws Exception
055    {
056        _cTypeEP = (ContentTypeExtensionPoint) _manager.lookup(ContentTypeExtensionPoint.ROLE);
057    }
058    
059    @Override
060    public boolean passesCondition(Map transientVars, Map args, PropertySet ps) throws WorkflowException
061    {
062        try
063        {
064            Content content = getContent(transientVars);
065            List<String> conditionFailures = getConditionFailures(transientVars);
066            
067            List<ModelItem> modelItems = content.getModel().stream().flatMap(model -> model.getModelItems().stream()).collect(Collectors.toList());
068            
069            if (!_validateValues(modelItems, Optional.of(content), "", content, conditionFailures))
070            {
071                return false;
072            }
073            
074            String[] allContentTypes = ArrayUtils.addAll(content.getTypes(), content.getMixinTypes());
075            for (String cTypeId : allContentTypes)
076            {
077                ContentType contentType = _cTypeEP.getExtension(cTypeId);
078                
079                for (ContentValidator contentValidator : contentType.getGlobalValidators())
080                {
081                    Errors errors = new Errors();
082                    contentValidator.validate(content, errors);
083                    if (errors.hasErrors())
084                    {
085                        conditionFailures.add(String.format("Validate condition failed for content on global validators", content.getId()));
086                        return false;
087                    }
088                }
089            }
090            
091            return true;
092        }
093        catch (AmetysRepositoryException e)
094        {
095            getConditionFailures(transientVars).add("Validate metadata condition failed (" + e.getMessage() + ")");
096            _logger.error("Cannot check condition ("  + e.getMessage() + "). Assuming it is false.", e);
097            return false; 
098        }
099    }
100    
101    private boolean _validateValues(List<ModelItem> modelItems, Optional<? extends ModelAwareDataHolder> dataHolder, String dataPath, Content content, List<String> conditionFailures)
102    {
103        for (ModelItem modelItem : modelItems)
104        {
105            String name = modelItem.getName();
106            if (modelItem instanceof ElementDefinition)
107            {
108                // simple element
109                ElementDefinition definition = (ElementDefinition) modelItem;
110                Object value = dataHolder.map(holder -> holder.getValue(name)).orElse(null);
111                
112                if (!ModelHelper.validateValue(definition, value).isEmpty())
113                {
114                    conditionFailures.add(String.format("Validate attribute condition failed for content %s on attribute %s with value %s", content.getId(), dataPath + name, String.valueOf(value)));
115                    return false;
116                }
117            }
118            else if (modelItem instanceof CompositeDefinition)
119            {
120                // composite
121                Optional<ModelAwareComposite> composite = dataHolder.map(holder -> holder.getComposite(name));
122                if (!_validateValues(((CompositeDefinition) modelItem).getChildren(), composite, dataPath + name + "/", content, conditionFailures))
123                {
124                    return false;
125                }
126            }
127            else if (modelItem instanceof RepeaterDefinition)
128            {
129                // repeater
130                RepeaterDefinition definition = (RepeaterDefinition) modelItem;
131                Optional<ModelAwareRepeater> repeater = dataHolder.map(holder -> holder.getRepeater(name));
132                
133                Optional<List<? extends ModelAwareRepeaterEntry>> entries = repeater.map(ModelAwareRepeater::getEntries);
134                
135                int repeaterSize = repeater.map(ModelAwareRepeater::getSize).orElse(0);
136                int minSize = definition.getMinSize();
137                int maxSize = definition.getMaxSize();
138                
139                if ((repeaterSize < minSize) || (maxSize > 0 && repeaterSize > maxSize))
140                {
141                    conditionFailures.add(String.format("Validate repeater size condition failed for content %s on repeater %s with size %s", content.getId(), dataPath + name, repeaterSize));
142                    return false;
143                }
144                
145                if (entries.isPresent())
146                {
147                    for (ModelAwareRepeaterEntry entry : entries.get())
148                    {
149                        if (!_validateValues(definition.getChildren(), Optional.of(entry), dataPath + name + "[" + (entry.getPosition())  + "]/", content, conditionFailures))
150                        {
151                            return false;
152                        }
153                    }
154                }
155            }
156        }
157        
158        return true;
159    }
160}