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.Collection; 020import java.util.Collections; 021import java.util.List; 022import java.util.Map; 023 024import org.apache.avalon.framework.configuration.Configuration; 025import org.apache.avalon.framework.configuration.ConfigurationException; 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.cocoon.ProcessingException; 028import org.apache.commons.lang3.StringUtils; 029import org.apache.commons.lang3.tuple.ImmutablePair; 030import org.apache.commons.lang3.tuple.Pair; 031import org.xml.sax.ContentHandler; 032import org.xml.sax.SAXException; 033 034import org.ametys.cms.data.ContentValue; 035import org.ametys.cms.model.ContentElementDefinition; 036import org.ametys.cms.repository.ContentQueryHelper; 037import org.ametys.cms.repository.ContentTypeExpression; 038import org.ametys.cms.repository.ModifiableContent; 039import org.ametys.core.util.XMLUtils; 040import org.ametys.plugins.repository.AmetysObjectIterable; 041import org.ametys.plugins.repository.AmetysObjectIterator; 042import org.ametys.plugins.repository.AmetysObjectResolver; 043import org.ametys.plugins.repository.query.expression.AndExpression; 044import org.ametys.plugins.repository.query.expression.DoubleExpression; 045import org.ametys.plugins.repository.query.expression.Expression; 046import org.ametys.plugins.repository.query.expression.Expression.Operator; 047import org.ametys.plugins.repository.query.expression.LongExpression; 048import org.ametys.plugins.repository.query.expression.StringExpression; 049import org.ametys.runtime.model.DefinitionContext; 050import org.ametys.runtime.model.ElementDefinition; 051import org.ametys.runtime.model.ModelItem; 052import org.ametys.runtime.model.type.ElementType; 053import org.ametys.runtime.model.type.ModelItemTypeConstants; 054 055/** 056 * Definition of content types attributes of type Content 057 */ 058public class ContentAttributeDefinition extends AttributeDefinition<ContentValue> implements ContentElementDefinition 059{ 060 /** attribute type for default values */ 061 public static final String ATTRIBUTE_DEFAULT_VALUE_TYPE = "attribute"; 062 063 private ContentTypesHelper _contentTypesHelper; 064 private ContentTypeExtensionPoint _contentTypeExtensionPoint; 065 066 private String _contentTypeId; 067 private String _invertRelationPath; 068 private boolean _forceInvert; 069 070 private boolean _isDefaultValueAlreadyChecked; 071 072 /** 073 * Defintion's constructor 074 * @param contentTypeExtensionPoint the content type extension point 075 * @param contentAttributeTypeExtensionPoint the content attribute type extension point 076 */ 077 public ContentAttributeDefinition(ContentTypeExtensionPoint contentTypeExtensionPoint, ContentTypesHelper contentAttributeTypeExtensionPoint) 078 { 079 _contentTypeExtensionPoint = contentTypeExtensionPoint; 080 _contentTypesHelper = contentAttributeTypeExtensionPoint; 081 } 082 083 public String getContentTypeId() 084 { 085 return _contentTypeId; 086 } 087 088 public void setContentTypeId(String contentTypeId) 089 { 090 _contentTypeId = contentTypeId; 091 } 092 093 /** 094 * Checks the default value of this definition 095 * @throws ConfigurationException if there is a problem with default value declaration 096 */ 097 @SuppressWarnings("unchecked") 098 public void checkDefaultValue() throws ConfigurationException 099 { 100 if (!_isDefaultValueAlreadyChecked) 101 { 102 if (_getParsedDefaultValues() != null) 103 { 104 List<Pair<String, Object>> parsedDefaultValues = new ArrayList<>(); 105 for (Pair<String, Object> parsedDefaultValue : _getParsedDefaultValues()) 106 { 107 String defaultValueType = parsedDefaultValue.getLeft(); 108 if (ATTRIBUTE_DEFAULT_VALUE_TYPE.equals(defaultValueType)) 109 { 110 assert parsedDefaultValue.getRight() instanceof Pair; 111 Pair<String, Configuration> defaultValue = (Pair<String, Configuration>) parsedDefaultValue.getRight(); 112 String attributeName = defaultValue.getLeft(); 113 Configuration defaultValueConfiguration = defaultValue.getRight(); 114 115 Object parsedAttributeDefaultValue = _checkAndParseAttributeDefaultValue(attributeName, defaultValueConfiguration); 116 parsedDefaultValues.add(new ImmutablePair<>(defaultValueType, new ImmutablePair<>(attributeName, parsedAttributeDefaultValue))); 117 } 118 else 119 { 120 parsedDefaultValues.add(parsedDefaultValue); 121 } 122 } 123 124 setParsedDefaultValues(parsedDefaultValues); 125 } 126 127 _isDefaultValueAlreadyChecked = true; 128 } 129 } 130 131 private Object _checkAndParseAttributeDefaultValue(String attributeName, Configuration defaultValueConfiguration) throws ConfigurationException 132 { 133 if (StringUtils.isEmpty(_contentTypeId)) 134 { 135 throw new ConfigurationException("The type '" + ATTRIBUTE_DEFAULT_VALUE_TYPE + "' is not available for the default value of item '" + getPath() + " (content)': this item does not specify a content type.", defaultValueConfiguration); 136 } 137 else 138 { 139 ContentType contentType = _contentTypeExtensionPoint.getExtension(_contentTypeId); 140 if (contentType.hasModelItem(attributeName) && contentType.getModelItem(attributeName) instanceof ElementDefinition) 141 { 142 ElementDefinition attribute = (ElementDefinition) contentType.getModelItem(attributeName); 143 ElementType attributeType = attribute.getType(); 144 if (ModelItemTypeConstants.STRING_TYPE_ID.equals(attributeType.getId()) || ModelItemTypeConstants.LONG_TYPE_ID.equals(attributeType.getId()) || ModelItemTypeConstants.DOUBLE_TYPE_ID.equals(attributeType.getId())) 145 { 146 // Parse the value of the attribute that must 147 return attributeType.parseConfiguration(defaultValueConfiguration); 148 } 149 else 150 { 151 throw new ConfigurationException("The type '" + ATTRIBUTE_DEFAULT_VALUE_TYPE + "' supports attributes of type " + ModelItemTypeConstants.STRING_TYPE_ID + ", " + ModelItemTypeConstants.LONG_TYPE_ID + " and " + ModelItemTypeConstants.DOUBLE_TYPE_ID + ". The attribute named '" + attributeName + "' is a " + attributeType.getId() + ", so it is not supported.", defaultValueConfiguration); 152 } 153 } 154 else 155 { 156 throw new ConfigurationException("The content type '" + _contentTypeId + "' has no attribute with named '" + attributeName + "'", defaultValueConfiguration); 157 } 158 } 159 } 160 161 @Override 162 protected List<ContentValue> _getDefaultValues(String defaultValueType, Object parsedDefaultValue) 163 { 164 if (ATTRIBUTE_DEFAULT_VALUE_TYPE.equals(defaultValueType)) 165 { 166 // Launch a repository query to get the corresponding content 167 168 // Create the expression on the content type 169 Expression contentTypeExpression = new ContentTypeExpression(Operator.EQ, _contentTypeId); 170 171 // Create the expression on the attribute, according to its type 172 @SuppressWarnings("unchecked") 173 Pair<String, Configuration> defaultValue = (Pair<String, Configuration>) parsedDefaultValue; 174 String attributeName = defaultValue.getLeft(); 175 Object value = defaultValue.getRight(); 176 177 ContentType contentType = _contentTypeExtensionPoint.getExtension(_contentTypeId); 178 ElementType attributeType = ((ElementDefinition) contentType.getModelItem(attributeName)).getType(); 179 180 Expression attributeExpression; 181 if (ModelItemTypeConstants.LONG_TYPE_ID.equals(attributeType.getId())) 182 { 183 attributeExpression = new LongExpression(attributeName, Operator.EQ, (Long) value); 184 } 185 else if (ModelItemTypeConstants.DOUBLE_TYPE_ID.equals(attributeType.getId())) 186 { 187 attributeExpression = new DoubleExpression(attributeName, Operator.EQ, (Double) value); 188 } 189 else 190 { 191 attributeExpression = new StringExpression(attributeName, Operator.EQ, (String) value); 192 } 193 194 // Launch the repository query 195 Expression expression = new AndExpression(contentTypeExpression, attributeExpression); 196 String xPathQuery = ContentQueryHelper.getContentXPathQuery(expression); 197 198 try 199 { 200 AmetysObjectResolver resolver = (AmetysObjectResolver) __serviceManager.lookup(AmetysObjectResolver.ROLE); 201 AmetysObjectIterable<ModifiableContent> contents = resolver.query(xPathQuery); 202 AmetysObjectIterator<ModifiableContent> iterator = contents.iterator(); 203 204 return iterator.hasNext() ? List.of(new ContentValue(iterator.next())) : List.of(); 205 } 206 catch (ServiceException e) 207 { 208 throw new RuntimeException("Unable to lookup after the ametys object resolver", e); 209 } 210 } 211 else 212 { 213 return super._getDefaultValues(defaultValueType, parsedDefaultValue); 214 } 215 } 216 217 /** 218 * Get the element's mutual relationship path. 219 * @return the element's mutual relationship path. 220 */ 221 public String getInvertRelationPath() 222 { 223 return _invertRelationPath; 224 } 225 226 /** 227 * Set the element's mutual relationship path. 228 * @param invertRelationPath the element's mutual relationship path, separated by slashes. 229 */ 230 public void setInvertRelationPath(String invertRelationPath) 231 { 232 _invertRelationPath = invertRelationPath; 233 } 234 235 /** 236 * Force mutual relationship regardless of user's rights (only applicable for an element with invert relation path). 237 * @param force true to force mutual relationship regardless of user's rights 238 */ 239 public void setForceInvert (boolean force) 240 { 241 _forceInvert = force; 242 } 243 244 /** 245 * Returns true if mutual relationship should be set regardless of user's rights 246 * @return true if mutual relationship should be set regardless of user's rights 247 */ 248 public boolean getForceInvert () 249 { 250 return _forceInvert; 251 } 252 253 public Collection< ? extends ModelItem> getModelItems() 254 { 255 if (_contentTypeId != null && _contentTypeExtensionPoint.hasExtension(_contentTypeId)) 256 { 257 ContentType contentType = _contentTypeExtensionPoint.getExtension(_contentTypeId); 258 return contentType.getModelItems(); 259 } 260 else 261 { 262 return Collections.singleton(_contentTypesHelper.getTitleAttributeDefinition()); 263 } 264 } 265 266 @Override 267 protected Map<String, Object> _toJSON(DefinitionContext context) throws ProcessingException 268 { 269 Map<String, Object> result = super._toJSON(context); 270 result.put("contentType", _contentTypeId); 271 result.put("canCreate", _contentTypesHelper.hasRight(_contentTypeId)); 272 return result; 273 } 274 275 @Override 276 public void toSAX(ContentHandler contentHandler, DefinitionContext context) throws SAXException 277 { 278 super.toSAX(contentHandler, context); 279 XMLUtils.createElementIfNotNull(contentHandler, "contentType", _contentTypeId); 280 } 281}