/*
* Licensed to "Neo Technology," Network Engine for Objects in Lund AB
* (http://neotechnology.com) under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership. Neo Technology 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 org.neo4j.neoclipse.graphdb;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.neoclipse.Activator;
import org.neo4j.neoclipse.property.NeoPropertySheetPage;
import org.neo4j.neoclipse.property.PropertyTransform.PropertyHandler;
import org.neo4j.neoclipse.view.Dialog;
import org.neo4j.neoclipse.view.ErrorMessage;
import org.neo4j.neoclipse.view.NeoGraphViewPart;
/**
* Utility class to handle node space manipulations.
*
* @author Anders Nawroth
*/
public class GraphDbUtil
{
private static final int OK = 0;
private static final String CONFIRM_DELETE_TITLE = "Confirm delete";
private static final String ADDING_REL_TYPECOUNT_WARNING_MESSAGE = "There has to be exactly one selected relationship type to add a relationship.";
private static final String ADDING_REL_WARNING_MESSAGE = "Two nodes must be selected in the database graph to add a relationship.";
private static final String ADDING_REL_WARNING_LABEL = "Adding relationship";
private static final String ADDING_NODE_WARNING_LABEL = "Adding node";
private static final String ADDING_NODE_WARNING_MESSAGE = "At least one node must be selected in the database graph to add a new node.";
private GraphDbUtil()
{
// no instances
}
/**
* Create a relationship between two nodes
*
* @param source start node of the relationship
* @param dest end node of the relationship
* @param relType type of relationship
* @param graphView current database graph view
*/
public static void createRelationship( final Node source, final Node dest,
final RelationshipType relType, final NeoGraphViewPart graphView )
{
List<Node> sourceNodes = null;
if ( source != null )
{
sourceNodes = new ArrayList<Node>();
sourceNodes.add( source );
}
List<Node> destNodes = null;
if ( dest != null )
{
destNodes = new ArrayList<Node>();
destNodes.add( dest );
}
createRelationship( sourceNodes, destNodes, relType, graphView );
}
/**
* Create relationship between two nodes. One node can be created, but not
* both
*
* @param sourceNodes source, is created if <code>null</code> is given
* @param destNodes destination, is created if <code>null</code> is given
* @param relType type of relationship
* @param graphView current database graph view
* @return
*/
private static void createRelationship( final List<Node> sourceNodes,
final List<Node> destNodes, final RelationshipType relType,
final NeoGraphViewPart graphView )
{
try
{
Activator.getDefault().getGraphDbServiceManager().submitTask(
new GraphRunnable()
{
public void run( final GraphDatabaseService graphDb )
{
createTheRelationship( sourceNodes, destNodes,
relType, graphView, graphDb );
}
}, "create relationship" ).get();
}
catch ( Exception e )
{
ErrorMessage.showDialog( "Create relationship(s)", e );
}
}
private static void createTheRelationship( List<Node> sourceNodes,
List<Node> destNodes, final RelationshipType relType,
final NeoGraphViewPart graphView, final GraphDatabaseService graphDb )
{
if ( relType == null )
{
throw new IllegalArgumentException(
"RelationshipType can not be null" );
}
if ( sourceNodes == null && destNodes == null )
{
throw new IllegalArgumentException(
"Both soure and destination can not be null" );
}
if ( graphDb == null )
{
throw new IllegalStateException(
"No active GraphDatabaseService was found" );
}
Node newInputNode = null;
Node createNode = null;
try
{
if ( destNodes == null )
{
destNodes = new ArrayList<Node>();
createNode = graphDb.createNode();
destNodes.add( createNode );
newInputNode = sourceNodes.get( 0 );
}
else if ( sourceNodes == null )
{
sourceNodes = new ArrayList<Node>();
createNode = graphDb.createNode();
sourceNodes.add( createNode );
newInputNode = destNodes.get( 0 );
}
for ( Node source : sourceNodes )
{
for ( Node dest : destNodes )
{
source.createRelationshipTo( dest, relType );
}
}
}
catch ( Exception e )
{
e.printStackTrace();
}
if ( graphView != null )
{
graphView.setDirty( true );
if ( destNodes.size() > 1 || sourceNodes.size() > 1 )
{
graphView.setInput( createNode );
}
else if ( newInputNode != null )
{
graphView.setInput( newInputNode );
}
else
{
graphView.refreshPreserveLayout();
}
}
}
/**
* Ask the user to confirm delete. Note that this method should only be
* called from inside the UI thread.
*
* @param count numbe rof items to delete
* @return true on yes to delete
*/
public static boolean confirmDelete( final int count )
{
return MessageDialog.openConfirm( null, CONFIRM_DELETE_TITLE,
"Do you really want to delete the selected " + count
+ " items?" );
}
/**
* Delete nodes and relationships from database.
*
* @param containers node and relationships
* @param graphView the current graph view
* @return
*/
public static void deletePropertyContainers(
final List<? extends PropertyContainer> containers,
final NeoGraphViewPart graphView )
{
if ( containers.isEmpty() )
{
return;
}
try
{
Activator.getDefault().getGraphDbServiceManager().submitTask(
new GraphRunnable()
{
public void run( final GraphDatabaseService graphDb )
{
deleteThePropertyContainers( containers, graphView,
graphDb );
}
}, "delete property containers" ).get();
}
catch ( Exception e )
{
ErrorMessage.showDialog( "Delete", e );
}
}
private static void deleteThePropertyContainers(
final List<? extends PropertyContainer> containers,
final NeoGraphViewPart graphView, final GraphDatabaseService graphDb )
{
try
{
Node inputNode = graphView.getCurrentNode();
Node newInputNode = null;
Iterator<? extends PropertyContainer> iter = containers.iterator();
while ( iter.hasNext() )
{
PropertyContainer container = iter.next();
if ( container instanceof Node )
{
Node node = (Node) container;
if ( node.equals( graphDb.getReferenceNode() ) )
{
boolean confirmed = MessageDialog.openConfirm( null,
CONFIRM_DELETE_TITLE,
"Do you really, really want to delete the REFERENCE NODE?" );
if ( !confirmed )
{
return;
}
}
if ( node.equals( inputNode ) && node.hasRelationship() )
{
newInputNode = node.getRelationships().iterator().next().getOtherNode(
node );
}
for ( Relationship rel : node.getRelationships() )
{
rel.delete();
}
iter.remove(); // remove from list to not mess up the list
node.delete();
}
else if ( container instanceof Relationship )
{
( (Relationship) container ).delete();
}
graphView.setDirty( true );
if ( newInputNode != null )
{
graphView.setInput( newInputNode );
}
}
}
catch ( Exception e )
{
ErrorMessage.showDialog( "Error when deleting", e );
}
}
/**
* Add a relationship between two nodes.
*
* @param relTypes relationships types to use (should only be one item)
* @param graphView the current graph view
*/
public static void addRelationshipAction(
final List<RelationshipType> relTypes,
final NeoGraphViewPart graphView )
{
if ( !isOneRelTypeSelected( relTypes ) )
{
return;
}
addRelationshipAction( relTypes.get( 0 ), graphView );
}
/**
* Add relationship between the selected two nodes.
*
* @param relTypes relationships types to use (should only be one item)
* @param graphView the current graph view
*/
public static void addRelationshipAction( final RelationshipType relType,
final NeoGraphViewPart graphView )
{
List<Node> currentSelectedNodes = graphView.getCurrentSelectedNodes();
if ( currentSelectedNodes.size() != 2 )
{
Dialog.openWarning( ADDING_REL_WARNING_LABEL,
ADDING_REL_WARNING_MESSAGE );
return;
}
Node source = currentSelectedNodes.get( 0 );
Node dest = currentSelectedNodes.get( 1 );
createRelationship( source, dest, relType, graphView );
}
/**
* Add outgoing relationships pointing to a new node.
*
* @param relTypes relationships types to use (should only be one item)
* @param graphView
*/
public static void addOutgoingNodeAction(
final List<RelationshipType> relTypes,
final NeoGraphViewPart graphView )
{
if ( !isOneRelTypeSelected( relTypes ) )
{
return;
}
addOutgoingNodeAction( relTypes.get( 0 ), graphView );
}
/**
* Add outgoing relationships pointing to a new node.
*
* @param relType relationship type to use
* @param graphView the current graph view
*/
public static void addOutgoingNodeAction( final RelationshipType relType,
final NeoGraphViewPart graphView )
{
List<Node> currentSelectedNodes = graphView.getCurrentSelectedNodes();
if ( !isOneOrMoreNodesSelected( currentSelectedNodes ) )
{
return;
}
createRelationship( currentSelectedNodes, null, relType, graphView );
}
/**
* Add incoming relationships coming from a new node.
*
* @param relTypes relationship types to use
* @param graphView
*/
public static void addIncomingNodeAction(
final List<RelationshipType> relTypes,
final NeoGraphViewPart graphView )
{
if ( !isOneRelTypeSelected( relTypes ) )
{
return;
}
addIncomingNodeAction( relTypes.get( 0 ), graphView );
}
/**
* Add incoming relationships coming from a new node.
*
* @param relTypes relationships types to use (should only be one item)
* @param graphView
*/
public static void addIncomingNodeAction( final RelationshipType relType,
final NeoGraphViewPart graphView )
{
List<Node> currentSelectedNodes = graphView.getCurrentSelectedNodes();
if ( !isOneOrMoreNodesSelected( currentSelectedNodes ) )
{
return;
}
createRelationship( null, currentSelectedNodes, relType, graphView );
}
/**
* Test precondition for operations.
*
* @return
*/
private static boolean isOneRelTypeSelected(
final List<RelationshipType> relTypes )
{
if ( relTypes.size() != 1 )
{
Dialog.openWarning( ADDING_REL_WARNING_LABEL,
ADDING_REL_TYPECOUNT_WARNING_MESSAGE );
return false;
}
return true;
}
/**
* Test precondition for operations.
*
* @return
*/
private static boolean isOneOrMoreNodesSelected(
final List<Node> currentSelectedNodes )
{
if ( currentSelectedNodes.size() < 1 )
{
Dialog.openWarning( ADDING_NODE_WARNING_LABEL,
ADDING_NODE_WARNING_MESSAGE );
return false;
}
return true;
}
/**
* Remove a property from Node/Relationship.
*
* @param container
* @param key
* @param propertySheet
*/
public static void removeProperty( final PropertyContainer container,
final String key, final NeoPropertySheetPage propertySheet )
{
boolean confirmation = MessageDialog.openConfirm( null,
"Confirm removal",
"Do you really want to remove the selected property?" );
if ( !confirmation )
{
return;
}
try
{
Activator.getDefault().getGraphDbServiceManager().submitTask(
new Runnable()
{
public void run()
{
container.removeProperty( key );
}
}, "removing a property" );
}
catch ( Exception e )
{
Dialog.openError( "Error",
"Error in Neo service: " + e.getMessage() );
}
stateChanged( container, key, true, propertySheet );
}
/**
* Add a property to Node/Relationship. The user will be asked for
* confirmation if the key already exists.
*
* @param container
* @param key
* @param propertyHandler
* @param propertySheet
*/
public static void addProperty( final PropertyContainer container,
final String key, final PropertyHandler propertyHandler,
final NeoPropertySheetPage propertySheet )
{
if ( container.hasProperty( key ) )
{
if ( !MessageDialog.openQuestion(
null,
"Key exists",
"The key \""
+ key
+ "\" already exists, do you want to overwrite the old value?" ) )
{
return;
}
}
InputDialog valueInput = new InputDialog( null, "Value entry",
"Please enter the value of the new property",
propertyHandler.render( propertyHandler.value() ),
propertyHandler.getValidator() );
if ( valueInput.open() != OK && valueInput.getReturnCode() != OK )
{
return;
}
Object val = null;
try
{
val = propertyHandler.parse( valueInput.getValue() );
}
catch ( IOException e )
{
Dialog.openError( "Error message",
"Error parsing the input value, no changes will be performed." );
return;
}
if ( setTheProperty( container, key, val ) )
{
stateChanged( container, key, true, propertySheet );
}
}
/**
* Set a property value, no questions asked.
*
* @param container
* @param key
* @param value
* @param propertySheet
*/
public static void setProperty( final PropertyContainer container,
final String key, final Object value,
final NeoPropertySheetPage propertySheet )
{
if ( setTheProperty( container, key, value ) )
{
stateChanged( container, key, false, propertySheet );
}
}
private static boolean setTheProperty( final PropertyContainer container,
final String key, final Object value )
{
try
{
Activator.getDefault().getGraphDbServiceManager().submitTask(
new Runnable()
{
public void run()
{
container.setProperty( key, value );
}
}, "set property" ).get();
}
catch ( Exception e )
{
ErrorMessage.showDialog( "Set property", e );
return false;
}
return true;
}
private static void stateChanged( final PropertyContainer container,
final String key, final boolean refresh,
final NeoPropertySheetPage propertySheet )
{
propertySheet.fireChangeEvent( container, key, refresh );
}
/**
* Rename a property key on Node/Relationship.
*
* @param container Node/Relationship
* @param key old key
* @param newKey new key
* @param propertySheet
*/
public static void renameProperty( final PropertyContainer container,
final String key, final String newKey,
final NeoPropertySheetPage propertySheet )
{
try
{
Activator.getDefault().getGraphDbServiceManager().submitTask(
new Runnable()
{
public void run()
{
container.setProperty( newKey,
container.getProperty( key ) );
container.removeProperty( key );
}
}, "rename property" ).get();
}
catch ( Exception e )
{
ErrorMessage.showDialog( "Rename property", e );
}
stateChanged( container, newKey, true, propertySheet );
}
public static Object getProperty( final PropertyContainer container,
final String key )
{
try
{
return Activator.getDefault().getGraphDbServiceManager().submitTask(
new Callable<Object>()
{
public Object call() throws Exception
{
return container.getProperty( key, null );
}
}, "get property" ).get();
}
catch ( Exception e )
{
ErrorMessage.showDialog( "Get property", e );
}
return null;
}
public static Map<String, Object> getProperties(
final PropertyContainer container )
{
try
{
return Activator.getDefault().getGraphDbServiceManager().submitTask(
new Callable<Map<String, Object>>()
{
public Map<String, Object> call() throws Exception
{
Map<String, Object> props = new HashMap<String, Object>();
for ( String key : container.getPropertyKeys() )
{
props.put( key, container.getProperty( key ) );
}
return props;
}
}, "get properties" ).get();
}
catch ( Exception e )
{
ErrorMessage.showDialog( "Get properties", e );
}
return null;
}
/**
* Get all relationships from the database. Note that relationship types not
* more in use can show up in the result.
*
* @param graphDb the graphdb instance
* @return the relationship types
*/
public static Set<RelationshipType> getRelationshipTypesFromDb(
final GraphDatabaseService graphDb )
{
if ( graphDb == null )
{
return Collections.emptySet();
}
Set<RelationshipType> relationshipTypes;
relationshipTypes = new HashSet<RelationshipType>();
for ( RelationshipType relType : graphDb.getRelationshipTypes() )
{
relationshipTypes.add( relType );
}
return relationshipTypes;
}
}