View Javadoc

1   /*
2    * Copyright 2009 University Corporation for Advanced Internet Development, Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package edu.internet2.middleware.shibboleth.common.config.resource;
18  
19  import java.io.File;
20  import java.util.ArrayList;
21  
22  import javax.xml.namespace.QName;
23  
24  import org.opensaml.xml.util.DatatypeHelper;
25  import org.opensaml.xml.util.XMLHelper;
26  import org.slf4j.Logger;
27  import org.slf4j.LoggerFactory;
28  import org.springframework.beans.factory.BeanCreationException;
29  import org.springframework.beans.factory.support.AbstractBeanDefinition;
30  import org.springframework.beans.factory.support.BeanDefinitionBuilder;
31  import org.springframework.beans.factory.xml.ParserContext;
32  import org.tmatesoft.svn.core.SVNException;
33  import org.tmatesoft.svn.core.SVNURL;
34  import org.tmatesoft.svn.core.auth.SVNAuthentication;
35  import org.tmatesoft.svn.core.auth.SVNPasswordAuthentication;
36  import org.tmatesoft.svn.core.auth.SVNUserNameAuthentication;
37  import org.tmatesoft.svn.core.wc.SVNClientManager;
38  import org.w3c.dom.Element;
39  
40  import edu.internet2.middleware.shibboleth.common.config.SpringConfigurationUtils;
41  import edu.internet2.middleware.shibboleth.common.resource.SVNBasicAuthenticationManager;
42  import edu.internet2.middleware.shibboleth.common.resource.SVNResource;
43  
44  /** Bean definition parser for {@link SVNResource}s. */
45  public class SVNResourceBeanDefinitionParser extends AbstractResourceBeanDefinitionParser {
46  
47      /** Schema type. */
48      public static final QName SCHEMA_TYPE = new QName(ResourceNamespaceHandler.NAMESPACE, "SVNResource");
49  
50      /** Configuration element attribute {@value} which holds the URL to the remote repository. */
51      public static final String REPOSITORY_URL_ATTRIB_NAME = "repositoryURL";
52  
53      /** Configuration element attribute {@value} which holds the timeout used when connecting to the SVN server. */
54      public static final String CTX_TIMEOUT_ATTRIB_NAME = "connectionTimeout";
55  
56      /** Configuration element attribute {@value} which holds the timeout used when reading from the SVN server. */
57      public static final String READ_TIMEOUT_ATTRIB_NAME = "readTimeout";
58  
59      /** Configuration element attribute {@value} which holds the path to the working copy directory. */
60      public static final String WORKING_COPY_DIR_ATTRIB_NAME = "workingCopyDirectory";
61  
62      /** Configuration element attribute {@value} which holds the path to the working copy directory. */
63      public static final String REVISION_ATTRIB_NAME = "revision";
64  
65      /**
66       * Configuration element attribute {@value} which holds the path to the resource file represented by the SVN
67       * resource.
68       */
69      public static final String RESOURCE_FILE_ATTRIB_NAME = "resourceFile";
70  
71      /** Configuration element attribute {@value} which holds the SVN username. */
72      public static final String USERNAME_ATTRIB_NAME = "username";
73  
74      /** Configuration element attribute {@value} which holds the SVN password. */
75      public static final String PASSWORD_ATTRIB_NAME = "password";
76  
77      /**
78       * Configuration element attribute {@value} which holds the hostname of the proxy server used when connecting to the
79       * SVN server.
80       */
81      public static final String PROXY_HOST_ATTRIB_NAME = "proxyHost";
82  
83      /**
84       * Configuration element attribute {@value} which holds the port of the proxy server used when connecting to the SVN
85       * server.
86       */
87      public static final String PROXY_PORT_ATTRIB_NAME = "proxyPort";
88  
89      /**
90       * Configuration element attribute {@value} which holds the username used with the proxy server used when connecting
91       * to the SVN server.
92       */
93      public static final String PROXY_USERNAME_ATTRIB_NAME = "proxyUsername";
94  
95      /**
96       * Configuration element attribute {@value} which holds the password used with the proxy server used when connecting
97       * to the SVN server.
98       */
99      public static final String PROXY_PASSWORD_ATTRIB_NAME = "proxyPassword";
100 
101     /** Default value of {@value #CTX_TIMEOUT_ATTRIB_NAME}, {@value} milliseconds. */
102     public static final int DEFAULT_CTX_TIMEOUT = 3000;
103 
104     /** Default value of {@value #READ_TIMEOUT_ATTRIB_NAME}, {@value} milliseconds. */
105     public static final int DEFAULT_READ_TIMEOUT = 5000;
106 
107     /** Default value of {@value #PROXY_PORT_ATTRIB_NAME}, {@value} . */
108     public static final int DEFAULT_PROXY_PORT = 8080;
109 
110     /** Class logger. */
111     private final Logger log = LoggerFactory.getLogger(SVNResourceBeanDefinitionParser.class);
112 
113     /** {@inheritDoc} */
114     protected Class getBeanClass(Element arg0) {
115         return SVNResource.class;
116     }
117 
118     /** {@inheritDoc} */
119     protected String resolveId(Element configElement, AbstractBeanDefinition beanDefinition, ParserContext parserContext) {
120         return SVNResource.class.getName() + ":"
121                 + DatatypeHelper.safeTrimOrNullString(configElement.getAttributeNS(null, REPOSITORY_URL_ATTRIB_NAME));
122     }
123 
124     /** {@inheritDoc} */
125     protected void doParse(Element configElement, ParserContext parserContext, BeanDefinitionBuilder builder)
126             throws BeanCreationException {
127         super.doParse(configElement, parserContext, builder);
128 
129         builder.addConstructorArgValue(buildClientManager(configElement));
130 
131         builder.addConstructorArgValue(getRespositoryUrl(configElement));
132 
133         builder.addConstructorArgValue(getWorkingCopyDirectory(configElement));
134 
135         builder.addConstructorArgValue(getRevision(configElement));
136 
137         builder.addConstructorArgValue(getResourceFile(configElement));
138 
139         addResourceFilter(configElement, parserContext, builder);
140     }
141 
142     /**
143      * Builds the SVN client manager from the given configuration options.
144      * 
145      * @param configElement element bearing the configuration options
146      * 
147      * @return the SVN client manager
148      */
149     protected SVNClientManager buildClientManager(Element configElement) {
150         ArrayList<SVNAuthentication> authnMethods = new ArrayList<SVNAuthentication>();
151         String username = getUsername(configElement);
152         if (username != null) {
153             authnMethods.add(new SVNUserNameAuthentication(username, false));
154 
155             String password = getPassword(configElement);
156             if (password != null) {
157                 authnMethods.add(new SVNPasswordAuthentication(username, password, false));
158             }
159         }
160 
161         String proxyHost = getProxyHost(configElement);
162         int proxyPort = getProxyPort(configElement);
163         String proxyUser = getProxyUsername(configElement);
164         String proxyPassword = getPassword(configElement);
165 
166         SVNBasicAuthenticationManager authnManager;
167         if (proxyHost == null) {
168             authnManager = new SVNBasicAuthenticationManager(authnMethods);
169         } else {
170             authnManager = new SVNBasicAuthenticationManager(authnMethods, proxyHost, proxyPort, proxyUser,
171                     proxyPassword);
172         }
173         authnManager.setConnectionTimeout(getConnectionTimeout(configElement));
174         authnManager.setReadTimeout(getReadTimeout(configElement));
175 
176         SVNClientManager clientManager = SVNClientManager.newInstance();
177         clientManager.setAuthenticationManager(authnManager);
178         return clientManager;
179     }
180 
181     /**
182      * Gets the value of the {@value #REPOSITORY_URL_ATTRIB_NAME} attribute.
183      * 
184      * @param configElement resource configuration element
185      * 
186      * @return value of the attribute
187      * 
188      * @throws BeanCreationException thrown if the attribute is missing or contains an invalid SVN URL
189      */
190     protected SVNURL getRespositoryUrl(Element configElement) throws BeanCreationException {
191         if (!configElement.hasAttributeNS(null, REPOSITORY_URL_ATTRIB_NAME)) {
192             log.error("SVN resource definition missing required '" + REPOSITORY_URL_ATTRIB_NAME + "' attribute");
193             throw new BeanCreationException("SVN resource definition missing required '" + REPOSITORY_URL_ATTRIB_NAME
194                     + "' attribute");
195         }
196 
197         String repositoryUrl = DatatypeHelper.safeTrimOrNullString(configElement.getAttributeNS(null,
198                 REPOSITORY_URL_ATTRIB_NAME));
199         try {
200             return SVNURL.parseURIDecoded(repositoryUrl);
201         } catch (SVNException e) {
202             log.error("SVN remote repository URL " + repositoryUrl + " is not valid", e);
203             throw new BeanCreationException("SVN remote repository URL " + repositoryUrl + " is not valid", e);
204         }
205     }
206 
207     /**
208      * Gets the value of the {@value #CTX_TIMEOUT_ATTRIB_NAME} attribute.
209      * 
210      * @param configElement resource configuration element
211      * 
212      * @return value of the attribute, or {@value #DEFAULT_CTX_TIMEOUT} if the attribute is not defined
213      * 
214      * @throws BeanCreationException thrown if the attribute is present but contains an empty string
215      */
216     protected int getConnectionTimeout(Element configElement) throws BeanCreationException {
217         if (!configElement.hasAttributeNS(null, CTX_TIMEOUT_ATTRIB_NAME)) {
218             return DEFAULT_CTX_TIMEOUT;
219         }
220 
221         return (int) SpringConfigurationUtils.parseDurationToMillis(CTX_TIMEOUT_ATTRIB_NAME + " on SVN resource",
222                 configElement.getAttributeNS(null, CTX_TIMEOUT_ATTRIB_NAME), 0);
223     }
224 
225     /**
226      * Gets the value of the {@value #READ_TIMEOUT_ATTRIB_NAME} attribute.
227      * 
228      * @param configElement resource configuration element
229      * 
230      * @return value of the attribute, or {@value #DEFAULT_READ_TIMEOUT} if the attribute is not defined
231      * 
232      * @throws BeanCreationException thrown if the attribute is present but contains an empty string
233      */
234     protected int getReadTimeout(Element configElement) throws BeanCreationException {
235         if (!configElement.hasAttributeNS(null, READ_TIMEOUT_ATTRIB_NAME)) {
236             return DEFAULT_READ_TIMEOUT;
237         }
238 
239         return (int) SpringConfigurationUtils.parseDurationToMillis(READ_TIMEOUT_ATTRIB_NAME + " on SVN resource",
240                 configElement.getAttributeNS(null, CTX_TIMEOUT_ATTRIB_NAME), 0);
241     }
242 
243     /**
244      * Gets the value of the {@value #REPOSITORY_URL_ATTRIB_NAME} attribute.
245      * 
246      * @param configElement resource configuration element
247      * 
248      * @return value of the attribute
249      * 
250      * @throws BeanCreationException thrown if the attribute is missing or contains an invalid directory path
251      */
252     protected File getWorkingCopyDirectory(Element configElement) throws BeanCreationException {
253         if (!configElement.hasAttributeNS(null, WORKING_COPY_DIR_ATTRIB_NAME)) {
254             log.error("SVN resource definition missing required '" + WORKING_COPY_DIR_ATTRIB_NAME + "' attribute");
255             throw new BeanCreationException("SVN resource definition missing required '" + WORKING_COPY_DIR_ATTRIB_NAME
256                     + "' attribute");
257         }
258 
259         File directory = new File(DatatypeHelper.safeTrimOrNullString(configElement.getAttributeNS(null,
260                 WORKING_COPY_DIR_ATTRIB_NAME)));
261         if (directory == null) {
262             log.error("SVN working copy directory may not be null");
263             throw new BeanCreationException("SVN working copy directory may not be null");
264         }
265 
266         if (!directory.exists()) {
267             boolean created = directory.mkdirs();
268             if (!created) {
269                 log.error("SVN working copy direction " + directory.getAbsolutePath()
270                         + " does not exist and could not be created");
271                 throw new BeanCreationException("SVN working copy direction " + directory.getAbsolutePath()
272                         + " does not exist and could not be created");
273             }
274         }
275 
276         if (!directory.isDirectory()) {
277             log.error("SVN working copy location " + directory.getAbsolutePath() + " is not a directory");
278             throw new BeanCreationException("SVN working copy location " + directory.getAbsolutePath()
279                     + " is not a directory");
280         }
281 
282         if (!directory.canRead()) {
283             log.error("SVN working copy directory " + directory.getAbsolutePath() + " can not be read by this process");
284             throw new BeanCreationException("SVN working copy directory " + directory.getAbsolutePath()
285                     + " can not be read by this process");
286         }
287 
288         if (!directory.canWrite()) {
289             log.error("SVN working copy directory " + directory.getAbsolutePath()
290                     + " can not be written to by this process");
291             throw new BeanCreationException("SVN working copy directory " + directory.getAbsolutePath()
292                     + " can not be written to by this process");
293         }
294 
295         return directory;
296     }
297 
298     /**
299      * Gets the value of the {@value #REVISION_ATTRIB_NAME} attribute.
300      * 
301      * @param configElement resource configuration element
302      * 
303      * @return value of the attribute
304      * 
305      * @throws BeanCreationException thrown if the attribute is missing or contains an invalid number
306      */
307     protected long getRevision(Element configElement) throws BeanCreationException {
308         if (!configElement.hasAttributeNS(null, REVISION_ATTRIB_NAME)) {
309             return -1;
310         } else {
311             try {
312                 return Long.parseLong(DatatypeHelper.safeTrimOrNullString(configElement.getAttributeNS(null,
313                         WORKING_COPY_DIR_ATTRIB_NAME)));
314             } catch (NumberFormatException e) {
315                 log
316                         .error("SVN resource definition attribute '" + REVISION_ATTRIB_NAME
317                                 + "' contains an invalid number");
318                 throw new BeanCreationException("SVN resource definition attribute '" + REVISION_ATTRIB_NAME
319                         + "' contains an invalid number");
320             }
321         }
322     }
323 
324     /**
325      * Gets the value of the {@value #RESOURCE_FILE_ATTRIB_NAME} attribute.
326      * 
327      * @param configElement resource configuration element
328      * 
329      * @return value of the attribute
330      * 
331      * @throws BeanCreationException thrown if the attribute is missing or contains an empty string
332      */
333     protected String getResourceFile(Element configElement) throws BeanCreationException {
334         if (!configElement.hasAttributeNS(null, RESOURCE_FILE_ATTRIB_NAME)) {
335             log.error("SVN resource definition missing required '" + RESOURCE_FILE_ATTRIB_NAME + "' attribute");
336             throw new BeanCreationException("SVN resource definition missing required '" + RESOURCE_FILE_ATTRIB_NAME
337                     + "' attribute");
338         }
339 
340         String filename = DatatypeHelper.safeTrimOrNullString(configElement.getAttributeNS(null,
341                 RESOURCE_FILE_ATTRIB_NAME));
342         if (filename == null) {
343             log.error("SVN resource definition attribute '" + RESOURCE_FILE_ATTRIB_NAME
344                     + "' may not be an empty string");
345             throw new BeanCreationException("SVN resource definition attribute '" + RESOURCE_FILE_ATTRIB_NAME
346                     + "' may not be an empty string");
347         }
348 
349         return filename;
350     }
351 
352     /**
353      * Gets the value of the {@value #USERNAME_ATTRIB_NAME} attribute.
354      * 
355      * @param configElement resource configuration element
356      * 
357      * @return value of the attribute
358      * 
359      * @throws BeanCreationException thrown if the attribute is present but contains an empty string
360      */
361     protected String getUsername(Element configElement) throws BeanCreationException {
362         if (configElement.hasAttributeNS(null, USERNAME_ATTRIB_NAME)) {
363             String username = DatatypeHelper.safeTrimOrNullString(configElement.getAttributeNS(null,
364                     USERNAME_ATTRIB_NAME));
365             if (username == null) {
366                 log
367                         .error("SVN resource definition attribute '" + USERNAME_ATTRIB_NAME
368                                 + "' may not be an empty string");
369                 throw new BeanCreationException("SVN resource definition attribute '" + USERNAME_ATTRIB_NAME
370                         + "' may not be an empty string");
371             }
372             return username;
373         }
374 
375         return null;
376     }
377 
378     /**
379      * Gets the value of the {@value #PASSWORD_ATTRIB_NAME} attribute.
380      * 
381      * @param configElement resource configuration element
382      * 
383      * @return value of the attribute
384      * 
385      * @throws BeanCreationException thrown if the attribute is present but contains an empty string
386      */
387     protected String getPassword(Element configElement) throws BeanCreationException {
388         if (configElement.hasAttributeNS(null, PASSWORD_ATTRIB_NAME)) {
389             String password = DatatypeHelper.safeTrimOrNullString(configElement.getAttributeNS(null,
390                     PASSWORD_ATTRIB_NAME));
391             if (password == null) {
392                 log
393                         .error("SVN resource definition attribute '" + PASSWORD_ATTRIB_NAME
394                                 + "' may not be an empty string");
395                 throw new BeanCreationException("SVN resource definition attribute '" + PASSWORD_ATTRIB_NAME
396                         + "' may not be an empty string");
397             }
398             return password;
399         }
400         return null;
401     }
402 
403     /**
404      * Gets the value of the {@value #PROXY_HOST_ATTRIB_NAME} attribute.
405      * 
406      * @param configElement resource configuration element
407      * 
408      * @return value of the attribute
409      * 
410      * @throws BeanCreationException thrown if the attribute is present but contains an empty string
411      */
412     protected String getProxyHost(Element configElement) throws BeanCreationException {
413         if (configElement.hasAttributeNS(null, PROXY_HOST_ATTRIB_NAME)) {
414             String host = DatatypeHelper.safeTrimOrNullString(configElement
415                     .getAttributeNS(null, PROXY_HOST_ATTRIB_NAME));
416             if (host == null) {
417                 log.error("SVN resource definition attribute '" + PROXY_HOST_ATTRIB_NAME
418                         + "' may not be an empty string");
419                 throw new BeanCreationException("SVN resource definition attribute '" + PROXY_HOST_ATTRIB_NAME
420                         + "' may not be an empty string");
421             }
422             return host;
423         }
424 
425         return null;
426     }
427 
428     /**
429      * Gets the value of the {@value #PROXY_PORT_ATTRIB_NAME} attribute.
430      * 
431      * @param configElement resource configuration element
432      * 
433      * @return value of the attribute, or {@value #DEFAULT_PROXY_PORT} if the attribute is not defined
434      * 
435      * @throws BeanCreationException thrown if the attribute is present but contains an empty string
436      */
437     protected int getProxyPort(Element configElement) throws BeanCreationException {
438         if (!configElement.hasAttributeNS(null, PROXY_PORT_ATTRIB_NAME)) {
439             return DEFAULT_PROXY_PORT;
440         }
441 
442         String port = DatatypeHelper.safeTrimOrNullString(configElement.getAttributeNS(null, PROXY_PORT_ATTRIB_NAME));
443         if (port == null) {
444             log.error("SVN resource definition attribute '" + PROXY_PORT_ATTRIB_NAME + "' may not be an empty string");
445             throw new BeanCreationException("SVN resource definition attribute '" + PROXY_PORT_ATTRIB_NAME
446                     + "' may not be an empty string");
447         }
448 
449         try {
450             return Integer.parseInt(port);
451         } catch (NumberFormatException e) {
452             log.error("SVN resource definition attribute '" + PROXY_PORT_ATTRIB_NAME + "' contains an invalid number");
453             throw new BeanCreationException("SVN resource definition attribute '" + PROXY_PORT_ATTRIB_NAME
454                     + "' contains an invalid number");
455         }
456     }
457 
458     /**
459      * Gets the value of the {@value #PROXY_USERNAME_ATTRIB_NAME} attribute.
460      * 
461      * @param configElement resource configuration element
462      * 
463      * @return value of the attribute
464      * 
465      * @throws BeanCreationException thrown if the attribute is present but contains an empty string
466      */
467     protected String getProxyUsername(Element configElement) throws BeanCreationException {
468         if (configElement.hasAttributeNS(null, PROXY_USERNAME_ATTRIB_NAME)) {
469             String username = DatatypeHelper.safeTrimOrNullString(configElement.getAttributeNS(null,
470                     PROXY_USERNAME_ATTRIB_NAME));
471             if (username == null) {
472                 log.error("SVN resource definition attribute '" + PROXY_USERNAME_ATTRIB_NAME
473                         + "' may not be an empty string");
474                 throw new BeanCreationException("SVN resource definition attribute '" + PROXY_USERNAME_ATTRIB_NAME
475                         + "' may not be an empty string");
476             }
477             return username;
478         }
479         return null;
480     }
481 
482     /**
483      * Gets the value of the {@value #PROXY_PASSWORD_ATTRIB_NAME} attribute.
484      * 
485      * @param configElement resource configuration element
486      * 
487      * @return value of the attribute
488      * 
489      * @throws BeanCreationException thrown if the attribute is present but contains an empty string
490      */
491     protected String getProxyPassword(Element configElement) throws BeanCreationException {
492         if (configElement.hasAttributeNS(null, PROXY_PASSWORD_ATTRIB_NAME)) {
493             String password = DatatypeHelper.safeTrimOrNullString(configElement.getAttributeNS(null,
494                     PROXY_PASSWORD_ATTRIB_NAME));
495             if (password == null) {
496                 log.error("SVN resource definition attribute '" + PROXY_PASSWORD_ATTRIB_NAME
497                         + "' may not be an empty string");
498                 throw new BeanCreationException("SVN resource definition attribute '" + PROXY_PASSWORD_ATTRIB_NAME
499                         + "' may not be an empty string");
500             }
501             return password;
502         }
503         return null;
504     }
505 
506 }