(TWILL-158) Added FileContext Location and LocationFactory
- Added new unit-tests.
- Minor improvement on HDFSLocationFactory to handle creation URI correctly
This closes #73 on GitHub.
Signed-off-by: Terence Yim <chtyim@apache.org>
diff --git a/twill-common/src/main/java/org/apache/twill/filesystem/LocalLocationFactory.java b/twill-common/src/main/java/org/apache/twill/filesystem/LocalLocationFactory.java
index 82847b2..8e7ab8b 100644
--- a/twill-common/src/main/java/org/apache/twill/filesystem/LocalLocationFactory.java
+++ b/twill-common/src/main/java/org/apache/twill/filesystem/LocalLocationFactory.java
@@ -31,7 +31,7 @@
* Constructs a LocalLocationFactory that Location created will be relative to system root.
*/
public LocalLocationFactory() {
- this(new File("/"));
+ this(new File(File.separator));
}
public LocalLocationFactory(File basePath) {
@@ -40,7 +40,7 @@
@Override
public Location create(String path) {
- return new LocalLocation(this, new File(basePath, path));
+ return create(new File(basePath, path).toURI());
}
@Override
diff --git a/twill-yarn/src/main/java/org/apache/twill/filesystem/FileContextLocation.java b/twill-yarn/src/main/java/org/apache/twill/filesystem/FileContextLocation.java
new file mode 100644
index 0000000..f92954e
--- /dev/null
+++ b/twill-yarn/src/main/java/org/apache/twill/filesystem/FileContextLocation.java
@@ -0,0 +1,219 @@
+/*
+ * 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.twill.filesystem;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import org.apache.hadoop.fs.CreateFlag;
+import org.apache.hadoop.fs.FileAlreadyExistsException;
+import org.apache.hadoop.fs.FileContext;
+import org.apache.hadoop.fs.FileStatus;
+import org.apache.hadoop.fs.Options;
+import org.apache.hadoop.fs.ParentNotDirectoryException;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.RemoteIterator;
+import org.apache.hadoop.fs.permission.FsPermission;
+import org.apache.hadoop.hdfs.HAUtil;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import javax.annotation.Nullable;
+
+/**
+ * An implementation of {@link Location} using {@link FileContext}.
+ */
+final class FileContextLocation implements Location {
+
+ private final FileContextLocationFactory locationFactory;
+ private final FileContext fc;
+ private final Path path;
+
+ FileContextLocation(FileContextLocationFactory locationFactory, FileContext fc, Path path) {
+ this.locationFactory = locationFactory;
+ this.fc = fc;
+ this.path = path;
+ }
+
+ @Override
+ public boolean exists() throws IOException {
+ return fc.util().exists(path);
+ }
+
+ @Override
+ public String getName() {
+ return path.getName();
+ }
+
+ @Override
+ public boolean createNew() throws IOException {
+ try {
+ fc.create(path, EnumSet.of(CreateFlag.CREATE), Options.CreateOpts.createParent()).close();
+ return true;
+ } catch (FileAlreadyExistsException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return fc.open(path);
+ }
+
+ @Override
+ public OutputStream getOutputStream() throws IOException {
+ return fc.create(path, EnumSet.of(CreateFlag.CREATE, CreateFlag.OVERWRITE), Options.CreateOpts.createParent());
+ }
+
+ @Override
+ public OutputStream getOutputStream(String permission) throws IOException {
+ return fc.create(path, EnumSet.of(CreateFlag.CREATE, CreateFlag.OVERWRITE),
+ Options.CreateOpts.perms(new FsPermission(permission)),
+ Options.CreateOpts.createParent());
+ }
+
+ @Override
+ public Location append(String child) throws IOException {
+ if (child.startsWith("/")) {
+ child = child.substring(1);
+ }
+ return new FileContextLocation(locationFactory, fc, new Path(URI.create(path.toUri() + "/" + child)));
+ }
+
+ @Override
+ public Location getTempFile(String suffix) throws IOException {
+ Path path = new Path(
+ URI.create(this.path.toUri() + "." + UUID.randomUUID() + (suffix == null ? TEMP_FILE_SUFFIX : suffix)));
+ return new FileContextLocation(locationFactory, fc, path);
+ }
+
+ @Override
+ public URI toURI() {
+ // In HA mode, the path URI returned by path created through FileContext is incompatible with the FileSystem,
+ // which is used inside Hadoop. It is due to the fact that FileContext is not HA aware and it always
+ // append "port" to the path URI, while the DistributedFileSystem always use the cluster logical
+ // name, which doesn't allow having port in it.
+ URI uri = path.toUri();
+ if (HAUtil.isLogicalUri(locationFactory.getConfiguration(), uri)) {
+ try {
+ // Need to strip out the port if in HA
+ return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(),
+ -1, uri.getPath(), uri.getQuery(), uri.getFragment());
+ } catch (URISyntaxException e) {
+ // Shouldn't happen
+ throw Throwables.propagate(e);
+ }
+ }
+
+ return uri;
+ }
+
+ @Override
+ public boolean delete() throws IOException {
+ return delete(false);
+ }
+
+ @Override
+ public boolean delete(boolean recursive) throws IOException {
+ return fc.delete(path, recursive);
+ }
+
+ @Nullable
+ @Override
+ public Location renameTo(Location destination) throws IOException {
+ Path targetPath = new Path(destination.toURI());
+ try {
+ fc.rename(path, targetPath, Options.Rename.OVERWRITE);
+ return new FileContextLocation(locationFactory, fc, targetPath);
+ } catch (FileAlreadyExistsException | FileNotFoundException | ParentNotDirectoryException e) {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean mkdirs() throws IOException {
+ try {
+ fc.mkdir(path, null, true);
+ return true;
+ } catch (FileAlreadyExistsException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public long length() throws IOException {
+ return fc.getFileStatus(path).getLen();
+ }
+
+ @Override
+ public long lastModified() throws IOException {
+ return fc.getFileStatus(path).getModificationTime();
+ }
+
+ @Override
+ public boolean isDirectory() throws IOException {
+ try {
+ return fc.getFileStatus(path).isDirectory();
+ } catch (FileNotFoundException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public List<Location> list() throws IOException {
+ RemoteIterator<FileStatus> statuses = fc.listStatus(path);
+ ImmutableList.Builder<Location> result = ImmutableList.builder();
+ while (statuses.hasNext()) {
+ FileStatus status = statuses.next();
+ if (!Objects.equals(path, status.getPath())) {
+ result.add(new FileContextLocation(locationFactory, fc, status.getPath()));
+ }
+ }
+ return result.build();
+
+ }
+
+ @Override
+ public LocationFactory getLocationFactory() {
+ return locationFactory;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ FileContextLocation that = (FileContextLocation) o;
+ return Objects.equals(path, that.path);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(path);
+ }
+}
diff --git a/twill-yarn/src/main/java/org/apache/twill/filesystem/FileContextLocationFactory.java b/twill-yarn/src/main/java/org/apache/twill/filesystem/FileContextLocationFactory.java
new file mode 100644
index 0000000..d64be71
--- /dev/null
+++ b/twill-yarn/src/main/java/org/apache/twill/filesystem/FileContextLocationFactory.java
@@ -0,0 +1,119 @@
+/*
+ * 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.twill.filesystem;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileContext;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.UnsupportedFileSystemException;
+
+import java.net.URI;
+import java.util.Objects;
+
+/**
+ * A {@link LocationFactory} implementation that uses {@link FileContext} to create {@link Location}.
+ */
+public class FileContextLocationFactory implements LocationFactory {
+
+ private final Configuration configuration;
+ private final FileContext fc;
+ private final Path pathBase;
+
+ /**
+ * Same as {@link #FileContextLocationFactory(Configuration, String) FileContextLocationFactory(configuration, "/")}.
+ */
+ public FileContextLocationFactory(Configuration configuration) {
+ this(configuration, "/");
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * @param configuration the hadoop configuration
+ * @param pathBase base path for all non-absolute location created through this {@link LocationFactory}.
+ */
+ public FileContextLocationFactory(Configuration configuration, String pathBase) {
+ this.configuration = configuration;
+ this.fc = createFileContext(configuration);
+ this.pathBase = new Path(pathBase.startsWith("/") ? pathBase : "/" + pathBase);
+ }
+
+ @Override
+ public Location create(String path) {
+ if (path.startsWith("/")) {
+ path = path.substring(1);
+ }
+ Path locationPath;
+ if (path.isEmpty()) {
+ locationPath = pathBase;
+ } else {
+ locationPath = new Path(path);
+ }
+ locationPath = locationPath.makeQualified(fc.getDefaultFileSystem().getUri(), pathBase);
+ return new FileContextLocation(this, fc, locationPath);
+ }
+
+ @Override
+ public Location create(URI uri) {
+ URI contextURI = fc.getWorkingDirectory().toUri();
+ if (Objects.equals(contextURI.getScheme(), uri.getScheme())
+ && Objects.equals(contextURI.getAuthority(), uri.getAuthority())) {
+ // A full URI
+ return new FileContextLocation(this, fc, new Path(uri));
+ }
+
+ if (uri.isAbsolute()) {
+ // Needs to be of the same scheme
+ Preconditions.checkArgument(Objects.equals(contextURI.getScheme(), uri.getScheme()),
+ "Only URI with '%s' scheme is supported", contextURI.getScheme());
+ Path locationPath = new Path(uri).makeQualified(fc.getDefaultFileSystem().getUri(), pathBase);
+ return new FileContextLocation(this, fc, locationPath);
+ }
+
+ return create(uri.getPath());
+ }
+
+ @Override
+ public Location getHomeLocation() {
+ return new FileContextLocation(this, fc, fc.getHomeDirectory());
+ }
+
+ /**
+ * Returns the {@link FileContext} used by this {@link LocationFactory}.
+ */
+ public FileContext getFileContext() {
+ return fc;
+ }
+
+ /**
+ * Returns the {@link Configuration} used by this {@link LocationFactory}.
+ */
+ public Configuration getConfiguration() {
+ return configuration;
+ }
+
+ private static FileContext createFileContext(Configuration configuration) {
+ try {
+ return FileContext.getFileContext(configuration);
+ } catch (UnsupportedFileSystemException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+}
diff --git a/twill-yarn/src/main/java/org/apache/twill/filesystem/HDFSLocation.java b/twill-yarn/src/main/java/org/apache/twill/filesystem/HDFSLocation.java
index 818fe23..aa29384 100644
--- a/twill-yarn/src/main/java/org/apache/twill/filesystem/HDFSLocation.java
+++ b/twill-yarn/src/main/java/org/apache/twill/filesystem/HDFSLocation.java
@@ -35,7 +35,7 @@
import java.util.UUID;
/**
- * A concrete implementation of {@link Location} for the HDFS filesystem.
+ * A concrete implementation of {@link Location} for the HDFS filesystem using {@link FileSystem}.
*/
final class HDFSLocation implements Location {
private final FileSystem fs;
diff --git a/twill-yarn/src/main/java/org/apache/twill/filesystem/HDFSLocationFactory.java b/twill-yarn/src/main/java/org/apache/twill/filesystem/HDFSLocationFactory.java
index 65146a8..728de32 100644
--- a/twill-yarn/src/main/java/org/apache/twill/filesystem/HDFSLocationFactory.java
+++ b/twill-yarn/src/main/java/org/apache/twill/filesystem/HDFSLocationFactory.java
@@ -17,6 +17,7 @@
*/
package org.apache.twill.filesystem;
+import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
@@ -24,10 +25,14 @@
import java.io.IOException;
import java.net.URI;
+import java.util.Objects;
/**
- * A {@link LocationFactory} that creates HDFS {@link Location}.
+ * A {@link LocationFactory} that creates HDFS {@link Location} using {@link FileSystem}.
+ *
+ * @deprecated Deprecated since 0.7.0. Use {@link FileContextLocationFactory} instead.
*/
+@Deprecated
public final class HDFSLocationFactory implements LocationFactory {
private final FileSystem fileSystem;
@@ -63,14 +68,21 @@
@Override
public Location create(URI uri) {
- if (!uri.toString().startsWith(fileSystem.getUri().toString())) {
+ URI fsURI = fileSystem.getUri();
+ if (Objects.equals(fsURI.getScheme(), uri.getScheme())
+ && Objects.equals(fsURI.getAuthority(), uri.getAuthority())) {
// It's a full URI
return new HDFSLocation(this, new Path(uri));
}
+
if (uri.isAbsolute()) {
+ // Needs to be of the same scheme
+ Preconditions.checkArgument(Objects.equals(fsURI.getScheme(), uri.getScheme()),
+ "Only URI with '%s' scheme is supported", fsURI.getScheme());
return new HDFSLocation(this, new Path(fileSystem.getUri() + uri.getPath()));
}
- return new HDFSLocation(this, new Path(fileSystem.getUri() + "/" + pathBase + "/" + uri.getPath()));
+
+ return create(uri.getPath());
}
@Override
diff --git a/twill-yarn/src/test/java/org/apache/twill/filesystem/FileContextLocationTest.java b/twill-yarn/src/test/java/org/apache/twill/filesystem/FileContextLocationTest.java
new file mode 100644
index 0000000..e4c3774
--- /dev/null
+++ b/twill-yarn/src/test/java/org/apache/twill/filesystem/FileContextLocationTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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.twill.filesystem;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hdfs.MiniDFSCluster;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+import java.io.IOException;
+
+/**
+ *
+ */
+public class FileContextLocationTest extends LocationTestBase {
+
+ private static MiniDFSCluster dfsCluster;
+
+ @BeforeClass
+ public static void init() throws IOException {
+ Configuration conf = new Configuration();
+ conf.set(MiniDFSCluster.HDFS_MINIDFS_BASEDIR, tmpFolder.newFolder().getAbsolutePath());
+ dfsCluster = new MiniDFSCluster.Builder(conf).numDataNodes(1).build();
+ }
+
+ @AfterClass
+ public static void finish() {
+ dfsCluster.shutdown();
+ }
+
+ @Override
+ protected LocationFactory createLocationFactory(String pathBase) throws Exception {
+ return new FileContextLocationFactory(dfsCluster.getFileSystem().getConf(), pathBase);
+ }
+}
diff --git a/twill-yarn/src/test/java/org/apache/twill/filesystem/HDFSLocationTest.java b/twill-yarn/src/test/java/org/apache/twill/filesystem/HDFSLocationTest.java
index 20f7403..d57d49f 100644
--- a/twill-yarn/src/test/java/org/apache/twill/filesystem/HDFSLocationTest.java
+++ b/twill-yarn/src/test/java/org/apache/twill/filesystem/HDFSLocationTest.java
@@ -30,14 +30,12 @@
public class HDFSLocationTest extends LocationTestBase {
private static MiniDFSCluster dfsCluster;
- private static LocationFactory locationFactory;
@BeforeClass
public static void init() throws IOException {
Configuration conf = new Configuration();
conf.set(MiniDFSCluster.HDFS_MINIDFS_BASEDIR, tmpFolder.newFolder().getAbsolutePath());
dfsCluster = new MiniDFSCluster.Builder(conf).numDataNodes(1).build();
- locationFactory = new HDFSLocationFactory(dfsCluster.getFileSystem());
}
@AfterClass
@@ -46,7 +44,7 @@
}
@Override
- protected LocationFactory getLocationFactory() {
- return locationFactory;
+ protected LocationFactory createLocationFactory(String pathBase) throws Exception {
+ return new HDFSLocationFactory(dfsCluster.getFileSystem(), pathBase);
}
}
diff --git a/twill-yarn/src/test/java/org/apache/twill/filesystem/LocalLocationTest.java b/twill-yarn/src/test/java/org/apache/twill/filesystem/LocalLocationTest.java
index 3f6d931..ba21beb 100644
--- a/twill-yarn/src/test/java/org/apache/twill/filesystem/LocalLocationTest.java
+++ b/twill-yarn/src/test/java/org/apache/twill/filesystem/LocalLocationTest.java
@@ -17,24 +17,17 @@
*/
package org.apache.twill.filesystem;
-import org.junit.BeforeClass;
-
-import java.io.IOException;
+import java.io.File;
/**
*
*/
public class LocalLocationTest extends LocationTestBase {
- private static LocationFactory locationFactory;
-
- @BeforeClass
- public static void init() throws IOException {
- locationFactory = new LocalLocationFactory(tmpFolder.newFolder());
- }
-
@Override
- protected LocationFactory getLocationFactory() {
- return locationFactory;
+ protected LocationFactory createLocationFactory(String pathBase) throws Exception {
+ File basePath = new File(tmpFolder.newFolder(), pathBase);
+ basePath.mkdirs();
+ return new LocalLocationFactory(basePath);
}
}
diff --git a/twill-yarn/src/test/java/org/apache/twill/filesystem/LocationTestBase.java b/twill-yarn/src/test/java/org/apache/twill/filesystem/LocationTestBase.java
index ee591e7..e01115b 100644
--- a/twill-yarn/src/test/java/org/apache/twill/filesystem/LocationTestBase.java
+++ b/twill-yarn/src/test/java/org/apache/twill/filesystem/LocationTestBase.java
@@ -17,13 +17,23 @@
*/
package org.apache.twill.filesystem;
+import com.google.common.base.Charsets;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.io.CharStreams;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
import java.net.URI;
+import java.net.URISyntaxException;
import java.util.List;
/**
@@ -34,10 +44,60 @@
@ClassRule
public static TemporaryFolder tmpFolder = new TemporaryFolder();
+ private final LoadingCache<String, LocationFactory> locationFactoryCache = CacheBuilder.newBuilder()
+ .build(new CacheLoader<String, LocationFactory>() {
+ @Override
+ public LocationFactory load(String key) throws Exception {
+ return createLocationFactory(key);
+ }
+ });
+
+ @Test
+ public void testBasic() throws Exception {
+ LocationFactory factory = locationFactoryCache.getUnchecked("basic");
+ URI baseURI = factory.create("/").toURI();
+
+ // Test basic location construction
+ Assert.assertEquals(factory.create("/file"), factory.create("/file"));
+ Assert.assertEquals(factory.create("/file2"),
+ factory.create(URI.create(baseURI.getScheme() + ":" + baseURI.getPath() + "/file2")));
+ Assert.assertEquals(factory.create("/file3"),
+ factory.create(
+ new URI(baseURI.getScheme(), baseURI.getAuthority(),
+ baseURI.getPath() + "/file3", null, null)));
+ Assert.assertEquals(factory.create("/"), factory.create("/"));
+ Assert.assertEquals(factory.create("/"), factory.create(URI.create(baseURI.getScheme() + ":" + baseURI.getPath())));
+
+ Assert.assertEquals(factory.create("/"),
+ factory.create(new URI(baseURI.getScheme(), baseURI.getAuthority(),
+ baseURI.getPath(), null, null)));
+
+ // Test file creation and rename
+ Location location = factory.create("/file");
+ Assert.assertTrue(location.createNew());
+ Assert.assertTrue(location.exists());
+
+ Location location2 = factory.create("/file2");
+ String message = "Testing Message";
+ try (Writer writer = new OutputStreamWriter(location2.getOutputStream(), Charsets.UTF_8)) {
+ writer.write(message);
+ }
+ long length = location2.length();
+ long lastModified = location2.lastModified();
+
+ location2.renameTo(location);
+
+ Assert.assertFalse(location2.exists());
+ try (Reader reader = new InputStreamReader(location.getInputStream(), Charsets.UTF_8)) {
+ Assert.assertEquals(message, CharStreams.toString(reader));
+ }
+ Assert.assertEquals(length, location.length());
+ Assert.assertEquals(lastModified, location.lastModified());
+ }
@Test
public void testDelete() throws IOException {
- LocationFactory factory = getLocationFactory();
+ LocationFactory factory = locationFactoryCache.getUnchecked("delete");
Location base = factory.create("test").getTempFile(".tmp");
Assert.assertTrue(base.mkdirs());
@@ -57,7 +117,7 @@
@Test
public void testHelper() {
- LocationFactory factory = LocationFactories.namespace(getLocationFactory(), "testhelper");
+ LocationFactory factory = LocationFactories.namespace(locationFactoryCache.getUnchecked("helper"), "testhelper");
Location location = factory.create("test");
Assert.assertTrue(location.toURI().getPath().endsWith("testhelper/test"));
@@ -68,7 +128,7 @@
@Test
public void testList() throws IOException {
- LocationFactory factory = getLocationFactory();
+ LocationFactory factory = locationFactoryCache.getUnchecked("list");
Location dir = factory.create("dir");
@@ -107,5 +167,5 @@
}
}
- protected abstract LocationFactory getLocationFactory();
+ protected abstract LocationFactory createLocationFactory(String pathBase) throws Exception;
}