/* See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* Esri Inc. licenses this file to You 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 com.esri.gpt.control.rest.search;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.esri.gpt.catalog.discovery.rest.RestQuery;
import com.esri.gpt.catalog.search.ASearchEngine;
import com.esri.gpt.catalog.search.GetRecordsGenerator;
import com.esri.gpt.catalog.search.SearchCriteria;
import com.esri.gpt.catalog.search.SearchEngineCSW;
import com.esri.gpt.catalog.search.SearchEngineFactory;
import com.esri.gpt.catalog.search.SearchRequestDefinition;
import com.esri.gpt.catalog.search.SearchResult;
import com.esri.gpt.framework.context.RequestContext;
import com.esri.gpt.framework.jsf.MessageBroker;
/**
* The Class SearchThread.
*
* @author UM,TM
*/
public class SearchThread extends Thread {
// clas variables ==============================================================
/** class logger *. */
private Logger LOG = Logger.getLogger(SearchThread.class.getCanonicalName());
// instance variables ==========================================================
/** The is working. */
private boolean isWorking = false;
private boolean stop;
/** The rid. */
private String rid;
/** The search context. */
private SearchContext searchContext;
/** The search status. */
private SearchStatus searchStatus = new SearchStatus();
/** The listeners. */
private ArrayList<ISearchListener> listeners;
private ASearchEngine searchEngine;
private CountDownLatch countDownLatch;
private volatile boolean timeUp;
private volatile ReentrantReadWriteLock globalLock = new ReentrantReadWriteLock();
private volatile Lock lockListenerEvent = globalLock.readLock();
private volatile Lock lockNotifyTimeUp = globalLock.writeLock();
// constructors ================================================================
/**
* Instantiates a new search thread.
*
* @param context
* the context
* @param rid
* the rid
*/
public SearchThread(SearchContext context, String rid, ASearchEngine engine,
CountDownLatch countDownLatch) {
this.setSearchContext(context);
this.setRID(rid);
this.setSearchEngine(engine);
this.countDownLatch = countDownLatch;
this.getSearchStatus().setRid(rid);
}
// properties ==================================================================
/**
* Gets the flag indicating if the search is in a working state.
*
* @return if the search is working
*/
public boolean getIsWorking() {
return this.isWorking;
}
/**
* Sets the flag indicating if the search is in a working state.
*
* @param isWorking
* <code>true</code> if the search is working
*/
public void setIsWorking(boolean isWorking) {
this.isWorking = isWorking;
if(isWorking == true) {
this.writeStatus(SearchStatus.STATUSTYPE_WORKING);
}
}
/**
* Gets the max search time.
*
* @return the max search time
*/
private int getMaxSearchTime() {
return this.getSearchContext().getMaxSearchTime();
}
/**
* Gets the search engine.
*
* @return the search engine
*/
public ASearchEngine getSearchEngine() {
return searchEngine;
}
/**
* Sets the search engine.
*
* @param searchEngine
* the new search engine
*/
public void setSearchEngine(ASearchEngine searchEngine) {
this.searchEngine = searchEngine;
}
/**
* Gets the RID.
*
* @return the RID
*/
public String getRID() {
return this.rid;
}
/**
* Sets the RID.
*
* @param rid
* the RID
*/
public void setRID(String rid) {
this.rid = rid;
}
/**
* Gets the search context.
*
* @return the search context
*/
public SearchContext getSearchContext() {
return this.searchContext;
}
/**
* Sets the search context.
*
* @param searchContext
* the search context
*/
public void setSearchContext(SearchContext searchContext) {
this.searchContext = searchContext;
}
public boolean isTimeUp() {
return timeUp;
}
/**
* Sets the time up. Called to notify the thread that its time is up.
*
* @param timeUp the new time up
*/
public void setTimeUp(boolean timeUp) {
lockNotifyTimeUp.lock();
try {
LOG.finer("Setting time up when status is " +
this.getSearchStatus().getStatusType());
if(timeUp == true &&
!(this.getSearchStatus().getStatusType()
.equals(SearchStatus.STATUSTYPE_COMPLETED) ||
this.getSearchStatus().getStatusType()
.equals(SearchStatus.STATUSTYPE_FAILED))
) {
this.getSearchStatus().setStatusType(SearchStatus.STATUSTYPE_SEARCH_TIMEOUT);
this.fireSearchEventWorker();
this.timeUp = timeUp;
}
} finally {
lockNotifyTimeUp.unlock();
}
}
/**
* Gets the search status.
*
* @return the search status
*/
public SearchStatus getSearchStatus() {
return this.searchStatus;
}
/**
* Sets the search status.
*
* @param searchStatus
* the search status
*/
public void setSearchStatus(SearchStatus searchStatus) {
this.searchStatus = searchStatus;
}
// methods =====================================================================/**
/**
* Write status.
*
* @param status the status
*/
private void writeStatus(String status) {
this.getSearchStatus().setStatusType(status);
this.fireSearchEvent();
}
/**
* Adds the action listener.
*
* @param listener
* the listener
*/
public void addActionListener(ISearchListener listener) {
if (this.listeners == null) {
listeners = new ArrayList<ISearchListener>();
}
listeners.add(listener);
}
/**
* Removes the action listener.
*
* @param listener
* the listener
*/
public void removeActionListener(ISearchListener listener) {
if (listeners == null || listeners.size() < 1) {
return;
}
listeners.remove(listener);
}
/**
* Fire search event.
*/
private void fireSearchEvent() {
lockListenerEvent.lock();
try {
this.fireSearchEventWorker();
} finally {
lockListenerEvent.unlock();
}
}
/**
* Fire search event worker. Use fire search event. This method does not
* have lockign and is called by fireSearchEvent;
*/
private void fireSearchEventWorker() {
LOG.fine("Writing " + this.getSearchStatus().getStatusType()
+ " to listners time up = " + this.isTimeUp());
if (this.isTimeUp() || listeners == null || listeners.size() < 1) {
return;
}
DistributedSearchEvent event = new DistributedSearchEvent(this, this
.getSearchStatus());
for (ISearchListener listener : listeners) {
try {
listener.searchEvent(event);
} catch (Exception e) {
LOG.log(Level.WARNING, "Error in listener", e);
}
}
}
/**
* Runs the process.
*/
public void run() {
try {
setIsWorking(true);
runWorker();
} finally {
if(countDownLatch != null) {
countDownLatch.countDown();
}
}
}
/**
* Runs the thread process
*
*/
public void runWorker() {
RequestContext rc = null;
try {
// rc = RequestContext.extract(this.getSearchContext().getHttpRequest());
rc = this.getSearchContext().getRequestContext();
RestQuery query = this.getSearchContext().getRestQuery();
SearchStatus status = this.getSearchStatus();
status.setStartTimestamp(new Timestamp(System.currentTimeMillis()));
if (this.isTimeUp()) {
throw new Exception("This thread has been stopped");
}
SearchResult result = new SearchResult();
SearchCriteria criteria = this.getSearchContext().getSearchCriteria();
ASearchEngine engine = this.getSearchEngine();
engine.setRequestDefinition(new SearchRequestDefinition(criteria, result));
engine.setHitsOnly(true);
engine.setConnectionTimeoutMs(this.getMaxSearchTime());
engine.setResponseTimeout(this.getMaxSearchTime());
if (LOG.isLoggable(Level.FINER)) {
LOG.finer("Starting SEARCH IN thread id= " + this.getId() + ", rid="
+ this.getRID());
}
if (this.rid.equalsIgnoreCase("local")) {
GetRecordsGenerator grg = new GetRecordsGenerator(rc);
if (engine.getHitsOnly()) {
grg.setResultType("HITS");
}
String cswRequest = grg.generateCswRequest(query);
SearchEngineCSW csw = (SearchEngineCSW) engine;
if (this.isTimeUp()) {
throw new Exception("This threads time is up");
}
csw.doSearch(cswRequest);
} else {
if (this.isTimeUp()) {
throw new Exception("This threads time is up");
}
engine.doSearch();
}
if (LOG.isLoggable(Level.FINER)) {
LOG.finer("ENDING SEARCH IN thread id= " + this.getId() + ", rid="
+ this.getRID());
}
status.setHitCount(result.getMaxQueryHits());
this.getSearchStatus().setEndTimestamp(
new Timestamp(System.currentTimeMillis()));
this.writeStatus(SearchStatus.STATUSTYPE_COMPLETED);
} catch (Exception e) {
this.getSearchStatus().setMessage(e.getMessage());
this.writeStatus(SearchStatus.STATUSTYPE_FAILED);
LOG.log(Level.WARNING, "Error during distributed search", e);
} finally {
this.getSearchStatus().setEndTimestamp(
new Timestamp(System.currentTimeMillis()));
this.setIsWorking(false);
}
}
}