Skip to content

Commit 4548915

Browse files
committed
refactor for Cid-serde integration
Signed-off-by: jorgee <[email protected]>
1 parent 49df891 commit 4548915

File tree

14 files changed

+323
-162
lines changed

14 files changed

+323
-162
lines changed

modules/nextflow/src/main/groovy/nextflow/cli/CmdCid.groovy

-28
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ class CmdCid extends CmdBase implements UsageAware {
4444
void show(ConfigMap config, List<String> args)
4545
void lineage(ConfigMap config, List<String> args)
4646
void diff(ConfigMap config, List<String> args)
47-
void query(ConfigMap config, List<String> args)
4847
}
4948

5049
interface SubCmd {
@@ -65,7 +64,6 @@ class CmdCid extends CmdBase implements UsageAware {
6564
commands << new CmdShow()
6665
commands << new CmdLineage()
6766
commands << new CmdDiff()
68-
commands << new CmdQuery()
6967
}
7068

7169
@Parameter(hidden = true)
@@ -260,30 +258,4 @@ class CmdCid extends CmdBase implements UsageAware {
260258

261259
}
262260

263-
class CmdQuery implements SubCmd {
264-
265-
@Override
266-
String getName() { 'query' }
267-
268-
@Override
269-
String getDescription() {
270-
return 'Search data in descriptions'
271-
}
272-
273-
void apply(List<String> args) {
274-
if (args.size() != 1) {
275-
println("ERROR: Incorrect number of parameters")
276-
usage()
277-
return
278-
}
279-
operation.query(config, args)
280-
}
281-
282-
@Override
283-
void usage() {
284-
println description
285-
println "Usage: nextflow $NAME $name <URIQuery>"
286-
}
287-
288-
}
289261
}

modules/nextflow/src/test/groovy/nextflow/cli/CmdCidTest.groovy

+37-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
package nextflow.cli
1919

20+
import nextflow.data.cid.serde.CidEncoder
21+
2022
import java.nio.file.Files
2123

2224
import nextflow.SysEnv
@@ -28,7 +30,6 @@ import nextflow.data.cid.model.Parameter
2830
import nextflow.data.cid.model.TaskOutput
2931
import nextflow.data.cid.model.TaskRun
3032
import nextflow.data.cid.model.WorkflowOutput
31-
import nextflow.data.cid.serde.CidEncoder
3233
import nextflow.plugin.Plugins
3334
import org.junit.Rule
3435
import spock.lang.Specification
@@ -177,7 +178,7 @@ class CmdCidTest extends Specification {
177178

178179
then:
179180
stdout.size() == 1
180-
stdout[0] == "No entry found for cid://12345."
181+
stdout[0] == "No entries found for cid://12345."
181182

182183
cleanup:
183184
folder?.deleteDir()
@@ -258,4 +259,38 @@ class CmdCidTest extends Specification {
258259

259260
}
260261

262+
def 'should show query results'(){
263+
given:
264+
def folder = Files.createTempDirectory('test').toAbsolutePath()
265+
def configFile = folder.resolve('nextflow.config')
266+
configFile.text = "workflow.data.enabled = true\nworkflow.data.store.location = '$folder'".toString()
267+
def cidFile = folder.resolve(".meta/12345/.data.json")
268+
Files.createDirectories(cidFile.parent)
269+
def launcher = Mock(Launcher){
270+
getOptions() >> new CliOptions(config: [configFile.toString()])
271+
}
272+
def encoder = new CidEncoder().withPrettyPrint(true)
273+
def entry = new WorkflowOutput("path/to/file",new Checksum("45372qe","nextflow","standard"),
274+
"cid://123987/file.bam", 1234, 123456789, 123456789, null)
275+
def jsonSer = encoder.encode(entry)
276+
def expectedOutput = jsonSer
277+
cidFile.text = jsonSer
278+
when:
279+
def cidCmd = new CmdCid(launcher: launcher, args: ["show", "cid:///?type=WorkflowOutput"])
280+
cidCmd.run()
281+
def stdout = capture
282+
.toString()
283+
.readLines()// remove the log part
284+
.findResults { line -> !line.contains('DEBUG') ? line : null }
285+
.findResults { line -> !line.contains('INFO') ? line : null }
286+
.findResults { line -> !line.contains('plugin') ? line : null }
287+
288+
then:
289+
stdout.size() == expectedOutput.readLines().size()
290+
stdout.join('\n') == expectedOutput
291+
292+
cleanup:
293+
folder?.deleteDir()
294+
}
295+
261296
}

modules/nextflow/src/test/groovy/nextflow/script/OutputDslTest.groovy

