/*
* The MIT License
*
* Copyright 2013 Tim Boudreau.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.timboudreau.netbeans.mongodb;
import com.mongodb.MongoClient;
import java.awt.Image;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.ConnectException;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Action;
import org.netbeans.api.annotations.common.StaticResource;
import org.openide.awt.StatusDisplayer;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Sheet;
import org.openide.util.ImageUtilities;
import org.openide.util.RequestProcessor;
import org.openide.util.WeakListeners;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;
import org.openide.util.lookup.Lookups;
import org.openide.util.lookup.ProxyLookup;
/**
*
* @author Tim Boudreau
*/
class OneConnectionNode extends AbstractNode implements PropertyChangeListener {
private MongoClient mongo;
private static final Logger logger = Logger.getLogger(OneConnectionNode.class.getName());
private final Object lock = new Object();
private final Disconnecter disconnecter = new Disconnecter();
private final InstanceContent content;
private final Problems problems = new Problems();
private final ConnectionConverter converter = new ConnectionConverter();
private volatile boolean problem;
@StaticResource
private final static String ERROR_BADGE = "com/timboudreau/netbeans/mongodb/error.png"; //NOI18N
OneConnectionNode(ConnectionInfo connection) {
this(connection, new InstanceContent());
}
OneConnectionNode(ConnectionInfo connection, InstanceContent content) {
this(connection, content, new ProxyLookup(new AbstractLookup(content), Lookups.fixed(connection)));
}
OneConnectionNode(ConnectionInfo connection, InstanceContent content, ProxyLookup lkp) {
super(Children.create(new OneConnectionChildren(lkp), true), lkp);
this.content = content;
content.add(problems);
content.add(connection, converter);
setDisplayName(connection.getDisplayName());
setName(connection.id());
setIconBaseWithExtension(MongoServicesNode.MONGO_CONNECTION);
connection.addPropertyChangeListener(WeakListeners.propertyChange(this, connection));
}
private void setProblem(boolean problem) {
this.problem = problem;
}
private boolean isProblem() {
return problem;
}
@Override
public Image getIcon(int ignored) {
Image result = super.getIcon(ignored);
if (isProblem()) {
Image errorBadge = ImageUtilities.loadImage(ERROR_BADGE);
result = ImageUtilities.mergeImages(result, errorBadge, 0, 0);
}
return result;
}
@Override
public Image getOpenedIcon(int ignored) {
return getIcon(ignored);
}
@Override
public Action[] getActions(boolean ignored) {
Action[] orig = super.getActions(ignored);
Action[] nue = new Action[orig.length + 1];
System.arraycopy(orig, 0, nue, 1, orig.length);
nue[0] = new DisconnectAction(getLookup());
return nue;
}
private MongoClient connect(boolean create) {
synchronized (lock) {
if (create && (mongo == null || !mongo.getConnector().isOpen())) {
ConnectionInfo connection = getLookup().lookup(ConnectionInfo.class);
try {
mongo = new MongoClient(connection.getHost(), connection.getPort());
mongo.getDatabaseNames();
content.add(disconnecter);
setProblem(false);
} catch (Exception ex) {
problems.handleException(ex, null);
if (ex instanceof ConnectException) {
// replaces the children so we can try again
disconnecter.close();
}
}
}
}
return mongo;
}
private class Problems extends ConnectionProblems {
@Override
public void handleException(Exception ex, String logMessage) {
ConnectionInfo connection = getLookup().lookup(ConnectionInfo.class);
if (logMessage == null) {
logMessage = ex.getMessage();
}
if (logMessage == null) {
logMessage = "Problem connecting to " + connection;
}
logger.log(Level.FINE, logMessage, ex); //NOI18N
String msg = ex.getLocalizedMessage();
if (msg != null) {
setShortDescription(msg);
}
setProblem(true);
StatusDisplayer.getDefault().setStatusText(logMessage);
fireIconChange();
}
@Override
protected <T> T retry(Callable<T> callable, Exception ex) throws Exception {
try {
MongoClient client;
synchronized (lock) {
client = mongo;
mongo = null;
}
if (client != null && client.getConnector().isOpen()) {
client.close();
}
} catch (Exception e) {
disconnecter.close();
logger.log(Level.INFO, "Reconnecting", e);
} finally {
ConnectionInfo info = getLookup().lookup(ConnectionInfo.class);
content.remove(info, converter);
content.add(info, converter);
}
connect(true);
return callable.call();
}
}
@Override
protected Sheet createSheet() {
Sheet sheet = Sheet.createDefault();
Sheet.Set set = Sheet.createPropertiesSet();
set.put(new ConnectionNameProperty(getLookup()));
set.put(new ConnectionHostProperty(getLookup()));
set.put(new ConnectionPortProperty(getLookup()));
sheet.put(set);
return sheet;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
switch (evt.getPropertyName()) {
case ConnectionInfo.PREFS_KEY_DISPLAY_NAME:
setDisplayName((String) evt.getNewValue());
break;
case ConnectionInfo.PREFS_KEY_HOST:
case ConnectionInfo.PREFS_KEY_PORT:
MongoDisconnect disconnect = getLookup().lookup(MongoDisconnect.class);
if (disconnect != null) {
disconnect.close();
}
break;
}
}
private class Disconnecter extends MongoDisconnect implements Runnable {
@Override
public void close() {
setChildren(Children.create(new OneConnectionChildren(getLookup()), true));
RequestProcessor.getDefault().post(this);
setProblem(false);
setShortDescription("");
}
@Override
public void run() {
MongoClient client;
try {
synchronized (lock) {
client = mongo;
mongo = null;
}
if (client != null) {
client.close();
}
} finally {
ConnectionInfo info = getLookup().lookup(ConnectionInfo.class);
content.remove(info, converter);
content.add(info, converter);
}
}
}
private class ConnectionConverter implements InstanceContent.Convertor<ConnectionInfo, MongoClient> {
@Override
public MongoClient convert(ConnectionInfo t) {
return connect(true);
}
@Override
public Class<? extends MongoClient> type(ConnectionInfo t) {
return MongoClient.class;
}
@Override
public String id(ConnectionInfo t) {
return "mongo"; //NOI18N
}
@Override
public String displayName(ConnectionInfo t) {
return id(t);
}
}
}