diff --git a/pom.xml b/pom.xml index 24e95d4..365a18c 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 1.3.1 0.3.2 - 1.19.3 + 1.19 2.8.0 3.3.0 @@ -34,6 +34,33 @@ 1.8 + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + + package + + shade + + + gradoop-demo-shaded + + + + org.gradoop.demo.server.Server + + + reference.conf + + + + + + @@ -43,6 +70,16 @@ org.gradoop gradoop-flink ${dep.gradoop.version} + + + org.apache.hadoop + hadoop-common + + + org.apache.flink + flink-hadoop-compatibility_2.11 + + @@ -50,12 +87,29 @@ org.apache.flink flink-java ${dep.flink.version} + + + org.apache.hadoop + hadoop-common + + + org.apache.flink + flink-shaded-hadoop2 + + + org.apache.flink flink-clients_2.11 ${dep.flink.version} + + + org.apache.flink + flink-shaded-hadoop2 + + @@ -77,5 +131,39 @@ ${dep.jersey.version} + + + org.apache.hadoop + hadoop-hdfs + 2.8.3 + + + org.apache.hadoop + hadoop-hdfs + 2.8.3 + test-jar + + + org.apache.hadoop + hadoop-common + 2.8.3 + test-jar + + + org.apache.hadoop + hadoop-client + 2.8.3 + + + org.apache.hadoop + hadoop-hdfs-client + 2.8.3 + + + junit + junit + 4.12 + test + - \ No newline at end of file + diff --git a/src/main/java/org/gradoop/demo/server/Constants.java b/src/main/java/org/gradoop/demo/server/Constants.java new file mode 100644 index 0000000..44d80e7 --- /dev/null +++ b/src/main/java/org/gradoop/demo/server/Constants.java @@ -0,0 +1,18 @@ +package org.gradoop.demo.server; + +import java.util.Set; + +import static com.google.common.collect.Sets.newHashSet; +import static java.util.Collections.unmodifiableSet; + +public final class Constants { + static final Set BUNDLED_DATABASE_NAMES = + unmodifiableSet(newHashSet("Pokec_Sample", "Graphalytics_SF1_Sample", "Example")); + + static final Set GRADOOP_FILE_NAMES = + unmodifiableSet(newHashSet("metadata.csv", "vertices.csv", "edges.csv")); + + static final String DATASOURCES_NAME_KEY = "datasourceNames"; + private Constants() { + } +} diff --git a/src/main/java/org/gradoop/demo/server/FetchStatus.java b/src/main/java/org/gradoop/demo/server/FetchStatus.java new file mode 100644 index 0000000..0144da4 --- /dev/null +++ b/src/main/java/org/gradoop/demo/server/FetchStatus.java @@ -0,0 +1,32 @@ +package org.gradoop.demo.server; + +import java.util.Objects; + +import static java.util.Objects.requireNonNull; + +final class FetchStatus { + enum Status { + FETCH_ERROR, + FETCHED_FROM_HDFS, + PRESENT_LOCALLY + } + private final String name; + private final Status status; + + FetchStatus(String name, Status status) { + requireNonNull(name); + requireNonNull(status); + this.name = name; + this.status = status; + } + String getName() { + return name; + } + Status getStatus() { + return status; + } + @Override + public String toString() { + return "[name: " + name + ", status: " + status + "]"; + } +} \ No newline at end of file diff --git a/src/main/java/org/gradoop/demo/server/GradoopGraphsetStore.java b/src/main/java/org/gradoop/demo/server/GradoopGraphsetStore.java new file mode 100644 index 0000000..678300b --- /dev/null +++ b/src/main/java/org/gradoop/demo/server/GradoopGraphsetStore.java @@ -0,0 +1,49 @@ +package org.gradoop.demo.server; + +import org.gradoop.flink.io.api.DataSource; + +import java.io.IOException; +import java.util.List; +import java.util.Set; + +/** + *

Represents a Store for Graphsets.

+ */ +interface GradoopGraphsetStore { + /** + * Returns a {@linkplain List} of names of {@linkplain DataSource}s that are available for exploration. A DataSource + * could be a set of files, for instance, if the DataSource is a {@linkplain org.gradoop.flink.io.impl.csv.CSVDataSource}, + * then it is a folder with three files: vertices.csv, edges.csv, and metadata.csv. + */ + Set getDataSourceNames(); + + /** + * Returns the absolute path of the files that represent this data source. Remember that every data source + * is a collection of three files: vertices.csv, edges.csv, and metadata.csv. + * @param dataSourceName + * @return a String representing the absolute path of the datasource, null otherwise + */ + String getPath(String dataSourceName); + + /** + *

Updates the Gradoop Graphsets from remote (HDFS, e.g.) location. It returns a list of strings + * indicating the status of the refresh operation. A graphset that is available locally is fetched again + * when this operation is done. + *

+ *

+ * Details: + *

    + *
  • Get the set of locally available graphsets (A)
  • + *
  • Get the set of graphsets available in HDFS (B)
  • + *
  • For each graphset b in B, remove it from A, (re) fetch it from HDFS, set its status as + * {@linkplain FetchStatus.Status#FETCHED_FROM_HDFS}, add the status to the return set (C)
  • + *
  • For each graphset a in A, set a status {@linkplain FetchStatus.Status#PRESENT_LOCALLY}
  • + *
  • Return C
  • + *
+ *

+ */ + Set refresh() throws IOException; + + /** Decides whether this store is "local-only" */ + boolean isLocal(); +} diff --git a/src/main/java/org/gradoop/demo/server/HdfsGradoopGraphsetStore.java b/src/main/java/org/gradoop/demo/server/HdfsGradoopGraphsetStore.java new file mode 100644 index 0000000..c072b57 --- /dev/null +++ b/src/main/java/org/gradoop/demo/server/HdfsGradoopGraphsetStore.java @@ -0,0 +1,123 @@ +package org.gradoop.demo.server; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.conf.Configured; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.FileUtil; +import org.apache.hadoop.fs.LocatedFileStatus; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.RemoteIterator; +import org.apache.hadoop.hdfs.HdfsConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.util.HashSet; +import java.util.Set; + +import static java.util.Objects.requireNonNull; +import static org.gradoop.demo.server.Constants.GRADOOP_FILE_NAMES; + +/** + *

Represents a store of graphsets that are available to explore using Gradoop and that are stored in HDFS.

+ */ +public class HdfsGradoopGraphsetStore extends Configured { + + public static final String DEFAULT_BASE_PATH_IN_HDFS = "/app/ugraph/gradoop-graphsets/"; + private static final Logger log = LoggerFactory.getLogger(HdfsGradoopGraphsetStore.class); + + private final String basePath; + private final Configuration config; + + public HdfsGradoopGraphsetStore() { + this(new Configuration(), DEFAULT_BASE_PATH_IN_HDFS); + } + + public HdfsGradoopGraphsetStore(Configuration config, String basePath) { + requireNonNull(config); + requireNonNull(basePath); + this.basePath = basePath; + this.config = config; + this.config.set("fs.hdfs.impl", org.apache.hadoop.hdfs.DistributedFileSystem.class.getName()); + this.config.set("fs.file.impl", org.apache.hadoop.fs.LocalFileSystem.class.getName()); + } + + public Set getDataSourceNames() throws IOException { + Path basePath = new Path(this.basePath); + FileSystem fs = basePath.getFileSystem(config); +// if (!fs.getFileStatus(basePath).isDirectory()) { +// throw new RuntimeException("path is not a directory: " + basePath); +// } + Set dataSources = new HashSet<>(16); // expected to be fewer than this + try { + RemoteIterator iter = fs.listFiles(basePath, true); + while (iter.hasNext()) { + LocatedFileStatus fstat = iter.next(); + String name = getGradoopGraphsetName(fstat.getPath().toUri().getPath()); + if (name != null && ! dataSources.contains(name)) { + boolean added = dataSources.add(name); + assert added; + log.debug("adding graphset: {}", name); + } + } + } catch (Exception e) { + throw new IOException(e); + } finally { + if (fs != null) { + fs.close(); + } + } + return dataSources; + } + + @Override + public String toString() { + return "[fs.default.name:" + config.get("fs.default.name") + ", fs.hdfs.impl: " + + config.get("fs.hdfs.impl") + ", basePath: " + basePath + "]"; + } + + private String getGradoopGraphsetName(String path) { + //TODO improve this logic + String prefix = basePath; + String rem = path.substring(prefix.length()); + int fsi = rem.indexOf('/'); // index of first slash in rem e.g. "one/foo" + if (fsi < 0) { + log.debug("Returning null, this is not a gradoop graphset: {}", path); + return null; + } + String folder = rem.substring(0, fsi); + if (rem.length() - folder.length() > 1) { + return folder; + } + log.debug("Returning null, this is not a gradoop graphset: {}", path); + return null; + } + + /* package-private */void copyGradoopFiles(String graphsetName, File localBase) throws Exception { + // copying all the files is critical, so make it an all-or-nothing operations, cleaning up + // however, transactional semantics are not intended here + Path basePath = new Path(this.basePath); + FileSystem fs = basePath.getFileSystem(config); + File localGraphsetFolder = new File(localBase, graphsetName); + boolean a = localGraphsetFolder.mkdirs(); + if (! a) { + throw new RuntimeException("Unlikely error in making the local folder: " + localGraphsetFolder); + } + Path localPath = new Path(localGraphsetFolder.getAbsolutePath()); + try { + for (String f : GRADOOP_FILE_NAMES) { + Path remotePath = new Path(this.basePath + "/" + graphsetName + "/" + f); + fs.copyToLocalFile(false, remotePath, localPath); + log.info("Copied {} to {}", remotePath.toString(), localPath); + File gf = new File(localPath.toString(), f); + log.debug("File {} -- is it copied? : {}", gf.getAbsolutePath(), gf.exists()); + } + } catch (Exception e) { + log.warn("Error copying graphset: {}, deleting the whole folder: " + e.getMessage()); + FileUtil.fullyDelete(localGraphsetFolder); + throw e; //rethrow + } + } +} diff --git a/src/main/java/org/gradoop/demo/server/LocalGradoopGraphsetStore.java b/src/main/java/org/gradoop/demo/server/LocalGradoopGraphsetStore.java new file mode 100644 index 0000000..b261513 --- /dev/null +++ b/src/main/java/org/gradoop/demo/server/LocalGradoopGraphsetStore.java @@ -0,0 +1,117 @@ +package org.gradoop.demo.server; + +import org.apache.hadoop.conf.Configuration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Paths; +import java.util.HashSet; +import java.util.Set; + +import static java.util.Arrays.stream; +import static java.util.Collections.emptySet; +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toSet; +import static org.gradoop.demo.server.FetchStatus.Status.FETCHED_FROM_HDFS; +import static org.gradoop.demo.server.FetchStatus.Status.FETCH_ERROR; +import static org.gradoop.demo.server.FetchStatus.Status.PRESENT_LOCALLY; + +class LocalGradoopGraphsetStore implements GradoopGraphsetStore { + + private static final Logger log = LoggerFactory.getLogger(LocalGradoopGraphsetStore.class); + static final String DEFAULT_LOCAL_PATH = System.getProperty("user.dir") + "/target/classes/data"; + private final File localBase; + private final HdfsGradoopGraphsetStore hdfsStore; + + LocalGradoopGraphsetStore(Configuration config, String localPath) { + this(config, HdfsGradoopGraphsetStore.DEFAULT_BASE_PATH_IN_HDFS, localPath); + } + + LocalGradoopGraphsetStore(Configuration config, String remoteBasePath, String localPath) { + requireNonNull(remoteBasePath); + requireNonNull(localPath); + localBase = Paths.get(localPath).toFile(); + if (!localBase.isDirectory()) { + throw new IllegalArgumentException("not a directory: " + localBase); + } + if (config != null) { + hdfsStore = new HdfsGradoopGraphsetStore(config, remoteBasePath); + } else { + hdfsStore = null; // we are configured to run off local + } + } + + @Override + public Set getDataSourceNames() { + // we are sure here that localBase is a directory! + return stream(localBase.listFiles(File::isDirectory)).map(File::getName).collect(toSet()); + } + + @Override + public String getPath(String dataSourceName) { + return localBase.getAbsolutePath() + "/" + dataSourceName; + } + + @Override + public Set refresh() throws IOException { + if (hdfsStore == null) { + return emptySet(); + } + Set localNames = this.getDataSourceNames(); + Set remoteNames = hdfsStore.getDataSourceNames(); + Set result = new HashSet<>(remoteNames.size() + localNames.size()); + for (String remoteName : remoteNames) { + FetchStatus fetched = fetch(remoteName); + if (fetched.getStatus() == FETCHED_FROM_HDFS) { + boolean a = result.add(fetched); + assert a; + boolean b = localNames.remove(remoteName); + if (!b) { + log.debug("graphset was not available locally: ", remoteName); + } + log.info("**********************************************"); + log.info("Graphset was copied from HDFS: {}", remoteName); + log.info("**********************************************"); + } + } + // remaining + localNames.forEach(name -> { + FetchStatus e = new FetchStatus(name, PRESENT_LOCALLY); + log.info("**********************************************"); + log.info("Graphset was present locally: {}", name); + log.info("**********************************************"); + result.add(e); + }); + return result; + } + + @Override + public boolean isLocal() { + return this.hdfsStore == null; + } + + /** + * Fetch the required files (metadata.csv, vertices.csv, edges.csv) for the given Gradoop graphset name + * the files are copied to {@linkplain #localBase}. The structure would be: + */ + private FetchStatus fetch(String graphsetName) { + try { + hdfsStore.copyGradoopFiles(graphsetName, localBase); + return new FetchStatus(graphsetName, FETCHED_FROM_HDFS); + } catch (Exception e) { + e.printStackTrace(System.err); + log.warn("{} could not be fetched from HDFS", graphsetName); + return new FetchStatus(graphsetName, FETCH_ERROR); + } + } + + @Override + public String toString() { + String hdfs = this.hdfsStore == null ? null : this.hdfsStore.toString(); + return "**** [HDFS Store: " + hdfs + ", Local Base Path: " + + this.localBase.getAbsolutePath() + "] ****"; + } +} diff --git a/src/main/java/org/gradoop/demo/server/RequestHandler.java b/src/main/java/org/gradoop/demo/server/RequestHandler.java index 9f2a369..1389ce2 100644 --- a/src/main/java/org/gradoop/demo/server/RequestHandler.java +++ b/src/main/java/org/gradoop/demo/server/RequestHandler.java @@ -17,7 +17,6 @@ package org.gradoop.demo.server; import org.apache.commons.lang.ArrayUtils; -import org.apache.flink.api.java.ExecutionEnvironment; import org.apache.flink.api.java.io.LocalCollectionOutputFormat; import org.apache.flink.api.java.tuple.Tuple3; import org.codehaus.jettison.json.JSONArray; @@ -26,7 +25,12 @@ import org.gradoop.common.model.impl.pojo.Edge; import org.gradoop.common.model.impl.pojo.GraphHead; import org.gradoop.common.model.impl.pojo.Vertex; -import org.gradoop.demo.server.functions.*; +import org.gradoop.demo.server.functions.AcceptNoneFilter; +import org.gradoop.demo.server.functions.LabelFilter; +import org.gradoop.demo.server.functions.LabelGroupReducer; +import org.gradoop.demo.server.functions.LabelMapper; +import org.gradoop.demo.server.functions.LabelReducer; +import org.gradoop.demo.server.functions.PropertyKeyMapper; import org.gradoop.demo.server.pojo.GroupingRequest; import org.gradoop.flink.io.impl.csv.CSVDataSource; import org.gradoop.flink.model.api.epgm.GraphCollection; @@ -41,8 +45,6 @@ import org.gradoop.flink.model.impl.operators.matching.common.statistics.GraphStatistics; import org.gradoop.flink.util.GradoopFlinkConfig; -import javax.ws.rs.*; -import javax.ws.rs.core.Response; import java.io.FileWriter; import java.io.IOException; import java.net.URL; @@ -54,404 +56,443 @@ import java.util.List; import java.util.Set; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; + +import static org.gradoop.demo.server.Constants.DATASOURCES_NAME_KEY; +import static org.gradoop.demo.server.Server.ENV; +import static org.gradoop.demo.server.Server.LOCAL_STORE; + /** * Handles REST requests to the server. */ @Path("") public class RequestHandler { - private final String META_FILENAME = "/metadata.json"; - - private static final ExecutionEnvironment ENV = ExecutionEnvironment.createLocalEnvironment(); - private GradoopFlinkConfig config = GradoopFlinkConfig.createConfig(ENV); - - /** - * Takes a database name via a POST request and returns the keys of all - * vertex and edge properties, and a boolean value specifying if the property has a numerical - * type. The return is a string in the JSON format, for easy usage in a JavaScript web page. - * - * @param databaseName name of the loaded database - * @return A JSON containing the vertices and edges property keys - */ - @POST - @Path("/keys/{databaseName}") - @Produces("application/json;charset=utf-8") - public Response getKeysAndLabels(@PathParam("databaseName") String databaseName) { - URL meta = RequestHandler.class.getResource("/data/" + databaseName + META_FILENAME); - try { - if (meta == null) { - JSONObject result = computeKeysAndLabels(databaseName); - if (result == null) { - return Response.serverError().build(); + private final String META_FILENAME = "/metadata.json"; + + private GradoopFlinkConfig config = GradoopFlinkConfig.createConfig(ENV); + + static JSONObject toJson(Set dataSourceNames) throws JSONException { + // caters to a drop-down + JSONObject result = new JSONObject(); + JSONArray array = new JSONArray(dataSourceNames); + result.put(DATASOURCES_NAME_KEY, array); + return result; + } + + /** + *

Returns the names of the databases to explore. + * Each database is a folder at a given location.

+ */ + @GET + @Path("/dbnames") + @Produces("application/json;charset=utf-8") + public Response getDatabaseNames() throws JSONException { + // always refresh from HDFS todo provide a flag? + try { + LOCAL_STORE.refresh(); + } catch (IOException e) { + // squelching exception here because we do not want to disrupt the other flow, let user use other datasets + e.printStackTrace(System.err); } + JSONObject result = toJson(LOCAL_STORE.getDataSourceNames()); return Response.ok(result.toString()).build(); - } else { - JSONObject result = readKeysAndLabels(databaseName); - if (result == null) { - return Response.serverError().build(); + } + + /** + * Takes a database name via a POST request and returns the keys of all + * vertex and edge properties, and a boolean value specifying if the property has a numerical + * type. The return is a string in the JSON format, for easy usage in a JavaScript web page. + * + * @param databaseName name of the loaded database + * @return A JSON containing the vertices and edges property keys + */ + @POST + @Path("/keys/{databaseName}") + @Produces("application/json;charset=utf-8") + public Response getKeysAndLabels(@PathParam("databaseName") String databaseName) { + URL meta = RequestHandler.class.getResource("/data/" + databaseName + META_FILENAME); + try { + if (meta == null) { + JSONObject result = computeKeysAndLabels(databaseName); + if (result == null) { + return Response.serverError().build(); + } + return Response.ok(result.toString()).build(); + } else { + JSONObject result = readKeysAndLabels(databaseName); + if (result == null) { + return Response.serverError().build(); + } + return Response.ok(readKeysAndLabels(databaseName).toString()).build(); + } + } catch (Exception e) { + e.printStackTrace(); + // if any exception is thrown, return an error to the client + return Response.serverError().build(); } - return Response.ok(readKeysAndLabels(databaseName).toString()).build(); - } - } catch (Exception e) { - e.printStackTrace(); - // if any exception is thrown, return an error to the client - return Response.serverError().build(); } - } - - @POST - @Path("/cypher") - @Produces("application/x-www-form-urlencoded;charset=utf-8") - public Response executeCypher( - @FormParam("databaseName") String databaseName, - @FormParam("query") String query, - @DefaultValue("false") @FormParam("attacheData") boolean attacheData) { - //load the database - String path = RequestHandler.class.getResource("/data/" + databaseName).getPath(); - - CSVDataSource source = new CSVDataSource(path, config); - - LogicalGraph graph = source.getLogicalGraph(); - - // TODO load proper statistics - GraphStatistics graphStatistics = new GraphStatistics(1, 1, 1, 1); - GraphCollection res = graph.cypher(query,attacheData, MatchStrategy.HOMOMORPHISM, MatchStrategy.ISOMORPHISM, graphStatistics); - - return createResponse(res); - } - - /** - * Compute property keys and labels. - * @param databaseName name of the database - * @return JSONObject containing property keys and labels - */ - private JSONObject computeKeysAndLabels(String databaseName) throws IOException { - - String path = RequestHandler.class.getResource("/data/" + databaseName).getPath(); - - CSVDataSource source = new CSVDataSource(path, config); - - LogicalGraph graph = source.getLogicalGraph(); - - JSONObject jsonObject = new JSONObject(); - - //compute the vertex and edge property keys and return them - try { - jsonObject.put("vertexKeys", getVertexKeys(graph)); - jsonObject.put("edgeKeys", getEdgeKeys(graph)); - jsonObject.put("vertexLabels", getVertexLabels(graph)); - jsonObject.put("edgeLabels", getEdgeLabels(graph)); - String dataPath = RequestHandler.class.getResource("/data/" + databaseName).getFile(); - FileWriter writer = new FileWriter(dataPath + META_FILENAME); - jsonObject.write(writer); - writer.flush(); - writer.close(); - - return jsonObject; - } catch (Exception e) { - e.printStackTrace(); - // if any exception is thrown, return an error to the client - return null; + + @POST + @Path("/cypher") + @Produces("application/x-www-form-urlencoded;charset=utf-8") + public Response executeCypher( + @FormParam("databaseName") String databaseName, + @FormParam("query") String query, + @DefaultValue("false") @FormParam("attacheData") boolean attacheData) { + //load the database + String path = RequestHandler.class.getResource("/data/" + databaseName).getPath(); + + CSVDataSource source = new CSVDataSource(path, config); + + LogicalGraph graph = source.getLogicalGraph(); + + // TODO load proper statistics + GraphStatistics graphStatistics = new GraphStatistics(1, 1, 1, 1); + GraphCollection res = graph.cypher(query, attacheData, MatchStrategy.HOMOMORPHISM, MatchStrategy.ISOMORPHISM, graphStatistics); + + return createResponse(res); + } + + /** + * Compute property keys and labels. + * + * @param databaseName name of the database + * @return JSONObject containing property keys and labels + */ + private JSONObject computeKeysAndLabels(String databaseName) throws IOException { + + String path = RequestHandler.class.getResource("/data/" + databaseName).getPath(); + + CSVDataSource source = new CSVDataSource(path, config); + + LogicalGraph graph = source.getLogicalGraph(); + + JSONObject jsonObject = new JSONObject(); + + //compute the vertex and edge property keys and return them + try { + jsonObject.put("vertexKeys", getVertexKeys(graph)); + jsonObject.put("edgeKeys", getEdgeKeys(graph)); + jsonObject.put("vertexLabels", getVertexLabels(graph)); + jsonObject.put("edgeLabels", getEdgeLabels(graph)); + String dataPath = RequestHandler.class.getResource("/data/" + databaseName).getFile(); + FileWriter writer = new FileWriter(dataPath + META_FILENAME); + jsonObject.write(writer); + writer.flush(); + writer.close(); + + return jsonObject; + } catch (Exception e) { + e.printStackTrace(); + // if any exception is thrown, return an error to the client + return null; + } } - } - - /** - * Read the property keys and labels from the buffered JSON. - * @param databaseName name of the database - * @return JSONObject containing the property keys and labels - * @throws IOException if reading fails - * @throws JSONException if JSON creation fails - */ - private JSONObject readKeysAndLabels(String databaseName) throws IOException, JSONException { - String dataPath = RequestHandler.class.getResource("/data/" + databaseName).getFile(); - String content = - new String(Files.readAllBytes(Paths.get(dataPath + META_FILENAME)), StandardCharsets.UTF_8); - - return new JSONObject(content); - } - - /** - * Takes any given graph and creates a JSONArray containing the vertex property keys and a - * boolean, - * specifying it the property has a numerical type. - * - * @param graph input graph - * @return JSON array with property keys and boolean, that is true if the property type is - * numercial - * @throws Exception if the collecting of the distributed data fails - */ - private JSONArray getVertexKeys(LogicalGraph graph) throws Exception { - - List, String, Boolean>> vertexKeys = graph.getVertices() - .flatMap(new PropertyKeyMapper<>()) - .groupBy(1) - .reduceGroup(new LabelGroupReducer()) - .collect(); - - return buildArrayFromKeys(vertexKeys); - } - - /** - * Takes any given graph and creates a JSONArray containing the edge property keys and a boolean, - * specifying it the property has a numerical type. - * - * @param graph input graph - * @return JSON array with property keys and boolean, that is true if the property type is - * numercial - * @throws Exception if the collecting of the distributed data fails - */ - private JSONArray getEdgeKeys(LogicalGraph graph) throws Exception { - - List, String, Boolean>> edgeKeys = graph.getEdges() - .flatMap(new PropertyKeyMapper<>()) - .groupBy(1) - .reduceGroup(new LabelGroupReducer()) - .collect(); - - return buildArrayFromKeys(edgeKeys); - } - - /** - * Convenience method. - * Takes a set of tuples of property keys and booleans, specifying if the property is numerical, - * and creates a JSON array containing the same data. - * - * @param keys set of tuples of property keys and booleans, that are true if the property type - * is numerical - * @return JSONArray containing the same data as the input - * @throws JSONException if the construction of the JSON fails - */ - private JSONArray buildArrayFromKeys(List, String, Boolean>> keys) - throws JSONException { - JSONArray keyArray = new JSONArray(); - for(Tuple3, String, Boolean> key : keys) { - JSONObject keyObject = new JSONObject(); - JSONArray labels = new JSONArray(); - key.f0.forEach(labels::put); - keyObject.put("labels", labels); - keyObject.put("name", key.f1); - keyObject.put("numerical", key.f2); - keyArray.put(keyObject); + + /** + * Read the property keys and labels from the buffered JSON. + * + * @param databaseName name of the database + * @return JSONObject containing the property keys and labels + * @throws IOException if reading fails + * @throws JSONException if JSON creation fails + */ + private JSONObject readKeysAndLabels(String databaseName) throws IOException, JSONException { + String dataPath = RequestHandler.class.getResource("/data/" + databaseName).getFile(); + String content = + new String(Files.readAllBytes(Paths.get(dataPath + META_FILENAME)), StandardCharsets.UTF_8); + + return new JSONObject(content); } - return keyArray; - } - - /** - * Compute the labels of the vertices. - * - * @param graph logical graph - * @return JSONArray containing the vertex labels - * @throws Exception if the computation fails - */ - private JSONArray getVertexLabels(LogicalGraph graph) throws Exception { - List> vertexLabels = graph.getVertices() - .map(new LabelMapper<>()) - .reduce(new LabelReducer()) - .collect(); - - if(vertexLabels.size() > 0) { - return buildArrayFromLabels(vertexLabels.get(0)); - } else { - return new JSONArray(); + + /** + * Takes any given graph and creates a JSONArray containing the vertex property keys and a + * boolean, + * specifying it the property has a numerical type. + * + * @param graph input graph + * @return JSON array with property keys and boolean, that is true if the property type is + * numercial + * @throws Exception if the collecting of the distributed data fails + */ + private JSONArray getVertexKeys(LogicalGraph graph) throws Exception { + + List, String, Boolean>> vertexKeys = graph.getVertices() + .flatMap(new PropertyKeyMapper<>()) + .groupBy(1) + .reduceGroup(new LabelGroupReducer()) + .collect(); + + return buildArrayFromKeys(vertexKeys); } - } - - /** - * Compute the labels of the edges. - * - * @param graph logical graph - * @return JSONArray containing the edge labels - * @throws Exception if the computation fails - */ - private JSONArray getEdgeLabels(LogicalGraph graph ) throws Exception { - List> edgeLabels = graph.getEdges() - .map(new LabelMapper<>()) - .reduce(new LabelReducer()) - .collect(); - - if(edgeLabels.size() > 0) { - return buildArrayFromLabels(edgeLabels.get(0)); - } else { - return new JSONArray(); + + /** + * Takes any given graph and creates a JSONArray containing the edge property keys and a boolean, + * specifying it the property has a numerical type. + * + * @param graph input graph + * @return JSON array with property keys and boolean, that is true if the property type is + * numercial + * @throws Exception if the collecting of the distributed data fails + */ + private JSONArray getEdgeKeys(LogicalGraph graph) throws Exception { + + List, String, Boolean>> edgeKeys = graph.getEdges() + .flatMap(new PropertyKeyMapper<>()) + .groupBy(1) + .reduceGroup(new LabelGroupReducer()) + .collect(); + + return buildArrayFromKeys(edgeKeys); } - } - - /** - * Create a JSON array from the sets of labels. - * - * @param labels set of labels - * @return JSON array of labels - */ - private JSONArray buildArrayFromLabels(Set labels) { - JSONArray labelArray = new JSONArray(); - labels.forEach(labelArray::put); - return labelArray; - } - - /** - * Get the complete graph in cytoscape-conform form. - * - * @param databaseName name of the database - * @return Response containing the graph as a JSON, in cytoscape conform format. - * @throws JSONException if JSON creation fails - * @throws IOException if reading fails - */ - - @POST - @Path("/graph/{databaseName}") - @Produces("application/json;charset=utf-8") - public Response getGraph(@PathParam("databaseName") String databaseName) throws Exception { - - String path = RequestHandler.class.getResource("/data/" + databaseName).getPath(); - - CSVDataSource source = new CSVDataSource(path, config); - - LogicalGraph graph = source.getLogicalGraph(); - - String json = CytoJSONBuilder.getJSONString( - graph.getGraphHead().collect(), - graph.getVertices().collect(), - graph.getEdges().collect()); - - return Response.ok(json).build(); - } - - - - /** - * Takes a {@link GroupingRequest}, executes a grouping with the parameters it contains and - * returns the results as a JSON. - * - * @param request GroupingRequest send to the server, containing the parameters for a - * {@link Grouping}. - * @return a JSON containing the result of the executed Grouping, a graph - * @throws Exception if the collecting of the distributed data fails - */ - @POST - @Path("/grouping") - @Produces("application/json;charset=utf-8") - public Response getData(GroupingRequest request) throws Exception { - - //load the database - String databaseName = request.getDbName(); - - String path = RequestHandler.class.getResource("/data/" + databaseName).getPath(); - - CSVDataSource source = new CSVDataSource(path, config); - - LogicalGraph graph = source.getLogicalGraph(); - - //if no edges are requested, remove them as early as possible - //else, apply the normal filters - if(request.getFilterAllEdges()) { - graph = graph.subgraph(new LabelFilter<>(request.getVertexFilters()), - new AcceptNoneFilter<>()); - } else{ - graph = graph.subgraph(new LabelFilter<>(request.getVertexFilters()), - new LabelFilter<>(request.getEdgeFilters())); + + /** + * Convenience method. + * Takes a set of tuples of property keys and booleans, specifying if the property is numerical, + * and creates a JSON array containing the same data. + * + * @param keys set of tuples of property keys and booleans, that are true if the property type + * is numerical + * @return JSONArray containing the same data as the input + * @throws JSONException if the construction of the JSON fails + */ + private JSONArray buildArrayFromKeys(List, String, Boolean>> keys) + throws JSONException { + JSONArray keyArray = new JSONArray(); + for (Tuple3, String, Boolean> key : keys) { + JSONObject keyObject = new JSONObject(); + JSONArray labels = new JSONArray(); + key.f0.forEach(labels::put); + keyObject.put("labels", labels); + keyObject.put("name", key.f1); + keyObject.put("numerical", key.f2); + keyArray.put(keyObject); + } + return keyArray; } - //construct the grouping with the parameters send by the request - Grouping.GroupingBuilder builder = new Grouping.GroupingBuilder(); - int position; - position = ArrayUtils.indexOf(request.getVertexKeys(), "label"); - if(position > -1) { - builder.useVertexLabel(true); - request.setVertexKeys((String[])ArrayUtils.remove(request.getVertexKeys(), position)); + /** + * Compute the labels of the vertices. + * + * @param graph logical graph + * @return JSONArray containing the vertex labels + * @throws Exception if the computation fails + */ + private JSONArray getVertexLabels(LogicalGraph graph) throws Exception { + List> vertexLabels = graph.getVertices() + .map(new LabelMapper<>()) + .reduce(new LabelReducer()) + .collect(); + + if (vertexLabels.size() > 0) { + return buildArrayFromLabels(vertexLabels.get(0)); + } else { + return new JSONArray(); + } } - builder.addVertexGroupingKeys(Arrays.asList(request.getVertexKeys())); - position = ArrayUtils.indexOf(request.getEdgeKeys(), "label"); - if(position > -1) { - builder.useEdgeLabel(true); - request.setEdgeKeys((String[])ArrayUtils.remove(request.getEdgeKeys(), position)); + /** + * Compute the labels of the edges. + * + * @param graph logical graph + * @return JSONArray containing the edge labels + * @throws Exception if the computation fails + */ + private JSONArray getEdgeLabels(LogicalGraph graph) throws Exception { + List> edgeLabels = graph.getEdges() + .map(new LabelMapper<>()) + .reduce(new LabelReducer()) + .collect(); + + if (edgeLabels.size() > 0) { + return buildArrayFromLabels(edgeLabels.get(0)); + } else { + return new JSONArray(); + } } - builder.addEdgeGroupingKeys(Arrays.asList(request.getEdgeKeys())); - - String[] vertexAggrFuncs = request.getVertexAggrFuncs(); - - for(String vertexAggrFunc : vertexAggrFuncs) { - String[] split = vertexAggrFunc.split(" "); - switch (split[0]) { - case "max": - builder.addVertexAggregator(new MaxAggregator(split[1], "max " + split[1])); - break; - case "min": - builder.addVertexAggregator(new MinAggregator(split[1], "min " + split[1])); - break; - case "sum": - builder.addVertexAggregator(new SumAggregator(split[1], "sum " + split[1])); - break; - case "count": - builder.addVertexAggregator(new CountAggregator()); - break; - } + + /** + * Create a JSON array from the sets of labels. + * + * @param labels set of labels + * @return JSON array of labels + */ + private JSONArray buildArrayFromLabels(Set labels) { + JSONArray labelArray = new JSONArray(); + labels.forEach(labelArray::put); + return labelArray; } - String[] edgeAggrFuncs = request.getEdgeAggrFuncs(); - - for(String edgeAggrFunc : edgeAggrFuncs) { - String[] split = edgeAggrFunc.split(" "); - switch (split[0]) { - case "max": - builder.addEdgeAggregator(new MaxAggregator(split[1], "max " + split[1])); - break; - case "min": - builder.addEdgeAggregator(new MinAggregator(split[1], "min " + split[1])); - break; - case "sum": - builder.addEdgeAggregator(new SumAggregator(split[1], "sum " + split[1])); - break; - case "count": - builder.addEdgeAggregator(new CountAggregator()); - break; - } + /** + * Get the complete graph in cytoscape-conform form. + * + * @param databaseName name of the database + * @return Response containing the graph as a JSON, in cytoscape conform format. + * @throws JSONException if JSON creation fails + * @throws IOException if reading fails + */ + + @POST + @Path("/graph/{databaseName}") + @Produces("application/json;charset=utf-8") + public Response getGraph(@PathParam("databaseName") String databaseName) throws Exception { + + String path = RequestHandler.class.getResource("/data/" + databaseName).getPath(); + + CSVDataSource source = new CSVDataSource(path, config); + + LogicalGraph graph = source.getLogicalGraph(); + + String json = CytoJSONBuilder.getJSONString( + graph.getGraphHead().collect(), + graph.getVertices().collect(), + graph.getEdges().collect()); + + return Response.ok(json).build(); } - // by default, we use the group reduce strategy - builder.setStrategy(GroupingStrategy.GROUP_REDUCE); + /** + * Takes a {@link GroupingRequest}, executes a grouping with the parameters it contains and + * returns the results as a JSON. + * + * @param request GroupingRequest send to the server, containing the parameters for a + * {@link Grouping}. + * @return a JSON containing the result of the executed Grouping, a graph + * @throws Exception if the collecting of the distributed data fails + */ + @POST + @Path("/grouping") + @Produces("application/json;charset=utf-8") + public Response getData(GroupingRequest request) throws Exception { + + //load the database + String databaseName = request.getDbName(); + + String path = RequestHandler.class.getResource("/data/" + databaseName).getPath(); + + CSVDataSource source = new CSVDataSource(path, config); + + LogicalGraph graph = source.getLogicalGraph(); + + //if no edges are requested, remove them as early as possible + //else, apply the normal filters + if (request.getFilterAllEdges()) { + graph = graph.subgraph(new LabelFilter<>(request.getVertexFilters()), + new AcceptNoneFilter<>()); + } else { + graph = graph.subgraph(new LabelFilter<>(request.getVertexFilters()), + new LabelFilter<>(request.getEdgeFilters())); + } + + //construct the grouping with the parameters send by the request + Grouping.GroupingBuilder builder = new Grouping.GroupingBuilder(); + int position; + position = ArrayUtils.indexOf(request.getVertexKeys(), "label"); + if (position > -1) { + builder.useVertexLabel(true); + request.setVertexKeys((String[]) ArrayUtils.remove(request.getVertexKeys(), position)); + } + builder.addVertexGroupingKeys(Arrays.asList(request.getVertexKeys())); - graph = builder.build().execute(graph); + position = ArrayUtils.indexOf(request.getEdgeKeys(), "label"); + if (position > -1) { + builder.useEdgeLabel(true); + request.setEdgeKeys((String[]) ArrayUtils.remove(request.getEdgeKeys(), position)); + } + builder.addEdgeGroupingKeys(Arrays.asList(request.getEdgeKeys())); + + String[] vertexAggrFuncs = request.getVertexAggrFuncs(); + + for (String vertexAggrFunc : vertexAggrFuncs) { + String[] split = vertexAggrFunc.split(" "); + switch (split[0]) { + case "max": + builder.addVertexAggregator(new MaxAggregator(split[1], "max " + split[1])); + break; + case "min": + builder.addVertexAggregator(new MinAggregator(split[1], "min " + split[1])); + break; + case "sum": + builder.addVertexAggregator(new SumAggregator(split[1], "sum " + split[1])); + break; + case "count": + builder.addVertexAggregator(new CountAggregator()); + break; + } + } - // specify the output collections - return createResponse(graph); - } + String[] edgeAggrFuncs = request.getEdgeAggrFuncs(); + + for (String edgeAggrFunc : edgeAggrFuncs) { + String[] split = edgeAggrFunc.split(" "); + switch (split[0]) { + case "max": + builder.addEdgeAggregator(new MaxAggregator(split[1], "max " + split[1])); + break; + case "min": + builder.addEdgeAggregator(new MinAggregator(split[1], "min " + split[1])); + break; + case "sum": + builder.addEdgeAggregator(new SumAggregator(split[1], "sum " + split[1])); + break; + case "count": + builder.addEdgeAggregator(new CountAggregator()); + break; + } + } - private Response createResponse(GraphCollection graph) { - List resultHead = new ArrayList<>(); - List resultVertices = new ArrayList<>(); - List resultEdges = new ArrayList<>(); + // by default, we use the group reduce strategy + builder.setStrategy(GroupingStrategy.GROUP_REDUCE); - graph.getGraphHeads().output(new LocalCollectionOutputFormat<>(resultHead)); - graph.getVertices().output(new LocalCollectionOutputFormat<>(resultVertices)); - graph.getEdges().output(new LocalCollectionOutputFormat<>(resultEdges)); + graph = builder.build().execute(graph); - return getResponse(resultHead, resultVertices, resultEdges); - } + // specify the output collections + return createResponse(graph); + } - private Response createResponse(LogicalGraph graph) { - List resultHead = new ArrayList<>(); - List resultVertices = new ArrayList<>(); - List resultEdges = new ArrayList<>(); + private Response createResponse(GraphCollection graph) { + List resultHead = new ArrayList<>(); + List resultVertices = new ArrayList<>(); + List resultEdges = new ArrayList<>(); - graph.getGraphHead().output(new LocalCollectionOutputFormat<>(resultHead)); - graph.getVertices().output(new LocalCollectionOutputFormat<>(resultVertices)); - graph.getEdges().output(new LocalCollectionOutputFormat<>(resultEdges)); + graph.getGraphHeads().output(new LocalCollectionOutputFormat<>(resultHead)); + graph.getVertices().output(new LocalCollectionOutputFormat<>(resultVertices)); + graph.getEdges().output(new LocalCollectionOutputFormat<>(resultEdges)); - return getResponse(resultHead, resultVertices, resultEdges); - } + return getResponse(resultHead, resultVertices, resultEdges); + } + + private Response createResponse(LogicalGraph graph) { + List resultHead = new ArrayList<>(); + List resultVertices = new ArrayList<>(); + List resultEdges = new ArrayList<>(); - private Response getResponse(List resultHead, List resultVertices, List resultEdges) { - try { - ENV.execute(); - // build the response JSON from the collections - String json = CytoJSONBuilder.getJSONString(resultHead, resultVertices, resultEdges); - return Response.ok(json).build(); + graph.getGraphHead().output(new LocalCollectionOutputFormat<>(resultHead)); + graph.getVertices().output(new LocalCollectionOutputFormat<>(resultVertices)); + graph.getEdges().output(new LocalCollectionOutputFormat<>(resultEdges)); - } catch (Exception e) { - e.printStackTrace(); - // if any exception is thrown, return an error to the client - return Response.serverError().build(); + return getResponse(resultHead, resultVertices, resultEdges); + } + + private Response getResponse(List resultHead, List resultVertices, List resultEdges) { + try { + ENV.execute(); + // build the response JSON from the collections + String json = CytoJSONBuilder.getJSONString(resultHead, resultVertices, resultEdges); + return Response.ok(json).build(); + + } catch (Exception e) { + e.printStackTrace(); + // if any exception is thrown, return an error to the client + return Response.serverError().build(); + } } - } } \ No newline at end of file diff --git a/src/main/java/org/gradoop/demo/server/Server.java b/src/main/java/org/gradoop/demo/server/Server.java index 99928f6..76aca16 100644 --- a/src/main/java/org/gradoop/demo/server/Server.java +++ b/src/main/java/org/gradoop/demo/server/Server.java @@ -20,69 +20,143 @@ import com.sun.jersey.api.core.PackagesResourceConfig; import com.sun.jersey.api.core.ResourceConfig; import com.sun.jersey.api.json.JSONConfiguration; + +import org.apache.flink.api.java.ExecutionEnvironment; +import org.apache.flink.api.java.RemoteEnvironment; +import org.apache.flink.api.java.utils.ParameterTool; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hdfs.HdfsConfiguration; import org.glassfish.grizzly.http.server.HttpHandler; import org.glassfish.grizzly.http.server.HttpServer; import org.glassfish.grizzly.http.server.StaticHttpHandler; -import javax.ws.rs.core.UriBuilder; import java.io.IOException; +import java.net.MalformedURLException; import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +import javax.ws.rs.core.UriBuilder; + +import static java.lang.System.exit; +import static java.lang.System.setOut; +import static java.util.Objects.requireNonNull; +import static org.apache.flink.api.java.ExecutionEnvironment.createLocalEnvironment; +import static org.apache.flink.api.java.ExecutionEnvironment.createRemoteEnvironment; +import static org.gradoop.demo.server.LocalGradoopGraphsetStore.DEFAULT_LOCAL_PATH; /** * Basic class, used for starting and stopping the server. */ public class Server { - /** - * URI that specifies where the server is run. - */ - private static final URI BASE_URI = getBaseURI(); - /** - * Default port - */ - private static final int PORT = 2342; - /** - * Path to demo application - */ - private static final String APPLICATION_PATH = "gradoop/html/grouping.html"; + /** + * URI that specifies where the server is run. + */ +// private static final URI BASE_URI = getBaseURI(); + public static final String DEFAULT_JM = "local"; + static volatile ExecutionEnvironment ENV = null; // volatile is just to ensure visibility + static volatile GradoopGraphsetStore LOCAL_STORE = null; + /** + * Default port + */ + private static final int PORT = 2342; + /** + * Path to demo application + */ + private static final String APPLICATION_PATH = "gradoop/html/grouping.html"; + + /** + * Creates the base URI. + * + * @return Base URI + */ + private static URI getBaseURI(String ip) { + return UriBuilder.fromUri("http://" + ip + "/").port(PORT).build(); + } - /** - * Creates the base URI. - * @return Base URI - */ - private static URI getBaseURI() { - return UriBuilder.fromUri("http://localhost/").port(PORT).build(); - } + /** + * Starts the server and adds the request handlers. + * + * @return the running server + * @throws IOException if server creation fails + */ + private static HttpServer startServer(String[] args) throws IOException, URISyntaxException { + System.out.println("Starting grizzly..."); + ResourceConfig rc = new PackagesResourceConfig("org/gradoop/demo/server"); + rc.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, true); + ParameterTool params = ParameterTool.fromArgs(args); + String ip = params.has("ip") ? params.get("ip") : "localhost"; + String jmHost = params.has("jmhost") ? params.get("jmhost") : DEFAULT_JM; + int jmPort = -1; + if (!DEFAULT_JM.equals(jmHost) && !params.has("jmport")) { + System.out.println("Error: provide Job Manager Port; exiting"); + exit(1); + } + if (!DEFAULT_JM.equals(jmHost)) { + jmPort = params.getInt("jmport"); + } + URI baseURI = getBaseURI(ip); + ENV = getExecutionEnvironment(jmHost, jmPort); + String hdfsParam = "hdfs"; + if (params.getBoolean(hdfsParam, false)) { + LOCAL_STORE = new LocalGradoopGraphsetStore(new HdfsConfiguration(true), DEFAULT_LOCAL_PATH); + } else { + LOCAL_STORE = new LocalGradoopGraphsetStore(null, DEFAULT_LOCAL_PATH); + } + System.out.println("Execution Environment: " + ENV); + System.out.println(LOCAL_STORE); + HttpServer server = GrizzlyServerFactory.createHttpServer(baseURI, rc); + HttpHandler staticHandler = new StaticHttpHandler( + Server.class.getResource("/web").getPath()); + server.getServerConfiguration().addHttpHandler(staticHandler, "/gradoop"); + System.out.printf("org.gradoop.demos.grouping.server started at %s%s%n" + + "Kill the process or ^c to stop it.%n", baseURI, APPLICATION_PATH); + return server; + } - /** - * Starts the server and adds the request handlers. - * - * @return the running server - * @throws IOException if server creation fails - */ - private static HttpServer startServer() throws IOException { - System.out.println("Starting grizzly..."); - ResourceConfig rc = new PackagesResourceConfig("org/gradoop/demo/server"); - rc.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, true); - HttpServer server = GrizzlyServerFactory.createHttpServer(BASE_URI, rc); - HttpHandler staticHandler = new StaticHttpHandler( - Server.class.getResource("/web").getPath()); - server.getServerConfiguration().addHttpHandler( staticHandler, "/gradoop" ); + private static ExecutionEnvironment getExecutionEnvironment(String jmHost, int jmPort) throws MalformedURLException { + requireNonNull(jmHost); + if (DEFAULT_JM.equals(jmHost)) { + return createLocalEnvironment(); + } +// String m2 = "/.m2/repository/org/gradoop"; +// String[] jarFiles = { +// System.getProperty("user.home") + m2 + "/gradoop-common/0.3.2/gradoop-common-0.3.2.jar", +// System.getProperty("user.home") + m2 + "/gradoop-flink/0.3.2/gradoop-flink-0.3.2.jar" +// }; + String[] jarFiles = { + System.getProperty("user.dir") + "/target/gradoop-demo-shaded.jar" + }; +// URL[] globalCP = { +// new URL("file://" + System.getProperty("user.dir") + "/target/gradoop-demo-shaded.jar") +// }; + ExecutionEnvironment ee = ExecutionEnvironment.createRemoteEnvironment(jmHost, jmPort, null); +// ExecutionEnvironment ee = ExecutionEnvironment.createRemoteEnvironment(jmHost, jmPort); - return server; - } + System.out.println("ee exec mode: " + ee.getConfig().getExecutionMode()); + return ee; + } - /** - * Main method. Run this to start the server. - * - * @param args command line parameters - * @throws IOException if server creation fails - */ - public static void main(String[] args) throws IOException { - HttpServer httpServer = startServer(); - System.out.printf("org.gradoop.demos.grouping.server started at %s%s%n" + - "Press any key to stop it.%n", getBaseURI(), APPLICATION_PATH); - System.in.read(); - httpServer.stop(); - } + /** + * Main method. Run this to start the server. + * + * @param args command line parameters + * @throws IOException if server creation fails + */ + public static void main(String[] args) throws IOException, InterruptedException, URISyntaxException { + HttpServer httpServer = startServer(args); +// System.in.read(); +// httpServer.stop(); + // for nohup + BlockingQueue q = new ArrayBlockingQueue<>(1); + synchronized (q) { // sync on local variable on for an indefinite wait + while (q.isEmpty()) { + q.wait(); + } + } + httpServer.stop(); + } } diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties new file mode 100644 index 0000000..be00727 --- /dev/null +++ b/src/main/resources/log4j.properties @@ -0,0 +1,9 @@ +# Root logger option +log4j.rootLogger=INFO, stdout +log4j.logger.org.gradoop.demo.server=DEBUG, stdout + +# Direct log messages to stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1} - %m%n \ No newline at end of file diff --git a/src/main/resources/web/html/cypher.html b/src/main/resources/web/html/cypher.html index 6c9cf0c..e37c474 100644 --- a/src/main/resources/web/html/cypher.html +++ b/src/main/resources/web/html/cypher.html @@ -17,31 +17,33 @@ - - - - - Gradoop Demo - - - - - - - - + + + + + Gradoop Demo + + + + + + + + - - - - - - - - - - - + + + + + + + + + + +