001/*
002 * Copyright 2011 The Kuali Foundation.
003 * 
004 * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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.kuali.ole.docstore.discovery.service;
017
018import org.kuali.ole.docstore.discovery.model.SearchCondition;
019import org.kuali.ole.docstore.discovery.model.SearchParams;
020import org.kuali.rice.core.api.config.property.ConfigContext;
021import org.slf4j.Logger;
022import org.slf4j.LoggerFactory;
023import org.springframework.util.StringUtils;
024
025import java.io.BufferedReader;
026import java.io.InputStreamReader;
027import java.io.UnsupportedEncodingException;
028import java.net.URL;
029import java.net.URLConnection;
030import java.net.URLDecoder;
031import java.net.URLEncoder;
032import java.text.CharacterIterator;
033import java.text.StringCharacterIterator;
034import java.util.*;
035
036public class DiscoveryServiceImpl
037        implements DiscoveryService {
038    private static final Logger LOG = LoggerFactory.getLogger(DiscoveryServiceImpl.class);
039    private static String docSearchUrl = null;
040    private static DiscoveryService discoveryService = null;
041
042    private DiscoveryServiceImpl() {
043        LOG.debug("DiscoveryServiceImpl ");
044        init();
045    }
046
047    public static DiscoveryService getInstance() {
048        if (null == discoveryService) {
049            discoveryService = new DiscoveryServiceImpl();
050        }
051        return discoveryService;
052    }
053
054    protected void init() {
055        LOG.debug("DiscoveryServiceImpl init ");
056        if(ConfigContext.getCurrentContextConfig()!=null){
057            setDocSearchUrl(ConfigContext.getCurrentContextConfig().getProperty("docSearchURL"));
058        }
059    }
060
061    public String search(SearchParams searchParams) {
062        String response = "";
063        LOG.debug("in search1");
064        String searchType = searchParams.getSearchType();
065        LOG.debug("searchType " + searchType);
066        StringBuffer query = new StringBuffer();
067        query.append(SolrServerManager.getInstance().getSolrCoreURL() + "/select");
068        if (searchType.equals(SEARCH_TYPE_MORE_FACET)) {
069            String facetPrefix = searchParams.getFacetPrefix();
070            String facetPageSize = searchParams.getFacetPageSize();
071            String facetSort = searchParams.getFacetSort();
072            String facetOffset = searchParams.getFacetOffset();
073            buildInitialQuery(query, searchParams);
074            query.append("&facet=true");
075
076            query.append("&facet.mincount=1");
077
078            query.append("&facet.prefix=" + facetPrefix);
079
080            query.append("&facet.offset=" + facetOffset);
081
082            query.append("&facet.limit=" + facetPageSize);
083
084            query.append("&facet.sort=" + facetSort);
085
086            query.append("&facet.field=" + searchParams.getFacetField());
087
088            Map<String, String> facetTermsMap = searchParams.getFacetTermsMap();
089            query.append(buildFilterQuery(facetTermsMap));
090        } else {
091            if (searchParams.getSearchFieldsList() != null && searchParams.getSearchFieldsList().size() <= 0 && (
092                    searchType != null && !searchType.equalsIgnoreCase(SEARCH_TYPE_LINK))) {
093                buildInitialQuery(query, searchParams);
094                searchParams.setSearchTerms("");
095            } else if (searchType != null && (searchType.equalsIgnoreCase(SEARCH_TYPE_ADVANCED)
096                    || searchType.equalsIgnoreCase(SEARCH_TYPE_FACET) || searchType
097                    .equalsIgnoreCase(SEARCH_TYPE_FACET_DELETE))) {
098                buildInitialQuery(query, searchParams);
099                if (searchParams.getSearchFieldsList().size() > 0) {
100                    query.append("AND(");
101                }
102                query.append(buildQueryWithSearchParameters(searchParams.getSearchFieldsList()));
103                LOG.debug("query for search terms............." + query.toString());
104                String searchTerms = buildQueryWithSearchParameters(searchParams.getSearchFieldsList())
105                        .replaceAll("_search", "");
106                try {
107                    searchTerms = URLDecoder.decode(searchTerms, "UTF-8");
108                } catch (UnsupportedEncodingException e) {
109                    e.printStackTrace();
110                }
111                searchParams.setSearchTerms(searchTerms.substring(0, searchTerms.lastIndexOf(")")));
112            }
113            if (SEARCH_TYPE_LINK.equals(searchType)) {
114                query.append("?q=id:");
115                query.append(searchParams.getLinkValue());
116            }
117            query.append("&rows=" + searchParams.getResultPageSize());
118            String docType = searchParams.getDocType();
119            LOG.info("docType-->" + docType);
120
121            query.append("&start=" + searchParams.getResultFromIndex());
122            List<String> facetList = new ArrayList<String>();
123            facetList.add(AUTHOR_FACET);
124            facetList.add(SUBJECT_FACET);
125            facetList.add(FORMAT_FACET);
126            facetList.add(LANGUAGE_FACET);
127            facetList.add(PUBLICATION_DATE_FACET);
128            facetList.add(GENRE_FACET);
129            query.append(buildQueryWithFacetParameters(facetList, 1, 10));
130            LOG.debug("sort terms buildQueryWithSortFields" + searchParams.getSortByTerms());
131            LOG.debug("sort Field buildQueryWithSortFields" + searchParams.getSortField());
132            LOG.debug("sort Order buildQueryWithSortFields" + searchParams.getSortOrder());
133            query.append(buildQueryWithSortFields(searchParams.getSortField(), searchParams.getSortOrder()));
134            query.append(buildQueryWithFieldListParameters(searchParams.getFieldList()));
135            Map<String, String> facetTermsMap = searchParams.getFacetTermsMap();
136            query.append(buildFilterQuery(facetTermsMap));
137            String holdingFields
138                    = "LocalId_display,Uri_display,HoldingsNote_display,ReceiptStatus_display,CallNumber_display,CallNumberPrefix_display,CallNumberType_display,ClassificationPart_display,Location_display,ShelvingSchemeCode_display";
139            String itemFields
140                    = "LocalId_display,ItemBarcode_display,ItemTypeFullValue_display,VendorLineItemIdentifier_display,ShelvingOrderValue_display,ShelvingSchemeValue_display,PurchaseOrderLineItemIdentifier_display,CopyNumber_display,Enumeration_display,Chronology_display,VolumeNumber_display,ItemStatus_display";
141            String instanceFields = "LocalId_display,Source_display";
142            String patronFields
143                    = "RecordNumber_display,BeginDate_display,Name_display,BorrowerType_display,BarCodeNumber_display,BarCodeStatus_display,";
144            String onixplFields
145                    = "ContractNumber_display,Title_display,Method_display,Status_display,Type_display,Licensor_display,Licensee_display";
146            String licenseBinaryFields
147                    = "Name_display,FileName_display,DateUploaded_display,Owner_display,Notes_display";
148            String eInstanceFields = "AccessStatus_display,Imprint_display,Platform_display,StatisticalSearchingCodeValue_display,EResource_name_display";
149            //query.append("&fl=" + fieldList);
150            query.append(
151                    "&fl=LocalId_display,Title_display,Author_display,Publisher_display,Description_display,Subject_display,Location_display,PublicationDate_display,Format_display,DocType,DocFormat,id,ItemLinks,BibliographicLinks,Barcode_display,instanceIdentifier,holdingsIdentifier,itemIdentifier,bibIdentifier,staffOnlyFlag"
152                    /*
153                                    + "," + "245a,245b"
154                                    + "," + "100a,110a,111a,700a,710a,711a,800a,810a,811a,400a,410a,411a"
155                                    + "," + "600a,610a,611a,630a,650a,651a,653a"
156                                    + "," + "505a"
157                                    + "," + "856u"
158                                    + "," + "260b"
159                    */ + "," + holdingFields + "," + itemFields + "," + instanceFields + "," + patronFields
160                            + "," + onixplFields + "," + licenseBinaryFields + "," + eInstanceFields);
161        }
162        LOG.debug("query---> " + query);
163        try {
164            String queryStr = query.toString().replaceAll(" ", "+");
165
166            searchParams.setSearchQuery(queryStr);
167            URL url = new URL(queryStr);
168            URLConnection urlc = null;
169            urlc = url.openConnection();
170            urlc.setDoOutput(true);
171            urlc.setAllowUserInteraction(false);
172            BufferedReader br = new BufferedReader(new InputStreamReader(urlc.getInputStream()));
173            StringBuilder sb = new StringBuilder();
174            String line;
175            while ((line = br.readLine()) != null) {
176                sb.append(line);
177                sb.append("\n");
178            }
179            br.close();
180            response = sb.toString();
181        } catch (Exception e) {
182            LOG.error("Exception:" + e.getMessage(), e);
183        }
184        return response;
185    }
186
187    public String getFieldList(String docType) {
188        String fieldList = "";
189        String holdingFields = HOLDINGS_FIELDS;
190        String itemFields = ITEM_FIELDS;
191        String instanceFields = INSTANCE_FIELDS;
192        String bibFields = BIB_FIELDS;
193
194        if (docType.equalsIgnoreCase(BIBLIOGRAPHIC)) {
195            fieldList = bibFields;
196
197        } else if (docType.equalsIgnoreCase(INSTANCE)) {
198            fieldList = instanceFields;
199
200        } else if (docType.equalsIgnoreCase(HOLDINGS)) {
201            fieldList = holdingFields;
202
203        } else if (docType.equalsIgnoreCase(ITEM)) {
204            fieldList = itemFields;
205        }
206        return fieldList;
207    }
208
209
210    public static void setDocSearchUrl(String docSearchUrl) {
211    }
212
213    public static String getDocSearchUrl() {
214        return docSearchUrl;
215    }
216
217    public String buildQuery(SearchParams searchParams) {
218        StringBuffer query = new StringBuffer();
219        String searchType = searchParams.getSearchType();
220        query.append(SolrServerManager.getInstance().getSolrCoreURL() + "/select");
221        buildInitialQuery(query, searchParams);
222        if (searchParams.getSearchFieldsList().size() > 0) {
223            query.append("AND(");
224        }
225        query.append(buildQueryWithSearchParameters(searchParams.getSearchFieldsList()));
226        if (searchParams.getResultPageSize() != null) {
227            query.append("&rows=" + searchParams.getResultPageSize());
228        }
229        if (searchParams.getResultFromIndex() != null) {
230            query.append("&start=" + searchParams.getResultFromIndex());
231        }
232        List<String> facetFieldList = searchParams.getFacetFieldList();
233        query.append(buildQueryWithFacetParameters(facetFieldList, 1, 10));
234        query.append(buildQueryWithSortFields(searchParams.getSortField(), searchParams.getSortOrder()));
235        query.append(buildQueryWithFieldListParameters(searchParams.getFieldList()));
236        Map<String, String> facetTermsMap = searchParams.getFacetTermsMap();
237        query.append(buildFilterQuery(facetTermsMap));
238
239        return query.toString();
240    }
241
242
243    public String buildQueryWithSearchParameters(List<SearchCondition> searchFieldsList) {
244        SearchCondition docSearchFieldsDTO = null;
245        StringBuffer queryStringbuffer = new StringBuffer();
246        StringBuffer highlightBuffer = new StringBuffer("&hl.fl=");
247        if (searchFieldsList != null && searchFieldsList.size() > 0) {
248
249            for (int i = 0; i < searchFieldsList.size(); i++) {
250                int searchScopeAddLimit = i;
251                docSearchFieldsDTO = searchFieldsList.get(i);
252                if (docSearchFieldsDTO.getOperator() != null) {
253                    //queryStringbuffer.append(docSearchFieldsDTO.getOperator());
254                }
255                queryStringbuffer.append("(");
256                if (docSearchFieldsDTO.getDocField().equalsIgnoreCase("all")) {
257                    queryStringbuffer.append("all_text");
258                    highlightBuffer.append("*");
259
260                } else {
261                    queryStringbuffer.append(docSearchFieldsDTO.getDocField());
262                    highlightBuffer.append(docSearchFieldsDTO.getDocField());
263
264
265                    if (i != searchFieldsList.size() - 1) {
266                        highlightBuffer.append(",");
267                    }
268                }
269                queryStringbuffer.append(":");
270                String searchScope = docSearchFieldsDTO.getSearchScope();
271                String searchText = docSearchFieldsDTO.getSearchText();
272                String searchOperator = docSearchFieldsDTO.getOperator();
273//                searchText = searchText.toLowerCase();
274                LOG.debug("searchText-->" + searchText);
275//                searchText = searchText.replaceAll("[~!(){}\\[\\]':-]+"," ");
276//                String modifiedSearchText = searchText.replaceAll("[~!(){}<>\\[\\]':\\-\\\\^]+", " ").toLowerCase();
277                String modifiedSearchText = getModifiedText(searchText);
278                String searchTextVal = null;
279                if (modifiedSearchText.length() > 0) {
280                    queryStringbuffer.append("(");
281                    if (searchScope.equalsIgnoreCase("AND")) {
282                        modifiedSearchText = modifiedSearchText.replaceAll("\\s+", " ");
283                        searchTextVal = modifiedSearchText.trim().replace(" ", " AND ");
284                    } else if (searchScope.equalsIgnoreCase("OR")) {
285                        modifiedSearchText = modifiedSearchText.replaceAll("\\s+", " ");
286                        searchTextVal = modifiedSearchText.trim().replace(" ", " OR ");
287                    } else if (searchScope.equalsIgnoreCase("phrase")) {
288                        searchTextVal = "\"" + searchText + "\"";
289                    }
290                    try {
291                        searchTextVal = URLEncoder.encode(searchTextVal, "UTF-8");
292                    } catch (UnsupportedEncodingException e) {
293                        e.printStackTrace();
294                    }
295                    queryStringbuffer.append(searchTextVal);
296                    LOG.debug("searchTextVal............" + searchTextVal + "........" + queryStringbuffer.toString());
297                    queryStringbuffer.append(")");
298                }
299                queryStringbuffer.append(")");
300                ++searchScopeAddLimit;
301                if (searchScopeAddLimit != searchFieldsList.size()) {
302                    queryStringbuffer.append(searchOperator);
303                }
304            }
305            queryStringbuffer.append(")");
306            String highLight = highlightBuffer.toString().replace("LocalId_search", "LocalId_display");
307            queryStringbuffer.append(highLight);
308
309            queryStringbuffer.append("&hl=true");
310        }
311
312
313        return queryStringbuffer.toString();
314    }
315
316    public String buildQueryWithFacetParameters(List<String> facetsParametersList, int facetMinCount, int facetLimit) {
317        String facetFieldName = "";
318        String queryWithFacetParameters = "";
319        if (facetsParametersList != null) {
320            StringBuffer facetsQueryStringbuffer = new StringBuffer();
321
322            facetsQueryStringbuffer.append("&facet=true");
323
324            facetsQueryStringbuffer.append("&facet.mincount=" + facetMinCount);
325
326            facetsQueryStringbuffer.append("&");
327
328            for (int i = 0; i < facetsParametersList.size(); i++) {
329                facetFieldName = facetsParametersList.get(i);
330                facetsQueryStringbuffer.append("facet.field=" + facetFieldName);
331                facetsQueryStringbuffer.append("&");
332
333            }
334            queryWithFacetParameters = facetsQueryStringbuffer.substring(0, facetsQueryStringbuffer.length() - 1);
335        }
336        return queryWithFacetParameters;
337    }
338
339
340    public String buildQueryWithSortFields(String sortField, String sortOrder) {
341        StringBuffer sortFieldsQuery = new StringBuffer();
342        if (null != sortField) {
343            sortFieldsQuery.append("&");
344            sortFieldsQuery.append("sort=");
345            sortFieldsQuery.append(sortField);
346            if (null != sortOrder) {
347                sortFieldsQuery.append(" ");
348                sortFieldsQuery.append(sortOrder);
349            }
350        }
351        return sortFieldsQuery.toString();
352    }
353
354    public String buildQueryWithFieldListParameters(List<String> fieldsList) {
355        String queryWithFieldListParameters = "";
356        if (fieldsList != null) {
357            StringBuffer fieldsListQueryStringbuffer = new StringBuffer();
358            fieldsListQueryStringbuffer.append("&");
359            fieldsListQueryStringbuffer.append("fl=");
360            for (int i = 0; i < fieldsList.size(); i++) {
361                fieldsListQueryStringbuffer.append(fieldsList.get(i));
362                fieldsListQueryStringbuffer.append(",");
363            }
364            queryWithFieldListParameters = fieldsListQueryStringbuffer
365                    .substring(0, fieldsListQueryStringbuffer.length() - 1);
366        }
367        return queryWithFieldListParameters;
368    }
369
370    public static String convertListToStringFieldValues(Map<String, String> map) {
371        StringBuffer sb = new StringBuffer();
372        Set set = map.keySet();
373        Iterator<String> ite = set.iterator();
374        while (ite.hasNext()) {
375            sb.append(ite.next());
376            sb.append("|");
377        }
378        String str = sb.toString();
379        if (str != null && str.length() > 0) {
380            str = str.substring(0, str.length() - 1);
381        }
382        return str;
383    }
384
385    public String buildFilterQuery(Map<String, String> facetTermsMap) {
386        String filterQuery = "";
387        int temp = 0;
388        int tokenInt = 0;
389        if ((null != facetTermsMap) && (facetTermsMap.size() > 0)) {
390
391            String facetTerms = convertListToStringFieldValues(facetTermsMap);
392            StringBuffer facetQueryTemp = new StringBuffer();
393            facetQueryTemp.append("&terms=" + facetTerms);
394            facetQueryTemp.append("&fq=");
395            StringTokenizer sttoken = new StringTokenizer(facetTerms, "|");
396            String token;
397            while (sttoken.hasMoreElements()) {
398                token = sttoken.nextToken();
399                facetQueryTemp.append("(");
400                facetQueryTemp.append(facetTermsMap.get(token));
401                facetQueryTemp.append(":\"");
402                facetQueryTemp.append(token);
403                facetQueryTemp.append("\")");
404                facetQueryTemp.append("AND");
405            }
406            if (facetQueryTemp.length() > 0) {
407                filterQuery = (facetQueryTemp.toString().substring(0, facetQueryTemp.toString().length() - 3));
408            }
409        }
410        return filterQuery;
411    }
412
413
414    /**
415     * @param query
416     * @param searchParams Usage: This method builds initial SOLR query with DocType and DocFormat as SolrParams
417     */
418    private void buildInitialQuery(StringBuffer query, SearchParams searchParams) {
419        query.append("?q=");
420        //query.append("(DocType:" + searchParams.getDocType() + ")");
421        if (searchParams.getDocFormat().equalsIgnoreCase("marc")) {
422            query.append("((DocType:" + searchParams.getDocType() + ")" + "OR(DocType:item))");
423        } else {
424            query.append("(DocType:" + searchParams.getDocType() + ")");
425        }
426
427        if (searchParams.getDocFormat() != null && !searchParams.getDocFormat().equalsIgnoreCase("all")) {
428            if ("dublin".equals(searchParams.getDocFormat())) {
429                searchParams.setDocFormat("dublin");
430            }
431            query.append("AND(DocFormat:" + searchParams.getDocFormat() + ")");
432        }
433    }
434
435    private String getModifiedText(String searchText) {
436        StringBuffer modifiedText = new StringBuffer();
437        StringCharacterIterator stringCharacterIterator = new StringCharacterIterator(searchText);
438        char character = stringCharacterIterator.current();
439        while (character != CharacterIterator.DONE) {
440
441            if (character == '\\') {
442                modifiedText.append("\\\\");
443            } else if (character == '?') {
444                modifiedText.append("\\?");
445            } else if (character == '*'  && StringUtils.isEmpty(modifiedText.toString())) {
446                modifiedText.append("\\*");
447            } else if (character == '+') {
448                modifiedText.append("\\+");
449            } else if (character == ':') {
450                modifiedText.append("\\:");
451            } else if (character == '{') {
452                modifiedText.append("\\{");
453            } else if (character == '}') {
454                modifiedText.append("\\}");
455            } else if (character == '[') {
456                modifiedText.append("\\[");
457            } else if (character == ']') {
458                modifiedText.append("\\]");
459            } else if (character == '(') {
460                modifiedText.append("\\(");
461            } else if (character == ')') {
462                modifiedText.append("\\)");
463            } else if (character == '^') {
464                modifiedText.append("\\^");
465            } else if (character == '~') {
466                modifiedText.append("\\~");
467            } else if (character == '-') {
468                modifiedText.append("\\-");
469            } else if (character == '!') {
470                modifiedText.append("\\!");
471            } else if (character == '\'') {
472                modifiedText.append("\\'");
473            } else if (character == '@') {
474                modifiedText.append("\\@");
475            } else if (character == '#') {
476                modifiedText.append("\\#");
477            } else if (character == '$') {
478                modifiedText.append("\\$");
479            } else if (character == '%') {
480                modifiedText.append("\\%");
481            } else {
482                //the char is not a special one
483                //add it to the result as is
484                modifiedText.append(character);
485            }
486            character = stringCharacterIterator.next();
487        }
488
489        return modifiedText.toString().toLowerCase();
490    }
491
492}