001/* 002 * Copyright 2018 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.contenttype; 017 018import java.util.ArrayList; 019import java.util.List; 020import java.util.regex.Pattern; 021 022import org.apache.avalon.framework.configuration.Configuration; 023import org.apache.avalon.framework.configuration.ConfigurationException; 024import org.apache.avalon.framework.service.ServiceException; 025import org.apache.avalon.framework.service.ServiceManager; 026import org.apache.commons.lang3.StringUtils; 027 028import org.ametys.cms.contenttype.DefaultContentType.AnnotableDefinition; 029import org.ametys.cms.data.type.ModelItemTypeConstants; 030import org.ametys.cms.model.restrictions.ContentRestrictedModelItemHelper; 031import org.ametys.cms.repository.ContentAttributeTypeExtensionPoint; 032import org.ametys.runtime.i18n.I18nizableText; 033import org.ametys.runtime.model.ElementDefinitionParser; 034import org.ametys.runtime.model.Enumerator; 035import org.ametys.runtime.model.Model; 036import org.ametys.runtime.model.ModelItemGroup; 037import org.ametys.runtime.model.type.ModelItemType; 038import org.ametys.runtime.parameter.Validator; 039import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 040 041/** 042 * This class parses the content attributes definition 043 */ 044public class ContentAttributeDefinitionParser extends ElementDefinitionParser 045{ 046 /** Pattern for annotation names */ 047 protected static Pattern __annotationNamePattern; 048 049 /** the content type extension point */ 050 protected ContentTypeExtensionPoint _contentTypeExtensionPoint; 051 052 /** the content attribute type extension point */ 053 protected ContentAttributeTypeExtensionPoint _contentAttributeTypeExtensionPoint; 054 055 /** 056 * Creates a content attribute definition parser. 057 * @param contentAttributeTypeExtensionPoint the extension point to use to get available element types 058 * @param enumeratorManager the enumerator component manager. 059 * @param validatorManager the validator component manager. 060 */ 061 public ContentAttributeDefinitionParser(ContentAttributeTypeExtensionPoint contentAttributeTypeExtensionPoint, ThreadSafeComponentManager<Enumerator> enumeratorManager, ThreadSafeComponentManager<Validator> validatorManager) 062 { 063 super(contentAttributeTypeExtensionPoint, enumeratorManager, validatorManager); 064 } 065 066 @Override 067 public AttributeDefinition parse(ServiceManager serviceManager, String pluginName, Configuration definitionConfig, Model model, ModelItemGroup parent) throws ConfigurationException 068 { 069 try 070 { 071 _contentAttributeTypeExtensionPoint = (ContentAttributeTypeExtensionPoint) serviceManager.lookup(ContentAttributeTypeExtensionPoint.ROLE); 072 _contentTypeExtensionPoint = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE); 073 } 074 catch (ServiceException e) 075 { 076 throw new ConfigurationException("Unable to parse the attribute '" + _parseName(definitionConfig) + "' in model'" + model.getId() + "'.", definitionConfig, e); 077 } 078 079 AttributeDefinition definition = (AttributeDefinition) super.parse(serviceManager, pluginName, definitionConfig, model, parent); 080 081 try 082 { 083 ContentRestrictedModelItemHelper restrictedModelItemHelper = (ContentRestrictedModelItemHelper) serviceManager.lookup(ContentRestrictedModelItemHelper.ROLE); 084 definition.setRestrictions(restrictedModelItemHelper._parseRestrictions(definitionConfig)); 085 } 086 catch (ServiceException e) 087 { 088 throw new ConfigurationException("Unable to resolve restrictions on the element '" + definition.getName() + "'.", e); 089 } 090 091 if (definition instanceof AnnotableDefinition) 092 { 093 ((AnnotableDefinition) definition).setSemanticAnnotations(_parseDefinitionWithAnnotations(pluginName, definitionConfig)); 094 } 095 096 if (definition instanceof ContentAttributeDefinition) 097 { 098 ContentAttributeDefinition contentAttributeDefinition = (ContentAttributeDefinition) definition; 099 contentAttributeDefinition.setContentTypeId(_parseContentTypeId(definitionConfig)); 100 101 String invertRelationPath = _parseInvertRelationPath(definitionConfig); 102 if (StringUtils.isNotEmpty(invertRelationPath)) 103 { 104 contentAttributeDefinition.setInvertRelationPath(invertRelationPath); 105 contentAttributeDefinition.setForceInvert(_parseForceInvert(definitionConfig)); 106 } 107 } 108 109 return definition; 110 } 111 112 @Override 113 protected AttributeDefinition _createModelItem(Configuration definitionConfig) throws ConfigurationException 114 { 115 ModelItemType type = _parseType(definitionConfig); 116 if (ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID.equals(type.getId())) 117 { 118 return new ContentAttributeDefinition(_contentTypeExtensionPoint, _contentAttributeTypeExtensionPoint); 119 } 120 else if (ModelItemTypeConstants.RICH_TEXT_ELEMENT_TYPE_ID.equals(type.getId())) 121 { 122 return new RichTextAttributeDefinition<>(); 123 } 124 else 125 { 126 return new AttributeDefinition<>(); 127 } 128 } 129 130 @Override 131 protected String _parseName(Configuration itemConfig) throws ConfigurationException 132 { 133 String itemName = itemConfig.getAttribute(_getNameConfigurationAttribute()); 134 135 if (!itemName.matches("^[a-zA-Z]((?!__)[a-zA-Z0-9_-])*$")) 136 { 137 throw new ConfigurationException("Invalid model item name: " + itemName + ". The item name must start with a letter and must contain only letters, digits, underscore or dash characters.", itemConfig); 138 } 139 140 return itemName; 141 } 142 143 /** 144 * Parses the semantic annotations of the model item 145 * @param pluginName the name of the plugin name declaring this item. 146 * @param itemConfig the model item configuration to use. 147 * @return the list of the declared annotations. 148 * @throws ConfigurationException if the configuration is not valid. 149 */ 150 protected List<SemanticAnnotation> _parseDefinitionWithAnnotations(String pluginName, Configuration itemConfig) throws ConfigurationException 151 { 152 Configuration annotationsConfiguration = itemConfig.getChild("annotations"); 153 List<SemanticAnnotation> annotations = new ArrayList<>(); 154 155 for (Configuration annotationConfig : annotationsConfiguration.getChildren("annotation")) 156 { 157 String id = annotationConfig.getAttribute("name"); 158 159 if (!_getAnnotationNamePattern().matcher(id).matches()) 160 { 161 throw new ConfigurationException("Invalid annonation name '" + id + "'. This value is not permitted: only [a-zA-Z][a-zA-Z0-9-_]* are allowed."); 162 } 163 164 I18nizableText label = _parseI18nizableText(annotationConfig, pluginName, "label"); 165 I18nizableText description = _parseI18nizableText(annotationConfig, pluginName, "description"); 166 annotations.add(new SemanticAnnotation(id, label, description)); 167 } 168 169 return annotations; 170 } 171 172 /** 173 * Get the annotation name pattern to test validity. 174 * @return The annotation name pattern. 175 */ 176 protected Pattern _getAnnotationNamePattern() 177 { 178 if (__annotationNamePattern == null) 179 { 180 // [a-zA-Z][a-zA-Z0-9_]* 181 __annotationNamePattern = Pattern.compile("[a-z][a-z0-9-_]*", Pattern.CASE_INSENSITIVE); 182 } 183 184 return __annotationNamePattern; 185 } 186 187 /** 188 * Parses the content type identifier attribute. 189 * @param itemConfig the item configuration to use. 190 * @return the identifier of the content type or <code>null</code> if none defined. 191 * @throws ConfigurationException if the defined content type des not exist 192 */ 193 protected String _parseContentTypeId(Configuration itemConfig) throws ConfigurationException 194 { 195 return itemConfig.getAttribute("contentType", null); 196 } 197 198 /** 199 * Parses the invert relation path attribute. 200 * @param itemConfig the item configuration to use. 201 * @return the invert relation path or <code>null</code> if none defined. 202 */ 203 protected String _parseInvertRelationPath(Configuration itemConfig) 204 { 205 return itemConfig.getAttribute("invert", null); 206 } 207 208 /** 209 * Parses the force invert attribute. 210 * @param itemConfig the item configuration to use. 211 * @return <code>true</code> if mutual relationship of the item should be set regardless of user's rights, <code>false</code> otherwise. 212 */ 213 protected boolean _parseForceInvert(Configuration itemConfig) 214 { 215 return itemConfig.getAttributeAsBoolean("forceInvert", false); 216 } 217}