/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed 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.springframework.xd.dirt.server.options;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.NamedOptionDef;
import org.kohsuke.args4j.OptionDef;
import org.kohsuke.args4j.spi.OptionHandler;
import org.kohsuke.args4j.spi.Parameters;
import org.kohsuke.args4j.spi.Setter;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
/**
* An {@link OptionHandler} that scans a resource pattern for existing resources (using a single '*' wildcard) and
* allows all String values <i>s</i> that would fit if that single wildcard was replaced by <i>s</i>.
*
* <p>
* Given that an option handler has to appear as an annotation parameter, expected usage is to sublcass this class,
* provide the canonical 3 arg constructor to {@link OptionHandler} and pass the resource pattern as the 4th argument.
* </p>
*
* @author Eric Bottard
*/
public abstract class ResourcePatternScanningOptionHandler extends OptionHandler<String> {
protected final Set<String> possibleValues = new HashSet<String>();
private final Set<String> excluded = new HashSet<String>();
protected ResourcePatternScanningOptionHandler(CmdLineParser parser, OptionDef option, Setter<String> setter,
String glob)
throws IOException {
super(parser, option, setter);
init(glob);
}
@Override
public int parseArguments(Parameters params) throws CmdLineException {
String s = params.getParameter(0);
if (!possibleValues.contains(s)) {
String errorMessage = String.format("'%s' is not a valid value. Possible values are %s", s, possibleValues);
if (this.option instanceof NamedOptionDef) {
NamedOptionDef named = (NamedOptionDef) this.option;
errorMessage = String.format("'%s' is not a valid value for option %s. Possible values are %s", s,
named.name(), possibleValues);
}
if (excluded.contains(s)) {
errorMessage += String.format(
". Note that '%s' has been explicitly excluded from the list of possible values,"
+ " even though a resource with that name exists", s);
}
throw new CmdLineException(owner, errorMessage);
}
setter.addValue(s);
return 1;
}
@Override
public String getDefaultMetaVariable() {
return possibleValues.toString().replace(",", " |");
}
private void init(String glob, String... excludes) throws IOException {
String resolved = CommandLinePropertySourceOverridingListener.getCurrentEnvironment().resolvePlaceholders(glob);
int protocolColon = resolved.indexOf(':');
String withoutProtocol = protocolColon != -1 ? resolved.substring(protocolColon + 1) : resolved;
Pattern capturing = Pattern.compile(".*" + withoutProtocol.replace("*", "([^/]*)") + ".*");
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
for (Resource r : resolver.getResources(resolved)) {
if (!shouldConsider(r)) {
continue;
}
String path = r.getURL().toString();
Matcher matcher = capturing.matcher(path);
if (!matcher.matches()) {
throw new IllegalStateException(
String.format("Expected to match '%s' with regex '%s'", path, capturing));
}
possibleValues.add(matcher.group(1));
}
}
/**
* Whether the matched Spring resource should even be considered for inclusion in the result set.
* Default implementation just returns true.
*/
protected boolean shouldConsider(Resource r) {
return true;
}
protected void exclude(String... excludes) {
excluded.addAll(Arrays.asList(excludes));
possibleValues.removeAll(excluded);
}
protected void include(String... includes) {
possibleValues.addAll(Arrays.asList(includes));
}
}