View Javadoc

1   package de.iqser.service;
2   
3   import java.net.MalformedURLException;
4   import java.net.URL;
5   import java.text.SimpleDateFormat;
6   import java.util.ArrayList;
7   import java.util.Collection;
8   import java.util.Iterator;
9   import java.util.List;
10  import java.util.SortedSet;
11  import java.util.TreeSet;
12  
13  import com.haerter.iqser.service.IqserService;
14  import com.haerter.iqser.service.client.IqserServiceConnectorImpl;
15  import com.haerter.iqser.service.client.ServiceConnector;
16  import com.iqser.core.exception.IQserException;
17  import com.iqser.core.exception.IQserTechnicalException;
18  import com.iqser.core.model.Attribute;
19  import com.iqser.core.model.Category;
20  import com.iqser.core.model.Content;
21  import com.iqser.core.model.RelationReason;
22  import com.iqser.core.model.SearchResult;
23  import com.liferay.portal.kernel.log.Log;
24  import com.liferay.portal.kernel.log.LogFactoryUtil;
25  
26  import de.iqser.beans.ContentBean;
27  import de.iqser.beans.FolderBean;
28  
29  /*
30   * Do not use IQserService as name to avoid confusion with com.haerter.iqser.service.IqserService;
31   */
32  public class PortletIQserService {
33  	
34  	private static Log _ilog = LogFactoryUtil.getLog(PortletIQserService.class);
35  	
36  	private static IqserService _iqserService= null;
37  	
38  	private static String _userId= null;
39  	
40  	private static IqserService getServiceInstance(String iqserWebserviceUrl) {
41  		if( _iqserService == null) {
42  			URL wsdlLocation = null;
43  			try {
44  				wsdlLocation = new URL(iqserWebserviceUrl);
45  			} catch (MalformedURLException e) {
46  				_ilog.error("MalformedURLException :" + e);
47  				return null;
48  			}
49  			try {
50  				ServiceConnector<IqserService> serviceConnector = new IqserServiceConnectorImpl(wsdlLocation);
51  				_iqserService = serviceConnector.getServicePort();
52  			} catch (Exception e) {
53  				_ilog.error("Cannot get connection to service :" + e);
54  				return null;
55  			}
56  			_ilog.info("Initialized _iqserService with Url :" + iqserWebserviceUrl);
57  		}
58  		return _iqserService;
59  	}
60  	
61  	private static String getUserId(String iqserWebserviceUrl) {
62  		if( _userId == null ) {
63  			try {
64  				_userId= getServiceInstance(iqserWebserviceUrl).login("admin", "admin");
65  			} catch (IQserException e) {
66  				_ilog.error("cannot get userId " + e);
67  			}
68  		}
69  		return _userId;
70  	}
71  	
72  	static String typeMockup(long contentId) {
73  		String type= "LF_CMS";
74  		// types from iqser database table "contents"
75  		if( contentId >= 1 && contentId <= 34) {
76  			type= "LF_DMS_pdf";
77  		}
78  		if( contentId == 25) {
79  			type= "LF_DMS_xls";
80  		}
81  		if( contentId >= 113 && contentId <= 115) {
82  			type= "DUMMY";
83  		}
84  		return type;
85  	}
86  	
87  	/**
88  	 * @param iqserWebserviceUrl URL for the iQser web service
89  	 * @param searchInput search input from user
90  	 * @param maxResultsStr maximum number of results returned by iQser
91  	 * @return a list of ContentBean objects
92  	 */
93  	public static List<ContentBean> searchIQSER(String iqserWebserviceUrl, String searchInput, String maxResultsStr) {
94  
95  		
96  		ArrayList<ContentBean> contentList= new ArrayList<ContentBean>();
97  		try {
98  			
99  			String userId= getUserId(iqserWebserviceUrl);
100 			int maxResults= checkMaxResultsValue(maxResultsStr);
101 			if (null != userId) {
102 				Collection<SearchResult> results = getServiceInstance(iqserWebserviceUrl).search(userId, searchInput, maxResults);
103 				for (SearchResult result : results) {
104 					// content is fetched later when it is needed
105 					String type= result.getType();
106 					if( type == null) {
107 						type= typeMockup(result.getContentId());
108 						// for bugs in iqser libs
109 					}
110 					float score= result.getScore();
111 					
112 					ContentBean bean= new ContentBean();
113 					bean.setContentFetched(false);		// mark bean that it contains only id and type
114 					long contentId= result.getContentId();
115 					bean.setContentId(contentId);
116 					bean.setType(type);
117 					bean.setScore(score);
118 					bean.setSearchInput(searchInput);	// to enable filtering the full text for the input
119 					contentList.add(bean);
120 				}
121 			}
122 		} catch (Exception e) {
123 			_ilog.error("error fetching results :" + e);
124 			return contentList;
125 		}
126  		
127  		return contentList;
128  	}
129 	
130 	/**
131 	 * @param iqserWebserviceUrl URL for the iQser web service
132 	 * @param contentId	used to get its related contents
133 	 * @param minScoreStr minimum score needed to fetch related content (0.0 to 1.00)
134 	 * @return  a list of ContentBean objects
135 	 */
136 	public static List<ContentBean> searchIQSER_Related(String iqserWebserviceUrl, int contentId, String minScoreStr) {
137 		
138 		ArrayList<ContentBean> relatedList= new ArrayList<ContentBean>();
139 		try {
140 			String userId= getUserId(iqserWebserviceUrl);
141 			float minScore= checkMinScoreValue(minScoreStr);	// checks if the portlet preference was OK, if not, a score of 0.1 is used
142 			if (null != userId) {
143 			
144 				Collection<SearchResult> related = getServiceInstance(iqserWebserviceUrl).getRelated(userId, contentId, minScore);
145 				for (SearchResult result : related) {
146 					// content is fetched later when it is needed
147 					String type= result.getType();
148 					if(type == null) {
149 						_ilog.error("getRelated returned a type null! Setting type to \"\".");
150 						type= "";	// to avoid null pointer exceptions
151 					}
152 					float score= result.getScore();
153 					
154 					ContentBean bean= new ContentBean();
155 					bean.setContentFetched(false);		// mark bean that it contains only id and type
156 					long relatedContentId= result.getContentId();
157 					bean.setContentId(relatedContentId);
158 					bean.setType(type);
159 					bean.setScore(score);
160 					bean.setSearchInput(null);	// no search input here
161 					
162 					// store content id of the content requesting for related data in the bean
163 					// to enable fetching of relation reasons (the method requires both id)
164 					bean.setRelatedContentId(contentId);
165 					relatedList.add(bean);
166 				}
167 
168 			}
169 		} catch (Exception e) {
170 			_ilog.error("error fetching results :" + e);
171 			return relatedList;
172 		}
173  		
174  		return relatedList;
175  	}
176 	
177 	/**
178 	 * @param iqserWebserviceUrl URL for the iQser web service
179 	 * @param contentId	used to get its related contents
180 	 * @return  a Content object
181 	 */
182 	public static Content getContent(String iqserWebserviceUrl, int contentId) {
183 		String userId= getUserId(iqserWebserviceUrl);
184 		Content content= null;
185 		try {
186 			content = getServiceInstance(iqserWebserviceUrl).getContent(userId, contentId);
187 		} catch (IQserException e) {
188 			_ilog.error("error in getContent " + e);
189 			return null;
190 		}
191  		return content;
192  	}
193 	
194 	private static float checkMinScoreValue(String minScoreStr) {
195 		float minScore= 0.1f;
196 		try {
197 			minScore= (new Float(minScoreStr)).floatValue();
198 		} catch(NumberFormatException e) {
199 			_ilog.error("Invalid String for minScore :" + minScoreStr + " Using fallback value of 0.1");
200 		}
201 		if( minScore >= 1.0f || minScore < 0.0f) {
202 			_ilog.error("Invalid number for minScore :" + minScoreStr + " Using fallback value of 0.1");
203 			minScore= 0.1f;
204 		}
205 		return minScore;
206 	}
207 	
208 	private static int checkMaxResultsValue(String maxResultsStr) {
209 		int maxResults= 0;	// default, returns all results
210 		try {
211 			maxResults= (new Integer(maxResultsStr)).intValue();
212 		} catch(NumberFormatException e) {
213 			_ilog.error("Invalid String for maxResults :" + maxResultsStr + " Using fallback value of 0");
214 		}
215 		if( maxResults < 0) {
216 			_ilog.error("Invalid number for maxResults :" + maxResultsStr + " Using fallback value of 0");
217 			maxResults= 0;
218 		}
219 		return maxResults;
220 	}
221 	
222 	/**
223 	 * @param iqserWebserviceUrl URL for the iQser web service
224 	 * @param maxLevel data up to this level are returned
225 	 * @return a root FolderBean which holds the complete tree structure
226 	 */
227 	public static FolderBean getIQSERToplevelCategories(String iqserWebserviceUrl, int maxLevel) {
228  		
229 		FolderBean rootNode= new FolderBean();
230 		
231 		List<Category> categories= null;
232 		try {
233 			categories = getServiceInstance(iqserWebserviceUrl).getTopLevelCategories();
234 
235 			//CM BEGINN: Log output from WS call
236 			if( _ilog.isInfoEnabled()) {
237 				_ilog.info("Size of categories : " + categories.size());
238 				for(Category c : categories){
239 					_ilog.info("Category <Name:" + c.getName()+"> <Parent-ID:"+c.getParentId()+">");
240 					List<Category> children = c.getChildren();
241 					if(!children.isEmpty()){
242 						for(Category child : children){
243 							_ilog.info("Category child <Name:" + c.getName()+"> <Parent-ID:"+c.getParentId()+">");
244 						}						
245 					}
246 				}
247 			}
248 			//CM END
249 			
250 			rootNode= addChildren(categories, rootNode, 0, maxLevel+1);	// call a recursive method to add children to the root node
251 			
252 		} catch (Exception e) {
253 			e.printStackTrace();
254 			return rootNode;
255 		}
256  		
257  		return rootNode;
258 	}
259 	
260 	private static FolderBean addChildren(List<Category> children, FolderBean folderBean, int level, int maxLevel) {
261 		int numChildren= children.size();
262 		level++;
263 		
264 		if( numChildren == 0 || level == maxLevel) {
265 			return folderBean;		// no children found, or maximum level reached
266 		}
267 		
268 		if( _ilog.isDebugEnabled()) {
269 			_ilog.debug("addChildren in level :" + level);
270 		}
271 		ArrayList<FolderBean> childrenList= new ArrayList<FolderBean>(numChildren);
272 		
273 		for( int index= 0; index < numChildren; index++) {
274 			Category child= children.get(index);
275 			
276 			FolderBean childBean= new FolderBean();
277 			String childName= child.getName();
278 			childBean.setFolderName(childName);
279 			List<Category> childrenOfChildren= child.getChildren();
280 			// recursive call:
281 			childBean= addChildren(childrenOfChildren, childBean, level, maxLevel);
282 			
283 			childrenList.add(childBean);
284 		}
285 		folderBean.setFolderList(childrenList);
286 		
287 		return folderBean;
288 	}
289 	
290 	/**
291 	 * @param iqserWebserviceUrl URL for the iQser web service
292 	 * @param contentList a list of contentBean objects, the objects may contain only id, type and score data
293 	 * @param start index from the SearchContainer
294 	 * @param stop index from the SearchContainer
295 	 * @return a partial list of ContentBean objects which contain fully initialized bean
296 	 */
297 	public static List<ContentBean> getPartList(String iqserWebserviceUrl, List<ContentBean> contentList, int start, int stop) {
298 		// return only a part of the list for paging
299 		ArrayList<ContentBean> partList= new ArrayList<ContentBean>();
300 		if( contentList == null) {
301 			return partList;	// empty list
302 		}
303 		int size= contentList.size();
304 		
305 		
306 		for( int index= start; index < stop; index++) {
307 			if( index >= size) {
308 				break;		// end of list reached
309 			}
310 			ContentBean bean= contentList.get(index);
311 			// check if we have all content data
312 			if( !bean.isContentFetched() ) {
313 				// bean contains only data for id and type ==> fetch missing data from iQser
314 				bean= fetchMissingData(iqserWebserviceUrl, bean);
315 				bean.setContentFetched(true);
316 				contentList.set(index, bean);	// put bean with all data in list for later reference
317 			}
318 			partList.add(bean);
319 		}
320  		return partList;
321  	}
322 	
323 	/**
324 	 * @param contentList a list of contentBean objects
325 	 * @return number of content objects
326 	 */
327 	public static int total(List<ContentBean> contentList) {
328 		if( contentList == null) {
329 			return 0;
330 		}
331 		else {
332 			return contentList.size();
333 		}
334 	}
335 	
336 	/**
337 	 * @param contentList a list of contentBean objects
338 	 * @return SortedSet containing found content types (unique)
339 	 */
340 	public static SortedSet<String> getAlltypes(List<ContentBean> contentList) {
341 		SortedSet<String> foundTypes= new TreeSet<String>();
342 		
343 		if( contentList != null && contentList.size() > 0) {
344 			String type= null;
345 			for( int index= 0; index < contentList.size(); index++) {
346 				ContentBean bean= contentList.get(index);
347 				type= bean.getType();
348 				foundTypes.add(type);
349 			}
350 		}
351 		if( _ilog.isDebugEnabled()) {
352 			_ilog.debug("**** found types :" + foundTypes);
353 		}
354 		return foundTypes;
355 	}
356 	
357 	private static ContentBean fetchMissingData(String iqserWebserviceUrl, ContentBean bean) {
358 		String userId= getUserId(iqserWebserviceUrl);
359 		boolean contentFetched= bean.isContentFetched();
360 		if( contentFetched) {
361 			return bean;	// no need to fetch data
362 		}
363 		
364 		Content content= null;
365 		long contentId= bean.getContentId();
366 		String type= bean.getType();
367 		try {
368 			content = getServiceInstance(iqserWebserviceUrl).getContent(userId, contentId);
369 		} catch (IQserException e) {
370 			_ilog.error("error fetching missing data " + e);
371 			return bean;
372 		}
373 		String fullText= content.getFulltext();
374 
375 		String searchInput= bean.getSearchInput();
376 		if( searchInput != null && searchInput.length() > 0) {
377 			String text= filterText(searchInput, fullText, 200);
378 			bean.setText(text);
379 		}
380 		else {
381 			// a search for related data ==> show beginning of text
382 			String startText= null;
383 			if( fullText.length() > 200) {
384 				startText= fullText.substring(0, 200);	// get first 200 chars
385 				startText= startText + " ...";
386 			}
387 			else {
388 				startText= fullText;
389 			}
390 			bean.setText(startText);
391 		}
392 		
393 		// check if the bean holds data called for a relation
394 		long relatedContentId= bean.getRelatedContentId();
395 		if( relatedContentId > -1) {
396 			// bean is not a result of a direct query, it was filled after a relation search
397 			try {
398 				Collection<RelationReason> relationReasons= getServiceInstance(iqserWebserviceUrl).getRelationReasons(relatedContentId, contentId);
399 				StringBuffer buf= new StringBuffer("Relation reasons: ");
400 				Iterator<RelationReason> iterator= relationReasons.iterator();
401 				while( iterator.hasNext() ) {
402 					RelationReason reason= iterator.next();
403 					String reasonText= reason.getRelationReasonText();
404 					double reasonWeight= reason.getRelationReasonWeight();
405 					String weightInfo= "" + reasonWeight;
406 					if( weightInfo.length() > 4) {
407 						// we do not want something like 0.193230152130127
408 						weightInfo= weightInfo.substring(0,4);
409 					}
410 					buf.append(reasonText);
411 					buf.append("(");
412 					buf.append(weightInfo);
413 					buf.append("), ");
414 					if( _ilog.isInfoEnabled()) {
415 						_ilog.info("reasonText : " + reasonText);
416 						_ilog.info("reasonWeight : " + reasonWeight);
417 					}
418 				}
419 				int bufSize= buf.length();
420 				buf.setLength(bufSize-2);	// get rid of last ", "
421 				bean.setRelationReasons( buf.toString());
422 				System.out.println( buf.toString());
423 			} catch (IQserTechnicalException e) {
424 				_ilog.error("error while searching for relation reasons " + e);
425 			}
426 		}
427 		
428 		if( type.startsWith("LF_DMS_")) {
429 			// a liferay dms document ==> get liferay document id
430 			String contentUrl= content.getContentUrl();
431 			int pos= contentUrl.lastIndexOf('-');
432 			String idStr= contentUrl.substring(pos+1);
433 			long dmsId= -1;
434 			try {
435 				dmsId= (new Long(idStr)).longValue();
436 			} catch (NumberFormatException e) {
437 				_ilog.error("NumberformatException for id :" + idStr);
438 			}
439 			bean.setDmsId(dmsId);
440 			// for other types the dmsId is set to -1 automatically
441 			
442 		}
443 		
444 		Collection<Attribute> attributes = content.getAttributes();
445 		SimpleDateFormat formatter= new SimpleDateFormat("dd.MM.yyyy");
446 		long modificationDateMillis= content.getModificationDate();
447 		String modificationDate= formatter.format(modificationDateMillis);
448 		bean.setChangeDate(modificationDate );	
449 		
450 		String contentProvider= content.getProvider();
451 		bean.setContentProvider(contentProvider);
452 		if( _ilog.isInfoEnabled()) {
453 			_ilog.info("Found content provider :" + contentProvider);
454 		}
455 		
456 		for (Attribute attr : attributes) {
457 			String value= attr.getValue();
458 			// allowed attributes for the title are in this order: NAME, Title, that means NAME can overwrite Title
459 			if( attr.getName().equalsIgnoreCase("title")) {
460 				bean.setTitle(value );
461 			}
462 			else if( attr.getName().equalsIgnoreCase("NAME")) {
463 				bean.setTitle(value );
464 			}
465 			// end title
466 			else if( attr.getName().equalsIgnoreCase("Author")) {
467 				bean.setAuthor(value );
468 			}
469 			else if( attr.getName().equalsIgnoreCase("CREATE_DATE")) {
470 				bean.setCreationDate(value );			// CMS content
471 			}
472 			else if( attr.getName().equalsIgnoreCase("Created")) {
473 				String timemillesStr= value;
474 				long timeMillis= 0;
475 				try {
476 					timeMillis= (new Long(timemillesStr)).longValue();
477 				} catch (NumberFormatException e) {
478 					_ilog.error("NumberformatException for timemillesStr :" + timemillesStr);
479 				}
480 				String formattedDate= formatter.format(timeMillis);
481 				bean.setCreationDate(formattedDate );			// DMS content
482 			}
483 			else if( attr.getName().equalsIgnoreCase("parentFolderId")) {
484 				String parentFolderIdStr= value;
485 				long parentFolderId= -1;
486 				try {
487 					parentFolderId= (new Long(parentFolderIdStr)).longValue();
488 				} catch (NumberFormatException e) {
489 					_ilog.error("NumberformatException for id :" + parentFolderIdStr);
490 				}
491 				bean.setParentFolderId(parentFolderId);
492 			}
493 			else if( attr.getName().equalsIgnoreCase("lfFileName")) {
494 				bean.setLfFileName(value );
495 			}
496 		}
497 	
498 		bean.setContentFetched(true);
499 		_ilog.info("fetched iQser content for contentId: " + contentId);
500 		return bean;
501 	}
502 	
503 	private static String filterText(String searchInput, String fullText, int maxLen) {
504 		
505 		String filteredText= fullText;
506 		
507 		// search for case insensitive first occurrence of search input
508 		String searchInputLowerCase= searchInput.toLowerCase();
509 		String fullTextLowerCase= fullText.toLowerCase();
510 		int pos= fullTextLowerCase.indexOf(searchInputLowerCase);
511 		// no we can forget the lower case strings
512 		if( pos == -1) {
513 			if( fullText.length() > maxLen) {
514 				return fullText.substring(0, maxLen);
515 			}
516 			else {
517 				return fullText;
518 			}	
519 		}
520 		// we found something
521 		int inputSize= searchInput.length();
522 		String searchInputWithCase= fullText.substring(pos, pos+inputSize);
523 		String beforeText= fullText.substring(0, pos); // get text from beginning to the search input
524 		int maxFragmentSize= maxLen / 2;
525 		if( beforeText.length() > maxFragmentSize) {
526 			beforeText= beforeText.substring( pos- maxFragmentSize);	// remove long irrelevant test before search input
527 			beforeText= "..." + beforeText;
528 		}
529 		// get text up to maxLen chars after the input
530 		String afterText= fullText.substring(pos + inputSize);
531 		if( afterText.length() > maxFragmentSize) {
532 				afterText= afterText.substring(0, maxFragmentSize);
533 				afterText= afterText + "...";
534 		}
535 		if( fullText.length() > maxLen) {
536 			// try to to filter meaningful text fragment
537 			int posPoint= beforeText.indexOf('.');		// Check for begin of sentence.
538 			if( posPoint > -1) {
539 				beforeText= beforeText.substring(posPoint);	// now we have the sentence before the search input
540 				beforeText= "..." + beforeText;
541 			}
542 			filteredText= beforeText + "<b class='search-hl'>" + searchInputWithCase + "</b>" + afterText;
543 			if( filteredText.length() > maxLen) {
544 				filteredText= filteredText.substring(0, maxLen+3);	// filtered text was too long (3 for possible ... at beginning)
545 				filteredText= filteredText + "...";
546 			}
547 		}
548 		else {
549 			filteredText= beforeText + "<b class='search-hl'>" + searchInputWithCase + "</b>" + afterText;
550 		}
551 		return filteredText;
552 	}
553 }