Skip to content

Commit c1c5a0f

Browse files
committed
Copy Lucene IOUtils (#29012)
As we have factored Elasticsearch into smaller libraries, we have ended up in a situation that some of the dependencies of Elasticsearch are not available to code that depends on these smaller libraries but not server Elasticsearch. This is a good thing, this was one of the goals of separating Elasticsearch into smaller libraries, to shed some of the dependencies from other components of the system. However, this now means that simple utility methods from Lucene that we rely on are no longer available everywhere. This commit copies IOUtils (with some small formatting changes for our codebase) into the fold so that other components of the system can rely on these methods where they no longer depend on Lucene.
1 parent bf5cb76 commit c1c5a0f

File tree

121 files changed

+660
-137
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

121 files changed

+660
-137
lines changed

buildSrc/src/main/resources/forbidden/es-all-signatures.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,5 @@ java.nio.channels.SocketChannel#connect(java.net.SocketAddress)
4848
# org.elasticsearch.common.Booleans#parseBoolean(java.lang.String) directly on the string.
4949
@defaultMessage use org.elasticsearch.common.Booleans#parseBoolean(java.lang.String)
5050
java.lang.Boolean#getBoolean(java.lang.String)
51+
52+
org.apache.lucene.util.IOUtils @ use @org.elasticsearch.core.internal.io instead

distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
import org.apache.lucene.search.spell.LevensteinDistance;
2626
import org.apache.lucene.util.CollectionUtil;
27-
import org.apache.lucene.util.IOUtils;
27+
import org.elasticsearch.core.internal.io.IOUtils;
2828
import org.elasticsearch.Version;
2929
import org.elasticsearch.bootstrap.JarHell;
3030
import org.elasticsearch.cli.EnvironmentAwareCommand;

distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/PluginCli.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
package org.elasticsearch.plugins;
2121

22-
import org.apache.lucene.util.IOUtils;
22+
import org.elasticsearch.core.internal.io.IOUtils;
2323
import org.elasticsearch.cli.Command;
2424
import org.elasticsearch.cli.LoggingAwareMultiCommand;
2525
import org.elasticsearch.cli.MultiCommand;

distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/RemovePluginCommand.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
import joptsimple.OptionSet;
2323
import joptsimple.OptionSpec;
24-
import org.apache.lucene.util.IOUtils;
24+
import org.elasticsearch.core.internal.io.IOUtils;
2525
import org.elasticsearch.cli.EnvironmentAwareCommand;
2626
import org.elasticsearch.cli.ExitCodes;
2727
import org.elasticsearch.cli.Terminal;
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.elasticsearch.core.internal.io;
19+
20+
import java.io.Closeable;
21+
import java.io.IOException;
22+
import java.nio.channels.FileChannel;
23+
import java.nio.file.FileVisitResult;
24+
import java.nio.file.FileVisitor;
25+
import java.nio.file.Files;
26+
import java.nio.file.Path;
27+
import java.nio.file.StandardOpenOption;
28+
import java.nio.file.attribute.BasicFileAttributes;
29+
import java.util.Arrays;
30+
import java.util.Collection;
31+
import java.util.LinkedHashMap;
32+
import java.util.Map;
33+
34+
/**
35+
* Utilities for common I/O methods. Borrowed heavily from Lucene (org.apache.lucene.util.IOUtils).
36+
*/
37+
public final class IOUtils {
38+
39+
private IOUtils() {
40+
41+
}
42+
43+
/**
44+
* Closes all given <tt>Closeable</tt>s. Some of the <tt>Closeable</tt>s may be null; they are ignored. After everything is closed, the
45+
* method either throws the first exception it hit while closing, or completes normally if there were no exceptions.
46+
*
47+
* @param objects objects to close
48+
*/
49+
public static void close(final Closeable... objects) throws IOException {
50+
close(Arrays.asList(objects));
51+
}
52+
53+
/**
54+
* Closes all given {@link Closeable}s.
55+
*
56+
* @param objects objects to close
57+
*
58+
* @see #close(Closeable...)
59+
*/
60+
public static void close(final Iterable<? extends Closeable> objects) throws IOException {
61+
Throwable th = null;
62+
63+
for (final Closeable object : objects) {
64+
try {
65+
if (object != null) {
66+
object.close();
67+
}
68+
} catch (final Throwable t) {
69+
addSuppressed(th, t);
70+
if (th == null) {
71+
th = t;
72+
}
73+
}
74+
}
75+
76+
if (th != null) {
77+
throw rethrowAlways(th);
78+
}
79+
}
80+
81+
/**
82+
* Closes all given {@link Closeable}s, suppressing all thrown exceptions. Some of the {@link Closeable}s may be null, they are ignored.
83+
*
84+
* @param objects objects to close
85+
*/
86+
public static void closeWhileHandlingException(final Closeable... objects) {
87+
closeWhileHandlingException(Arrays.asList(objects));
88+
}
89+
90+
/**
91+
* Closes all given {@link Closeable}s, suppressing all thrown exceptions.
92+
*
93+
* @param objects objects to close
94+
*
95+
* @see #closeWhileHandlingException(Closeable...)
96+
*/
97+
public static void closeWhileHandlingException(final Iterable<? extends Closeable> objects) {
98+
for (final Closeable object : objects) {
99+
// noinspection EmptyCatchBlock
100+
try {
101+
if (object != null) {
102+
object.close();
103+
}
104+
} catch (final Throwable t) {
105+
106+
}
107+
}
108+
}
109+
110+
/**
111+
* Adds a {@link Throwable} to the list of suppressed {@link Exception}s of the first {@link Throwable}.
112+
*
113+
* @param exception the exception to add a suppression to, if non-null
114+
* @param suppressed the exception to suppress
115+
*/
116+
private static void addSuppressed(final Throwable exception, final Throwable suppressed) {
117+
if (exception != null && suppressed != null) {
118+
exception.addSuppressed(suppressed);
119+
}
120+
}
121+
122+
/**
123+
* This utility method takes a previously caught (non-null) {@link Throwable} and rethrows either the original argument if it was a
124+
* subclass of the {@link IOException} or an {@link RuntimeException} with the cause set to the argument.
125+
* <p>
126+
* This method <strong>never returns any value</strong>, even though it declares a return value of type {@link Error}. The return
127+
* value declaration is very useful to let the compiler know that the code path following the invocation of this method is unreachable.
128+
* So in most cases the invocation of this method will be guarded by an {@code if} and used together with a {@code throw} statement, as
129+
* in:
130+
* <p>
131+
* <pre>{@code
132+
* if (t != null) throw IOUtils.rethrowAlways(t)
133+
* }
134+
* </pre>
135+
*
136+
* @param th the throwable to rethrow; <strong>must not be null</strong>
137+
* @return this method always results in an exception, it never returns any value; see method documentation for details and usage
138+
* example
139+
* @throws IOException if the argument was an instance of {@link IOException}
140+
* @throws RuntimeException with the {@link RuntimeException#getCause()} set to the argument, if it was not an instance of
141+
* {@link IOException}
142+
*/
143+
private static Error rethrowAlways(final Throwable th) throws IOException, RuntimeException {
144+
if (th == null) {
145+
throw new AssertionError("rethrow argument must not be null.");
146+
}
147+
148+
if (th instanceof IOException) {
149+
throw (IOException) th;
150+
}
151+
152+
if (th instanceof RuntimeException) {
153+
throw (RuntimeException) th;
154+
}
155+
156+
if (th instanceof Error) {
157+
throw (Error) th;
158+
}
159+
160+
throw new RuntimeException(th);
161+
}
162+
163+
/**
164+
* Deletes all given files, suppressing all thrown {@link IOException}s. Some of the files may be null, if so they are ignored.
165+
*
166+
* @param files the paths of files to delete
167+
*/
168+
public static void deleteFilesIgnoringExceptions(final Path... files) {
169+
deleteFilesIgnoringExceptions(Arrays.asList(files));
170+
}
171+
172+
/**
173+
* Deletes all given files, suppressing all thrown {@link IOException}s. Some of the files may be null, if so they are ignored.
174+
*
175+
* @param files the paths of files to delete
176+
*/
177+
public static void deleteFilesIgnoringExceptions(final Collection<? extends Path> files) {
178+
for (final Path name : files) {
179+
if (name != null) {
180+
// noinspection EmptyCatchBlock
181+
try {
182+
Files.delete(name);
183+
} catch (final Throwable ignored) {
184+
185+
}
186+
}
187+
}
188+
}
189+
190+
/**
191+
* Deletes one or more files or directories (and everything underneath it).
192+
*
193+
* @throws IOException if any of the given files (or their sub-hierarchy files in case of directories) cannot be removed.
194+
*/
195+
public static void rm(final Path... locations) throws IOException {
196+
final LinkedHashMap<Path,Throwable> unremoved = rm(new LinkedHashMap<>(), locations);
197+
if (!unremoved.isEmpty()) {
198+
final StringBuilder b = new StringBuilder("could not remove the following files (in the order of attempts):\n");
199+
for (final Map.Entry<Path,Throwable> kv : unremoved.entrySet()) {
200+
b.append(" ")
201+
.append(kv.getKey().toAbsolutePath())
202+
.append(": ")
203+
.append(kv.getValue())
204+
.append("\n");
205+
}
206+
throw new IOException(b.toString());
207+
}
208+
}
209+
210+
private static LinkedHashMap<Path,Throwable> rm(final LinkedHashMap<Path,Throwable> unremoved, final Path... locations) {
211+
if (locations != null) {
212+
for (final Path location : locations) {
213+
// TODO: remove this leniency
214+
if (location != null && Files.exists(location)) {
215+
try {
216+
Files.walkFileTree(location, new FileVisitor<Path>() {
217+
@Override
218+
public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException {
219+
return FileVisitResult.CONTINUE;
220+
}
221+
222+
@Override
223+
public FileVisitResult postVisitDirectory(final Path dir, final IOException impossible) throws IOException {
224+
assert impossible == null;
225+
226+
try {
227+
Files.delete(dir);
228+
} catch (final IOException e) {
229+
unremoved.put(dir, e);
230+
}
231+
return FileVisitResult.CONTINUE;
232+
}
233+
234+
@Override
235+
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
236+
try {
237+
Files.delete(file);
238+
} catch (final IOException exc) {
239+
unremoved.put(file, exc);
240+
}
241+
return FileVisitResult.CONTINUE;
242+
}
243+
244+
@Override
245+
public FileVisitResult visitFileFailed(final Path file, final IOException exc) throws IOException {
246+
if (exc != null) {
247+
unremoved.put(file, exc);
248+
}
249+
return FileVisitResult.CONTINUE;
250+
}
251+
});
252+
} catch (final IOException impossible) {
253+
throw new AssertionError("visitor threw exception", impossible);
254+
}
255+
}
256+
}
257+
}
258+
return unremoved;
259+
}
260+
261+
// TODO: replace with constants class if needed (cf. org.apache.lucene.util.Constants)
262+
private static final boolean LINUX = System.getProperty("os.name").startsWith("Linux");
263+
private static final boolean MAC_OS_X = System.getProperty("os.name").startsWith("Mac OS X");
264+
265+
/**
266+
* Ensure that any writes to the given file is written to the storage device that contains it. The {@code isDir} parameter specifies
267+
* whether or not the path to sync is a directory. This is needed because we open for read and ignore an {@link IOException} since not
268+
* all filesystems and operating systems support fsyncing on a directory. For regular files we must open for write for the fsync to have
269+
* an effect.
270+
*
271+
* @param fileToSync the file to fsync
272+
* @param isDir if true, the given file is a directory (we open for read and ignore {@link IOException}s, because not all file
273+
* systems and operating systems allow to fsync on a directory)
274+
*/
275+
public static void fsync(final Path fileToSync, final boolean isDir) throws IOException {
276+
try (FileChannel file = FileChannel.open(fileToSync, isDir ? StandardOpenOption.READ : StandardOpenOption.WRITE)) {
277+
file.force(true);
278+
} catch (final IOException ioe) {
279+
if (isDir) {
280+
assert (LINUX || MAC_OS_X) == false :
281+
"on Linux and MacOSX fsyncing a directory should not throw IOException, "+
282+
"we just don't want to rely on that in production (undocumented); got: " + ioe;
283+
// ignore exception if it is a directory
284+
return;
285+
}
286+
// throw original exception
287+
throw ioe;
288+
}
289+
}
290+
291+
}

0 commit comments

Comments
 (0)