View Javadoc

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