+3-3
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ class OutputDslTest extends Specification {
7777
"file2","${outputDir}/barbar/file2.txt"
7878
""".stripIndent()
7979
and:
80-
1 * session.notifyFilePublish(outputDir.resolve('foo/file1.txt'), file1)
81-
1 * session.notifyFilePublish(outputDir.resolve('barbar/file2.txt'), file2)
82-
1 * session.notifyFilePublish(outputDir.resolve('index.csv'))
80+
1 * session.notifyFilePublish(outputDir.resolve('foo/file1.txt'), file1, null)
81+
1 * session.notifyFilePublish(outputDir.resolve('barbar/file2.txt'), file2, null)
82+
1 * session.notifyFilePublish(outputDir.resolve('index.csv'), null, null)
8383

8484
cleanup:
8585
SysEnv.pop()

modules/nf-cid/src/main/nextflow/data/cid/CidStore.groovy

+6-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ interface CidStore extends Closeable {
5353
*/
5454
CidHistoryLog getHistoryLog()
5555

56-
List<Object> query(URI queryString)
56+
/**
57+
* Search for cid entries.
58+
* @queryString Json-path like query string. (Only simple and nested field operators are supported(No array, wildcards,etc.)
59+
* @return List of Cid object's fulfilling the queryString
60+
*/
61+
List<CidSerializable> search(String queryString)
5762

5863
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package nextflow.data.cid
2+
3+
import groovy.util.logging.Slf4j
4+
import nextflow.data.cid.fs.CidPath
5+
import nextflow.data.cid.serde.CidSerializable
6+
7+
import java.nio.file.Path
8+
9+
@Slf4j
10+
class CidUtils {
11+
12+
public static List query(CidStore store, URI uri) {
13+
String key = uri.authority ? uri.authority + uri.path : uri.path
14+
try {
15+
if (key == CidPath.SEPARATOR) {
16+
return store.search(uri.query)
17+
} else {
18+
return searchPath(store, key, uri.query ? parseQuery(uri.query) : null)
19+
}
20+
} catch(Throwable e){
21+
log.debug("Exception querying $uri. $e.message")
22+
return []
23+
}
24+
25+
}
26+
27+
private static List<Object> searchPath(CidStore store, String path, Map<String, String> params, String[] childs = []) {
28+
final results = new LinkedList<Object>()
29+
final object = store.load(path)
30+
if (object) {
31+
if (childs && childs.size() > 0) {
32+
final output = navigate(object, childs.join('.'))
33+
if (output) {
34+
treatObject(output, params, results)
35+
} else {
36+
throw new FileNotFoundException("Cid object $path/${childs ? childs.join('/') : ''} not found.")
37+
}
38+
} else {
39+
treatObject(object, params, results)
40+
}
41+
} else {
42+
// If there isn't metadata check the parent to check if it is a subfolder of a task/workflow output
43+
final currentPath = Path.of(path)
44+
final parent = currentPath.getParent()
45+
if (parent) {
46+
ArrayList<String> newChilds = new ArrayList<String>()
47+
newChilds.add(currentPath.getFileName().toString())
48+
newChilds.addAll(childs)
49+
return searchPath(store, parent.toString(), params, newChilds as String[])
50+
} else {
51+
throw new FileNotFoundException("Cid object $path/${childs ? childs.join('/') : ''} not found.")
52+
}
53+
}
54+
return results
55+
}
56+
57+
protected static void treatObject(def object, Map<String, String> params, List<Object> results) {
58+
if (params) {
59+
if (object instanceof Collection) {
60+
(object as Collection).forEach { treatObject(it, params, results) }
61+
} else if (checkParams(object, params)) {
62+
results.add(object)
63+
}
64+
} else {
65+
results.add(object)
66+
}
67+
}
68+
69+
public static Map<String, String> parseQuery(String queryString) {
70+
return queryString.split('&').collectEntries {
71+
it.split('=').collect { URLDecoder.decode(it, 'UTF-8') }
72+
} as Map<String, String>
73+
}
74+
75+
public static boolean checkParams(Object object, Map<String,String> params) {
76+
for (final entry : params.entrySet()) {
77+
final value = navigate(object, entry.key)
78+
if (!value || value.toString() != entry.value.toString() ) {
79+
return false
80+
}
81+
}
82+
return true
83+
}
84+
85+
protected static Object navigate(Object obj, String path){
86+
if (!obj)
87+
return null
88+
try{
89+
// type has been replaced by class when evaluating CidSerializable objects
90+
if (obj instanceof CidSerializable && path == 'type')
91+
return obj.getClass()?.simpleName
92+
path.tokenize('.').inject(obj) { current, key ->
93+
if (current == null) return null
94+
95+
if (current instanceof Map) {
96+
return current[key] // Navigate Map properties
97+
}
98+
99+
if (current.metaClass.hasProperty(current, key)) {
100+
return current.getAt(key) // Navigate Object properties
101+
}
102+
log.debug("No property found for $key")
103+
return null // Property not found
104+
}
105+
} catch (Throwable e) {
106+
log.debug("Error navigating to $path in object", e)
107+
return null
108+
}
109+
}
110+
}

modules/nf-cid/src/main/nextflow/data/cid/DefaultCidStore.groovy

+12-79
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
package nextflow.data.cid
1919

20-
import groovy.json.JsonSlurper
2120
import groovy.transform.CompileStatic
2221
import groovy.util.logging.Slf4j
2322

@@ -27,7 +26,6 @@ import java.nio.file.Files
2726
import java.nio.file.Path
2827
import java.nio.file.attribute.BasicFileAttributes
2928

30-
import nextflow.data.cid.fs.CidPath
3129
import nextflow.data.cid.serde.CidEncoder
3230
import nextflow.data.cid.serde.CidSerializable
3331
import nextflow.data.config.DataConfig
@@ -84,7 +82,7 @@ class DefaultCidStore implements CidStore {
8482
final path = metaLocation.resolve("$key/$METADATA_FILE")
8583
log.debug("Loading from path $path")
8684
if (path.exists())
87-
return encoder.decode(path.text)
85+
return encoder.decode(path.text) as CidSerializable
8886
log.debug("File for key $key not found")
8987
return null
9088
}
@@ -107,26 +105,18 @@ class DefaultCidStore implements CidStore {
107105
void close() throws IOException { }
108106

109107
@Override
110-
List<Object> query(URI uri) {
111-
log.debug("Query $uri.query, Path: $uri.path, Scheme: $uri.path $uri.authority $uri.rawPath $uri.rawAuthority")
108+
List<CidSerializable> search(String queryString) {
109+
112110
def params = null
113-
if (uri.query) {
114-
params = uri.query.split('&').collectEntries {
115-
it.split('=').collect { URLDecoder.decode(it, 'UTF-8') }
116-
} as Map<String, String>
117-
}
118-
String key = uri.authority ? uri.authority + uri.path : uri.path
119-
if (key == CidPath.SEPARATOR) {
120-
searchAllFiles(params)
121-
} else {
122-
searchPath(key, params)
111+
if (queryString) {
112+
params = CidUtils.parseQuery(queryString)
123113
}
114+
return searchAllFiles(params)
124115

125116
}
126117

127-
private List<Object> searchAllFiles (Map<String,String> params) {
128-
final results = new LinkedList<Object>()
129-
final slurper = new JsonSlurper()
118+
private List<CidSerializable> searchAllFiles (Map<String,String> params) {
119+
final results = new LinkedList<CidSerializable>()
130120

131121
Files.walkFileTree(metaLocation, new FileVisitor<Path>() {
132122

@@ -137,10 +127,11 @@ class DefaultCidStore implements CidStore {
137127

138128
@Override
139129
FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
140-
141130
if (file.name.startsWith('.data.json') ) {
142-
final cidObject = slurper.parse( file.text.toCharArray() ) as Map
143-
DefaultCidStore.treatObject(cidObject, params, results)
131+
final cidObject = encoder.decode(file.text)
132+
if (CidUtils.checkParams(cidObject, params)){
133+
results.add(cidObject as CidSerializable)
134+
}
144135
}
145136
FileVisitResult.CONTINUE
146137
}
@@ -158,62 +149,4 @@ class DefaultCidStore implements CidStore {
158149

159150
return results
160151
}
161-
162-
private List<Object> searchPath( String path, Map<String,String> params, String[] childs = []) {
163-
final results = new LinkedList<Object>()
164-
final slurper = new JsonSlurper()
165-
final object = load(path)
166-
if ( object ) {
167-
final cidObject = slurper.parse(object.toString().toCharArray()) as Map
168-
if (childs && childs.size() > 0) {
169-
final output = cidObject.navigate(childs.join('.'))
170-
if (output) {
171-
treatObject(output, params, results)
172-
} else {
173-
throw new FileNotFoundException("Cid object $path/${childs ? childs.join('/') : ''} not found.")
174-
}
175-
} else {
176-
treatObject(cidObject, params, results)
177-
}
178-
} else {
179-
// If there isn't metadata check the parent to check if it is a subfolder of a task/workflow output
180-
final currentPath = Path.of(path)
181-
final parent = currentPath.getParent()
182-
if( parent) {
183-
ArrayList<String> newChilds = new ArrayList<String>()
184-
newChilds.add(currentPath.getFileName().toString())
185-
newChilds.addAll(childs)
186-
return searchPath(parent.toString(), params, newChilds as String[])
187-
} else {
188-
throw new FileNotFoundException("Cid object $path/${childs ? childs.join('/') :''} not found.")
189-
}
190-
}
191-
return results
192-
}
193-
194-
protected static void treatObject(def object, Map<String,String> params, List<Object> results) {
195-
if (params) {
196-
if (object instanceof Collection) {
197-
(object as Collection).forEach { treatObject(it, params, results) }
198-
} else if (checkParams(object as Map, params)) {
199-
results.add(object)
200-
}
201-
} else {
202-
results.add(object)
203-
}
204-
}
205-
206-
private static boolean checkParams(Map object, Map<String,String> params) {
207-
log.debug("Checking $object, $params")
208-
for (final entry : params.entrySet()) {
209-
final value = object.navigate(entry.key)
210-
log.debug("comparing $value, $entry.value")
211-
if (!value || value.toString() != entry.value.toString() ) {
212-
return false
213-
}
214-
}
215-
log.debug("Returning true")
216-
return true
217-
}
218-
219152
}

0 commit comments

Comments
 (0)