Repository: incubator-rya Updated Branches: refs/heads/develop 358c13b83 -> 15ec5d5fa
http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/15ec5d5f/extras/rya.pcj.fluo/pcj.fluo.integration/src/test/java/org/apache/rya/indexing/pcj/fluo/integration/QueryIT.java ---------------------------------------------------------------------- diff --git a/extras/rya.pcj.fluo/pcj.fluo.integration/src/test/java/org/apache/rya/indexing/pcj/fluo/integration/QueryIT.java b/extras/rya.pcj.fluo/pcj.fluo.integration/src/test/java/org/apache/rya/indexing/pcj/fluo/integration/QueryIT.java new file mode 100644 index 0000000..32b7d84 --- /dev/null +++ b/extras/rya.pcj.fluo/pcj.fluo.integration/src/test/java/org/apache/rya/indexing/pcj/fluo/integration/QueryIT.java @@ -0,0 +1,234 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.rya.indexing.pcj.fluo.integration; + +import static org.junit.Assert.assertEquals; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.rya.indexing.pcj.fluo.ITBase; +import org.apache.rya.indexing.pcj.fluo.api.CreatePcj; +import org.apache.rya.indexing.pcj.fluo.api.InsertTriples; +import org.junit.Test; +import org.openrdf.model.impl.NumericLiteralImpl; +import org.openrdf.model.impl.URIImpl; +import org.openrdf.model.vocabulary.XMLSchema; +import org.openrdf.query.BindingSet; +import org.openrdf.query.impl.BindingImpl; + +import com.google.common.collect.Sets; + +import mvm.rya.api.domain.RyaStatement; +import mvm.rya.indexing.external.tupleSet.PcjTables.VariableOrder; + +/** + * Performs integration tests over the Fluo application geared towards various query structures. + * <p> + * These tests are being ignore so that they will not run as unit tests while building the application. + */ +public class QueryIT extends ITBase { + + /** + * Tests when there are a bunch of variables across a bunch of joins. + */ + @Test + public void complexQuery() throws Exception { + // A query that find people who live in the USA, have been recruited by Geek Squad, + // and are skilled with computers. The resulting binding set includes everybody who + // was involved in the recruitment process. + final String sparql = + "SELECT ?recruiter ?candidate ?leader " + + "{ " + + "?recruiter <http://recruiterFor> <http://GeekSquad>. " + + "?candidate <http://skilledWith> <http://Computers>. " + + "?candidate <http://livesIn> \"USA\". " + + "?leader <http://leaderOf> <http://GeekSquad>. " + + "?recruiter <http://talksTo> ?candidate. " + + "?candidate <http://talksTo> ?leader. " + + "}"; + + // Triples that will be streamed into Fluo after the PCJ has been created. + final Set<RyaStatement> streamedTriples = Sets.newHashSet( + // Leaders + makeRyaStatement("http://Alice", "http://leaderOf", "http://GeekSquad"), + makeRyaStatement("http://Bob", "http://leaderOf", "http://GeekSquad"), + + // Recruiters + makeRyaStatement("http://Charlie", "http://recruiterFor", "http://GeekSquad"), + makeRyaStatement("http://David", "http://recruiterFor", "http://GeekSquad"), + + // Candidates + makeRyaStatement("http://Eve", "http://skilledWith", "http://Computers"), + makeRyaStatement("http://Eve", "http://livesIn", "USA"), + makeRyaStatement("http://Frank", "http://skilledWith", "http://Computers"), + makeRyaStatement("http://Frank", "http://livesIn", "USA"), + makeRyaStatement("http://George", "http://skilledWith", "http://Computers"), + makeRyaStatement("http://George", "http://livesIn", "Germany"), + makeRyaStatement("http://Harry", "http://skilledWith", "http://Negotiating"), + makeRyaStatement("http://Harry", "http://livesIn", "USA"), + makeRyaStatement("http://Ivan", "http://skilledWith", "http://Computers"), + makeRyaStatement("http://Ivan", "http://livesIn", "USA"), + + // Candidates the recruiters talk to. + makeRyaStatement("http://Charlie", "http://talksTo", "http://Eve"), + makeRyaStatement("http://Charlie", "http://talksTo", "http://George"), + makeRyaStatement("http://Charlie", "http://talksTo", "http://Harry"), + makeRyaStatement("http://David", "http://talksTo", "http://Eve"), + makeRyaStatement("http://David", "http://talksTo", "http://Frank"), + makeRyaStatement("http://David", "http://talksTo", "http://Ivan"), + + // Recruits that talk to leaders. + makeRyaStatement("http://Eve", "http://talksTo", "http://Alice"), + makeRyaStatement("http://George", "http://talksTo", "http://Alice"), + makeRyaStatement("http://Harry", "http://talksTo", "http://Bob"), + makeRyaStatement("http://Ivan", "http://talksTo", "http://Bob")); + + // The expected results of the SPARQL query once the PCJ has been computed. + final Set<BindingSet> expected = new HashSet<>(); + expected.add( makeBindingSet( + new BindingImpl("recruiter", new URIImpl("http://Charlie")), + new BindingImpl("candidate", new URIImpl("http://Eve")), + new BindingImpl("leader", new URIImpl("http://Alice")))); + expected.add( makeBindingSet( + new BindingImpl("recruiter", new URIImpl("http://David")), + new BindingImpl("candidate", new URIImpl("http://Eve")), + new BindingImpl("leader", new URIImpl("http://Alice")))); + expected.add( makeBindingSet( + new BindingImpl("recruiter", new URIImpl("http://David")), + new BindingImpl("candidate", new URIImpl("http://Ivan")), + new BindingImpl("leader", new URIImpl("http://Bob")))); + + // Create the PCJ in Fluo. + new CreatePcj().withRyaIntegration(fluoClient, RYA_TABLE_PREFIX, ryaRepo, accumuloConn, new HashSet<VariableOrder>(), sparql); + + // Stream the data into Fluo. + new InsertTriples().insert(fluoClient, streamedTriples); + + // Verify the end results of the query match the expected results. + fluo.waitForObservers(); + final Set<BindingSet> results = getQueryBindingSetValues(fluoClient, sparql); + assertEquals(expected, results); + } + + @Test + public void withURIFilters() throws Exception { + final String sparql = + "SELECT ?customer ?worker ?city " + + "{ " + + "FILTER(?customer = <http://Alice>) " + + "FILTER(?city = <http://London>) " + + "?customer <http://talksTo> ?worker. " + + "?worker <http://livesIn> ?city. " + + "?worker <http://worksAt> <http://Chipotle>. " + + "}"; + + // Triples that will be streamed into Fluo after the PCJ has been created. + final Set<RyaStatement> streamedTriples = Sets.newHashSet( + makeRyaStatement("http://Alice", "http://talksTo", "http://Bob"), + makeRyaStatement("http://Bob", "http://livesIn", "http://London"), + makeRyaStatement("http://Bob", "http://worksAt", "http://Chipotle"), + + makeRyaStatement("http://Alice", "http://talksTo", "http://Charlie"), + makeRyaStatement("http://Charlie", "http://livesIn", "http://London"), + makeRyaStatement("http://Charlie", "http://worksAt", "http://Chipotle"), + + makeRyaStatement("http://Alice", "http://talksTo", "http://David"), + makeRyaStatement("http://David", "http://livesIn", "http://London"), + makeRyaStatement("http://David", "http://worksAt", "http://Chipotle"), + + makeRyaStatement("http://Alice", "http://talksTo", "http://Eve"), + makeRyaStatement("http://Eve", "http://livesIn", "http://Leeds"), + makeRyaStatement("http://Eve", "http://worksAt", "http://Chipotle"), + + makeRyaStatement("http://Frank", "http://talksTo", "http://Alice"), + makeRyaStatement("http://Frank", "http://livesIn", "http://London"), + makeRyaStatement("http://Frank", "http://worksAt", "http://Chipotle")); + + // The expected results of the SPARQL query once the PCJ has been computed. + final Set<BindingSet> expected = new HashSet<>(); + expected.add( makeBindingSet( + new BindingImpl("customer", new URIImpl("http://Alice")), + new BindingImpl("worker", new URIImpl("http://Bob")), + new BindingImpl("city", new URIImpl("http://London")))); + expected.add( makeBindingSet( + new BindingImpl("customer", new URIImpl("http://Alice")), + new BindingImpl("worker", new URIImpl("http://Charlie")), + new BindingImpl("city", new URIImpl("http://London")))); + expected.add( makeBindingSet( + new BindingImpl("customer", new URIImpl("http://Alice")), + new BindingImpl("worker", new URIImpl("http://David")), + new BindingImpl("city", new URIImpl("http://London")))); + + // Create the PCJ in Fluo. + new CreatePcj().withRyaIntegration(fluoClient, RYA_TABLE_PREFIX, ryaRepo, accumuloConn, new HashSet<VariableOrder>(), sparql); + + // Stream the data into Fluo. + new InsertTriples().insert(fluoClient, streamedTriples); + + // Verify the end results of the query match the expected results. + fluo.waitForObservers(); + final Set<BindingSet> results = getQueryBindingSetValues(fluoClient, sparql); + assertEquals(expected, results); + } + + @Test + public void withNumericFilters() throws Exception { + final String sparql = + "SELECT ?name ?age " + + "{" + + "FILTER(?age < 30) ." + + "?name <http://hasAge> ?age." + + "?name <http://playsSport> \"Soccer\" " + + "}"; + + final Set<RyaStatement> streamedTriples = Sets.newHashSet( + makeRyaStatement("http://Alice", "http://hasAge", 18), + makeRyaStatement("http://Bob", "http://hasAge", 30), + makeRyaStatement("http://Charlie", "http://hasAge", 14), + makeRyaStatement("http://David", "http://hasAge", 16), + makeRyaStatement("http://Eve", "http://hasAge", 35), + + makeRyaStatement("http://Alice", "http://playsSport", "Soccer"), + makeRyaStatement("http://Bob", "http://playsSport", "Soccer"), + makeRyaStatement("http://Charlie", "http://playsSport", "Basketball"), + makeRyaStatement("http://Charlie", "http://playsSport", "Soccer"), + makeRyaStatement("http://David", "http://playsSport", "Basketball")); + + // The expected results of the SPARQL query once the PCJ has been computed. + final Set<BindingSet> expected = new HashSet<>(); + expected.add( makeBindingSet( + new BindingImpl("name", new URIImpl("http://Alice")), + new BindingImpl("age", new NumericLiteralImpl(18, XMLSchema.INTEGER)))); + expected.add( makeBindingSet( + new BindingImpl("name", new URIImpl("http://Charlie")), + new BindingImpl("age", new NumericLiteralImpl(14, XMLSchema.INTEGER)))); + + // Create the PCJ in Fluo. + new CreatePcj().withRyaIntegration(fluoClient, RYA_TABLE_PREFIX, ryaRepo, accumuloConn, new HashSet<VariableOrder>(), sparql); + + // Stream the data into Fluo. + new InsertTriples().insert(fluoClient, streamedTriples); + + // Verify the end results of the query match the expected results. + fluo.waitForObservers(); + final Set<BindingSet> results = getQueryBindingSetValues(fluoClient, sparql); + assertEquals(expected, results); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/15ec5d5f/extras/rya.pcj.fluo/pcj.fluo.integration/src/test/java/org/apache/rya/indexing/pcj/fluo/integration/RyaExportIT.java ---------------------------------------------------------------------- diff --git a/extras/rya.pcj.fluo/pcj.fluo.integration/src/test/java/org/apache/rya/indexing/pcj/fluo/integration/RyaExportIT.java b/extras/rya.pcj.fluo/pcj.fluo.integration/src/test/java/org/apache/rya/indexing/pcj/fluo/integration/RyaExportIT.java new file mode 100644 index 0000000..ae728cd --- /dev/null +++ b/extras/rya.pcj.fluo/pcj.fluo.integration/src/test/java/org/apache/rya/indexing/pcj/fluo/integration/RyaExportIT.java @@ -0,0 +1,187 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.rya.indexing.pcj.fluo.integration; + +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.apache.accumulo.core.client.Connector; +import org.apache.accumulo.core.client.Scanner; +import org.apache.accumulo.core.client.TableNotFoundException; +import org.apache.accumulo.core.data.Key; +import org.apache.accumulo.core.data.Value; +import org.apache.accumulo.core.security.Authorizations; +import org.apache.hadoop.io.Text; +import org.apache.rya.indexing.pcj.fluo.ITBase; +import org.apache.rya.indexing.pcj.fluo.api.CreatePcj; +import org.apache.rya.indexing.pcj.fluo.api.InsertTriples; +import org.apache.rya.indexing.pcj.fluo.app.export.rya.RyaExportParameters; +import org.apache.rya.indexing.pcj.fluo.app.query.FluoQueryColumns; +import org.junit.Test; +import org.openrdf.model.impl.URIImpl; +import org.openrdf.query.BindingSet; +import org.openrdf.query.impl.BindingImpl; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; + +import io.fluo.api.client.Snapshot; +import io.fluo.api.data.Bytes; +import mvm.rya.api.domain.RyaStatement; +import mvm.rya.api.resolver.RyaTypeResolverException; +import mvm.rya.indexing.external.tupleSet.AccumuloPcjSerializer; +import mvm.rya.indexing.external.tupleSet.PcjTables; +import mvm.rya.indexing.external.tupleSet.PcjTables.PcjException; +import mvm.rya.indexing.external.tupleSet.PcjTables.PcjMetadata; +import mvm.rya.indexing.external.tupleSet.PcjTables.VariableOrder; + +/** + * Performs integration tests over the Fluo application geared towards Rya PCJ exporting. + * <p> + * These tests are being ignore so that they will not run as unit tests while building the application. + */ +public class RyaExportIT extends ITBase { + + /** + * Configure the export observer to use the Mini Accumulo instance as the + * export destination for new PCJ results. + */ + @Override + protected Map<String, String> makeExportParams() { + final HashMap<String, String> params = new HashMap<>(); + + final RyaExportParameters ryaParams = new RyaExportParameters(params); + ryaParams.setExportToRya(true); + ryaParams.setAccumuloInstanceName(accumulo.getInstanceName()); + ryaParams.setZookeeperServers(accumulo.getZooKeepers()); + ryaParams.setExporterUsername("root"); + ryaParams.setExporterPassword("password"); + + return params; + } + + @Test + public void resultsExported() throws Exception { + final String sparql = + "SELECT ?customer ?worker ?city " + + "{ " + + "FILTER(?customer = <http://Alice>) " + + "FILTER(?city = <http://London>) " + + "?customer <http://talksTo> ?worker. " + + "?worker <http://livesIn> ?city. " + + "?worker <http://worksAt> <http://Chipotle>. " + + "}"; + + // Triples that will be streamed into Fluo after the PCJ has been created. + final Set<RyaStatement> streamedTriples = Sets.newHashSet( + makeRyaStatement("http://Alice", "http://talksTo", "http://Bob"), + makeRyaStatement("http://Bob", "http://livesIn", "http://London"), + makeRyaStatement("http://Bob", "http://worksAt", "http://Chipotle"), + + makeRyaStatement("http://Alice", "http://talksTo", "http://Charlie"), + makeRyaStatement("http://Charlie", "http://livesIn", "http://London"), + makeRyaStatement("http://Charlie", "http://worksAt", "http://Chipotle"), + + makeRyaStatement("http://Alice", "http://talksTo", "http://David"), + makeRyaStatement("http://David", "http://livesIn", "http://London"), + makeRyaStatement("http://David", "http://worksAt", "http://Chipotle"), + + makeRyaStatement("http://Alice", "http://talksTo", "http://Eve"), + makeRyaStatement("http://Eve", "http://livesIn", "http://Leeds"), + makeRyaStatement("http://Eve", "http://worksAt", "http://Chipotle"), + + makeRyaStatement("http://Frank", "http://talksTo", "http://Alice"), + makeRyaStatement("http://Frank", "http://livesIn", "http://London"), + makeRyaStatement("http://Frank", "http://worksAt", "http://Chipotle")); + + // The expected results of the SPARQL query once the PCJ has been computed. + final Set<BindingSet> expectedResults = new HashSet<>(); + expectedResults.add( makeBindingSet( + new BindingImpl("customer", new URIImpl("http://Alice")), + new BindingImpl("worker", new URIImpl("http://Bob")), + new BindingImpl("city", new URIImpl("http://London")))); + expectedResults.add( makeBindingSet( + new BindingImpl("customer", new URIImpl("http://Alice")), + new BindingImpl("worker", new URIImpl("http://Charlie")), + new BindingImpl("city", new URIImpl("http://London")))); + expectedResults.add( makeBindingSet( + new BindingImpl("customer", new URIImpl("http://Alice")), + new BindingImpl("worker", new URIImpl("http://David")), + new BindingImpl("city", new URIImpl("http://London")))); + + // Create the PCJ in Fluo. + new CreatePcj().withRyaIntegration(fluoClient, RYA_TABLE_PREFIX, ryaRepo, accumuloConn, new HashSet<VariableOrder>(), sparql); + + // Stream the data into Fluo. + new InsertTriples().insert(fluoClient, streamedTriples); + + // Fetch the exported results from Accumulo once the observers finish working. + fluo.waitForObservers(); + + // Fetch expcted results from the PCJ table that is in Accumulo. + final String exportTableName; + try(Snapshot snapshot = fluoClient.newSnapshot()) { + final Bytes queryId = snapshot.get(Bytes.of(sparql), FluoQueryColumns.QUERY_ID); + exportTableName = snapshot.get(queryId, FluoQueryColumns.QUERY_RYA_EXPORT_TABLE_NAME).toString(); + } + + final Multimap<String, BindingSet> results = loadPcjResults(accumuloConn, exportTableName); + + // Verify the end results of the query match the expected results. + final Multimap<String, BindingSet> expected = HashMultimap.create(); + expected.putAll("customer;worker;city", expectedResults); + expected.putAll("worker;city;customer", expectedResults); + expected.putAll("city;customer;worker", expectedResults); + + assertEquals(expected, results); + } + + /** + * Scan accumulo for the results that are stored in a PCJ table. The + * multimap stores a set of deserialized binding sets that were in the PCJ + * table for every variable order that is found in the PCJ metadata. + */ + private static Multimap<String, BindingSet> loadPcjResults(final Connector accumuloConn, final String pcjTableName) throws PcjException, TableNotFoundException, RyaTypeResolverException { + final Multimap<String, BindingSet> fetchedResults = HashMultimap.create(); + + // Get the variable orders the data was written to. + final PcjTables pcjs = new PcjTables(); + final PcjMetadata pcjMetadata = pcjs.getPcjMetadata(accumuloConn, pcjTableName); + + // Scan Accumulo for the stored results. + for(final VariableOrder varOrder : pcjMetadata.getVarOrders()) { + final Scanner scanner = accumuloConn.createScanner(pcjTableName, new Authorizations()); + scanner.fetchColumnFamily( new Text(varOrder.toString()) ); + + for(final Entry<Key, Value> entry : scanner) { + final byte[] serializedResult = entry.getKey().getRow().getBytes(); + final BindingSet result = AccumuloPcjSerializer.deSerialize(serializedResult, varOrder.toArray()); + fetchedResults.put(varOrder.toString(), result); + } + } + + return fetchedResults; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/15ec5d5f/extras/rya.pcj.fluo/pom.xml ---------------------------------------------------------------------- diff --git a/extras/rya.pcj.fluo/pom.xml b/extras/rya.pcj.fluo/pom.xml new file mode 100644 index 0000000..6419aa4 --- /dev/null +++ b/extras/rya.pcj.fluo/pom.xml @@ -0,0 +1,130 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF 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. +--> +<project + xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <parent> + <groupId>org.apache.rya</groupId> + <artifactId>rya.extras</artifactId> + <version>3.2.10-SNAPSHOT</version> + </parent> + + <modelVersion>4.0.0</modelVersion> + <artifactId>rya.pcj.fluo.parent</artifactId> + + <name>Apache Rya PCJ Fluo Parent</name> + <description>The parent pom file for any pcj.fluo project.</description> + + <packaging>pom</packaging> + + <modules> + <module>pcj.fluo.api</module> + <module>pcj.fluo.app</module> + <module>pcj.fluo.client</module> + <module>pcj.fluo.integration</module> + <module>pcj.fluo.demo</module> + </modules> + + <properties> + <fluo.version>1.0.0-beta-2</fluo.version> + <jcommander.version>1.48</jcommander.version> + <hadoop.version>2.5.0</hadoop.version> + </properties> + + <build> + <plugins> + <plugin> + <artifactId>maven-failsafe-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>integration-test</goal> + <goal>verify</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + + <!-- + note, this parent pom can likely be removed provided the contents of its + dependencyManagement section are transferred to org.apache.rya:rya-project + --> + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.apache.hadoop</groupId> + <artifactId>hadoop-client</artifactId> + <version>${hadoop.version}</version> + </dependency> + <dependency> + <groupId>org.apache.hadoop</groupId> + <artifactId>hadoop-minicluster</artifactId> + <version>${hadoop.version}</version> + </dependency> + <dependency> + <groupId>org.apache.rya</groupId> + <artifactId>rya.pcj.fluo.api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.rya</groupId> + <artifactId>rya.pcj.fluo.app</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.rya</groupId> + <artifactId>rya.pcj.fluo.client</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.openrdf.sesame</groupId> + <artifactId>sesame-queryrender</artifactId> + <version>${openrdf.sesame.version}</version> + </dependency> + + <dependency> + <groupId>io.fluo</groupId> + <artifactId>fluo-api</artifactId> + <version>${fluo.version}</version> + </dependency> + <dependency> + <groupId>io.fluo</groupId> + <artifactId>fluo-core</artifactId> + <version>${fluo.version}</version> + </dependency> + <dependency> + <groupId>io.fluo</groupId> + <artifactId>fluo-mini</artifactId> + <version>${fluo.version}</version> + </dependency> + + <dependency> + <groupId>com.beust</groupId> + <artifactId>jcommander</artifactId> + <version>${jcommander.version}</version> + </dependency> + </dependencies> + </dependencyManagement> +</project>
