001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.vfs2.util; 018 019import java.lang.reflect.Array; 020import java.lang.reflect.Constructor; 021import java.lang.reflect.InvocationTargetException; 022import java.lang.reflect.Method; 023import java.lang.reflect.Modifier; 024import java.util.ArrayList; 025import java.util.Iterator; 026import java.util.List; 027import java.util.Map; 028import java.util.TreeMap; 029 030import org.apache.commons.logging.Log; 031import org.apache.commons.logging.LogFactory; 032import org.apache.commons.vfs2.FileSystemConfigBuilder; 033import org.apache.commons.vfs2.FileSystemException; 034import org.apache.commons.vfs2.FileSystemManager; 035import org.apache.commons.vfs2.FileSystemOptions; 036 037/** 038 * This class use reflection to set a configuration value using the fileSystemConfigBuilder associated the a scheme.<br> 039 * <br> 040 * Example:<br> 041 * 042 * <pre> 043 * FileSystemOptions fso = new FileSystemOptions(); 044 * DelegatingFileSystemOptionsBuilder delegate = new DelegatingFileSystemOptionsBuilder(VFS.getManager()); 045 * delegate.setConfigString(fso, "sftp", "identities", "c:/tmp/test.ident"); 046 * delegate.setConfigString(fso, "http", "proxyPort", "8080"); 047 * delegate.setConfigClass(fso, "sftp", "userinfo", TrustEveryoneUserInfo.class); 048 * </pre> 049 */ 050public class DelegatingFileSystemOptionsBuilder { 051 @SuppressWarnings("unchecked") // OK, it is a String 052 private static final Class<String>[] STRING_PARAM = new Class[] { String.class }; 053 private static final Map<String, Class<?>> PRIMATIVE_TO_OBJECT = new TreeMap<>(); 054 private static final Log log = LogFactory.getLog(DelegatingFileSystemOptionsBuilder.class); 055 056 private final FileSystemManager manager; 057 private final Map<String, Map<String, List<Method>>> beanMethods = new TreeMap<>(); 058 059 static { 060 PRIMATIVE_TO_OBJECT.put(Void.TYPE.getName(), Void.class); 061 PRIMATIVE_TO_OBJECT.put(Boolean.TYPE.getName(), Boolean.class); 062 PRIMATIVE_TO_OBJECT.put(Byte.TYPE.getName(), Byte.class); 063 PRIMATIVE_TO_OBJECT.put(Character.TYPE.getName(), Character.class); 064 PRIMATIVE_TO_OBJECT.put(Short.TYPE.getName(), Short.class); 065 PRIMATIVE_TO_OBJECT.put(Integer.TYPE.getName(), Integer.class); 066 PRIMATIVE_TO_OBJECT.put(Long.TYPE.getName(), Long.class); 067 PRIMATIVE_TO_OBJECT.put(Double.TYPE.getName(), Double.class); 068 PRIMATIVE_TO_OBJECT.put(Float.TYPE.getName(), Float.class); 069 } 070 071 /** 072 * Context. 073 */ 074 private static final class Context { 075 private final FileSystemOptions fso; 076 private final String scheme; 077 private final String name; 078 private final Object[] values; 079 080 private List<Method> configSetters; 081 private FileSystemConfigBuilder fileSystemConfigBuilder; 082 083 private Context(final FileSystemOptions fso, final String scheme, final String name, final Object[] values) { 084 this.fso = fso; 085 this.scheme = scheme; 086 this.name = name; 087 this.values = values; 088 } 089 } 090 091 /** 092 * Constructor.<br> 093 * Pass in your fileSystemManager instance. 094 * 095 * @param manager the manager to use to get the fileSystemConfigBuilder assocated to a scheme 096 */ 097 public DelegatingFileSystemOptionsBuilder(final FileSystemManager manager) { 098 this.manager = manager; 099 } 100 101 protected FileSystemManager getManager() { 102 return manager; 103 } 104 105 /** 106 * Set a single string value. 107 * 108 * @param fso FileSystemOptions 109 * @param scheme scheme 110 * @param name name 111 * @param value value 112 * @throws FileSystemException if an error occurs. 113 */ 114 public void setConfigString(final FileSystemOptions fso, final String scheme, final String name, final String value) 115 throws FileSystemException { 116 setConfigStrings(fso, scheme, name, new String[] { value }); 117 } 118 119 /** 120 * Set an array of string value. 121 * 122 * @param fso FileSystemOptions 123 * @param scheme scheme 124 * @param name name 125 * @param values values 126 * @throws FileSystemException if an error occurs. 127 */ 128 public void setConfigStrings(final FileSystemOptions fso, final String scheme, final String name, 129 final String[] values) throws FileSystemException { 130 final Context ctx = new Context(fso, scheme, name, values); 131 132 setValues(ctx); 133 } 134 135 /** 136 * Set a single class value.<br> 137 * The class has to implement a no-args constructor, else the instantiation might fail. 138 * 139 * @param fso FileSystemOptions 140 * @param scheme scheme 141 * @param name name 142 * @param className className 143 * @throws FileSystemException if an error occurs. 144 * @throws IllegalAccessException if a class canoot be accessed. 145 * @throws InstantiationException if a class cannot be instantiated. 146 */ 147 public void setConfigClass(final FileSystemOptions fso, final String scheme, final String name, 148 final Class<?> className) throws FileSystemException, IllegalAccessException, InstantiationException { 149 setConfigClasses(fso, scheme, name, new Class[] { className }); 150 } 151 152 /** 153 * Set an array of class values.<br> 154 * The class has to implement a no-args constructor, else the instantiation might fail. 155 * 156 * @param fso FileSystemOptions 157 * @param scheme scheme 158 * @param name name 159 * @param classNames classNames 160 * @throws FileSystemException if an error occurs. 161 * @throws IllegalAccessException if a class canoot be accessed. 162 * @throws InstantiationException if a class cannot be instantiated. 163 */ 164 public void setConfigClasses(final FileSystemOptions fso, final String scheme, final String name, 165 final Class<?>[] classNames) throws FileSystemException, IllegalAccessException, InstantiationException { 166 final Object[] values = new Object[classNames.length]; 167 for (int iterClassNames = 0; iterClassNames < values.length; iterClassNames++) { 168 values[iterClassNames] = classNames[iterClassNames].newInstance(); 169 } 170 171 final Context ctx = new Context(fso, scheme, name, values); 172 173 setValues(ctx); 174 } 175 176 /** 177 * sets the values using the informations of the given context.<br> 178 */ 179 private void setValues(final Context ctx) throws FileSystemException { 180 // find all setter methods suitable for the given "name" 181 if (!fillConfigSetters(ctx)) { 182 throw new FileSystemException("vfs.provider/config-key-invalid.error", ctx.scheme, ctx.name); 183 } 184 185 // get the fileSystemConfigBuilder 186 ctx.fileSystemConfigBuilder = getManager().getFileSystemConfigBuilder(ctx.scheme); 187 188 // try to find a setter which could accept the value 189 final Iterator<Method> iterConfigSetters = ctx.configSetters.iterator(); 190 while (iterConfigSetters.hasNext()) { 191 final Method configSetter = iterConfigSetters.next(); 192 if (convertValuesAndInvoke(configSetter, ctx)) { 193 return; 194 } 195 } 196 197 throw new FileSystemException("vfs.provider/config-value-invalid.error", ctx.scheme, ctx.name, ctx.values); 198 } 199 200 /** 201 * tries to convert the value and pass it to the given method 202 */ 203 private boolean convertValuesAndInvoke(final Method configSetter, final Context ctx) throws FileSystemException { 204 final Class<?>[] parameters = configSetter.getParameterTypes(); 205 if (parameters.length < 2) { 206 return false; 207 } 208 if (!parameters[0].isAssignableFrom(FileSystemOptions.class)) { 209 return false; 210 } 211 212 final Class<?> valueParameter = parameters[1]; 213 Class<?> type; 214 if (valueParameter.isArray()) { 215 type = valueParameter.getComponentType(); 216 } else { 217 if (ctx.values.length > 1) { 218 return false; 219 } 220 221 type = valueParameter; 222 } 223 224 if (type.isPrimitive()) { 225 final Class<?> objectType = PRIMATIVE_TO_OBJECT.get(type.getName()); 226 if (objectType == null) { 227 log.warn(Messages.getString("vfs.provider/config-unexpected-primitive.error", type.getName())); 228 return false; 229 } 230 type = objectType; 231 } 232 233 final Class<? extends Object> valueClass = ctx.values[0].getClass(); 234 if (type.isAssignableFrom(valueClass)) { 235 // can set value directly 236 invokeSetter(valueParameter, ctx, configSetter, ctx.values); 237 return true; 238 } 239 if (valueClass != String.class) { 240 log.warn(Messages.getString("vfs.provider/config-unexpected-value-class.error", valueClass.getName(), 241 ctx.scheme, ctx.name)); 242 return false; 243 } 244 245 final Object convertedValues = Array.newInstance(type, ctx.values.length); 246 247 Constructor<?> valueConstructor; 248 try { 249 valueConstructor = type.getConstructor(STRING_PARAM); 250 } catch (final NoSuchMethodException e) { 251 valueConstructor = null; 252 } 253 if (valueConstructor != null) { 254 // can convert using constructor 255 for (int iterValues = 0; iterValues < ctx.values.length; iterValues++) { 256 try { 257 Array.set(convertedValues, iterValues, 258 valueConstructor.newInstance(new Object[] { ctx.values[iterValues] })); 259 } catch (final InstantiationException e) { 260 throw new FileSystemException(e); 261 } catch (final IllegalAccessException e) { 262 throw new FileSystemException(e); 263 } catch (final InvocationTargetException e) { 264 throw new FileSystemException(e); 265 } 266 } 267 268 invokeSetter(valueParameter, ctx, configSetter, convertedValues); 269 return true; 270 } 271 272 Method valueFactory; 273 try { 274 valueFactory = type.getMethod("valueOf", STRING_PARAM); 275 if (!Modifier.isStatic(valueFactory.getModifiers())) { 276 valueFactory = null; 277 } 278 } catch (final NoSuchMethodException e) { 279 valueFactory = null; 280 } 281 282 if (valueFactory != null) { 283 // can convert using factory method (valueOf) 284 for (int iterValues = 0; iterValues < ctx.values.length; iterValues++) { 285 try { 286 Array.set(convertedValues, iterValues, 287 valueFactory.invoke(null, new Object[] { ctx.values[iterValues] })); 288 } catch (final IllegalAccessException e) { 289 throw new FileSystemException(e); 290 } catch (final InvocationTargetException e) { 291 throw new FileSystemException(e); 292 } 293 } 294 295 invokeSetter(valueParameter, ctx, configSetter, convertedValues); 296 return true; 297 } 298 299 return false; 300 } 301 302 /** 303 * invokes the method with the converted values 304 */ 305 private void invokeSetter(final Class<?> valueParameter, final Context ctx, final Method configSetter, 306 final Object values) throws FileSystemException { 307 Object[] args; 308 if (valueParameter.isArray()) { 309 args = new Object[] { ctx.fso, values }; 310 } else { 311 args = new Object[] { ctx.fso, Array.get(values, 0) }; 312 } 313 try { 314 configSetter.invoke(ctx.fileSystemConfigBuilder, args); 315 } catch (final IllegalAccessException e) { 316 throw new FileSystemException(e); 317 } catch (final InvocationTargetException e) { 318 throw new FileSystemException(e); 319 } 320 } 321 322 /** 323 * fills all available set*() methods for the context-scheme into the context. 324 */ 325 private boolean fillConfigSetters(final Context ctx) throws FileSystemException { 326 final Map<String, List<Method>> schemeMethods = getSchemeMethods(ctx.scheme); 327 final List<Method> configSetters = schemeMethods.get(ctx.name.toLowerCase()); 328 if (configSetters == null) { 329 return false; 330 } 331 332 ctx.configSetters = configSetters; 333 return true; 334 } 335 336 /** 337 * get (cached) list of set*() methods for the given scheme 338 */ 339 private Map<String, List<Method>> getSchemeMethods(final String scheme) throws FileSystemException { 340 Map<String, List<Method>> schemeMethods = beanMethods.get(scheme); 341 if (schemeMethods == null) { 342 schemeMethods = createSchemeMethods(scheme); 343 beanMethods.put(scheme, schemeMethods); 344 } 345 346 return schemeMethods; 347 } 348 349 /** 350 * create the list of all set*() methods for the given scheme 351 */ 352 private Map<String, List<Method>> createSchemeMethods(final String scheme) throws FileSystemException { 353 final FileSystemConfigBuilder fscb = getManager().getFileSystemConfigBuilder(scheme); 354 if (fscb == null) { 355 throw new FileSystemException("vfs.provider/no-config-builder.error", scheme); 356 } 357 358 final Map<String, List<Method>> schemeMethods = new TreeMap<>(); 359 360 final Method[] methods = fscb.getClass().getMethods(); 361 for (final Method method : methods) { 362 if (!Modifier.isPublic(method.getModifiers())) { 363 continue; 364 } 365 366 final String methodName = method.getName(); 367 if (!methodName.startsWith("set")) { 368 // not a setter 369 continue; 370 } 371 372 final String key = methodName.substring(3).toLowerCase(); 373 374 List<Method> configSetter = schemeMethods.get(key); 375 if (configSetter == null) { 376 configSetter = new ArrayList<>(2); 377 schemeMethods.put(key, configSetter); 378 } 379 configSetter.add(method); 380 } 381 382 return schemeMethods; 383 } 384}