package org.docear.plugin.services.features.documentretrieval;
import java.io.File;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.UnknownHostException;
import java.rmi.UnexpectedException;
import java.util.Collection;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.swing.JProgressBar;
import javax.swing.ProgressMonitor;
import org.apache.commons.lang.NullArgumentException;
import org.docear.plugin.core.util.CoreUtils;
import org.docear.plugin.services.ADocearServiceFeature;
import org.docear.plugin.services.ServiceController;
import org.docear.plugin.services.features.documentretrieval.documentsearch.view.DocumentSearchView;
import org.docear.plugin.services.features.documentretrieval.documentsearch.workspace.ShowDocumentSearchNode;
import org.docear.plugin.services.features.documentretrieval.model.DocumentEntries;
import org.docear.plugin.services.features.documentretrieval.model.DocumentModelNode;
import org.docear.plugin.services.features.documentretrieval.model.DocumentsModel;
import org.docear.plugin.services.features.documentretrieval.recommendations.view.RecommendationsView;
import org.docear.plugin.services.features.documentretrieval.recommendations.workspace.ShowRecommendationsNode;
import org.docear.plugin.services.features.documentretrieval.view.DocumentView;
import org.docear.plugin.services.features.documentretrieval.workspace.DownloadFolderNode;
import org.docear.plugin.services.features.io.DocearConnectionProvider;
import org.docear.plugin.services.features.io.DocearServiceResponse;
import org.docear.plugin.services.features.io.DocearServiceResponse.Status;
import org.docear.plugin.services.features.io.UnauthorizedException;
import org.docear.plugin.services.features.user.DocearUser;
import org.docear.plugin.services.xml.DocearXmlBuilder;
import org.docear.plugin.services.xml.DocearXmlElement;
import org.docear.plugin.services.xml.DocearXmlRootElement;
import org.freeplane.core.ui.components.UITools;
import org.freeplane.core.ui.ribbon.event.IActionEventListener;
import org.freeplane.core.user.IUserAccountChangeListener;
import org.freeplane.core.user.UserAccountChangeEvent;
import org.freeplane.core.user.UserAccountController;
import org.freeplane.core.util.LogUtils;
import org.freeplane.core.util.TextUtils;
import org.freeplane.features.mode.ModeController;
import org.freeplane.n3.nanoxml.IXMLParser;
import org.freeplane.n3.nanoxml.IXMLReader;
import org.freeplane.n3.nanoxml.StdXMLReader;
import org.freeplane.n3.nanoxml.XMLParserFactory;
import org.freeplane.plugin.workspace.URIUtils;
import org.freeplane.plugin.workspace.WorkspaceController;
import org.freeplane.plugin.workspace.mindmapmode.FileFolderDropHandler;
import org.freeplane.plugin.workspace.model.AWorkspaceTreeNode;
import org.freeplane.plugin.workspace.nodes.FolderLinkNode;
public abstract class DocumentRetrievalController extends ADocearServiceFeature {
private static Object mutex = new Object();
private static boolean isRequesting;
public static final long RECOMMENDATIONS_AUTOSHOW_INTERVAL = 1000*60*60*24*7; // every 7 days in milliseconds
private static IActionEventListener aeListener = new RibbonActionEventListener();
private File downloadsFolder;
private FolderLinkNode downloadsNode;
protected int documentsAvailable;
private Integer documentsSetId;
protected static DocumentRetrievalController controller;
private DocumentEntries autoRecommendations;
protected Boolean AUTO_RECOMMENDATIONS_LOCK = false;
protected static DocumentView view;
protected abstract DocearServiceResponse getRequestResponse(boolean userRequest);
protected abstract void sendReceiveConfirmation(final DocumentsModel model);
public abstract void refreshDocuments();
public static void setController(DocumentRetrievalController ctrl) {
controller = ctrl;
}
public static DocumentRetrievalController getController() {
return controller;
}
public static void initializeDocumentSearcher() {
if(view == null) {
view = new DocumentSearchView();
}
}
public static void initializeRecommendations() {
if(view == null) {
view = new RecommendationsView();
}
}
public static void destroyView() {
view = null;
}
public static DocumentView getView() {
return view;
}
public DocumentEntries getNewDocuments(boolean userRequest) throws UnknownHostException, UnauthorizedException, UnexpectedException, AlreadyInUseException {
synchronized (mutex ) {
if(isRequesting) {
throw new AlreadyInUseException();
}
isRequesting = true;
LogUtils.info("requesting recommendations");
}
try {
DocearUser user = ServiceController.getCurrentUser();
String userName = user.getUsername();
if (!CoreUtils.isEmpty(userName)) {
DocearServiceResponse response = getRequestResponse(userRequest);
if (response == null) {
return null;
}
if (response.getStatus() == Status.OK) {
try {
DocearXmlBuilder xmlBuilder = new DocearXmlBuilder();
// LogUtils.info(response.getContentAsString());
IXMLReader reader = new StdXMLReader(new InputStreamReader(response.getContent(), "UTF8"));
IXMLParser parser = XMLParserFactory.createDefaultXMLParser();
parser.setBuilder(xmlBuilder);
parser.setReader(reader);
parser.parse();
DocearXmlRootElement result = (DocearXmlRootElement) xmlBuilder.getRoot();
Collection<DocearXmlElement> documents = result.findAll("document");
DocumentEntries documentEntries = new DocumentEntries(documents.size());
java.util.Iterator<DocearXmlElement> iterator = documents.iterator();
int id = 0;
if (iterator.hasNext()) {
DocearXmlElement document = iterator.next();
//"recommendations" element
DocearXmlElement documentsElement = document.getParent().getParent();
this.setDocumentsSetId(Integer.valueOf(documentsElement.getAttributeValue("id")));
try {
this.documentsAvailable = Integer.valueOf(documentsElement.getAttributeValue("documentsAvailable"));
}
catch(NumberFormatException ignore) {}
String evaluationLabel = documentsElement.getAttributeValue("evaluationLabel");
evaluationLabel = ((evaluationLabel == null || evaluationLabel.trim().isEmpty()) ? "How good are these recommendations?" : evaluationLabel);
String strId = documentsElement.getAttributeValue("id");
id = ((strId == null || strId.trim().isEmpty()) ? 0 : Integer.parseInt(strId));
documentEntries.addDocumentEntry(id, null, documentsElement.getAttributeValue("descriptor"), evaluationLabel, null, null, false);
}
for (DocearXmlElement document : documents) {
try {
// exclude reference documents -> may not have a sourceid and the parent does not have a fulltext attribute
if(!document.hasParent("document")) {
String title = document.find("title").getContent();
String url = document.find("sourceid").getContent();
DocearXmlElement recommendationElement = document.getParent();
String prefix = recommendationElement.getAttributeValue("prefix");
String click = recommendationElement.getAttributeValue("fulltext");
boolean highlighted = ("true".equals(recommendationElement.getAttributeValue("highlighted")) ? true:false);
documentEntries.addDocumentEntry(id, prefix, title, "", url, click, highlighted);
}
}
catch (Exception e) {
LogUtils.warn("error while parsing recommendations: " + e.getMessage());
}
}
return documentEntries;
}
catch (Exception e) {
LogUtils.severe(e);
}
}
else if (response.getStatus() == Status.NO_CONTENT) {
return null;
}
else if (response.getStatus() == Status.UNKNOWN_HOST) {
throw new UnknownHostException("no connection");
}
else if (response.getStatus() == Status.UNAUTHORIZED) {
throw new UnauthorizedException("unauthorized");
}
else {
throw new UnexpectedException("unkown");
}
}
else {
throw new IllegalStateException("no username set");
}
return null;
}
finally {
synchronized (mutex ) {
isRequesting = false;
}
LogUtils.info("finished recommendation request");
}
}
public void refreshDocuments(DocumentEntries documentEntries) {
DocumentsModel model = null;
if(documentEntries == null) {
try {
model = requestDocuments();
} catch (AlreadyInUseException e) {
return;
}
}
else {
model = new DocumentsModel(documentEntries);
}
updateDocumentView(model);
}
public void updateDocumentView(DocumentsModel model) {
if(model == null) {
model = getExceptionModel(new NullArgumentException("model is null"));
}
try {
if (view != null) {
view.setModel(model);
}
}
catch (NoSuchElementException e) {
LogUtils.severe(e);
}
}
protected DocumentsModel requestDocuments() throws AlreadyInUseException {
DocumentsModel model = null;
if (ServiceController.getCurrentUser().isRecommendationsEnabled()) {
final ProgressMonitor monitor = new ProgressMonitor(UITools.getFrame(), TextUtils.getText("recommendations.request.wait.text"), null, 0, 100);
monitor.setMillisToDecideToPopup(0);
monitor.setMillisToPopup(0);
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
Future<DocumentsModel> task = executor.submit(new Callable<DocumentsModel>() {
public DocumentsModel call() throws Exception {
DocumentsModel model = null;
monitor.setProgress(1);
((JProgressBar)monitor.getAccessibleContext().getAccessibleChild(1)).setIndeterminate(true);
long l = System.currentTimeMillis();
DocumentEntries documentEntries = getNewDocuments(true);
LogUtils.info("recommendation request time: "+(System.currentTimeMillis()-l));
model = new DocumentsModel(documentEntries);
return model;
}
});
model = task.get(DocearConnectionProvider.CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);
if (model.getChildCount(model.getRootNode()) > 0) {
//handshake -> send receive confirmation
sendReceiveConfirmation(model);
}
}
catch (Exception e) {
executor.shutdownNow();
if(e instanceof AlreadyInUseException) {
throw (AlreadyInUseException)e;
}
LogUtils.warn(e);
model = getExceptionModel(e);
}
finally {
monitor.close();
}
}
else {
model = new DocumentsModel(null);
}
return model;
}
protected DocumentsModel getExceptionModel(Exception e) {
DocumentsModel model = new DocumentsModel();
String message = "";
if (e instanceof UnknownHostException) {
message = TextUtils.getText("recommendations.error.no_connection");
}
else {
message = TextUtils.getText("recommendations.error.unknown");
}
model.setRoot(DocumentModelNode.createNoRecommendationsNode(message));
return model;
}
public void closeDocumentView() {
if (controller != null) {
shutdown();
controller = null;
if (view != null) {
view.close();
}
}
}
public void setAutoRecommendations(DocumentEntries recommendations) {
this.autoRecommendations = recommendations;
}
public DocumentEntries getAutoRecommendations() {
while(isLocked()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
return autoRecommendations;
}
private boolean isLocked() {
synchronized (AUTO_RECOMMENDATIONS_LOCK ) {
return AUTO_RECOMMENDATIONS_LOCK;
}
}
public boolean isAutoRecommending() {
synchronized (AUTO_RECOMMENDATIONS_LOCK ) {
return AUTO_RECOMMENDATIONS_LOCK;
}
}
public URI getDownloadsFolder() {
return downloadsFolder.toURI();
}
public void refreshDownloadsFolder() {
WorkspaceController.getCurrentModeExtension().getView().expandPath(downloadsNode.getTreePath());
downloadsNode.refresh();
}
@Override
protected void installDefaults(ModeController modeController) {
AWorkspaceTreeNode wsRoot = WorkspaceController.getModeExtension(modeController).getModel().getRoot();
wsRoot.insertChildNode(new ShowDocumentSearchNode(), 0);
wsRoot.insertChildNode(new ShowRecommendationsNode(), 1);
downloadsNode = new DownloadFolderNode();
updateDownloadNode();
wsRoot.insertChildNode(downloadsNode,2);
UserAccountController.getController().addUserAccountChangeListener(new IUserAccountChangeListener() {
public void activated(UserAccountChangeEvent event) {
if(event.getUser() instanceof DocearUser) {
try {
updateDownloadNode();
downloadsNode.refresh();
}
catch (Exception e) {
LogUtils.warn("Could not switch download folder node: "+ e.getMessage());
}
}
}
public void aboutToDeactivate(UserAccountChangeEvent event) {}
});
WorkspaceController.getModeExtension(modeController).getView().getTransferHandler().registerNodeDropHandler(DownloadFolderNode.class, new FileFolderDropHandler());
modeController.getUserInputListenerFactory().getRibbonBuilder().getRibbonActionEventHandler().addListener(aeListener);
}
private void updateDownloadNode() {
downloadsFolder = new File( URIUtils.getFile(ServiceController.getController().getUserSettingsHome()),"downloads");
if(!downloadsFolder.exists()) {
try {
downloadsFolder.mkdirs();
}
catch (Exception e) {
LogUtils.warn("Exception in org.docear.plugin.services.features.recommendations.RecommendationsController.updateDownloadNode():"+ e.getMessage());
}
}
downloadsNode.setPath(downloadsFolder.toURI());
}
public int getDocumentsAvailable() {
return documentsAvailable;
}
public Integer getDocumentsSetId() {
return documentsSetId;
}
public void setDocumentsSetId(Integer documentsSetId) {
this.documentsSetId = documentsSetId;
}
}