001/* 002 * Copyright 2021 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.plugins.workspaces.members; 017 018import java.util.Arrays; 019import java.util.Collection; 020import java.util.HashSet; 021import java.util.List; 022import java.util.stream.Collectors; 023 024import org.apache.avalon.framework.component.Component; 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027import org.apache.avalon.framework.service.Serviceable; 028 029import org.ametys.cms.content.ContentHelper; 030import org.ametys.cms.data.ContentValue; 031import org.ametys.cms.repository.Content; 032import org.ametys.cms.search.content.ContentSearcherFactory; 033import org.ametys.cms.search.content.ContentSearcherFactory.SimpleContentSearcher; 034import org.ametys.cms.search.query.ContentQuery; 035import org.ametys.cms.search.query.NotQuery; 036import org.ametys.cms.search.query.OrQuery; 037import org.ametys.cms.search.query.Query; 038import org.ametys.cms.search.query.Query.Operator; 039import org.ametys.plugins.repository.AmetysObjectIterable; 040import org.ametys.plugins.repository.AmetysObjectResolver; 041import org.ametys.plugins.workspaces.WorkspacesConstants; 042import org.ametys.runtime.plugin.component.AbstractLogEnabled; 043 044/** 045 * Helper for members 046 * 047 */ 048public class MemberHelper extends AbstractLogEnabled implements Serviceable, Component 049{ 050 /** The Avalon role*/ 051 public static final String ROLE = MemberHelper.class.getName(); 052 053 /** The content searcher */ 054 protected ContentSearcherFactory _contentSearcherFactory; 055 /** The AMetys object resolver */ 056 protected AmetysObjectResolver _resolver; 057 /** The content helper */ 058 protected ContentHelper _contentHelper; 059 060 @Override 061 public void service(ServiceManager smanager) throws ServiceException 062 { 063 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 064 _contentHelper = (ContentHelper) smanager.lookup(ContentHelper.ROLE); 065 _contentSearcherFactory = (ContentSearcherFactory) smanager.lookup(ContentSearcherFactory.ROLE); 066 } 067 068 /** 069 * Get the skills' id of a member 070 * @param content the content 071 * @return the skills' id. 072 */ 073 public List<String> getSkills(Content content) 074 { 075 if (content.hasValue("skills")) 076 { 077 ContentValue[] skillValues = content.getValue("skills", false, new ContentValue[0]); 078 079 if (skillValues.length != 0) 080 { 081 return Arrays.stream(skillValues) 082 .map(v -> v.getContentId()) 083 .collect(Collectors.toList()); 084 } 085 } 086 087 return List.of(); 088 } 089 090 /** 091 * Get the keywords' id of a member 092 * @param content the content 093 * @return the keywords' id. 094 */ 095 public List<String> getKeywords(Content content) 096 { 097 if (content.hasValue("keywords")) 098 { 099 ContentValue[] keywordValues = content.getValue("keywords", false, new ContentValue[0]); 100 101 if (keywordValues.length != 0) 102 { 103 return Arrays.stream(keywordValues) 104 .map(v -> v.getContentId()) 105 .collect(Collectors.toList()); 106 } 107 } 108 109 return List.of(); 110 } 111 112 /** 113 * Get the related members based on common skills and keywords 114 * @param content the current member content 115 * @param max the max number of related members 116 * @return the related members 117 * @throws Exception if search failed 118 */ 119 public List<Content> getRelatedMembers(Content content, int max) throws Exception 120 { 121 List<String> skills = getSkills(content); 122 List<String> keywords = getKeywords(content); 123 124 if (!skills.isEmpty() || !keywords.isEmpty()) 125 { 126 SimpleContentSearcher searcher = _contentSearcherFactory.create(WorkspacesConstants.MEMBER_CONTENT_TYPE_ID); 127 128 // search user content with at least one common skill or one common keyword 129 // results will be sorted by score, so by the number of common skills and keywords 130 131 Collection<Query> queries = new HashSet<>(); 132 for (String skillId : skills) 133 { 134 Query skillQuery = new ContentQuery("skills", Operator.EQ, skillId, _resolver, _contentHelper); 135 queries.add(skillQuery); 136 } 137 138 for (String keywordId : keywords) 139 { 140 Query keywordQuery = new ContentQuery("keywords", Operator.EQ, keywordId, _resolver, _contentHelper); 141 queries.add(keywordQuery); 142 } 143 144 145 AmetysObjectIterable<Content> relatedMembers = searcher 146 .withFilterQueries(List.of(new NotQuery(() -> "id:\"" + content.getId() + "\""))) // exclude current content 147 .withLimits(0, max) 148 .search(new OrQuery(queries)); 149 150 return relatedMembers.stream() 151 .collect(Collectors.toList()); 152 } 153 154 return List.of(); 155 } 156 157}