/** * Copyright (c) 2002-2013 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.noser.neo4j.android.dbinspector.implementation; import java.util.Formatter; import java.util.HashMap; import java.util.List; import java.util.Map; import org.neo4j.android.client.GraphDatabase; import org.neo4j.android.client.Neo4jServiceException; import org.neo4j.android.common.ParcelableNode; import org.neo4j.android.common.ParcelableRelationship; import roboguice.activity.RoboActivity; import roboguice.inject.ContentView; import roboguice.inject.InjectView; import roboguice.util.Ln; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.os.RemoteException; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.google.inject.Inject; import com.noser.neo4j.android.dbinspector.R; import com.noser.neo4j.android.dbinspector.base.DBInspectorConstants; import com.noser.neo4j.android.dbinspector.base.DBInspectorException; import com.noser.neo4j.android.dbinspector.interfaces.IDBManager; @ContentView(R.layout.main_activity) public class MainActivity extends RoboActivity { @Inject private Context context; @Inject private IDBManager dbManager; @Inject private LayoutInflater inflater; @InjectView(R.id.mainDatabaseListLayout) private LinearLayout mainDatabaseListLayout; @InjectView(R.id.mainCreateButton) private ImageButton mainCreateButton; @InjectView(R.id.mainMenuButton) private ImageButton mainMenuButton; private Map<String, Integer> databaseNameToIndexMap = new HashMap<String, Integer>(); @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main_activity_menu, menu); return true; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mainCreateButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { askCreationOfNeo4jDatabase(); } }); mainMenuButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { openOptionsMenu(); } }); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.main_test_database: createTestDatabase(); return true; case R.id.main_refresh_databases: updateNeo4jDatabaseList(); return true; default: { return super.onOptionsItemSelected(item); } } } @Override protected void onStart() { super.onStart(); new WaitForServiceTask().execute(); } private void createTestDatabase() { new CreateTestDatabaseTask().execute(); } private void askCreationOfNeo4jDatabase() { AlertDialog dialog = new AlertDialog.Builder(this).create(); dialog.setTitle(R.string.main_create_database_title); View mainCreateDatabaseView = inflater.inflate(R.layout.main_activity_createdatabase, null); final EditText mainCreateDatabaseEdit = (EditText) mainCreateDatabaseView.findViewById(R.id.mainCreateDatabaseEdit); dialog.setView(mainCreateDatabaseView); dialog.setButton(getResources().getString(android.R.string.yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String databaseName = mainCreateDatabaseEdit.getText().toString(); if (!databaseName.isEmpty()) { try { if (dbManager.neo4jDatabaseExists(databaseName)) { showDatabaseAlreadyExistsDialog(databaseName); } else { if (dbManager.isCurrentNeo4jDatabaseOpen()) { shutdownNeo4jDatabase(dbManager.getCurrentNeo4jDatabaseName()); } openOrCreateNeo4jDatabase(databaseName); } } catch (DBInspectorException e) { Ln.e(e, "failed to check for existing database '" + databaseName + "'."); showErrorDialog(); } } } }); dialog.setButton2(getResources().getString(android.R.string.no), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); dialog.show(); } private void openOrShutdownNeo4jDatabase(final String databaseName) { try { if (dbManager.isNeo4jDatabaseOpen(databaseName)) { confirmShutdownOfNeo4jDatabase(databaseName); } else { confirmOpenOfNeo4jDatabase(databaseName); } } catch (DBInspectorException e) { Ln.e(e, "failed to check state of database '" + databaseName + "'."); showErrorDialog(); } } private void confirmShutdownOfNeo4jDatabase(final String databaseName) { AlertDialog dialog = new AlertDialog.Builder(this).create(); dialog.setTitle(R.string.main_close_database_title); Formatter formatter = new Formatter(); formatter.format(getResources().getString(R.string.main_close_database_question), databaseName); dialog.setMessage(formatter.toString()); dialog.setButton(getResources().getString(android.R.string.yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { shutdownNeo4jDatabase(databaseName); } }); dialog.setButton2(getResources().getString(android.R.string.no), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); dialog.show(); formatter.close(); } private void confirmDeletionOfNeo4jDatabase(final String databaseName) { AlertDialog dialog = new AlertDialog.Builder(this).create(); dialog.setTitle(R.string.main_delete_database_title); Formatter formatter = new Formatter(); formatter.format(getResources().getString(R.string.main_delete_database_question), databaseName); dialog.setMessage(formatter.toString()); dialog.setButton(getResources().getString(android.R.string.yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (dbManager.isCurrentNeo4jDatabaseOpen()) { shutdownNeo4jDatabase(dbManager.getCurrentNeo4jDatabaseName()); } deleteNeo4jDatabase(databaseName); } }); dialog.setButton2(getResources().getString(android.R.string.no), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); dialog.show(); formatter.close(); } private void confirmExportOfNeo4jDatabase(final String databaseName) { AlertDialog dialog = new AlertDialog.Builder(this).create(); dialog.setTitle(R.string.main_export_database_title); Formatter formatter = new Formatter(); formatter.format(getResources().getString(R.string.main_export_database_question), databaseName); dialog.setMessage(formatter.toString()); dialog.setButton(getResources().getString(android.R.string.yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (dbManager.isCurrentNeo4jDatabaseOpen()) { shutdownNeo4jDatabase(dbManager.getCurrentNeo4jDatabaseName()); } exportNeo4jDatabase(databaseName); } }); dialog.setButton2(getResources().getString(android.R.string.no), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); dialog.show(); formatter.close(); } private void confirmOpenOfNeo4jDatabase(final String databaseName) { AlertDialog dialog = new AlertDialog.Builder(this).create(); dialog.setTitle(R.string.main_open_database_title); Formatter formatter = new Formatter(); formatter.format(getResources().getString(R.string.main_open_database_question), databaseName); dialog.setMessage(formatter.toString()); dialog.setButton(getResources().getString(android.R.string.yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (dbManager.isCurrentNeo4jDatabaseOpen()) { shutdownNeo4jDatabase(dbManager.getCurrentNeo4jDatabaseName()); } openOrCreateNeo4jDatabase(databaseName); } }); dialog.setButton2(getResources().getString(android.R.string.no), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); dialog.show(); formatter.close(); } private void updateNeo4jDatabaseList() { runOnUiThread(new Runnable() { @Override public void run() { List<String> databaseNames; try { databaseNames = dbManager.listAvailableNeo4jDatabases(); } catch (DBInspectorException e) { Ln.e(e, "failed to list available databases."); showErrorDialog(); return; } mainDatabaseListLayout.removeAllViews(); databaseNameToIndexMap.clear(); int index = 0; for (final String databaseName : databaseNames) { View mainDatabaseListItem = inflater.inflate(R.layout.main_activity_listitem, null); mainDatabaseListItem.setId(index); databaseNameToIndexMap.put(databaseName, index); TextView mainDatabaseText = (TextView) mainDatabaseListItem.findViewById(R.id.mainDatabaseText); mainDatabaseText.setText(databaseName); ImageView mainDatabaseStateView = (ImageView) mainDatabaseListItem.findViewById(R.id.mainDatabaseStateView); updateNeo4jDatabaseState(databaseName, mainDatabaseStateView); ImageButton mainOpenDatabaseButton = (ImageButton) mainDatabaseListItem .findViewById(R.id.mainOpenDatabaseButton); mainOpenDatabaseButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (databaseName.equals(dbManager.getCurrentNeo4jDatabaseName())) { showRootNodeActivity(); } else { openOrShutdownNeo4jDatabase(databaseName); } } }); ImageButton mainDeleteDatabaseButton = (ImageButton) mainDatabaseListItem .findViewById(R.id.mainDeleteDatabaseButton); mainDeleteDatabaseButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { confirmDeletionOfNeo4jDatabase(databaseName); } }); ImageButton mainExportDatabaseButton = (ImageButton) mainDatabaseListItem .findViewById(R.id.mainExportDatabaseButton); mainExportDatabaseButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { confirmExportOfNeo4jDatabase(databaseName); } }); mainDatabaseListLayout.addView(mainDatabaseListItem); index++; } mainDatabaseListLayout.requestLayout(); } }); } private void updateNeo4jDatabaseState(final String databaseName) { runOnUiThread(new Runnable() { @Override public void run() { Integer index = databaseNameToIndexMap.get(databaseName); if (index != null) { View mainDatabaseListItem = mainDatabaseListLayout.findViewById(index); ImageView mainDatabaseStateView = (ImageView) mainDatabaseListItem.findViewById(R.id.mainDatabaseStateView); updateNeo4jDatabaseState(databaseName, mainDatabaseStateView); } else { Ln.e("databaseName '" + databaseName + "' not found in databaseNameToIndexMap '" + databaseNameToIndexMap + "'"); } } }); } private void updateNeo4jDatabaseState(String databaseName, ImageView mainDatabaseStateView) { try { if (dbManager.isNeo4jDatabaseOpen(databaseName)) { if (databaseName.equals(dbManager.getCurrentNeo4jDatabaseName())) { mainDatabaseStateView.setImageResource(android.R.drawable.presence_online); } else { mainDatabaseStateView.setImageResource(android.R.drawable.presence_busy); } } else { mainDatabaseStateView.setImageResource(android.R.drawable.presence_offline); } } catch (DBInspectorException e) { Ln.e(e, "failed to check state of database '" + databaseName + "'."); showErrorDialog(); } } private void openOrCreateNeo4jDatabase(String databaseName) { Ln.i("opening/creating database '" + databaseName + "'"); new OpenOrCreateNeo4jDatabaseTask().execute(databaseName); } private void shutdownNeo4jDatabase(String databaseName) { Ln.i("shutting down database '" + databaseName + "'"); try { dbManager.shutdownNeo4jDatabase(databaseName); updateNeo4jDatabaseState(databaseName); } catch (DBInspectorException e) { Ln.e(e, "failed to shutdown the database '" + databaseName + "'."); showErrorDialog(); } } private void deleteNeo4jDatabase(String databaseName) { Ln.i("deleting database '" + databaseName + "'"); try { dbManager.deleteNeo4jDatabase(databaseName); updateNeo4jDatabaseList(); } catch (DBInspectorException e) { Ln.e(e, "failed to delete the database '" + databaseName + "'."); showErrorDialog(); } } private void exportNeo4jDatabase(String databaseName) { Ln.i("exporting database '" + databaseName + "'"); try { dbManager.exportNeo4jDatabase(databaseName); updateNeo4jDatabaseList(); } catch (DBInspectorException e) { Ln.e(e, "failed to export the database '" + databaseName + "'."); showErrorDialog(); } } private void showDatabaseAlreadyExistsDialog(final String databaseName) { runOnUiThread(new Runnable() { @Override public void run() { AlertDialog dialog = new AlertDialog.Builder(context).create(); dialog.setTitle(R.string.error_title); Formatter formatter = new Formatter(); formatter.format(getResources().getString(R.string.main_create_database_already_exists_error), databaseName); dialog.setMessage(formatter.toString()); dialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); dialog.show(); formatter.close(); } }); } private void showErrorDialog() { runOnUiThread(new Runnable() { @Override public void run() { AlertDialog dialog = new AlertDialog.Builder(context).create(); dialog.setTitle(R.string.error_title); dialog.setMessage(getResources().getText(R.string.database_error)); dialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); dialog.show(); } }); } private void showRootNodeActivity() { Intent intent = new Intent(getApplicationContext(), RootNodeActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); startActivity(intent); } private class WaitForServiceTask extends AsyncTask<Void, Void, Void> { private ProgressDialog progressDialog; @Override protected void onPreExecute() { progressDialog = ProgressDialog.show(context, "", getResources().getString(R.string.main_waiting_for_service), true); } @Override protected Void doInBackground(Void... params) { while (!dbManager.isNeo4jServiceAvailable()) { try { Thread.sleep(DBInspectorConstants.WAIT_FOR_SERVICE_TIME); } catch (InterruptedException e) { // do nothing. } } Ln.d("service connected."); return null; } @Override protected void onPostExecute(Void result) { progressDialog.dismiss(); updateNeo4jDatabaseList(); } } private class OpenOrCreateNeo4jDatabaseTask extends AsyncTask<String, Void, Boolean> { private ProgressDialog progressDialog; @Override protected void onPreExecute() { progressDialog = ProgressDialog.show(context, "", getResources().getString(R.string.main_opening_database), true); } @Override protected Boolean doInBackground(String... params) { String databaseName = params[0]; try { dbManager.openOrCreateNeo4jDatabase(databaseName); return true; } catch (DBInspectorException e) { Ln.e(e, "failed to open database '" + databaseName + "'"); return false; } } @Override protected void onPostExecute(Boolean result) { progressDialog.dismiss(); updateNeo4jDatabaseList(); if (result) { showRootNodeActivity(); } else { showErrorDialog(); } } } /** * this creates a test graph with a node count = TEST_DATABASE_DENSITY ^ * TEST_DATABASE_DEPTH + TEST_DATABASE_DENSITY + 1. */ private class CreateTestDatabaseTask extends AsyncTask<Void, Void, Boolean> { private int testIndex; private ProgressDialog progressDialog; @Override protected void onPreExecute() { progressDialog = ProgressDialog.show(context, "", getResources().getString(R.string.main_creating_test_database), true); } @Override protected Boolean doInBackground(Void... params) { try { dbManager.deleteNeo4jDatabase(DBInspectorConstants.TEST_DATABASE); dbManager.openOrCreateNeo4jDatabase(DBInspectorConstants.TEST_DATABASE); populateDatabase(); Ln.i("test database '" + DBInspectorConstants.TEST_DATABASE + "' created."); return true; } catch (Exception e) { Ln.e(e, "failed to create test database '" + DBInspectorConstants.TEST_DATABASE + "'."); return false; } } @Override protected void onPostExecute(Boolean result) { progressDialog.dismiss(); updateNeo4jDatabaseList(); if (!result) { showErrorDialog(); } } private void populateDatabase() throws Neo4jServiceException, RemoteException { GraphDatabase database = dbManager.getCurrentNeo4jDatabase(); ParcelableNode referenceNode = database.getReferenceNode(); testIndex = 0; database.beginTx(); try { referenceNode.setProperty("test_node_index", "" + 0); referenceNode.setProperty("timestamp", "" + System.currentTimeMillis()); database.updateNode(referenceNode); createStarRelationships(database, referenceNode.getId(), 0); database.txSuccess(); } finally { database.txFinish(); } } private void createStarRelationships(GraphDatabase database, long centerNodeId, int depth) throws RemoteException, Neo4jServiceException { if (depth < DBInspectorConstants.TEST_DATABASE_DEPTH) { for (int index = 0; index < DBInspectorConstants.TEST_DATABASE_DENSITY; index++) { testIndex++; ParcelableNode node = createNode(testIndex); long nodeId = database.createNode(node); ParcelableRelationship relationship = createRelationship(centerNodeId, nodeId, testIndex); database.createRelationship(relationship); createStarRelationships(database, nodeId, depth + 1); } } } private ParcelableNode createNode(int index) { ParcelableNode node = new ParcelableNode(); node.setProperty("test_node_index", "" + index); node.setProperty("timestamp", "" + System.currentTimeMillis()); Ln.d("created node '" + node + "'"); return node; } private ParcelableRelationship createRelationship(long startNodeId, long endNodeId, int index) { ParcelableRelationship relationship = new ParcelableRelationship(); relationship.setStartNodeId(startNodeId); relationship.setEndNodeId(endNodeId); relationship.setName("test_relationship"); relationship.setProperty("test_relationship_index", "" + index); relationship.setProperty("timestamp", "" + System.currentTimeMillis()); Ln.d("created relationship '" + relationship + "'"); return relationship; } } }