Skip to content

Commit 277d666

Browse files
authored
feat(java): let the storage API reach the local filesystem (#960)
* feat(java): let the storage API reach the local filesystem The storage API merged in #958 has no implementation, so nothing can open a GraphAr file yet. This adds the local filesystem adapter that the same issue asked for, keeping the module free of any GraphAr layout, format, or query concern: it resolves a URI to a path, opens a seekable input over it, and writes through a position-reporting output. Directories are created on demand for an output file, an existing target is refused unless the caller asked to replace it, and a read past the end of a file reports the end of the stream rather than a partial buffer. Closes the adapter half of #953. Not-tested: only the file scheme is exercised; other schemes are the concern of later adapters. * feat(java): reuse one transfer buffer for direct-buffer writes Review feedback on #960: a write from a buffer without a backing array allocated a fresh staging array on every call. The output is single threaded already, because it tracks its own position, so one instance buffer can serve every such write. It is allocated on first use, so an output that only ever sees heap buffers never pays for it. Adds the missing test for a direct buffer larger than one transfer chunk, which the previous test did not reach: with the copy loop reduced to a single pass the new test fails with "expected:<20000> but was:<8192>".
1 parent 9dcaef8 commit 277d666

8 files changed

Lines changed: 548 additions & 0 deletions

File tree

maven-projects/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
<module>spark</module>
8080
<module>info</module>
8181
<module>storage-api</module>
82+
<module>storage-local</module>
8283
</modules>
8384

8485
<build>
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
Licensed to the Apache Software Foundation (ASF) under one
5+
or more contributor license agreements. See the NOTICE file
6+
distributed with this work for additional information
7+
regarding copyright ownership. The ASF licenses this file
8+
to you under the Apache License, Version 2.0 (the
9+
"License"); you may not use this file except in compliance
10+
with the License. You may obtain a copy of the License at
11+
12+
http://www.apache.org/licenses/LICENSE-2.0
13+
14+
Unless required by applicable law or agreed to in writing,
15+
software distributed under the License is distributed on an
16+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
KIND, either express or implied. See the License for the
18+
specific language governing permissions and limitations
19+
under the License.
20+
21+
-->
22+
23+
<project xmlns="http://maven.apache.org/POM/4.0.0"
24+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
25+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
26+
<modelVersion>4.0.0</modelVersion>
27+
28+
<parent>
29+
<groupId>org.apache.graphar</groupId>
30+
<artifactId>graphar-root</artifactId>
31+
<version>${graphar.version}</version>
32+
<relativePath>../pom.xml</relativePath>
33+
</parent>
34+
35+
<artifactId>graphar-storage-local</artifactId>
36+
<packaging>jar</packaging>
37+
<version>${graphar.version}</version>
38+
39+
<name>graphar-storage-local</name>
40+
41+
<properties>
42+
<maven.compiler.source>11</maven.compiler.source>
43+
<maven.compiler.target>11</maven.compiler.target>
44+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
45+
</properties>
46+
47+
<dependencies>
48+
<dependency>
49+
<groupId>org.apache.graphar</groupId>
50+
<artifactId>graphar-storage-api</artifactId>
51+
<version>${project.version}</version>
52+
</dependency>
53+
<dependency>
54+
<groupId>junit</groupId>
55+
<artifactId>junit</artifactId>
56+
<version>4.13.2</version>
57+
<scope>test</scope>
58+
</dependency>
59+
</dependencies>
60+
61+
<build>
62+
<plugins>
63+
<plugin>
64+
<groupId>com.diffplug.spotless</groupId>
65+
<artifactId>spotless-maven-plugin</artifactId>
66+
<version>${spotless-maven-plugin.version}</version>
67+
<configuration>
68+
<java>
69+
<googleJavaFormat>
70+
<version>1.7</version>
71+
<style>AOSP</style>
72+
</googleJavaFormat>
73+
</java>
74+
</configuration>
75+
</plugin>
76+
<plugin>
77+
<groupId>org.apache.maven.plugins</groupId>
78+
<artifactId>maven-javadoc-plugin</artifactId>
79+
<executions>
80+
<execution>
81+
<id>attach-javadocs</id>
82+
<goals>
83+
<goal>jar</goal>
84+
</goals>
85+
</execution>
86+
</executions>
87+
</plugin>
88+
</plugins>
89+
</build>
90+
</project>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.graphar.storage.local;
21+
22+
import java.io.IOException;
23+
import java.net.URI;
24+
import java.nio.file.Files;
25+
import java.nio.file.Path;
26+
import org.apache.graphar.storage.InputFile;
27+
import org.apache.graphar.storage.SeekableInput;
28+
29+
final class LocalInputFile implements InputFile {
30+
private final Path path;
31+
32+
LocalInputFile(Path path) {
33+
this.path = path;
34+
}
35+
36+
@Override
37+
public URI uri() {
38+
return path.toUri();
39+
}
40+
41+
@Override
42+
public long size() throws IOException {
43+
return Files.size(path);
44+
}
45+
46+
@Override
47+
public SeekableInput open() throws IOException {
48+
return new LocalSeekableInput(path);
49+
}
50+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.graphar.storage.local;
21+
22+
import java.io.IOException;
23+
import java.net.URI;
24+
import java.nio.file.Files;
25+
import java.nio.file.Path;
26+
import java.nio.file.StandardOpenOption;
27+
import org.apache.graphar.storage.OutputFile;
28+
import org.apache.graphar.storage.PositionOutput;
29+
30+
final class LocalOutputFile implements OutputFile {
31+
private final Path path;
32+
33+
LocalOutputFile(Path path) {
34+
this.path = path;
35+
}
36+
37+
@Override
38+
public URI uri() {
39+
return path.toUri();
40+
}
41+
42+
@Override
43+
public PositionOutput create() throws IOException {
44+
createParentDirectories();
45+
return new LocalPositionOutput(
46+
Files.newOutputStream(
47+
path, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE));
48+
}
49+
50+
@Override
51+
public PositionOutput createOrOverwrite() throws IOException {
52+
createParentDirectories();
53+
return new LocalPositionOutput(
54+
Files.newOutputStream(
55+
path,
56+
StandardOpenOption.CREATE,
57+
StandardOpenOption.TRUNCATE_EXISTING,
58+
StandardOpenOption.WRITE));
59+
}
60+
61+
private void createParentDirectories() throws IOException {
62+
Path parent = path.getParent();
63+
if (parent != null) {
64+
Files.createDirectories(parent);
65+
}
66+
}
67+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.graphar.storage.local;
21+
22+
import java.io.IOException;
23+
import java.io.OutputStream;
24+
import java.nio.ByteBuffer;
25+
import org.apache.graphar.storage.PositionOutput;
26+
27+
final class LocalPositionOutput implements PositionOutput {
28+
private static final int BUFFER_SIZE = 8192;
29+
30+
private final OutputStream output;
31+
private long position;
32+
private byte[] transfer;
33+
34+
LocalPositionOutput(OutputStream output) {
35+
this.output = output;
36+
}
37+
38+
@Override
39+
public long position() {
40+
return position;
41+
}
42+
43+
@Override
44+
public void write(ByteBuffer source) throws IOException {
45+
if (source.hasArray()) {
46+
int length = source.remaining();
47+
write(source.array(), source.arrayOffset() + source.position(), length);
48+
source.position(source.position() + length);
49+
return;
50+
}
51+
52+
if (transfer == null) {
53+
transfer = new byte[BUFFER_SIZE];
54+
}
55+
while (source.hasRemaining()) {
56+
int length = Math.min(source.remaining(), transfer.length);
57+
source.get(transfer, 0, length);
58+
write(transfer, 0, length);
59+
}
60+
}
61+
62+
@Override
63+
public void write(byte[] source, int offset, int length) throws IOException {
64+
output.write(source, offset, length);
65+
position += length;
66+
}
67+
68+
@Override
69+
public void flush() throws IOException {
70+
output.flush();
71+
}
72+
73+
@Override
74+
public void close() throws IOException {
75+
output.close();
76+
}
77+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.graphar.storage.local;
21+
22+
import java.io.IOException;
23+
import java.nio.ByteBuffer;
24+
import java.nio.channels.FileChannel;
25+
import java.nio.file.Path;
26+
import java.nio.file.StandardOpenOption;
27+
import org.apache.graphar.storage.SeekableInput;
28+
29+
final class LocalSeekableInput implements SeekableInput {
30+
private final FileChannel channel;
31+
32+
LocalSeekableInput(Path path) throws IOException {
33+
this.channel = FileChannel.open(path, StandardOpenOption.READ);
34+
}
35+
36+
@Override
37+
public long position() throws IOException {
38+
return channel.position();
39+
}
40+
41+
@Override
42+
public void seek(long newPosition) throws IOException {
43+
if (newPosition < 0) {
44+
throw new IllegalArgumentException("Seek position cannot be negative: " + newPosition);
45+
}
46+
channel.position(newPosition);
47+
}
48+
49+
@Override
50+
public int read(ByteBuffer destination) throws IOException {
51+
return channel.read(destination);
52+
}
53+
54+
@Override
55+
public void close() throws IOException {
56+
channel.close();
57+
}
58+
}

0 commit comments

Comments
 (0)