Skip to content

Commit d3b83cd

Browse files
committed
Use query parameters for URI-specific settings
1 parent 227a089 commit d3b83cd

File tree

5 files changed

+282
-11
lines changed

5 files changed

+282
-11
lines changed

src/main/java/com/github/robtimus/filesystems/sftp/SFTPEnvironment.java

+172
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,16 @@
1919

2020
import java.io.File;
2121
import java.io.IOException;
22+
import java.io.UnsupportedEncodingException;
23+
import java.lang.annotation.Documented;
24+
import java.lang.annotation.ElementType;
25+
import java.lang.annotation.Repeatable;
26+
import java.lang.annotation.Retention;
27+
import java.lang.annotation.RetentionPolicy;
28+
import java.lang.annotation.Target;
2229
import java.net.Socket;
2330
import java.net.URI;
31+
import java.net.URLDecoder;
2432
import java.nio.charset.Charset;
2533
import java.nio.charset.UnsupportedCharsetException;
2634
import java.nio.file.FileSystemException;
@@ -88,6 +96,7 @@ public class SFTPEnvironment implements Map<String, Object> {
8896
private static final String SOCKET_FACTORY = "socketFactory"; //$NON-NLS-1$
8997
// timeOut should have been timeout, but that's a breaking change...
9098
private static final String TIMEOUT = "timeOut"; //$NON-NLS-1$
99+
private static final String TIMEOUT_QUERY_PARAM = "timeout"; //$NON-NLS-1$
91100
private static final String CLIENT_VERSION = "clientVersion"; //$NON-NLS-1$
92101
private static final String HOST_KEY_ALIAS = "hostKeyAlias"; //$NON-NLS-1$
93102
private static final String SERVER_ALIVE_INTERVAL = "serverAliveInterval"; //$NON-NLS-1$
@@ -107,6 +116,10 @@ public class SFTPEnvironment implements Map<String, Object> {
107116

108117
private static final String DEFAULT_DIR = "defaultDir"; //$NON-NLS-1$
109118
private static final String POOL_CONFIG = "poolConfig"; //$NON-NLS-1$
119+
private static final String POOL_CONFIG_MAX_WAIT_TIME = POOL_CONFIG + ".maxWaitTime"; //$NON-NLS-1$
120+
private static final String POOL_CONFIG_MAX_IDLE_TIME = POOL_CONFIG + ".maxIdleTime"; //$NON-NLS-1$
121+
private static final String POOL_CONFIG_INITIAL_SIZE = POOL_CONFIG + ".initialSize"; //$NON-NLS-1$
122+
private static final String POOL_CONFIG_MAX_SIZE = POOL_CONFIG + ".maxSize"; //$NON-NLS-1$
110123
private static final String FILE_SYSTEM_EXCEPTION_FACTORY = "fileSystemExceptionFactory"; //$NON-NLS-1$
111124

112125
private final Map<String, Object> map;
@@ -148,6 +161,7 @@ public SFTPEnvironment withUsername(String username) {
148161
* @param timeout The connection timeout in milliseconds.
149162
* @return This object.
150163
*/
164+
@QueryParam(CONNECT_TIMEOUT)
151165
public SFTPEnvironment withConnectTimeout(int timeout) {
152166
put(CONNECT_TIMEOUT, timeout);
153167
return this;
@@ -211,6 +225,7 @@ public SFTPEnvironment withConfig(Properties config) {
211225
* @throws NullPointerException if the given key or value is {@code null}.
212226
* @see #withConfig(Properties)
213227
*/
228+
@QueryParam(CONFIG + ".<key>")
214229
public SFTPEnvironment withConfig(String key, String value) {
215230
getConfig().setProperty(key, value);
216231
return this;
@@ -250,6 +265,7 @@ private Properties getConfig() {
250265
* @throws NullPointerException If the given key or value is {@code null}.
251266
* @since 3.2
252267
*/
268+
@QueryParam(APPENDED_CONFIG + ".<key>")
253269
public SFTPEnvironment withAppendedConfig(String key, String value) {
254270
return withAppendedConfig(key, value, AppendedConfig.DEFAULT_APPENDER);
255271
}
@@ -302,6 +318,7 @@ public SFTPEnvironment withSocketFactory(SocketFactory factory) {
302318
* @return This object.
303319
* @see Socket#setSoTimeout(int)
304320
*/
321+
@QueryParam(TIMEOUT_QUERY_PARAM)
305322
public SFTPEnvironment withTimeout(int timeout) {
306323
put(TIMEOUT, timeout);
307324
return this;
@@ -313,6 +330,7 @@ public SFTPEnvironment withTimeout(int timeout) {
313330
* @param version The client version.
314331
* @return This object.
315332
*/
333+
@QueryParam(CLIENT_VERSION)
316334
public SFTPEnvironment withClientVersion(String version) {
317335
put(CLIENT_VERSION, version);
318336
return this;
@@ -324,6 +342,7 @@ public SFTPEnvironment withClientVersion(String version) {
324342
* @param alias The host key alias.
325343
* @return This object.
326344
*/
345+
@QueryParam(HOST_KEY_ALIAS)
327346
public SFTPEnvironment withHostKeyAlias(String alias) {
328347
put(HOST_KEY_ALIAS, alias);
329348
return this;
@@ -335,6 +354,7 @@ public SFTPEnvironment withHostKeyAlias(String alias) {
335354
* @param interval The server alive interval in milliseconds.
336355
* @return This object.
337356
*/
357+
@QueryParam(SERVER_ALIVE_INTERVAL)
338358
public SFTPEnvironment withServerAliveInterval(int interval) {
339359
put(SERVER_ALIVE_INTERVAL, interval);
340360
return this;
@@ -346,6 +366,7 @@ public SFTPEnvironment withServerAliveInterval(int interval) {
346366
* @param count The maximum number of server alive messages.
347367
* @return This object.
348368
*/
369+
@QueryParam(SERVER_ALIVE_COUNT_MAX)
349370
public SFTPEnvironment withServerAliveCountMax(int count) {
350371
put(SERVER_ALIVE_COUNT_MAX, count);
351372
return this;
@@ -471,6 +492,7 @@ public SFTPEnvironment withConfigRepository(ConfigRepository repository) {
471492
* @param agentForwarding {@code true} to enable strict agent forwarding, or {@code false} to disable it.
472493
* @return This object.
473494
*/
495+
@QueryParam(AGENT_FORWARDING)
474496
public SFTPEnvironment withAgentForwarding(boolean agentForwarding) {
475497
put(AGENT_FORWARDING, agentForwarding);
476498
return this;
@@ -482,6 +504,7 @@ public SFTPEnvironment withAgentForwarding(boolean agentForwarding) {
482504
* @param encoding The filename encoding to use.
483505
* @return This object.
484506
*/
507+
@QueryParam(FILENAME_ENCODING)
485508
public SFTPEnvironment withFilenameEncoding(Charset encoding) {
486509
put(FILENAME_ENCODING, encoding);
487510
return this;
@@ -496,6 +519,7 @@ public SFTPEnvironment withFilenameEncoding(Charset encoding) {
496519
* @param pathname The default directory to use.
497520
* @return This object.
498521
*/
522+
@QueryParam(DEFAULT_DIR)
499523
public SFTPEnvironment withDefaultDirectory(String pathname) {
500524
put(DEFAULT_DIR, pathname);
501525
return this;
@@ -513,6 +537,10 @@ public SFTPEnvironment withDefaultDirectory(String pathname) {
513537
* @return This object.
514538
* @since 3.0
515539
*/
540+
@QueryParam(POOL_CONFIG_MAX_WAIT_TIME)
541+
@QueryParam(POOL_CONFIG_MAX_IDLE_TIME)
542+
@QueryParam(POOL_CONFIG_INITIAL_SIZE)
543+
@QueryParam(POOL_CONFIG_MAX_SIZE)
516544
public SFTPEnvironment withPoolConfig(SFTPPoolConfig poolConfig) {
517545
put(POOL_CONFIG, poolConfig);
518546
return this;
@@ -529,6 +557,11 @@ public SFTPEnvironment withFileSystemExceptionFactory(FileSystemExceptionFactory
529557
return this;
530558
}
531559

560+
SFTPEnvironment withQueryString(String rawQueryString) {
561+
new QueryParamProcessor(this).processQueryString(rawQueryString);
562+
return this;
563+
}
564+
532565
boolean hasUsername() {
533566
return containsKey(USERNAME);
534567
}
@@ -984,4 +1017,143 @@ public String toString() {
9841017
return value;
9851018
}
9861019
}
1020+
1021+
/**
1022+
* Indicates which query parameters can be used to define environment values.
1023+
*
1024+
* @author Rob Spoor
1025+
* @since 3.3
1026+
*/
1027+
@Target(ElementType.METHOD)
1028+
@Retention(RetentionPolicy.SOURCE)
1029+
@Documented
1030+
@Repeatable(QueryParams.class)
1031+
public @interface QueryParam {
1032+
1033+
/**
1034+
* The name of the query parameter.
1035+
*/
1036+
String value();
1037+
}
1038+
1039+
/**
1040+
* A container for {@link QueryParam} annotations.
1041+
*
1042+
* @author Rob Spoor
1043+
* @since 3.3
1044+
*/
1045+
@Target(ElementType.METHOD)
1046+
@Retention(RetentionPolicy.SOURCE)
1047+
@Documented
1048+
public @interface QueryParams {
1049+
1050+
/**
1051+
* The contained {@link QueryParam} annotations.
1052+
*/
1053+
QueryParam[] value();
1054+
}
1055+
1056+
static final class QueryParamProcessor {
1057+
1058+
private final SFTPEnvironment env;
1059+
private SFTPPoolConfig.Builder poolConfigBuilder;
1060+
1061+
private QueryParamProcessor(SFTPEnvironment env) {
1062+
this.env = env;
1063+
}
1064+
1065+
private void processQueryString(String rawQueryString) {
1066+
int start = 0;
1067+
int indexOfAmp = rawQueryString.indexOf('&', start);
1068+
while (indexOfAmp != -1) {
1069+
processQueryParam(rawQueryString, start, indexOfAmp);
1070+
start = indexOfAmp + 1;
1071+
indexOfAmp = rawQueryString.indexOf('&', start);
1072+
}
1073+
processQueryParam(rawQueryString, start, rawQueryString.length());
1074+
1075+
if (poolConfigBuilder != null) {
1076+
env.withPoolConfig(poolConfigBuilder.build());
1077+
}
1078+
}
1079+
1080+
private void processQueryParam(String rawQueryString, int start, int end) {
1081+
int indexOfEquals = rawQueryString.indexOf('=', start);
1082+
if (indexOfEquals == -1 || indexOfEquals > end) {
1083+
String key = decode(rawQueryString.substring(start, end));
1084+
processQueryParam(key, ""); //$NON-NLS-1$
1085+
} else {
1086+
String key = decode(rawQueryString.substring(start, indexOfEquals));
1087+
String value = decode(rawQueryString.substring(indexOfEquals + 1, end));
1088+
processQueryParam(key, value);
1089+
}
1090+
}
1091+
1092+
private void processQueryParam(String key, String value) {
1093+
switch (key) {
1094+
case CONNECT_TIMEOUT:
1095+
env.withConnectTimeout(Integer.parseInt(value));
1096+
break;
1097+
case TIMEOUT_QUERY_PARAM:
1098+
env.withTimeout(Integer.parseInt(value));
1099+
break;
1100+
case CLIENT_VERSION:
1101+
env.withClientVersion(value);
1102+
break;
1103+
case HOST_KEY_ALIAS:
1104+
env.withHostKeyAlias(value);
1105+
break;
1106+
case SERVER_ALIVE_INTERVAL:
1107+
env.withServerAliveInterval(Integer.parseInt(value));
1108+
break;
1109+
case SERVER_ALIVE_COUNT_MAX:
1110+
env.withServerAliveCountMax(Integer.parseInt(value));
1111+
break;
1112+
case AGENT_FORWARDING:
1113+
env.withAgentForwarding(Boolean.parseBoolean(value));
1114+
break;
1115+
case FILENAME_ENCODING:
1116+
env.withFilenameEncoding(Charset.forName(value));
1117+
break;
1118+
case DEFAULT_DIR:
1119+
env.withDefaultDirectory(value);
1120+
break;
1121+
case POOL_CONFIG_MAX_WAIT_TIME:
1122+
poolConfigBuilder().withMaxWaitTime(Duration.parse(value));
1123+
break;
1124+
case POOL_CONFIG_MAX_IDLE_TIME:
1125+
poolConfigBuilder().withMaxIdleTime(Duration.parse(value));
1126+
break;
1127+
case POOL_CONFIG_INITIAL_SIZE:
1128+
poolConfigBuilder().withInitialSize(Integer.parseInt(value));
1129+
break;
1130+
case POOL_CONFIG_MAX_SIZE:
1131+
poolConfigBuilder().withMaxSize(Integer.parseInt(value));
1132+
break;
1133+
default:
1134+
if (key.startsWith(CONFIG + ".")) { //$NON-NLS-1$
1135+
env.withConfig(key.substring(CONFIG.length() + 1), value);
1136+
} else if (key.startsWith(APPENDED_CONFIG + ".")) { //$NON-NLS-1$
1137+
env.withAppendedConfig(key.substring(APPENDED_CONFIG.length() + 1), value);
1138+
}
1139+
// else ignore
1140+
break;
1141+
}
1142+
}
1143+
1144+
private String decode(String value) {
1145+
try {
1146+
return URLDecoder.decode(value, "UTF-8"); //$NON-NLS-1$
1147+
} catch (UnsupportedEncodingException e) {
1148+
throw new IllegalStateException(e);
1149+
}
1150+
}
1151+
1152+
private SFTPPoolConfig.Builder poolConfigBuilder() {
1153+
if (poolConfigBuilder == null) {
1154+
poolConfigBuilder = env.getPoolConfig().toBuilder();
1155+
}
1156+
return poolConfigBuilder;
1157+
}
1158+
}
9871159
}

src/main/java/com/github/robtimus/filesystems/sftp/SFTPFileSystemProvider.java

+18-11
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
import com.github.robtimus.filesystems.LinkOptionSupport;
5252
import com.github.robtimus.filesystems.Messages;
5353
import com.github.robtimus.filesystems.URISupport;
54+
import com.github.robtimus.filesystems.sftp.SFTPEnvironment.QueryParam;
55+
import com.github.robtimus.filesystems.sftp.SFTPEnvironment.QueryParams;
5456

5557
/**
5658
* A provider for SFTP file systems.
@@ -93,7 +95,7 @@ public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException
9395
boolean allowUserInfo = !environment.hasUsername();
9496
boolean allowPath = !environment.hasDefaultDir();
9597

96-
checkURI(uri, allowUserInfo, allowPath);
98+
checkURI(uri, allowUserInfo, allowPath, false);
9799

98100
addUserInfoIfNeeded(environment, uri.getUserInfo());
99101
addDefaultDirIfNeeded(environment, uri.getPath());
@@ -135,7 +137,7 @@ private void addDefaultDirIfNeeded(SFTPEnvironment environment, String path) {
135137
*/
136138
@Override
137139
public FileSystem getFileSystem(URI uri) {
138-
checkURI(uri, true, false);
140+
checkURI(uri, true, false, false);
139141

140142
URI normalizedURI = normalizeWithoutPassword(uri);
141143
return fileSystems.get(normalizedURI);
@@ -145,26 +147,31 @@ public FileSystem getFileSystem(URI uri) {
145147
* Return a {@code Path} object by converting the given {@link URI}. The resulting {@code Path} is associated with a {@link FileSystem} that
146148
* already exists or is constructed automatically.
147149
* <p>
148-
* The URI must have a {@link URI#getScheme() scheme} equal to {@link #getScheme()}, and no {@link URI#getQuery() query} or
149-
* {@link URI#getFragment() fragment}. Because the original credentials were possibly provided through an environment map,
150-
* the URI can contain {@link URI#getUserInfo() user information}, although for security reasons this should only contain a password to support
151-
* automatically creating file systems.
150+
* The URI must have a {@link URI#getScheme() scheme} equal to {@link #getScheme()}, and no {@link URI#getFragment() fragment}. Because the
151+
* original credentials were possibly provided through an environment map, the URI can contain {@link URI#getUserInfo() user information},
152+
* although for security reasons this should only contain a password to support automatically creating file systems.
152153
* <p>
153154
* If no matching file system existed yet, a new one is created. The {@link SFTPEnvironment#setDefault(SFTPEnvironment) default environment} is
154-
* used for this, to allow configuring the resulting file system.
155+
* used for this, to allow configuring the resulting file system. URI specific settings can also be provided through the
156+
* {@link URI#getQuery() query}; see usages of {@link QueryParam} and {@link QueryParams} for the possible query parameters.
155157
* <p>
156158
* Remember to close any newly created file system.
157159
*/
158160
@Override
159161
@SuppressWarnings("resource")
160162
public Path getPath(URI uri) {
161-
checkURI(uri, true, true);
163+
checkURI(uri, true, true, true);
162164

163165
URI normalizedURI = normalizeWithoutPassword(uri);
164166
SFTPEnvironment env = SFTPEnvironment.copyOfDefault();
165167

166168
addUserInfoIfNeeded(env, uri.getUserInfo());
167-
// Do not add any default dir
169+
// Do not add any default dir from the path
170+
171+
String rawQueryString = uri.getRawQuery();
172+
if (rawQueryString != null) {
173+
env.withQueryString(rawQueryString);
174+
}
168175

169176
try {
170177
SFTPFileSystem fs = fileSystems.addIfNotExists(normalizedURI, env);
@@ -178,7 +185,7 @@ public Path getPath(URI uri) {
178185
}
179186
}
180187

181-
private void checkURI(URI uri, boolean allowUserInfo, boolean allowPath) {
188+
private void checkURI(URI uri, boolean allowUserInfo, boolean allowPath, boolean allowQuery) {
182189
if (!uri.isAbsolute()) {
183190
throw Messages.uri().notAbsolute(uri);
184191
}
@@ -194,7 +201,7 @@ private void checkURI(URI uri, boolean allowUserInfo, boolean allowPath) {
194201
if (!allowPath && !hasEmptyPath(uri) && !"/".equals(uri.getPath())) { //$NON-NLS-1$
195202
throw Messages.uri().hasPath(uri);
196203
}
197-
if (uri.getQuery() != null && !uri.getQuery().isEmpty()) {
204+
if (!allowQuery && uri.getQuery() != null && !uri.getQuery().isEmpty()) {
198205
throw Messages.uri().hasQuery(uri);
199206
}
200207
if (uri.getFragment() != null && !uri.getFragment().isEmpty()) {

src/main/java/com/github/robtimus/filesystems/sftp/SFTPPoolConfig.java

+13
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,19 @@ public String toString() {
9494
+ "]";
9595
}
9696

97+
Builder toBuilder() {
98+
Builder builder = custom()
99+
.withInitialSize(initialSize())
100+
.withMaxSize(maxSize());
101+
builder = maxWaitTime()
102+
.map(builder::withMaxWaitTime)
103+
.orElse(builder);
104+
builder = maxIdleTime()
105+
.map(builder::withMaxIdleTime)
106+
.orElse(builder);
107+
return builder;
108+
}
109+
97110
/**
98111
* Returns a default {@link SFTPPoolConfig} object. This has the same configuration as an object returned by {@code custom().build()}.
99112
*

0 commit comments

Comments
 (0)