Index: main/plugin-cms/sitemap.xmap =================================================================== --- main/plugin-cms/sitemap.xmap (revision 25394) +++ main/plugin-cms/sitemap.xmap (working copy) @@ -25,6 +25,7 @@ <map:generator name="referencing-contents" src="org.ametys.cms.content.ReferencingContentsGenerator" logger="org.ametys.cms.content.ReferencingContentsGenerator"/> <map:generator name="sql-search" src="org.ametys.cms.repository.SQLSearchGenerator" logger="org.ametys.cms.repository.SearchGenerator"/> <map:generator name="search" src="org.ametys.cms.repository.SearchGenerator" logger="org.ametys.cms.repository.SearchGenerator"/> + <map:generator name="neo-search" src="org.ametys.cms.search.generators.SearchGenerator" logger="org.ametys.cms.search"/> <map:generator name="search-columns" src="org.ametys.cms.repository.SearchColumnsGenerator"/> <map:generator name="simple-contents" src="org.ametys.cms.repository.SimpleContentsGenerator"/> @@ -88,6 +89,8 @@ <map:action name="set-auto-backup" src="org.ametys.cms.content.autosave.SetContentAutoBackup" logger="org.ametys.cms.content.autosave.SetContentAutoBackup"/> <map:action name="delete-auto-backup" src="org.ametys.cms.content.autosave.DeleteContentAutoBackup" logger="org.ametys.cms.content.autosave.DeleteContentAutoBackup"/> + <map:action name="search-model" src="org.ametys.cms.search.actions.GetSearchModelAction"/> + <map:action name="select-workspace" src="org.ametys.cms.repository.SelectWorkspaceAction" logger="org.ametys.cms.repository.SelectWorkspaceAction"/> <map:action name="import-simple-contents" src="org.ametys.cms.repository.ImportSimpleContentsAction"/> @@ -476,6 +479,15 @@ <!-- + | CONTENT SEARCH + --> + + <map:match pattern="neo-search/list.xml"> + <map:generate type="neo-search"/> + <map:transform type="i18n"> + <map:parameter name="locale" value="{locale:locale}"/> + </map:transform> + <map:serialize type="xml"/> + </map:match> + <map:match pattern="sql-search/list.xml"> <map:generate type="sql-search"/> <map:transform type="i18n"> @@ -497,6 +509,12 @@ <map:serialize type="xml"/> </map:match> + <map:match pattern="search/model.json"> + <map:act type="search-model"> + <map:read type="json"/> + </map:act> + </map:match> + <map:match pattern="search/export.xls"> <map:act type="set-header"> <map:parameter name="Content-Disposition" value="attachment"/> Index: main/plugin-cms/src/org/ametys/cms/search/SearchModelExtensionPoint.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/search/SearchModelExtensionPoint.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/search/SearchModelExtensionPoint.java (revision 0) @@ -0,0 +1,124 @@ +/* + * Copyright 2013 Anyware Services + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ametys.cms.search; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.Set; + +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.DefaultConfiguration; +import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; +import org.apache.cocoon.Constants; +import org.apache.cocoon.environment.Context; +import org.apache.commons.io.IOUtils; + +import org.ametys.runtime.plugin.component.AbstractThreadSafeComponentExtensionPoint; + +/** + * Extension point for {@link SearchModel}s. + */ +public class SearchModelExtensionPoint extends AbstractThreadSafeComponentExtensionPoint<SearchModel> +{ + /** The Avalon role */ + public static final String ROLE = SearchModelExtensionPoint.class.getName(); + + private boolean _initialized; + + @Override + public SearchModel getExtension(String id) + { + _delayedInitializeExtensions(); + return super.getExtension(id); + } + + @Override + public Set<String> getExtensionsIds() + { + _delayedInitializeExtensions(); + return super.getExtensionsIds(); + } + + @Override + public boolean hasExtension(String id) + { + _delayedInitializeExtensions(); + return super.hasExtension(id); + } + + @Override + public void initializeExtensions() throws Exception + { + // Nothing + } + + private void _delayedInitializeExtensions() + { + if (!_initialized) + { + try + { + Context ctx = (Context) _context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); + + File root = new File(ctx.getRealPath("WEB-INF/param/search")); + + if (root.exists()) + { + File[] files = root.listFiles(); + + for (File modelFile : files) + { + String fileName = modelFile.getName(); + + InputStream is = null; + try + { + is = new FileInputStream(modelFile); + + String id = "search-model." + fileName.substring(0, fileName.lastIndexOf('.')); + + if (super.hasExtension(id)) + { + throw new IllegalArgumentException("The search model of id " + id + " at " + modelFile.getAbsolutePath() + " is already declared."); + } + + DefaultConfiguration conf = new DefaultConfiguration("extension"); + + Configuration c = new DefaultConfigurationBuilder(true).build(is); + + conf.setAttribute("id", id); + conf.addChild(c); + + addComponent("unknown", "unknown", id, StaticSearchModel.class, conf); + } + finally + { + IOUtils.closeQuietly(is); + } + } + } + + super.initializeExtensions(); + _initialized = true; + } + catch (Exception e) + { + getLogger().error("Unable to initialized search models", e); + } + } + } +} Index: main/plugin-cms/src/org/ametys/cms/search/SearchModel.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/search/SearchModel.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/search/SearchModel.java (revision 0) @@ -0,0 +1,839 @@ +/* + * Copyright 2013 Anyware Services + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ametys.cms.search; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.Map; + +import org.apache.avalon.framework.activity.Disposable; +import org.apache.avalon.framework.configuration.Configurable; +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; +import org.apache.avalon.framework.configuration.DefaultConfiguration; +import org.apache.avalon.framework.context.Context; +import org.apache.avalon.framework.context.ContextException; +import org.apache.avalon.framework.context.Contextualizable; +import org.apache.avalon.framework.logger.AbstractLogEnabled; +import org.apache.avalon.framework.service.ServiceException; +import org.apache.avalon.framework.service.ServiceManager; +import org.apache.avalon.framework.service.Serviceable; +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.StringUtils; + +import org.ametys.cms.contenttype.ContentType; +import org.ametys.cms.contenttype.ContentTypeEnumerator; +import org.ametys.cms.contenttype.ContentTypeExtensionPoint; +import org.ametys.cms.contenttype.MetadataDefinition; +import org.ametys.cms.contenttype.MetadataType; +import org.ametys.cms.languages.LanguageEnumerator; +import org.ametys.cms.repository.LanguageExpression; +import org.ametys.cms.repository.WorkflowStepExpression; +import org.ametys.cms.repository.comment.CommentExpression; +import org.ametys.cms.workflow.DefaultWorkflowStepEnumerator; +import org.ametys.plugins.repository.query.expression.AndExpression; +import org.ametys.plugins.repository.query.expression.BooleanExpression; +import org.ametys.plugins.repository.query.expression.DateExpression; +import org.ametys.plugins.repository.query.expression.DoubleExpression; +import org.ametys.plugins.repository.query.expression.Expression; +import org.ametys.plugins.repository.query.expression.Expression.Operator; +import org.ametys.plugins.repository.query.expression.FullTextExpression; +import org.ametys.plugins.repository.query.expression.LongExpression; +import org.ametys.plugins.repository.query.expression.OrExpression; +import org.ametys.plugins.repository.query.expression.StringExpression; +import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; +import org.ametys.runtime.util.I18nizableText; +import org.ametys.runtime.util.parameter.Enumerator; +import org.ametys.runtime.util.parameter.ParameterHelper; +import org.ametys.runtime.util.parameter.ParameterHelper.ParameterType; +import org.ametys.runtime.util.parameter.StaticEnumerator; +import org.ametys.runtime.util.parameter.Validator; + +/** + * This abstract class represents a search model + * + */ +public abstract class SearchModel extends AbstractLogEnabled implements Serviceable, Contextualizable, Configurable, Disposable +{ + /** The valid system properties */ + public static final String[] SYSTEM_PROPERTY_NAMES = {"creator", "contributor", "lastModified", "comments", "workflowStep", "contentLanguage", "contentType", "fulltext"}; + /** Prefix for id of metadata search criteria */ + public static final String SEARCH_CRITERIA_METADATA_PREFIX = "metadata-"; + /** Prefix for id of system property search criteria */ + public static final String SEARCH_CRITERIA_SYSTEM_PREFIX = "property-"; + + /** The content type extension point */ + protected ContentTypeExtensionPoint _cTypeEP; + /** ComponentManager for {@link Validator}s. */ + protected ThreadSafeComponentManager<Validator> _validatorManager; + /** ComponentManager for {@link Enumerator}s. */ + protected ThreadSafeComponentManager<Enumerator> _enumeratorManager; + /** The service manager */ + protected ServiceManager _manager; + /** The context. */ + protected Context _context; + + boolean _initialized; + + @Override + public void contextualize(Context context) throws ContextException + { + _context = context; + } + + @Override + public void service(ServiceManager manager) throws ServiceException + { + _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); + _manager = manager; + } + + @Override + public void dispose() + { + _validatorManager.dispose(); + _validatorManager = null; + _enumeratorManager.dispose(); + _enumeratorManager = null; + } + + @Override + public void configure(Configuration configuration) throws ConfigurationException + { + try + { + _validatorManager = new ThreadSafeComponentManager<Validator>(); + _validatorManager.enableLogging(getLogger()); + _validatorManager.contextualize(_context); + _validatorManager.service(_manager); + + _enumeratorManager = new ThreadSafeComponentManager<Enumerator>(); + _enumeratorManager.enableLogging(getLogger()); + _enumeratorManager.contextualize(_context); + _enumeratorManager.service(_manager); + + try + { + DefaultConfiguration conf = new DefaultConfiguration("criteria"); + + DefaultConfiguration enumConf = new DefaultConfiguration("enumeration"); + + DefaultConfiguration customEnumerator = new DefaultConfiguration("custom-enumerator"); + customEnumerator.setAttribute("class", DefaultWorkflowStepEnumerator.class.getName()); + conf.addChild(customEnumerator); + + DefaultConfiguration excludeConf = new DefaultConfiguration("exclude-workflow-steps"); + DefaultConfiguration stepId = new DefaultConfiguration("id"); + stepId.setValue(9999); + excludeConf.addChild(stepId); + conf.addChild(excludeConf); + + conf.addChild(enumConf); + + _enumeratorManager.addComponent("cms", null, DefaultWorkflowStepEnumerator.class.getName(), DefaultWorkflowStepEnumerator.class, conf); + } + catch (Exception e) + { + throw new ConfigurationException("Unable to instantiate enumerator for class: " + DefaultWorkflowStepEnumerator.class.getName(), e); + } + + try + { + DefaultConfiguration conf = new DefaultConfiguration("criteria"); + + DefaultConfiguration enumConf = new DefaultConfiguration("enumeration"); + + DefaultConfiguration customEnumerator = new DefaultConfiguration("custom-enumerator"); + customEnumerator.setAttribute("class", ContentTypeEnumerator.class.getName()); + + List<String> contentTypes = getContentTypes(); + if (contentTypes.size() > 0) + { + DefaultConfiguration cTypeConf = new DefaultConfiguration("content-types"); + cTypeConf.setValue(StringUtils.join(contentTypes, ",")); + customEnumerator.addChild(cTypeConf); + + DefaultConfiguration allOptionConf = new DefaultConfiguration("all-option"); + allOptionConf.setValue("concat"); + customEnumerator.addChild(allOptionConf); + } + + enumConf.addChild(customEnumerator); + conf.addChild(enumConf); + + _enumeratorManager.addComponent("cms", null, ContentTypeEnumerator.class.getName(), ContentTypeEnumerator.class, conf); + } + catch (Exception e) + { + throw new ConfigurationException("Unable to instantiate enumerator for class: " + ContentTypeEnumerator.class.getName(), e); + } + + try + { + _enumeratorManager.addComponent("cms", null, LanguageEnumerator.class.getName(), LanguageEnumerator.class, new DefaultConfiguration("enumeration")); + } + catch (Exception e) + { + throw new ConfigurationException("Unable to instantiate enumerator for class: " + LanguageEnumerator.class.getName(), e); + } + + _enumeratorManager.initialize(); + _validatorManager.initialize(); + } + catch (Exception e) + { + throw new ConfigurationException("Unable to create local component managers", configuration, e); + } + } + + /** + * Get the list of content types + * @return The list of content types + */ + public abstract List<String> getContentTypes(); + + /** + * Get the URL for search + * @return the URL for search + */ + public abstract String getSearchUrl(); + + /** + * Get the plugin name for search + * @return the plugin name for search + */ + public abstract String getSearchUrlPlugin(); + + /** + * Get the URL for CVS export of results + * @return the URL for CVS export + */ + public abstract String getExportCSVUrl(); + + /** + * Get the plugin name for CVS export of results + * @return the plugin name for CVS export + */ + public abstract String getExportCSVUrlPlugin(); + + /** + * Get the URL for XML export of results + * @return the URL for XML export + */ + public abstract String getExportXMLUrl(); + + /** + * Get the plugin name for XML export of results + * @return the plugin name for XML export + */ + public abstract String getExportXMLUrlPlugin(); + + /** + * Get the URL for print results + * @return the URL for print results + */ + public abstract String getPrintUrl(); + + /** + * Get the plugin name for print results + * @return the plugin name for print results + */ + public abstract String getPrintUrlPlugin(); + + /** + * Get the list of search criteria in simple mode + * @return the list of search criteria in simple mode + */ + public abstract Map<String, SearchCriteria> getCriteria(); + + /** + * Get a simple search criteria by its id + * @param id The search criteria id + * @return the criteria or <code>null</code> if not found + */ + public SearchCriteria getCriteria (String id) + { + Map<String, SearchCriteria> criteria = getCriteria(); + if (criteria.containsKey(id)) + { + return criteria.get(id); + } + return null; + } + + /** + * Get the list of search criteria in advanced mode + * @return the list of search criteria in advanced mode + */ + public abstract Map<String, SearchCriteria> getAdvancedCriteria(); + + /** + * Get the query to execute in simple mode + * @param values The search criteria values + * @return the query + */ + public String createQuery(Map<String, Object> values) + { + List<Expression> expressions = new ArrayList<Expression>(); + + Expression cTypeExpr = createContentTypeExpressions(values); + if (cTypeExpr != null) + { + expressions.add(cTypeExpr); + } + + expressions.addAll(getCriteriaExpressions(values)); + + // TODO sortCriteria + + return org.ametys.plugins.repository.query.QueryHelper.getXPathQuery(null, "ametys:content", getAndExpression(expressions), null); + } + + /** + * Get the query to execute in SQL mode + * @param sqlQuery The sql query + * @return the query + */ + public abstract String createSqlQuery (String sqlQuery); + + /** + * Get the column for results + * @return the column for results + */ + public abstract List<SearchColumn> getResultColumns(); + + /** + * Get the common ancestor of content types concerned by search + * @return The common ancestor or <code>null</code> if there is no common ancestor + */ + public ContentType getCommonContentTypeAncestor () + { + List<String> cTypes = getContentTypes(); + if (cTypes.isEmpty()) + { + return null; + } + + if (cTypes.size() == 1) + { + return _cTypeEP.getExtension(cTypes.get(0)); + } + + // FIXME Get the common ancestor + return null; + } + + /** + * Get the {@link MetadataDefinition} of a metadata representing by its path + * @param cType The initial content type + * @param metadataPath The path of the metadata separated by '/' + * @return The metadata definition or <code>null</code> if the metadata does not exist. + */ + public MetadataDefinition getMetadataDefinition (ContentType cType, String metadataPath) + { + if (cType == null) + { + if ("title".equals(metadataPath)) + { + String cTypeId = _cTypeEP.getExtensionsIds().iterator().next(); + ContentType firstCType = _cTypeEP.getExtension(cTypeId); + return firstCType.getMetadataDefinition(metadataPath); + } + + return null; + } + + String[] path = metadataPath.split("/"); + + MetadataDefinition metadataDefinition = null; + for (int i = 0; i < path.length; i++) + { + // TODO if metadata est de type CONTENT ou CONTENT_REF + metadataDefinition = metadataDefinition != null ? metadataDefinition.getMetadataDefinition(path[i]) : cType.getMetadataDefinition(path[i]); + if (metadataDefinition == null) + { + return null; + } + } + + return metadataDefinition; + } + + /** + * Determines if the medatata path is a valid path + * @param cType The common content type ancestor + * @param metadataPath The metadata path + * @return true if the path is the valid metadata path + */ + public boolean isMetadataExist (ContentType cType, String metadataPath) + { + return getMetadataDefinition(cType, metadataPath) != null; + } + + /** + * Determines if the property is a valid system property + * @param propertyName The property + * @return <code>true</code> if the property is valid + */ + public boolean isValidSystemProperty (String propertyName) + { + return ArrayUtils.contains(SYSTEM_PROPERTY_NAMES, propertyName); + } + + /** + * Get the system search criteria + * @param propertyName The system property name + * @return The search criteria or <code>null</code> if the property is not a valid system property. + */ + public SystemSearchCriteria getSystemSearchCriteria (String propertyName) + { + if (!isValidSystemProperty(propertyName)) + { + return null; + } + + SystemSearchCriteria systemSC = new SystemSearchCriteria(); + systemSC.setPropertyName(propertyName); + systemSC.setId(propertyName); + + if (propertyName.equals("creator")) + { + systemSC.setLabel(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_CREATOR")); + systemSC.setDescription(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_CREATOR")); + systemSC.setType(MetadataType.USER); + } + else if (propertyName.equals("contributor")) + { + systemSC.setLabel(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_AUTHOR")); + systemSC.setDescription(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_AUTHOR")); + systemSC.setType(MetadataType.USER); + } + else if (propertyName.equals("workflowStep")) + { + systemSC.setLabel(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_WORKFLOW_STEP")); + systemSC.setDescription(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_WORKFLOW_STEP")); + systemSC.setType(MetadataType.LONG); + try + { + systemSC.setEnumerator(_enumeratorManager.lookup(DefaultWorkflowStepEnumerator.class.getName())); + } + catch (Exception e) + { + getLogger().error("Unable to lookup enumerator role: '" + DefaultWorkflowStepEnumerator.class.getName() + "'", e); + } + } + else if (propertyName.equals("lastModified")) + { + systemSC.setLabel(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_LASTMODIFIED")); + systemSC.setDescription(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_LASTMODIFIED")); + systemSC.setType(MetadataType.DATE); + } + else if (propertyName.equals("comments")) + { + systemSC.setLabel(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_COMMENTS")); + systemSC.setDescription(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_COMMENTS")); + systemSC.setType(MetadataType.STRING); + + StaticEnumerator staticEnumerator = new StaticEnumerator(); + staticEnumerator.add(new I18nizableText("plugin.cms", "PLUGINS_CMS_CONTENT_COMMENTS_SEARCH_ALL"), "with-comments"); + staticEnumerator.add(new I18nizableText("plugin.cms", "PLUGINS_CMS_CONTENT_COMMENTS_SEARCH_VALIDATED"), "with-validated-comments"); + staticEnumerator.add(new I18nizableText("plugin.cms", "PLUGINS_CMS_CONTENT_COMMENTS_SEARCH_NOTVALIDATED"), "with-nonvalidated-comments"); + systemSC.setEnumerator(staticEnumerator); + } + else if (propertyName.equals("contentLanguage")) + { + systemSC.setLabel(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_LANGUAGE")); + systemSC.setDescription(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_LANGUAGE")); + systemSC.setType(MetadataType.STRING); + try + { + systemSC.setEnumerator(_enumeratorManager.lookup(LanguageEnumerator.class.getName())); + } + catch (Exception e) + { + getLogger().error("Unable to lookup enumerator role: '" + DefaultWorkflowStepEnumerator.class.getName() + "'", e); + } + } + else if (propertyName.equals("contentType")) + { + systemSC.setLabel(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_CONTENT_TYPE")); + systemSC.setDescription(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_CONTENT_TYPE")); + systemSC.setType(MetadataType.STRING); + try + { + systemSC.setEnumerator(_enumeratorManager.lookup(ContentTypeEnumerator.class.getName())); + } + catch (Exception e) + { + getLogger().error("Unable to lookup enumerator role: '" + ContentTypeEnumerator.class.getName() + "'", e); + } + } + if (propertyName.equals("fulltext")) + { + systemSC.setLabel(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_FULLTEXT")); + systemSC.setDescription(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_FULLTEXT")); + systemSC.setType(MetadataType.STRING); + } + + return systemSC; + } + + /** + * Get the system result columns + * @param propertyName The system property name + * @return The result column or <code>null</code> if the property is not a valid system property. + */ + public SearchColumn getSystemSearchColumn (String propertyName) + { + if (!isValidSystemProperty(propertyName)) + { + return null; + } + + SearchColumn column = new SearchColumn(); + column.setId(propertyName); + column.setEditable(false); + column.setSortable(true); + column.setMapping(propertyName); + + if (propertyName.equals("creator")) + { + column.setTitle(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_CREATOR")); + column.setType(MetadataType.USER); + column.setRenderer("Ametys.cms.content.EditContentsGrid.renderDisplay"); + column.setWidth(150); + } + else if (propertyName.equals("contributor")) + { + column.setTitle(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_AUTHOR")); + column.setType(MetadataType.USER); + column.setRenderer("Ametys.cms.content.EditContentsGrid.renderDisplay"); + column.setWidth(150); + } + else if (propertyName.equals("workflowStep")) + { + column.setTitle(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_WORKFLOW_STEP")); + column.setType(MetadataType.STRING); + column.setWidth(40); + column.setRenderer("Ametys.cms.content.EditContentsGrid.renderWorkflowStep"); + column.setMapping("workflow-step"); + } + else if (propertyName.equals("lastModified")) + { + column.setTitle(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_LASTMODIFIED")); + column.setType(MetadataType.DATE); + column.setWidth(100); + column.setRenderer("Ext.util.Format.dateRenderer(Ext.Date.patterns.FriendlyDateTime)"); + } + else if (propertyName.equals("comments")) + { + column.setTitle(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_COMMENTS")); + column.setType(MetadataType.STRING); + column.setWidth(40); + column.setRenderer("Ametys.cms.content.EditContentsGrid.renderBooleanIcon"); + } + else if (propertyName.equals("contentLanguage")) + { + column.setTitle(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_LANGUAGE")); + column.setType(MetadataType.STRING); + column.setWidth(40); + column.setMapping("@language"); + column.setRenderer("Ametys.cms.content.EditContentsGrid.renderLanguage"); + } + else if (propertyName.equals("contentType")) + { + column.setTitle(new I18nizableText("plugin.cms", "UITOOL_SEARCH_CONTENT_CONTENT_TYPE")); + column.setType(MetadataType.STRING); + column.setWidth(150); + column.setMapping("content-type"); + } + return column; + } + + /** + * Create expression on content type + * @param values The submitted values + * @return The content type expression or null if no + */ + @SuppressWarnings("unchecked") + protected Expression createContentTypeExpressions(Map<String, Object> values) + { + List<String> cTypes = getContentTypes(); + + Object cTypeParam = values.get(SEARCH_CRITERIA_SYSTEM_PREFIX + "contentType-eq"); + + if (cTypeParam instanceof String && StringUtils.isNotEmpty((String) cTypeParam)) + { + return _cTypeEP.createHierarchicalCTExpression((String) cTypeParam); + } + else if (cTypeParam != null && cTypeParam instanceof List<?>) + { + cTypes = (List<String>) cTypeParam; + return _cTypeEP.createHierarchicalCTExpression(cTypes.toArray(new String[cTypes.size()])); + } + else if (cTypes != null && cTypes.size() > 0) + { + return _cTypeEP.createHierarchicalCTExpression(cTypes.toArray(new String[cTypes.size()])); + } + else + { + return null; + } + } + + /** + * Returns the expression to execute + * @param values The submitted values + * @return The expression to execute + */ + protected List<Expression> getCriteriaExpressions(Map<String, Object> values) + { + List<Expression> expressions = new ArrayList<Expression>(); + + for (String id : values.keySet()) + { + SearchCriteria criteria = getCriteria(id); + if (criteria != null) + { + String value = (String) values.get(id); + if (criteria instanceof MetadataSearchCriteria) + { + Expression metadataExpr = getMetadataExpression((MetadataSearchCriteria) criteria, value); + if (metadataExpr != null) + { + expressions.add(metadataExpr); + } + } + else if (criteria instanceof SystemSearchCriteria) + { + Expression sysExpr = getSystemPropertyExpression((SystemSearchCriteria) criteria, value); + if (sysExpr != null) + { + expressions.add(sysExpr); + } + } + } + } + + return expressions; + } + + /** + * Create a comparison's test for a metadata + * @param criteria The search criteria + * @param value The value. Can be null. + * @return The expression that test the comparison of the metadata with the value + */ + protected Expression getMetadataExpression(MetadataSearchCriteria criteria, String value) + { + if (StringUtils.isEmpty(value)) + { + return null; + } + + switch (criteria.getType()) + { + case DATE: + case DATETIME: + Date dateValue = (Date) ParameterHelper.castValue(value, ParameterType.DATE); + + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTime(dateValue); + calendar.set(Calendar.HOUR, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.MILLISECOND, 0); + + if (criteria.getOperator().equals(Operator.LE)) + { + calendar.add(Calendar.DAY_OF_YEAR, 1); + return new DateExpression(criteria.getMetadataPath(), Operator.LT, calendar.getTime()); + } + + return new DateExpression(criteria.getMetadataPath(), criteria.getOperator(), calendar.getTime()); + + case LONG: + Long longValue = (Long) ParameterHelper.castValue(value, ParameterType.LONG); + return new LongExpression(criteria.getMetadataPath(), criteria.getOperator(), longValue); + + case DOUBLE: + Double doubleValue = (Double) ParameterHelper.castValue(value, ParameterType.DOUBLE); + return new DoubleExpression(criteria.getMetadataPath(), criteria.getOperator(), doubleValue); + + case BOOLEAN: + Boolean boolValue = (Boolean) ParameterHelper.castValue(value, ParameterType.BOOLEAN); + return new BooleanExpression(criteria.getMetadataPath(), boolValue); + + case STRING: + String stringValue = (String) ParameterHelper.castValue(value, ParameterType.STRING); + if (criteria.getOperator().equals(Operator.WD)) + { + String[] wildcardValues = StringUtils.isEmpty(stringValue) ? new String[0] : value.split("\\s"); + + List<Expression> expressions = new ArrayList<Expression>(); + for (String wildcardValue : wildcardValues) + { + expressions.add(new StringExpression(criteria.getMetadataPath(), criteria.getOperator(), wildcardValue, false, true)); + } + + return getAndExpression(expressions); + } + else + { + return new StringExpression(criteria.getMetadataPath(), criteria.getOperator(), stringValue); + } + default: + return null; + } + } + + /** + * Create a comparison's test for a system property + * @param criteria The search criteria + * @param value The value. Can be null. + * @return The expression that test the comparison of the metadata with the value + */ + protected Expression getSystemPropertyExpression (SystemSearchCriteria criteria, String value) + { + if (StringUtils.isEmpty(value)) + { + return null; + } + + String propertyName = criteria.getPropertyName(); + + if ("workflowStep".equals(propertyName)) + { + int stepId = Integer.parseInt(value); + if (stepId != 0) + { + return new WorkflowStepExpression(Operator.EQ, stepId); + } + } + else if ("comments".equals(propertyName)) + { + if ("with-comments".equals(value)) + { + return new CommentExpression(true, true); + } + else if ("with-validated-comments".equals(value)) + { + return new CommentExpression(true, false); + } + else // if ("with-nonvalidated-comments".equals(comments)) + { + return new CommentExpression(false, true); + } + } + else if ("contentLanguage".equals(propertyName)) + { + return new LanguageExpression(Operator.EQ, value); + } + else if ("contributor".equals(propertyName) || "creator".equals(propertyName)) + { + return new StringExpression(propertyName, criteria.getOperator(), value); + } + else if ("lastModified".equals(propertyName)) + { + Date dateValue = (Date) ParameterHelper.castValue(value, ParameterType.DATE); + + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTime(dateValue); + calendar.set(Calendar.HOUR, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.MILLISECOND, 0); + + if (criteria.getOperator().equals(Operator.LE)) + { + calendar.add(Calendar.DAY_OF_YEAR, 1); + return new DateExpression(propertyName, Operator.LT, calendar.getTime()); + } + + return new DateExpression(propertyName, criteria.getOperator(), calendar.getTime()); + } + else if ("fulltext".equals(propertyName)) + { + return new FullTextExpression(value); + } + + return null; + } + + /** + * Create a comparison's test for a metadata of type <code>Date</code> + * @param metadataName The id of the metadata to test + * @param testOperator The comparator to use between TEST_Operator_* constants. + * @param value The value to test + * @return The expression that test the comparison of the metadata with the value using the testOperator + */ + protected Expression createExpression(String metadataName, Date value, Operator testOperator) + { + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTime(value); + calendar.set(Calendar.HOUR, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.MILLISECOND, 0); + + if (testOperator.equals(Operator.LE)) + { + calendar.add(Calendar.DAY_OF_YEAR, 1); + return new DateExpression(metadataName, Operator.LT, calendar.getTime()); + } + + return new DateExpression(metadataName, testOperator, calendar.getTime()); + } + + + /** + * Get an AND expression on an expression collection. + * @param expressions the expression collection to include in the AND. + * @return an AND expression or null if the collection is empty. + */ + protected Expression getAndExpression(Collection<Expression> expressions) + { + Expression expression = null; + + if (!expressions.isEmpty()) + { + Expression[] exprArr = expressions.toArray(new Expression[expressions.size()]); + expression = new AndExpression(exprArr); + } + + return expression; + } + + /** + * Get an OR expression on an expression collection. + * @param expressions the expression collection to include in the OR. + * @return an OR expression or null if the collection is empty. + */ + protected Expression getOrExpression(Collection<Expression> expressions) + { + Expression expression = null; + + if (!expressions.isEmpty()) + { + Expression[] exprArr = expressions.toArray(new Expression[expressions.size()]); + expression = new OrExpression(exprArr); + } + + return expression; + } + +} Index: main/plugin-cms/src/org/ametys/cms/search/StaticSearchModel.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/search/StaticSearchModel.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/search/StaticSearchModel.java (revision 0) @@ -0,0 +1,410 @@ +/* + * Copyright 2013 Anyware Services + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ametys.cms.search; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; +import org.apache.commons.lang.StringUtils; + +import org.ametys.cms.contenttype.AbstractMetadataSetElement; +import org.ametys.cms.contenttype.ContentType; +import org.ametys.cms.contenttype.MetadataDefinition; +import org.ametys.cms.contenttype.MetadataDefinitionReference; +import org.ametys.cms.contenttype.MetadataSet; +import org.ametys.plugins.repository.query.expression.Expression.Operator; +import org.ametys.runtime.util.I18nizableText; +import org.ametys.runtime.util.parameter.DefaultValidator; +import org.ametys.runtime.util.parameter.Validator; + +/** + * Static implementation of a {@link SearchModel} + */ +public class StaticSearchModel extends SearchModel +{ + private List<String> _cTypes; + private String _searchUrl; + private String _searchUrlPlugin; + private String _exportCSVUrl; + private String _exportCSVUrlPlugin; + private String _exportXMLUrl; + private String _exportXMLUrlPlugin; + private String _printUrl; + private String _printUrlPlugin; + + private Map<String, SearchCriteria> _searchCriteria; + private Map<String, SearchCriteria> _advancedSearchCriteria; + private List<SearchColumn> _columns; + + @Override + public void configure(Configuration configuration) throws ConfigurationException + { + Configuration searchConfig = configuration.getChild("SearchModel"); + _cTypes = _configureContentTypes(searchConfig.getChild("content-types")); + + super.configure(configuration); + + _configureSearchUrl(searchConfig); + _configureExportCSVUrl(searchConfig); + _configureExportXMLUrl(searchConfig); + _configurePrintUrl(searchConfig); + + _searchCriteria = _configureCriteria(searchConfig.getChild("simple-search-criteria")); + _advancedSearchCriteria = _configureCriteria(searchConfig.getChild("advanced-search-criteria")); + _columns = _configureColumns(searchConfig.getChild("columns").getChild("default", false)); + } + + @Override + public List<String> getContentTypes() + { + return _cTypes; + } + + @Override + public String getSearchUrl() + { + return _searchUrl; + } + + @Override + public String getSearchUrlPlugin() + { + return _searchUrlPlugin; + } + + @Override + public String getExportCSVUrl() + { + return _exportCSVUrl; + } + + @Override + public String getExportCSVUrlPlugin() + { + return _exportCSVUrlPlugin; + } + + @Override + public String getExportXMLUrl() + { + return _exportXMLUrl; + } + + @Override + public String getExportXMLUrlPlugin() + { + return _exportXMLUrlPlugin; + } + + @Override + public String getPrintUrl() + { + return _printUrl; + } + + @Override + public String getPrintUrlPlugin() + { + return _printUrlPlugin; + } + + @Override + public Map<String, SearchCriteria> getCriteria() + { + return _searchCriteria; + } + + @Override + public Map<String, SearchCriteria> getAdvancedCriteria() + { + return _advancedSearchCriteria; + } + + @Override + public String createSqlQuery(String sqlQuery) + { + // TODO Auto-generated method stub + return null; + } + + @Override + public List<SearchColumn> getResultColumns() + { + return _columns; + } + + /** + * Configure the content type ids + * @param configuration The content types configuration + * @return The set of content type ids + * @throws ConfigurationException If an error occurs + */ + protected List<String> _configureContentTypes(Configuration configuration) throws ConfigurationException + { + List<String> cTypes = new ArrayList<String>(); + for (Configuration cType : configuration.getChildren("content-type")) + { + cTypes.add(cType.getAttribute("id")); + } + return cTypes; + } + + private void _configureSearchUrl(Configuration configuration) + { + _searchUrlPlugin = configuration.getAttribute("plugin", "cms"); + _searchUrl = configuration.getChild("search-url").getValue("search/list.xml"); + } + + private void _configureExportCSVUrl(Configuration configuration) + { + _exportCSVUrlPlugin = configuration.getAttribute("plugin", "cms"); + _exportCSVUrl = configuration.getChild("export-csv-url").getValue("search/export.csv"); + } + + private void _configureExportXMLUrl(Configuration configuration) + { + _exportXMLUrlPlugin = configuration.getAttribute("plugin", "cms"); + _exportXMLUrl = configuration.getChild("export-xml-url").getValue("search/export.xml"); + } + + private void _configurePrintUrl(Configuration configuration) + { + _printUrlPlugin = configuration.getAttribute("plugin", "cms"); + _printUrl = configuration.getChild("print-url").getValue("search/print.html"); + } + + private List<SearchColumn> _configureColumns (Configuration configuration) throws ConfigurationException + { + ContentType cType = getCommonContentTypeAncestor(); + + List<SearchColumn> columns = new ArrayList<SearchColumn>(); + + for (Configuration conf : configuration.getChildren("column")) + { + SearchColumn column = null; + + String metadataPath = conf.getAttribute("metadata-ref", null); + if (StringUtils.isNotEmpty(metadataPath)) + { + if (!isMetadataExist(cType, metadataPath)) + { + throw new ConfigurationException("The metadata of path '" + metadataPath + "' does not exist for concerned content type(s)."); + } + + MetadataDefinition metadataDefinition = getMetadataDefinition(cType, metadataPath); + + column = new SearchColumn(); + column.setId(metadataPath.replaceAll("/", ".")); + column.setTitle(_configureI18nizableText(conf.getChild("label", false), metadataDefinition.getLabel())); + column.setType(metadataDefinition.getType()); + column.setWidth(conf.getChild("width").getValueAsInteger(200)); + column.setRenderer(conf.getChild("renderer").getValue(null)); + column.setHidden(conf.getChild("hidden").getValueAsBoolean(false)); + column.setEditable(conf.getChild("editable").getValueAsBoolean(true)); + column.setSortable(conf.getChild("sortable").getValueAsBoolean(true)); + + columns.add(column); + } + else if (conf.getAttribute("system-ref", null) != null) + { + String property = conf.getAttribute("system-ref", null); + + if (property.equals("*")) + { + for (String propertyName : SYSTEM_PROPERTY_NAMES) + { + if (!propertyName.equals("fulltext")) + { + column = getSystemSearchColumn(propertyName); + columns.add(column); + } + } + } + else + { + if (!isValidSystemProperty(property)) + { + throw new ConfigurationException("The property '" + property + "' is not a valid system property."); + } + + column = getSystemSearchColumn(property); + column.setWidth(conf.getChild("width").getValueAsInteger(100)); + column.setRenderer(conf.getChild("renderer").getValue("")); + column.setHidden(conf.getChild("hidden").getValueAsBoolean(false)); + column.setSortable(conf.getChild("hidden").getValueAsBoolean(true)); + + columns.add(column); + } + } + } + return columns; + } + + private Map<String, SearchCriteria> _configureCriteria(Configuration configuration) throws ConfigurationException + { + ContentType cType = getCommonContentTypeAncestor(); + + Map<String, SearchCriteria> criteria = new LinkedHashMap<String, SearchCriteria>(); + + for (Configuration conf : configuration.getChildren("criteria")) + { + String metadataPath = conf.getAttribute("metadata-ref", null); + if (StringUtils.isNotEmpty(metadataPath)) + { + if (metadataPath.equals("*")) + { + MetadataSet metadataSet = cType.getMetadataSetForView("main"); + for (AbstractMetadataSetElement subMetadataSetElement : metadataSet.getElements()) + { + // Get only simple metadata (ignore composites and repeaters) + if (subMetadataSetElement instanceof MetadataDefinitionReference) + { + String metadataName = ((MetadataDefinitionReference) subMetadataSetElement).getMetadataName(); + MetadataDefinition metadataDefinition = cType.getMetadataDefinition(metadataName); + + MetadataSearchCriteria metaSC = _configureMetadataSearchCriteria(conf, metadataDefinition, metadataName); + criteria.put(metaSC.getId(), metaSC); + } + } + } + else + { + metadataPath = metadataPath.replaceAll("\\.", "/"); + if (!isMetadataExist(cType, metadataPath)) + { + throw new ConfigurationException("The metadata of path '" + metadataPath + "' does not exist for concerned content type(s)."); + } + + MetadataDefinition metadataDefinition = getMetadataDefinition(cType, metadataPath); + + MetadataSearchCriteria metaSC = _configureMetadataSearchCriteria (conf, metadataDefinition, metadataPath); + criteria.put(metaSC.getId(), metaSC); + } + } + else if (conf.getAttribute("system-ref", null) != null) + { + String property = conf.getAttribute("system-ref", null); + + if (property.equals("*")) + { + for (String propertyName : SYSTEM_PROPERTY_NAMES) + { + SystemSearchCriteria systemSC = _createSystemSearchCriteria(propertyName, conf); + criteria.put(systemSC.getId(), systemSC); + } + } + else + { + if (!isValidSystemProperty(property)) + { + throw new ConfigurationException("The property '" + property + "' is not a valid system property."); + } + + SystemSearchCriteria systemSC = _createSystemSearchCriteria(property, conf); + criteria.put(systemSC.getId(), systemSC); + } + } + + // TODO custom-ref + } + return criteria; + } + + private MetadataSearchCriteria _configureMetadataSearchCriteria (Configuration conf, MetadataDefinition metadataDefinition, String metadataPath) throws ConfigurationException + { + MetadataSearchCriteria metadataSC = new MetadataSearchCriteria(); + + metadataSC.setMetadataPath(metadataPath); + metadataSC.setLabel(_configureI18nizableText(conf.getChild("label", false), metadataDefinition.getLabel())); + metadataSC.setDescription(_configureI18nizableText(conf.getChild("description", false), metadataDefinition.getDescription())); + metadataSC.setType(metadataDefinition.getType()); + metadataSC.setValidator(metadataDefinition.getValidator()); + metadataSC.setEnumerator(metadataDefinition.getEnumerator()); + + metadataSC.setWidget(conf.getChild("widget").getValue(metadataDefinition.getWidget())); + metadataSC.setWidgetParameters(metadataDefinition.getWidgetParameters()); + + Operator operator = _configureTestOperator(conf); + metadataSC.setOperator(operator); + metadataSC.setHidden(conf.getAttributeAsBoolean("hidden", false)); + metadataSC.setInitClassName(conf.getChild("oninit").getValue(null)); + metadataSC.setChangeClassName(conf.getChild("onchange").getValue(null)); + metadataSC.setSubmitClassName(conf.getChild("onsubmit").getValue(null)); + metadataSC.setValue(conf.getChild("value").getValue(null)); + + metadataSC.setId(SEARCH_CRITERIA_METADATA_PREFIX + metadataPath.replaceAll("/", ".") + "-" + operator.name().toLowerCase()); + + return metadataSC; + } + + private SystemSearchCriteria _createSystemSearchCriteria (String propertyName, Configuration conf) throws ConfigurationException + { + SystemSearchCriteria systemSC = getSystemSearchCriteria(propertyName); + + systemSC.setLabel(_configureI18nizableText(conf.getChild("label", false), systemSC.getLabel())); + systemSC.setDescription(_configureI18nizableText(conf.getChild("description", false), systemSC.getDescription())); + + Operator operator = _configureTestOperator(conf); + systemSC.setOperator(operator); + systemSC.setHidden(conf.getAttributeAsBoolean("hidden", false)); + systemSC.setInitClassName(conf.getChild("oninit").getValue(null)); + systemSC.setChangeClassName(conf.getChild("onchange").getValue(null)); + systemSC.setSubmitClassName(conf.getChild("onsubmit").getValue(null)); + systemSC.setValue(conf.getChild("value").getValue(null)); + + systemSC.setId(SEARCH_CRITERIA_SYSTEM_PREFIX + propertyName + "-" + operator.name().toLowerCase()); + + return systemSC; + } + + private I18nizableText _configureI18nizableText(Configuration config, I18nizableText defaultValue) throws ConfigurationException + { + if (config != null) + { + String text = config.getValue(); + boolean i18nSupported = config.getAttributeAsBoolean("i18n", false); + if (i18nSupported) + { + String catalogue = config.getAttribute("catalogue", null); + return new I18nizableText(catalogue, text); + } + else + { + return new I18nizableText(text); + } + } + else + { + return defaultValue; + } + } + + private Operator _configureTestOperator(Configuration configuration) throws ConfigurationException + { + try + { + return Operator.valueOf(configuration.getChild("test-operator").getValue("eq").toUpperCase()); + } + catch (IllegalArgumentException e) + { + throw new ConfigurationException("Invalid operator", configuration, e); + } + } +} Index: main/plugin-cms/src/org/ametys/cms/search/SearchCriteria.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/search/SearchCriteria.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/search/SearchCriteria.java (revision 0) @@ -0,0 +1,143 @@ +/* + * Copyright 2013 Anyware Services + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ametys.cms.search; + +import org.ametys.cms.contenttype.MetadataType; +import org.ametys.plugins.repository.query.expression.Expression.Operator; +import org.ametys.runtime.util.parameter.Parameter; + + +/** + * This class represents a search criteria of a {@link SearchModel} + * + */ +public class SearchCriteria extends Parameter<MetadataType> +{ + private Operator _operator; + private String _onInitClassName; + private String _onSubmitClassName; + private String _onChangeClassName; + private boolean _hidden; + private Object _value; + + /** + * Get the operator such as 'equals to', 'greater than', 'not equals', ...) + * @return the operator + */ + public Operator getOperator() + { + return _operator; + } + + /** + * Set the operator + * @param operator The operator to set + */ + public void setOperator(Operator operator) + { + this._operator = operator; + } + + /** + * Get the JS class name to execute on 'init' event + * @return the JS class name to execute on 'init' event + */ + public String getInitClassName() + { + return _onInitClassName; + } + + /** + * Set the JS class name to execute on 'init' event + * @param className the JS class name + */ + public void setInitClassName(String className) + { + this._onInitClassName = className; + } + + /** + * Get the JS class name to execute on 'submit' event + * @return the JS class name to execute on 'submit' event + */ + public String getSubmitClassName() + { + return _onSubmitClassName; + } + + /** + * Set the JS class name to execute on 'submit' event + * @param className the JS class name + */ + public void setSubmitClassName(String className) + { + this._onSubmitClassName = className; + } + + /** + * Get the JS class name to execute on 'change' event + * @return the JS class name to execute on 'change' event + */ + public String getChangeClassName() + { + return _onChangeClassName; + } + + /** + * Set the JS class name to execute on 'change' event + * @param className the JS class name + */ + public void setChangeClassName(String className) + { + this._onChangeClassName = className; + } + + /** + * Determines if the criteria is hidden + * @return <code>true</code> if the criteria is hidden + */ + public boolean isHidden() + { + return _hidden; + } + + /** + * Set the hidden property of the criteria + * @param hidden true to hide the search criteria + */ + public void setHidden (boolean hidden) + { + this._hidden = hidden; + } + + /** + * Get the value of search criteria + * @return the value + */ + public Object getValue() + { + return this._value; + } + + /** + * Set the value of search criteria (if it is a hidden field) + * @param value The value to set + */ + public void setValue (Object value) + { + this._value = value; + } +} Index: main/plugin-cms/src/org/ametys/cms/search/model.xml =================================================================== --- main/plugin-cms/src/org/ametys/cms/search/model.xml (revision 0) +++ main/plugin-cms/src/org/ametys/cms/search/model.xml (revision 0) @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<SearchModel> + + <content-types> + <content-type id=""/> + <content-type id=""/> + </content-types> + + <search-url></search-url> + <export-csv-url></export-csv-url> + <export-xml-url></export-xml-url> + <print-url></print-url> + + <simple-search-criteria> + + <criteria metadata-ref="title"> + <test-operator>wd</test-operator> + </criteria> + + <criteria metadata-ref="foo" hidden="true"><!-- Hidden criteria --> + <value>foo</value> + </criteria> + + <criteria metadata-ref="contact/email"/> <!-- Criteria on composite metadata --> + + <criteria metadata-ref="site/department"/> <!-- Criteria on a referenced content --> + + <criteria system-ref="author"/> + <criteria system-ref="workflow-step"/> + + <criteria system-ref="lastModified"> + <label i18n="true">SEARCH_LASTMODIFIED_AFTER</label> + <test-operator>gt</test-operator> + </criteria> + + <criteria system-ref="lastModified"> + <label i18n="true">SEARCH_LASTMODIFIED_BEFORE</label> + <test-operator>lt</test-operator> + </criteria> + + <criteria system-ref="contentLanguage" hidden="true"> + <value>fr</value> + <onsubmit>MyClassJSStatic</onsubmit> + </criteria> + + </simple-search-criteria> + + <advanced-search-criteria/> + + <sql-search-criteria/> + + <columns> + <!-- Si default n'existe pas, on renvoie nos propres default --> + <default> + <column metadata-ref="title"/> + <column system-ref="author"/> + </default> + + <exclude> + <column system-ref="with-comments"/> + </exclude> + + <column system-ref="lastModified"> + <width>130</width> <!-- Surcharge de la largeur --> + </column> + + <column system-ref="contentLanguage"> + <renderer>MyClassJSStatic</renderer> <!-- Surcharge du renderer --> + </column> + </columns> +</SearchModel> \ No newline at end of file Index: main/plugin-cms/src/org/ametys/cms/search/actions/GetSearchModelAction.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/search/actions/GetSearchModelAction.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/search/actions/GetSearchModelAction.java (revision 0) @@ -0,0 +1,196 @@ +/* + * Copyright 2013 Anyware Services + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ametys.cms.search.actions; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.avalon.framework.parameters.Parameters; +import org.apache.avalon.framework.service.ServiceException; +import org.apache.avalon.framework.service.ServiceManager; +import org.apache.cocoon.ProcessingException; +import org.apache.cocoon.acting.ServiceableAction; +import org.apache.cocoon.environment.ObjectModelHelper; +import org.apache.cocoon.environment.Redirector; +import org.apache.cocoon.environment.Request; +import org.apache.cocoon.environment.SourceResolver; + +import org.ametys.cms.search.SearchColumn; +import org.ametys.cms.search.SearchCriteria; +import org.ametys.cms.search.SearchModel; +import org.ametys.cms.search.SearchModelExtensionPoint; +import org.ametys.runtime.cocoon.JSonReader; +import org.ametys.runtime.util.I18nizableText; +import org.ametys.runtime.util.parameter.Enumerator; +import org.ametys.runtime.util.parameter.ParameterHelper; +import org.ametys.runtime.util.parameter.Validator; + +/** + * Get the search model configuration as JSON object + * + */ +public class GetSearchModelAction extends ServiceableAction +{ + private SearchModelExtensionPoint _searchModelManager; + + @Override + public void service(ServiceManager smanager) throws ServiceException + { + super.service(smanager); + _searchModelManager = (SearchModelExtensionPoint) smanager.lookup(SearchModelExtensionPoint.ROLE); + } + + @Override + public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception + { + Request request = ObjectModelHelper.getRequest(objectModel); + String modelId = request.getParameter("model"); + + SearchModel model = _searchModelManager.getExtension(modelId); + + Map<String, Object> jsonObject = _searchModel2JsonObject (model); + + request.setAttribute(JSonReader.OBJECT_TO_READ, jsonObject); + return EMPTY_MAP; + } + + private Map<String, Object> _searchModel2JsonObject (SearchModel model) throws ProcessingException + { + Map<String, Object> jsonObject = new HashMap<String, Object>(); + jsonObject.put("searchUrl", model.getSearchUrl()); + jsonObject.put("searchUrlPlugin", model.getSearchUrlPlugin()); + jsonObject.put("exportCVSUrl", model.getExportCSVUrl()); + jsonObject.put("exportCVSUrlPlugin", model.getExportCSVUrlPlugin()); + jsonObject.put("exportXMLUrl", model.getExportXMLUrlPlugin()); + jsonObject.put("exportXMLUrlPlugin", model.getExportXMLUrl()); + jsonObject.put("printUrl", model.getPrintUrl()); + jsonObject.put("printUrlPlugin", model.getPrintUrlPlugin()); + + jsonObject.put("simple-criteria", _criteria2JsonObject (model.getCriteria())); + jsonObject.put("columns", _column2JsonObject (model.getResultColumns())); + + return jsonObject; + } + + private Map<String, Object> _column2JsonObject (List<SearchColumn> columns) + { + Map<String, Object> jsonObject = new LinkedHashMap<String, Object>(); + + for (SearchColumn column : columns) + { + jsonObject.put(column.getId(), _column2JsonObject(column)); + } + + return jsonObject; + } + + private Map<String, Object> _column2JsonObject (SearchColumn column) + { + Map<String, Object> jsonObject = new LinkedHashMap<String, Object>(); + + jsonObject.put("title", column.getTitle()); + jsonObject.put("type", column.getType().name()); + jsonObject.put("hidden", column.isHidden()); + jsonObject.put("renderer", column.getRenderer()); + jsonObject.put("width", column.getWidth()); + jsonObject.put("mapping", column.getMapping()); + + return jsonObject; + } + + private Map<String, Object> _criteria2JsonObject (Map<String, SearchCriteria> criteria) throws ProcessingException + { + Map<String, Object> jsonObject = new LinkedHashMap<String, Object>(); + + for (SearchCriteria sc : criteria.values()) + { + jsonObject.put(sc.getId(), _criteria2JsonObject(sc)); + } + + return jsonObject; + } + + private Map<String, Object> _criteria2JsonObject (SearchCriteria criteria) throws ProcessingException + { + Map<String, Object> jsonObject = new LinkedHashMap<String, Object>(); + + jsonObject.put("label", criteria.getLabel()); + jsonObject.put("description", criteria.getDescription()); + jsonObject.put("type", criteria.getType().name()); + + Validator validator = criteria.getValidator(); + if (validator != null) + { + Map<String, Object> val2Json = validator.toJson(); + if (val2Json.containsKey("mandatory")) + { + // Override mandatory property + val2Json.put("mandatory", false); + } + jsonObject.put("validation", val2Json); + } + + String widget = criteria.getWidget(); + + if (widget != null) + { + jsonObject.put("widget", widget); + } + + Map<String, I18nizableText> widgetParameters = criteria.getWidgetParameters(); + if (widgetParameters != null && widgetParameters.size() > 0) + { + jsonObject.put("widget-params", criteria.getWidgetParameters()); + } + + Object defaultValue = criteria.getDefaultValue(); + if (defaultValue != null) + { + jsonObject.put("default-value", criteria.getDefaultValue()); + } + + Enumerator enumerator = criteria.getEnumerator(); + if (enumerator != null) + { + try + { + List<Map<String, Object>> options = new ArrayList<Map<String, Object>>(); + + for (Map.Entry<Object, I18nizableText> entry : enumerator.getEntries().entrySet()) + { + String valueAsString = ParameterHelper.valueToString(entry.getKey()); + I18nizableText entryLabel = entry.getValue(); + + Map<String, Object> option = new HashMap<String, Object>(); + option.put("label", entryLabel != null ? entryLabel : valueAsString); + option.put("value", valueAsString); + options.add(option); + } + + jsonObject.put("enumeration", options); + } + catch (Exception e) + { + throw new ProcessingException("Unable to enumerate entries with enumerator: " + enumerator, e); + } + } + + return jsonObject; + } +} Index: main/plugin-cms/src/org/ametys/cms/search/SearchColumn.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/search/SearchColumn.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/search/SearchColumn.java (revision 0) @@ -0,0 +1,199 @@ +/* + * Copyright 2013 Anyware Services + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ametys.cms.search; + +import org.ametys.cms.contenttype.MetadataType; +import org.ametys.runtime.util.I18nizableText; + + +/** + * This class represents a result column + * + */ +public class SearchColumn +{ + private String _id; + private I18nizableText _title; + private int _width; + private String _renderer; + private boolean _hidden; + private MetadataType _type; + private String _mapping; + private boolean _editable; + private boolean _sortable; + + /** + * Get the id + * @return The id + */ + public String getId() + { + return this._id; + } + + /** + * Set the id + * @param id The id to set + */ + public void setId(String id) + { + this._id = id; + } + + /** + * Get the path expression to extract the field value + * @return The path expression + */ + public String getMapping() + { + return this._mapping != null ? this._mapping : "metadata/" + this._id.replaceAll("\\.", "/"); + } + + /** + * Set the path expression to extract the field value + * @param mapping The the path expression + */ + public void setMapping(String mapping) + { + this._mapping = mapping; + } + + /** + * Get the column title + * @return the column title + */ + public I18nizableText getTitle() + { + return _title; + } + + /** + * Set the column title + * @param title The title to set + */ + public void setTitle(I18nizableText title) + { + this._title = title; + } + + /** + * The column's width + * @return The width + */ + public int getWidth() + { + return _width; + } + + /** + * Set the column's width + * @param width The width to set + */ + public void setWidth (int width) + { + this._width = width; + } + + /** + * Determines if the column is hidden by default + * @return <code>true</code> if the column is hidden by default + */ + public boolean isHidden () + { + return this._hidden; + } + + /** + * Set the hidden property + * @param hidden <code>true</code> to hidden the columns by default + */ + public void setHidden (boolean hidden) + { + this._hidden = hidden; + } + + /** + * Determines if the property is editable + * @return <code>true</code> if the property is editable + */ + public boolean isEditable () + { + return this._editable; + } + + /** + * Set the sortable property + * @param sortable <code>true</code> to authorized sort + */ + public void setSortable (boolean sortable) + { + this._sortable = sortable; + } + + /** + * Determines if the column is sortable + * @return <code>true</code> if the column is sortable + */ + public boolean isSortable () + { + return this._sortable; + } + + /** + * Set the editable property + * @param editable <code>true</code> to authorized edition + */ + public void setEditable (boolean editable) + { + this._editable = editable; + } + + /** + * Get the JS class name for renderer + * @return The renderer + */ + public String getRenderer() + { + return _renderer; + } + + /** + * Set the JS class name for renderer + * @param renderer The renderer + */ + public void setRenderer(String renderer) + { + this._renderer = renderer; + } + + /** + * Get the type + * @return the type + */ + public MetadataType getType () + { + return this._type; + } + + /** + * Set the type + * @param type The type + */ + public void setType (MetadataType type) + { + this._type = type; + } +} Index: main/plugin-cms/src/org/ametys/cms/search/SystemSearchCriteria.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/search/SystemSearchCriteria.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/search/SystemSearchCriteria.java (revision 0) @@ -0,0 +1,43 @@ +/* + * Copyright 2013 Anyware Services + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ametys.cms.search; + +/** + * This class is a search criteria on a system prorety (author, lastModified, with-comments, ...) + * + */ +public class SystemSearchCriteria extends SearchCriteria +{ + private String _property; + + /** + * Get the system property name + * @return The property name + */ + public String getPropertyName() + { + return _property; + } + + /** + * Set the system property name + * @param property The property name + */ + public void setPropertyName(String property) + { + _property = property; + } +} Index: main/plugin-cms/src/org/ametys/cms/search/generators/SearchGenerator.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/search/generators/SearchGenerator.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/search/generators/SearchGenerator.java (revision 0) @@ -0,0 +1,395 @@ +/* + * Copyright 2013 Anyware Services + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ametys.cms.search.generators; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.jcr.RepositoryException; + +import org.apache.avalon.framework.service.ServiceException; +import org.apache.avalon.framework.service.ServiceManager; +import org.apache.cocoon.ProcessingException; +import org.apache.cocoon.environment.ObjectModelHelper; +import org.apache.cocoon.environment.Request; +import org.apache.cocoon.generation.ServiceableGenerator; +import org.apache.cocoon.xml.AttributesImpl; +import org.apache.cocoon.xml.XMLUtils; +import org.apache.commons.lang.StringUtils; +import org.xml.sax.SAXException; + +import org.ametys.cms.contenttype.AbstractMetadataSetElement; +import org.ametys.cms.contenttype.ContentType; +import org.ametys.cms.contenttype.ContentTypeExtensionPoint; +import org.ametys.cms.contenttype.MetadataDefinitionReference; +import org.ametys.cms.contenttype.MetadataManager; +import org.ametys.cms.contenttype.MetadataSet; +import org.ametys.cms.languages.Language; +import org.ametys.cms.languages.LanguagesManager; +import org.ametys.cms.repository.Content; +import org.ametys.cms.repository.RequestAttributeWorkspaceSelector; +import org.ametys.cms.repository.WorkflowAwareContent; +import org.ametys.cms.repository.comment.CommentableContent; +import org.ametys.cms.search.SearchColumn; +import org.ametys.cms.search.SearchModel; +import org.ametys.cms.search.SearchModelExtensionPoint; +import org.ametys.plugins.repository.AmetysObjectIterable; +import org.ametys.plugins.repository.AmetysObjectResolver; +import org.ametys.plugins.workflow.Workflow; +import org.ametys.runtime.user.User; +import org.ametys.runtime.user.UsersManager; +import org.ametys.runtime.util.I18nizableText; +import org.ametys.runtime.util.parameter.ParameterHelper; + +import com.opensymphony.workflow.loader.StepDescriptor; +import com.opensymphony.workflow.loader.WorkflowDescriptor; +import com.opensymphony.workflow.spi.Step; + +/** + * Search contents + * + */ +public class SearchGenerator extends ServiceableGenerator +{ + /** The namespace uri */ + protected static final String __I18N_NAMESPACE_URI = "http://apache.org/cocoon/i18n/2.1"; + + /** The search model manager */ + protected SearchModelExtensionPoint _searchModelManager; + /** The {@link AmetysObjectResolver} */ + protected AmetysObjectResolver _resolver; + /** The ContentType Manager*/ + protected ContentTypeExtensionPoint _contentTypeExtensionPoint; + /** The language manager */ + protected LanguagesManager _languagesManager; + /** The metadata manager */ + protected MetadataManager _metadataManager; + /** The workflow */ + protected Workflow _workflow; + /** The user manager */ + protected UsersManager _usersManager; + + /** The cache for users' name */ + protected Map<String, String> _userCache; + + + @Override + public void service(ServiceManager smanager) throws ServiceException + { + super.service(smanager); + _searchModelManager = (SearchModelExtensionPoint) smanager.lookup(SearchModelExtensionPoint.ROLE); + _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); + _contentTypeExtensionPoint = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); + _languagesManager = (LanguagesManager) manager.lookup(LanguagesManager.ROLE); + _metadataManager = (MetadataManager) manager.lookup(MetadataManager.ROLE); + _workflow = (Workflow) manager.lookup(Workflow.ROLE); + _usersManager = (UsersManager) manager.lookup(UsersManager.ROLE); + } + + @Override + public void generate() throws IOException, SAXException, ProcessingException + { + // Clear cache + clearUserCache(); + + Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); + + Request request = ObjectModelHelper.getRequest(objectModel); + String modelId = (String) jsParameters.get("model"); + + SearchModel model = _searchModelManager.getExtension(modelId); + + String workspaceName = parameters.getParameter("repository-workspace", null); + + // Get parameters from request + int begin = jsParameters.get("start") != null ? (Integer) jsParameters.get("start") : 0; // Index of search + int offset = jsParameters.get("limit") != null ? (Integer) jsParameters.get("limit") : Integer.MAX_VALUE; // Number of results to SAX + + String originalWorkspace = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); + + try + { + if (StringUtils.isNotEmpty(workspaceName)) + { + RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); + } + + String xPathQuery = model.createQuery((Map<String, Object>) jsParameters.get("values")); + + // SAX results between begin and begin + offset + contentHandler.startDocument(); + contentHandler.startPrefixMapping("i18n", __I18N_NAMESPACE_URI); + + XMLUtils.startElement(contentHandler, "contents"); + + AmetysObjectIterable<Content> it = _resolver.query(xPathQuery); + // Index of search + it.skip(begin); + + int index = 0; + while (it.hasNext() && index < offset) + { + Content content = it.next(); + + // TODO Check right + saxContent(content, model); + index++; + } + it.close(); + + XMLUtils.createElement(contentHandler, "total", String.valueOf(it.getSize())); + XMLUtils.endElement(contentHandler, "contents"); + + contentHandler.endPrefixMapping("i18n"); + contentHandler.endDocument(); + } + catch (Exception e) + { + getLogger().error("Cannot search for contents : " + e.getMessage(), e); + throw new ProcessingException("Cannot search for contents : " + e.getMessage(), e); + } + finally + { + if (StringUtils.isNotEmpty(workspaceName)) + { + RequestAttributeWorkspaceSelector.setForcedWorkspace(request, originalWorkspace); + } + } + } + + /** + * SAX a content + * @param content The content to SAX + * @param model The search model + * @throws SAXException if errors occurs while SAXing + * @throws RepositoryException if errors occurs while retrieving content's metadata + * @throws IOException on error if errors occurs while SAXing + */ + protected void saxContent (Content content, SearchModel model) throws SAXException, RepositoryException, IOException + { + List<SearchColumn> columns = model.getResultColumns(); + + AttributesImpl attrs = new AttributesImpl(); + + ContentType contentType = _contentTypeExtensionPoint.getExtension(content.getType()); + + attrs.addCDATAAttribute("smallIcon", contentType != null ? contentType.getSmallIcon() : "/plugins/cms/resources/img/contenttype/unknown-small.png"); + attrs.addCDATAAttribute("mediumIcon", contentType != null ? contentType.getMediumIcon() : "/plugins/cms/resources/img/contenttype/unknown-medium.png"); + attrs.addCDATAAttribute("largeIcon", contentType != null ? contentType.getLargeIcon() : "/plugins/cms/resources/img/contenttype/unknown-large.png"); + attrs.addCDATAAttribute("id", content.getId()); + attrs.addCDATAAttribute("name", content.getName()); + attrs.addCDATAAttribute("title", content.getTitle()); + attrs.addCDATAAttribute("language", content.getLanguage()); + + XMLUtils.startElement(contentHandler, "content", attrs); + + // System properties + saxSystemProperties(columns, content, contentType); + + // Metadata + XMLUtils.startElement(contentHandler, "metadata"); + MetadataSet metadataSet = getMetadataToSAX(model, model.getResultColumns(), contentType); + _metadataManager.saxReadableMetadata(contentHandler, content, metadataSet); + XMLUtils.endElement(contentHandler, "metadata"); + + XMLUtils.endElement(contentHandler, "content"); + } + + /** + * SAX the system properties + * @param columns The + * @param content + * @param cType + * @throws SAXException + * @throws RepositoryException + */ + protected void saxSystemProperties (List<SearchColumn> columns, Content content, ContentType cType) throws SAXException, RepositoryException + { + for (SearchColumn searchColumn : columns) + { + String id = searchColumn.getId(); + + if ("workflowStep".equals(id) && content instanceof WorkflowAwareContent) + { + saxContentCurrentState((WorkflowAwareContent) content); + } + else if ("contentType".equals(id) && cType != null) + { + cType.getLabel().toSAX(contentHandler, "content-type"); + } + else if ("creator".equals(id)) + { + String login = content.getCreator(); + XMLUtils.createElement(contentHandler, "creator", login); + XMLUtils.createElement(contentHandler, "creatorDisplay", getUserFullName(login)); + } + else if ("contributor".equals(id)) + { + String login = content.getLastContributor(); + XMLUtils.createElement(contentHandler, "contributor", login); + XMLUtils.createElement(contentHandler, "contributorDisplay", getUserFullName(login)); + } + else if ("lastModified".equals(id)) + { + XMLUtils.createElement(contentHandler, "lastModified", ParameterHelper.valueToString(content.getLastModified())); + } + else if ("comments".equals(id)) + { + if (content instanceof CommentableContent) + { + boolean hasComment = ((CommentableContent) content).getComments(true, true).size() != 0; + XMLUtils.createElement(contentHandler, "comments", String.valueOf(hasComment)); + } + } + else if ("contentLanguage".equals(id)) + { + Language language = _languagesManager.getAvailableLanguages().get(content.getLanguage()); + if (language != null) + { + XMLUtils.createElement(contentHandler, "contentLanguageIcon", language.getSmallIcon()); + } + + XMLUtils.createElement(contentHandler, "contentLanguage", content.getLanguage()); + } + } + } + /** + * Extracts the metadata to SAX + * @param model The search model + * @param columns The result columns + * @param contentType The content type + * @return the metadata to SAX in a {@link MetadataSet} + */ + protected MetadataSet getMetadataToSAX (SearchModel model, List<SearchColumn> columns, ContentType contentType) + { + MetadataSet metadataSet = new MetadataSet(); + + AbstractMetadataSetElement metadataSetElmt = metadataSet; + for (SearchColumn column : columns) + { + String id = column.getId(); + + if (!model.isValidSystemProperty(id)) + { + String[] path = id.split("\\/|\\."); + + if (contentType.getMetadataDefinition(path[0]) != null) + { + for (String metadataName : path) + { + MetadataDefinitionReference metadataRef = metadataSetElmt.getMetadataDefinitionReference(metadataName); + if (metadataRef == null) + { + metadataRef = new MetadataDefinitionReference(metadataName); + } + metadataSetElmt.addElement(metadataRef); + metadataSetElmt = metadataRef; + } + + metadataSetElmt = metadataSet; + } + } + } + + return metadataSet; + } + + /** + * SAX workflow current step of a {@link Content} + * @param content The content + * @throws SAXException if an error occurs while SAXing + * @throws RepositoryException if an error occurs while retrieving current step + */ + protected void saxContentCurrentState(WorkflowAwareContent content) throws SAXException, RepositoryException + { + int currentStepId = 0; + + long workflowId = content.getWorkflowId(); + Iterator iterator = _workflow.getCurrentSteps(workflowId).iterator(); + + while (iterator.hasNext()) + { + Step step = (Step) iterator.next(); + currentStepId = step.getStepId(); + } + + + String workflowName = _workflow.getWorkflowName(workflowId); + WorkflowDescriptor workflowDescriptor = _workflow.getWorkflowDescriptor(workflowName); + StepDescriptor stepDescriptor = workflowDescriptor.getStep(currentStepId); + + if (stepDescriptor != null) + { + I18nizableText workflowStepName = new I18nizableText("application", stepDescriptor.getName()); + + AttributesImpl attr = new AttributesImpl(); + attr.addAttribute("", "id", "id", "CDATA", String.valueOf(currentStepId)); + + XMLUtils.startElement(contentHandler, "workflow-step", attr); + workflowStepName.toSAX(contentHandler); + XMLUtils.endElement(contentHandler, "workflow-step"); + + String[] icons = new String[]{"-small", "-medium", "-large"}; + for (String icon : icons) + { + if ("application".equals(workflowStepName.getCatalogue())) + { + XMLUtils.createElement(contentHandler, "workflow-icon" + icon, "/plugins/cms/resources_workflow/" + workflowStepName.getKey() + icon + ".png"); + } + else + { + String pluginName = workflowStepName.getCatalogue().substring("plugin.".length()); + XMLUtils.createElement(contentHandler, "workflow-icon" + icon, "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + icon + ".png"); + } + } + } + } + + /** + * Clear the user cache + */ + protected void clearUserCache () + { + _userCache = new HashMap<String, String>(); + } + + /** + * Get the user full name + * @param login the user login + * @return the user name + */ + protected String getUserFullName (String login) + { + String name = _userCache.get(login); + if (name != null) + { + return name; + } + + User user = _usersManager.getUser(login); + if (user == null) + { + return ""; + } + + name = user.getFullName(); + _userCache.put(login, name); + return name; + } +} Index: main/plugin-cms/src/org/ametys/cms/search/MetadataSearchCriteria.java =================================================================== --- main/plugin-cms/src/org/ametys/cms/search/MetadataSearchCriteria.java (revision 0) +++ main/plugin-cms/src/org/ametys/cms/search/MetadataSearchCriteria.java (revision 0) @@ -0,0 +1,43 @@ +/* + * Copyright 2013 Anyware Services + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ametys.cms.search; + +/** + * This class is a search criteria on a metadata of a content + * + */ +public class MetadataSearchCriteria extends SearchCriteria +{ + String _metadataPath; + + /** + * Get the path of metadata (separated by '/') + * @return the path of metadata + */ + public String getMetadataPath() + { + return _metadataPath; + } + + /** + * Set the metadata path + * @param metadataPath The metadata path separated by '/'. + */ + public void setMetadataPath (String metadataPath) + { + _metadataPath = metadataPath; + } +}