/**
* Copyright (c) 2011, SOCIETIES Consortium (WATERFORD INSTITUTE OF TECHNOLOGY (TSSG), HERIOT-WATT UNIVERSITY (HWU), SOLUTA.NET
* (SN), GERMAN AEROSPACE CENTRE (Deutsches Zentrum fuer Luft- und Raumfahrt e.V.) (DLR), Zavod za varnostne tehnologije
* informacijske družbe in elektronsko poslovanje (SETCCE), INSTITUTE OF COMMUNICATION AND COMPUTER SYSTEMS (ICCS), LAKE
* COMMUNICATIONS (LAKE), INTEL PERFORMANCE LEARNING SOLUTIONS LTD (INTEL), PORTUGAL TELECOM INOVAÇÃO, SA (PTIN), IBM Corp.,
* INSTITUT TELECOM (ITSUD), AMITEC DIACHYTI EFYIA PLIROFORIKI KAI EPIKINONIES ETERIA PERIORISMENIS EFTHINIS (AMITEC), TELECOM
* ITALIA S.p.a.(TI), TRIALOG (TRIALOG), Stiftelsen SINTEF (SINTEF), NEC EUROPE LTD (NEC))
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
* conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.societies.orchestration.crm;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.societies.api.cis.directory.ICisDirectoryRemote;
import org.societies.api.cis.orchestration.ICommunityRecommendationManager;
import org.societies.api.cis.orchestration.model.IFilter;
import org.societies.api.comm.xmpp.interfaces.ICommManager;
import org.societies.api.css.directory.ICssDirectoryRemote;
import org.societies.api.identity.IIdentity;
import org.societies.api.identity.InvalidFormatException;
import org.societies.api.schema.cis.community.Criteria;
import org.societies.api.schema.cis.community.MembershipCrit;
import org.societies.api.schema.cis.directory.CisAdvertisementRecord;
import org.societies.api.schema.css.directory.CssAdvertisementRecord;
import org.societies.cis.directory.client.CisDirectoryRemoteClient;
import org.societies.css.mgmt.CssDirectoryRemoteClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
/**
* Implementation of the {@link ICommunityRecommendationManager} interface.
*
* @author Chris Lima
*/
@Service
public class CommunityRecommendationManager implements ICommunityRecommendationManager {
/** The logging facility. */
private static final Logger LOG = LoggerFactory.getLogger(CommunityRecommendationManager.class);
private int limit = 10;
private List<CssAdvertisementRecord> cssAdvertsList;
private List<CisAdvertisementRecord> cisList;
private CisDirectoryRemoteClient callback = new CisDirectoryRemoteClient();
private CssDirectoryRemoteClient cssDirCallback = new CssDirectoryRemoteClient();
/** Service references */
private ICisDirectoryRemote cisDirectoryRemote;
private ICssDirectoryRemote cssDirectoryRemote;
private ICommManager commMgr;
private final String cssOwnerStr;
/**
*
* Default is 10 results
*
* @param limit Maximum results to return
*/
@Override
public void setLimit(int limit){
this.limit = limit;
}
/**
*
*/
public CommunityRecommendationManager() {
this.cssOwnerStr = null;
}
/**
* Only for JUnit testing!
*/
public CommunityRecommendationManager(CisDirectoryRemoteClient mockCallback, ICisDirectoryRemote cisDirectoryRemote, ICommManager commMgr) {
this.cssOwnerStr = "mock.societies.local";
this.cisDirectoryRemote = cisDirectoryRemote;
this.callback = mockCallback;
this.commMgr = commMgr;
LOG.info(this.getClass() + " instantiated for Unit Tests");
}
/**
* For spring injection
*/
@Autowired(required=true)
CommunityRecommendationManager(ICisDirectoryRemote cisDirectoryRemote, ICssDirectoryRemote cssDirectoryRemote,ICommManager commMgr) {
this.cisDirectoryRemote = cisDirectoryRemote;
this.cssDirectoryRemote = cssDirectoryRemote;
this.commMgr = commMgr;
this.cssOwnerStr = commMgr.getIdManager().getThisNetworkNode().getBareJid();
LOG.info(this.getClass()+" instantiated");
LOG.info("CSS Owner: "+cssOwnerStr);
}
/**
* @return the limit
*/
@Override
public int getLimit(){
return this.limit;
}
/**
*
* Sort the results based on the rank available in {@link RankCisAdv}
*
* @param filterType True for primary filter and false for secondary filter
* @param cisList List of all {@link CisAdvertisementRecord} retrieved
* @param filter Array of {@link Filter} to analyze
* @return sorted list of {@link CisAdvertisementRecord}
*/
private List<CisAdvertisementRecord> sort(boolean filterType, List<CisAdvertisementRecord> cisList, IFilter[] filter) {
if (filter.length <= 0){
LOG.error("Filter array needs at least one filter! Returning all the CISs...");
//Returning all CISs if no filter is specified
return cisList;
}
else {
List<RankCisAdv> rankResultList = new ArrayList<RankCisAdv>();
for (CisAdvertisementRecord cisAdv : cisList) {
MembershipCrit criteriaList = cisAdv.getMembershipCrit();
LOG.info("CIS CisAdvertisementRecord: "+cisAdv.getName() + " Number of Membership criteria: "+criteriaList.getCriteria().size()+" Owner: " + cisAdv.getCssownerid());
if ((criteriaList != null)) {
RankCisAdv tempRankCIS = new RankCisAdv();
for (Criteria memberCriteria : criteriaList.getCriteria())
{
for (IFilter tempFilter : filter)
{
//Check if context attribute is the same of the criteria
if (tempFilter.getCtxAttribute().contains(memberCriteria.getAttrib())) {
boolean condition = checkCriteria(memberCriteria, tempFilter);
if (condition) {
tempRankCIS.incrementRank();
}
}
}
}
//Primary filter
if (tempRankCIS.getRank() > 0){
if (filterType == true && tempRankCIS.getRank() == criteriaList.getCriteria().size()){
tempRankCIS.setCisAdv(cisAdv);
rankResultList.add(tempRankCIS);
}
//Secondary filter
else if (filterType == false) {
tempRankCIS.setCisAdv(cisAdv);
rankResultList.add(tempRankCIS);
}
}
}
}
//Show results before sort
for (RankCisAdv tempRankCisAdv : rankResultList) {
LOG.info("Rank before sort: "+tempRankCisAdv.getRank()+ " Adv: "+tempRankCisAdv.getCisAdv().getName());
}
//order by rank
Collections.sort(rankResultList, new RankCisAdv());
List<CisAdvertisementRecord> result = new ArrayList<CisAdvertisementRecord>();
for (RankCisAdv tempRankCisAdv : rankResultList) {
LOG.info("Rank after sort: "+tempRankCisAdv.getRank()+ " Adv: "+tempRankCisAdv.getCisAdv().getName());
result.add(tempRankCisAdv.getCisAdv());
}
return result;
}
}
/**
*
* Check the criteria available in {@link Criteria}. The Value2 of a criteria is not been used.
*
* @param criteria to evaluate
* @param filter used to compare
* @return Return true if a given criteria matches the filter operator
*/
private boolean checkCriteria(Criteria criteria, IFilter filter) {
LOG.info("Criteria Attribute: "+criteria.getAttrib());
LOG.info("Criteria Value1: "+criteria.getValue1());
//TODO: Value2 is not been used.
// LOG.info("Criteria Value2: "+criteria.getValue2());
//Check if value is numeric or not
switch (isNumeric(criteria.getValue1()) ? 1 : 2){
//Numeric value
case 1:
double number = Double.parseDouble(criteria.getValue1());
double filterNumber = Double.parseDouble(filter.getValue());
switch (filter.getOperator()){
case EQUAL:
if(number == (filterNumber)) return true;
else return false;
case NOT_EQUAL:
if(number != (filterNumber)) return true;
else return false;
case LESS:
if(number < (filterNumber)) return true;
else return false;
case LESS_OR_EQUAL:
if(number <= (filterNumber)) return true;
else return false;
case GREATER:
if(number > (filterNumber)) return true;
else return false;
case GREATER_OR_EQUAL:
if(number >= (filterNumber)) return true;
else return false;
}
break;
//String value
case 2:
String stringValue = criteria.getValue1();
switch (filter.getOperator()){
case EQUAL:
if(stringValue.equalsIgnoreCase(filter.getValue())) return true;
else return false;
case NOT_EQUAL:
if(!stringValue.equalsIgnoreCase(filter.getValue())) return true;
else return false;
case SIMILAR:
if (stringValue.length() > 5){
String[] words = getSynonyms(stringValue);
String[] filterWords = getSynonyms(filter.getValue());
LOG.info("Synonyms from remote cis: "+Arrays.toString(words));
LOG.info("Synonyms from search: "+Arrays.toString(filterWords));
for (String str1 : words){
for (String str2 : filterWords){
if (str1.equalsIgnoreCase(str2)){
return true;
}
}
}
}
return false;
}
break;
}
return false;
}
/**
*
* Find synonyms for string values. Used by {@link SIMILAR} operator
*
* @param stringValue word to be submitted for NLP analyzes.
* @return A array of synonyms word. At least the stringValue is returned if no synonyms is found.
*/
private String[] getSynonyms(final String stringValue) {
//TODO:Change API key
final String APIKEY = "b9a370e35951ab0e1c7f973f90690a879546b35f";
//AlchemyAPI api key allows 1000 queries a day
final SynonymsAux alchemyObj = SynonymsAux.GetInstanceFromString(APIKEY);
ExecutorService executor = Executors.newFixedThreadPool(2);
List<FutureTask<String[]>> taskList = new ArrayList<FutureTask<String[]>>();
// Start thread for the first half of the numbers
FutureTask<String[]> futureTaskCategory = new FutureTask<String[]>(new Callable<String[]>() {
@Override
public String[] call() throws Exception {
Document docCategory = null;
docCategory = alchemyObj.TextGetCategory(stringValue);
String[] arrayCategory = new String[0];
if (docCategory != null) {
NodeList result = docCategory.getElementsByTagName("text");
arrayCategory = new String[result.getLength()];
for (int i = 0; i < result.getLength(); i++) {
arrayCategory[i] = result.item(i).getTextContent().toLowerCase();
}
}
return arrayCategory;
}
});
taskList.add(futureTaskCategory);
executor.execute(futureTaskCategory);
FutureTask<String[]> futureTaskConcept = new FutureTask<String[]>(new Callable<String[]>() {
@Override
public String[] call() throws Exception {
Document docConcept = null;
docConcept = alchemyObj.TextGetRankedConcepts(stringValue);
String[] arrayConcept = new String[0];
if (docConcept != null) {
NodeList result = docConcept.getElementsByTagName("text");
arrayConcept = new String[result.getLength()];
for (int i = 0; i < result.getLength(); i++) {
arrayConcept[i] = result.item(i).getTextContent().toLowerCase();
}
}
return arrayConcept;
}
});
taskList.add(futureTaskConcept);
executor.execute(futureTaskConcept);
List<String[]> list = new ArrayList<String[]>();
for(FutureTask<String[]> fut : taskList){
try {
list.add(fut.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
executor.shutdown();
String[] arrayCategory = list.get(0);
String[] arrayConcept = list.get(1);
//Create an array plus one for the default value
String [] result = Arrays.copyOf(arrayCategory, arrayCategory.length + arrayConcept.length +1);
System.arraycopy(arrayConcept, 0, result, arrayCategory.length, arrayConcept.length);
//Include the string value in the last position
result[result.length-1] = stringValue;
return result;
}
/**
* @return list of remote {@link CisAdvertisementRecord} which the CSS user is not the owner
*/
private List<CisAdvertisementRecord> relevantCISs() {
this.cisDirectoryRemote.findAllCisAdvertisementRecords(callback);
List<CisAdvertisementRecord> cisAdvertsList = callback.getResultList();
List<CisAdvertisementRecord> cisList = new ArrayList<CisAdvertisementRecord>();
if (cisAdvertsList != null) {
for (CisAdvertisementRecord cisAdv : cisAdvertsList)
{
//Considering only CISs which the user is not the owner
if (!cssOwnerStr.equalsIgnoreCase(cisAdv.getCssownerid())) {
cisList.add(cisAdv);
}
}
}
else {
LOG.error("CisAdvertisementRecord is null!");
}
return cisList;
}
/**
*
* Return true if the string is a numeric value.
*
* @return True for numeric values
*/
private static boolean isNumeric(String str)
{
for (char character : str.toCharArray())
{
if (!Character.isDigit(character)) {
return false;
}
}
return true;
}
/**
*
* Return a list of CIS advertisements sorted by relevance. {@link CisAdvertisementRecord} enables to retrieve CIS information.
*
* @param limit Maximum results to return. Default value if null is 10.
* @param primaryfilter An array of filters. The primary filter is applied for exact results. All the filters must match.
* @param secondaryfilter An array of filters. The secondary filter is applied for extended results.
* @return list of CIS advertisements sorted by relevance
*/
@Override
public List<CisAdvertisementRecord> getCISAdvResults(int limit, IFilter[] primaryfilter, IFilter[] secondaryfilter) {
if (limit > 0){
this.limit = limit;
}
//Get remote CISs
cisList = relevantCISs();
if (!cisList.isEmpty()) {
if (primaryfilter != null && secondaryfilter == null){
return sort(true, cisList, primaryfilter);
}
else if (primaryfilter == null && secondaryfilter != null){
return sort(false, cisList, secondaryfilter);
}
else if (primaryfilter == null && secondaryfilter == null){
LOG.error("Primary and secondary filters are nulls!");
}
else {
//TODO:Change to accept two filters?
List<CisAdvertisementRecord> resultsPrimaryFilter = sort(true, cisList, primaryfilter);
List<CisAdvertisementRecord> resultsSecondaryFilter = sort(false, cisList, secondaryfilter);
resultsPrimaryFilter.addAll(resultsSecondaryFilter);
return resultsPrimaryFilter;
}
}
else {
LOG.error("There is no remote CISs now!");
}
return new ArrayList<CisAdvertisementRecord>();
}
/**
*
* Return a list of CIS identities sorted by relevance. The {@link IIdentity} list can be used to retrieve more information from the Context Broker
*
* @param limit Maximum results to return. Default value if null is 10.
* @param primaryfilter An array of filters. The primary filter is applied when you want an exact result
* @param secondaryfilter An array of filters. The secondary filter is when not all parameters provide a match
* @return list of CIS advertisements sorted by relevance
*/
@Override
public List<IIdentity> getResults(int limit, IFilter[] primaryfilter, IFilter[] secondaryfilter) {
if (limit >= 0){
this.limit = limit;
}
List<IIdentity> cisListID = new ArrayList<IIdentity>();
List<CisAdvertisementRecord> results = new ArrayList<CisAdvertisementRecord>();
cisList = relevantCISs();
if (!cisList.isEmpty()) {
if (primaryfilter != null && secondaryfilter == null){
results = sort(true, cisList, primaryfilter);
}
else if (primaryfilter == null && secondaryfilter != null){
results = sort(false, cisList, secondaryfilter);
}
else if (primaryfilter == null && secondaryfilter == null){
LOG.error("Primary and secondary filters are nulls!");
}
else {
//TODO:Change to accept two filters?
List<CisAdvertisementRecord> resultsPrimaryFilter = sort(true, cisList, primaryfilter);
List<CisAdvertisementRecord> resultsSecondaryFilter = sort(false, cisList, secondaryfilter);
resultsPrimaryFilter.addAll(resultsSecondaryFilter);
results = resultsPrimaryFilter;
}
IIdentity remoteCis = null;
for (CisAdvertisementRecord cisAdv : results) {
try {
remoteCis = this.commMgr.getIdManager().fromJid(cisAdv.getId());
} catch (InvalidFormatException e) {
e.printStackTrace();
}
if (remoteCis != null) {
LOG.info("CIS Identity: "+remoteCis.getJid());
cisListID.add(remoteCis);
}
}
}
else {
LOG.error("There is no remote CISs now!");
}
return cisListID;
}
/**
* Return a list of {@link CssAdvertisementRecord}
*/
@Deprecated
private List<CssAdvertisementRecord> getCSSResults() {
//CIS
List<CisAdvertisementRecord> cisList = relevantCISs();
List<CssAdvertisementRecord> cssAdvList = new ArrayList<CssAdvertisementRecord>();
if (!cisList.isEmpty()) {
List<String> cssList = new ArrayList<String>();
for (CisAdvertisementRecord cisAdv : cisList)
{
cssList.add(cisAdv.getCssownerid());
//CSS
this.cssDirectoryRemote.searchByID(cssList, cssDirCallback);
cssAdvertsList = cssDirCallback.getResultList();
if (cssAdvertsList != null) {
for (CssAdvertisementRecord temp : cssAdvertsList) {
cssAdvList.add(temp);
LOG.info("CSS CssAdvertisementRecord: "+temp.getName() + " \t ID " + temp.getId());
}
}
}
}
return cssAdvList;
}
}