001/* 002 * Copyright 2019 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.web.frontoffice.search.metamodel.impl; 017 018import java.util.Collection; 019import java.util.Collections; 020import java.util.Comparator; 021import java.util.Map; 022import java.util.stream.Stream; 023 024import org.apache.avalon.framework.service.ServiceException; 025import org.apache.commons.lang3.LocaleUtils; 026import org.apache.commons.lang3.StringUtils; 027import org.apache.commons.lang3.tuple.Pair; 028import org.slf4j.Logger; 029import org.slf4j.LoggerFactory; 030 031import org.ametys.cms.content.ContentHelper; 032import org.ametys.cms.contenttype.ContentType; 033import org.ametys.cms.data.ContentValue; 034import org.ametys.cms.data.type.ModelItemTypeConstants; 035import org.ametys.cms.model.ContentElementDefinition; 036import org.ametys.cms.repository.Content; 037import org.ametys.cms.repository.ContentTypeExpression; 038import org.ametys.cms.repository.LanguageExpression; 039import org.ametys.core.util.LambdaUtils; 040import org.ametys.plugins.repository.AmetysObjectIterable; 041import org.ametys.plugins.repository.AmetysObjectResolver; 042import org.ametys.plugins.repository.RepositoryConstants; 043import org.ametys.plugins.repository.query.QueryHelper; 044import org.ametys.plugins.repository.query.expression.AndExpression; 045import org.ametys.plugins.repository.query.expression.Expression; 046import org.ametys.plugins.repository.query.expression.Expression.Operator; 047import org.ametys.plugins.repository.query.expression.OrExpression; 048import org.ametys.runtime.i18n.I18nizableText; 049import org.ametys.runtime.model.ElementDefinition; 050import org.ametys.runtime.model.Enumerator; 051import org.ametys.runtime.model.ModelItem; 052import org.ametys.web.frontoffice.search.metamodel.RestrictedEnumerator; 053 054/** 055 * {@link ReferencingSearchServiceCriterionDefinition} for a {@link ModelItemTypeConstants#CONTENT_ELEMENT_TYPE_ID} attribute. 056 */ 057public class ContentReferencingSearchServiceCriterionDefinition extends ReferencingSearchServiceCriterionDefinition<ContentValue> implements ContentElementDefinition 058{ 059 private static final Logger __LOGGER = LoggerFactory.getLogger(ContentReferencingSearchServiceCriterionDefinition.class); 060 061 /** The resolver */ 062 protected AmetysObjectResolver _resolver; 063 /** The content helper */ 064 protected ContentHelper _contentHelper; 065 066 /** 067 * Constructor used to create a FO criterion definition on a referenced item of type content 068 * @param reference the item referenced by this criterion 069 * @param referencePath the path of the criterion's reference 070 * @param baseContentType the content type defining the reference 071 */ 072 public ContentReferencingSearchServiceCriterionDefinition(ElementDefinition reference, String referencePath, ContentType baseContentType) 073 { 074 super(reference, referencePath, baseContentType); 075 } 076 077 @Override 078 public boolean isEnumerated() 079 { 080 return true; 081 } 082 083 @Override 084 public RestrictedEnumerator<ContentValue> getRestrictedEnumerator(Map<String, Object> contextualParameters) 085 { 086 Enumerator<ContentValue> enumerator = new ContentEnumerator(this, contextualParameters); 087 return new RestrictedWrappedEnumerator<>(enumerator, getName()); 088 } 089 090 /** 091 * Gets the content type expression for retrieving enumeration of contents 092 * @param parentCTypeId The parent content type id 093 * @return The {@link Expression} 094 */ 095 protected Expression getContentTypeExpression(String parentCTypeId) 096 { 097 Stream<String> subCTypesIds = _getContentTypeExtensionPoint().getSubTypes(parentCTypeId).stream(); 098 Expression[] exprs = Stream.concat(Stream.of(parentCTypeId), subCTypesIds) 099 .map(cTypeId -> new ContentTypeExpression(Operator.EQ, cTypeId)) 100 .toArray(Expression[]::new); 101 return new OrExpression(exprs); 102 } 103 104 105 /** 106 * Get the order of the content id value for a search criterion definition referencing a model item of type content 107 * @param contentValue the content value 108 * @return the order or null if there is no order 109 */ 110 public Long getOrder(ContentValue contentValue) 111 { 112 return contentValue.getContentIfExists() 113 .filter(_getContentHelper()::isReferenceTable) 114 .filter(content -> content.hasDefinition("order")) 115 .filter(content -> content.hasValue("order")) 116 .map(content -> content.<Long>getValue("order")) 117 .orElse(null); 118 } 119 120 @Override 121 public ContentElementDefinition getReference() 122 { 123 return (ContentElementDefinition) super.getReference(); 124 } 125 126 public String getContentTypeId() 127 { 128 return getReference().getContentTypeId(); 129 } 130 131 public void setContentTypeId(String contentTypeId) 132 { 133 throw new UnsupportedOperationException("The criterion definition '" + getName() + "' references a criterion of type content. The content type id can not be set to the criterion definition."); 134 } 135 136 public Collection< ? extends ModelItem> getModelItems() 137 { 138 return getReference().getModelItems(); 139 } 140 141 /** 142 * Retrieves the {@link AmetysObjectResolver} 143 * @return the {@link AmetysObjectResolver} 144 */ 145 protected AmetysObjectResolver _getAmetysObjectResolver() 146 { 147 if (_resolver == null) 148 { 149 try 150 { 151 _resolver = (AmetysObjectResolver) __serviceManager.lookup(AmetysObjectResolver.ROLE); 152 } 153 catch (ServiceException e) 154 { 155 throw new RuntimeException("Unable to lookup after the ametys object resolver", e); 156 } 157 } 158 159 return _resolver; 160 } 161 162 /** 163 * Retrieves the {@link ContentHelper} 164 * @return the {@link ContentHelper} 165 */ 166 protected ContentHelper _getContentHelper() 167 { 168 if (_contentHelper == null) 169 { 170 try 171 { 172 _contentHelper = (ContentHelper) __serviceManager.lookup(ContentHelper.ROLE); 173 } 174 catch (ServiceException e) 175 { 176 throw new RuntimeException("Unable to lookup after the content helper", e); 177 } 178 } 179 180 return _contentHelper; 181 } 182 183 static class ContentEnumerator implements Enumerator<ContentValue> 184 { 185 ContentReferencingSearchServiceCriterionDefinition _criterionDefinition; 186 Map<String, Object> _contextualParameters; 187 188 ContentEnumerator(ContentReferencingSearchServiceCriterionDefinition criterionDefinition, Map<String, Object> contextualParameters) 189 { 190 _criterionDefinition = criterionDefinition; 191 _contextualParameters = contextualParameters; 192 } 193 194 public Map<ContentValue, I18nizableText> getEntries() throws Exception 195 { 196 String language = (String) _contextualParameters.get("lang"); 197 198 String contentTypeId = _criterionDefinition.getContentTypeId(); 199 if (contentTypeId != null) 200 { 201 try 202 { 203 @SuppressWarnings("synthetic-access") 204 boolean multilingual = _criterionDefinition._getContentTypeExtensionPoint().getExtension(contentTypeId).isMultilingual(); 205 Expression expr = new AndExpression( 206 _criterionDefinition.getContentTypeExpression(contentTypeId), 207 multilingual || StringUtils.isEmpty(language) ? null : new LanguageExpression(Operator.EQ, language)); 208 AmetysObjectIterable<Content> contents = _criterionDefinition._getAmetysObjectResolver().query(QueryHelper.getXPathQuery(null, RepositoryConstants.NAMESPACE_PREFIX + ":content", expr)); 209 210 return contents.stream() 211 .map(content -> Pair.of(new ContentValue(_criterionDefinition._getAmetysObjectResolver(), content.getId()), content.getTitle(LocaleUtils.toLocale(language)))) 212 .sorted(Comparator.comparing(Pair::getRight)) // sort by title 213 .collect(LambdaUtils.Collectors.toLinkedHashMap(Pair::getLeft, pair -> new I18nizableText(pair.getRight()))); 214 } 215 catch (Exception e) 216 { 217 __LOGGER.error("Failed to get content enumeration for content type {}", contentTypeId, e); 218 return Collections.EMPTY_MAP; 219 } 220 } 221 222 return Collections.EMPTY_MAP; 223 } 224 } 225} 226