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.Set;
021
022import org.apache.avalon.framework.activity.Initializable;
023import org.apache.commons.lang3.ArrayUtils;
024
025import org.ametys.cms.contenttype.ContentType;
026import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
027import org.ametys.cms.contenttype.ContentValidator;
028import org.ametys.cms.contenttype.MetadataDefinition;
029import org.ametys.cms.contenttype.MetadataType;
030import org.ametys.cms.contenttype.RepeaterDefinition;
031import org.ametys.cms.repository.Content;
032import org.ametys.plugins.repository.AmetysRepositoryException;
033import org.ametys.plugins.repository.metadata.CompositeMetadata;
034import org.ametys.plugins.repository.metadata.UnknownMetadataException;
035import org.ametys.runtime.parameter.Errors;
036
037import com.opensymphony.module.propertyset.PropertySet;
038import com.opensymphony.workflow.Condition;
039import com.opensymphony.workflow.WorkflowException;
040
041
042/**
043 * OSWorkflow condition to check all content metadata are valid
044 */
045public class ValidateMetadataCondition extends AbstractContentWorkflowComponent implements Condition, Initializable
046{
047    private ContentTypeExtensionPoint _cTypeEP;
048    
049    @Override
050    public void initialize() throws Exception
051    {
052        _cTypeEP = (ContentTypeExtensionPoint) _manager.lookup(ContentTypeExtensionPoint.ROLE);
053    }
054    
055    @Override
056    public boolean passesCondition(Map transientVars, Map args, PropertySet ps) throws WorkflowException
057    {
058        try
059        {
060            Content content = getContent(transientVars);
061            
062            String[] allContentTypes = ArrayUtils.addAll(content.getTypes(), content.getMixinTypes());
063            for (String cTypeId : allContentTypes)
064            {
065                ContentType contentType = _cTypeEP.getExtension(cTypeId);
066                
067                Set<String> metadataNames = contentType.getMetadataNames();
068                for (String metadataName : metadataNames)
069                {
070                    MetadataDefinition metadataDef = contentType.getMetadataDefinition(metadataName);
071                    if (!validateMetadata(content.getMetadataHolder(), metadataDef, metadataName, new Errors()))
072                    {
073                        List<String> conditionFailures = getConditionFailures(transientVars);
074                        if (conditionFailures != null)
075                        {
076                            conditionFailures.add(String.format("Validate metadata condition failed for content %s on metadata ", content.getId(), metadataName));
077                        }
078                        return false;
079                    }
080                }
081                
082                for (ContentValidator contentValidator : contentType.getGlobalValidators())
083                {
084                    Errors errors = new Errors();
085                    contentValidator.validate(content, errors);
086                    if (errors.hasErrors())
087                    {
088                        getConditionFailures(transientVars).add(String.format("Validate metadata condition failed for content on global validators", content.getId()));
089                        return false;
090                    }
091                }
092            }
093            
094            return true;
095        }
096        catch (AmetysRepositoryException e)
097        {
098            getConditionFailures(transientVars).add("Validate metadata condition failed (" + e.getMessage() + ")");
099            _logger.error("Cannot check condition ("  + e.getMessage() + "). Assuming it is false.", e);
100            return false; 
101        }
102    }
103    
104    /**
105     * Validate a metadata
106     * @param parentMetadata The parent metadata
107     * @param metadataDef The metadata definition
108     * @param metadataName The metadata name
109     * @param errors The errors
110     * @return <code>true</code> if metadata is valid
111     */
112    protected boolean validateMetadata (CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, Errors errors)
113    {
114        MetadataType type = metadataDef.getType();
115        
116        if (metadataDef instanceof RepeaterDefinition)
117        {
118            return _validateRepeaterMetadata(parentMetadata, metadataDef, metadataName, errors);
119        }
120        else if (type.equals(MetadataType.COMPOSITE))
121        {
122            return _validateCompositeMetadata(parentMetadata, metadataDef, metadataName, errors);
123        }
124        else
125        {
126            return _validateOtherMetadata(parentMetadata, metadataDef, metadataName, errors);
127        }
128    }
129
130    private boolean _validateOtherMetadata(CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, Errors errors)
131    {
132        if (metadataDef.getValidator() == null)
133        {
134            // no validator, the metadata is valid
135            return true;
136        }
137        
138        Object value = getValue(parentMetadata, metadataDef, metadataName);
139         
140        metadataDef.getValidator().validate(value, errors);
141        
142        // the metadata is valid is there is no errors
143        return !errors.hasErrors();
144    }
145
146    private boolean _validateCompositeMetadata(CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, Errors errors)
147    {
148        CompositeMetadata metadata = null;
149        if (parentMetadata != null && parentMetadata.hasMetadata(metadataName))
150        {
151            metadata = parentMetadata.getCompositeMetadata(metadataName);
152        }
153        
154        Set<String> subMetadataNames = metadataDef.getMetadataNames();
155        for (String subMetadataName : subMetadataNames)
156        {
157            MetadataDefinition subMetadataDef = metadataDef.getMetadataDefinition(subMetadataName);
158            if (!validateMetadata(metadata, subMetadataDef, subMetadataName, errors))
159            {
160                return false;
161            }
162        }
163        
164        return true;
165    }
166
167    private boolean _validateRepeaterMetadata(CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, Errors errors)
168    {
169        RepeaterDefinition repeaterDef = (RepeaterDefinition) metadataDef;
170                    
171        if (parentMetadata != null && parentMetadata.hasMetadata(metadataName))
172        {
173            CompositeMetadata metadata = parentMetadata.getCompositeMetadata(metadataName);
174            
175            // Metadata exists
176            String[] entries = metadata.getMetadataNames();
177            
178            // Check size
179            if (entries.length < repeaterDef.getMinSize() || (repeaterDef.getMaxSize() != -1 && entries.length > repeaterDef.getMaxSize()))
180            {
181                return false;
182            }
183            
184            // Check inside repeater values
185            for (String entryName : entries)
186            {
187                CompositeMetadata entry = metadata.getCompositeMetadata(entryName);
188                Set<String> subMetadataNames = metadataDef.getMetadataNames();
189                for (String subMetadataName : subMetadataNames)
190                {
191                    MetadataDefinition subMetadataDef = metadataDef.getMetadataDefinition(subMetadataName);
192                    if (!validateMetadata(entry, subMetadataDef, subMetadataName, errors))
193                    {
194                        return false;
195                    }
196                }
197            }
198        }
199        
200        return repeaterDef.getMinSize() == 0;
201    }
202    
203    /**
204     * Get the metadata value
205     * @param metadata The metadata
206     * @param metadataDef The metadata definition
207     * @param metadataName The metadata name
208     * @return The metadata value
209     */
210    protected Object getValue (CompositeMetadata metadata, MetadataDefinition metadataDef, String metadataName)
211    {
212        if (metadata == null)
213        {
214            return null;
215        }
216        
217        try
218        {
219            switch (metadataDef.getType())
220            {
221                case STRING:
222                    return metadata.getStringArray(metadataName);
223                case MULTILINGUAL_STRING:
224                    return metadata.getMultilingualString(metadataName).getValues().stream().toArray(String[]::new);
225                case DOUBLE:
226                    return ArrayUtils.toObject(metadata.getDoubleArray(metadataName));
227                case LONG:
228                    return ArrayUtils.toObject(metadata.getLongArray(metadataName));
229                case BOOLEAN:
230                    return ArrayUtils.toObject(metadata.getBooleanArray(metadataName));
231                case DATE:
232                case DATETIME:
233                    return metadata.getDateArray(metadataName);
234                case BINARY:
235                    return metadata.getBinaryMetadata(metadataName);
236                case FILE:
237                    return metadata.getType(metadataName) == org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType.BINARY ? metadata.getBinaryMetadata(metadataName) :  metadata.getString(metadataName);
238                case RICH_TEXT:
239                    return metadata.getRichText(metadataName);
240                case COMPOSITE:
241                case USER:
242                case GEOCODE:
243                case REFERENCE:
244                    return metadata.getCompositeMetadata(metadataName);
245                case CONTENT:
246                    return metadata.getStringArray(metadataName);
247                default:
248                    return null;
249            }
250        }
251        catch (UnknownMetadataException e)
252        {
253            return null;
254        }
255    }
256}