View Javadoc

1   /*
2    * Copyright 2008 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.resource;
18  
19  import java.io.File;
20  import java.io.FileInputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  
24  import org.joda.time.DateTime;
25  import org.opensaml.util.resource.AbstractFilteredResource;
26  import org.opensaml.util.resource.ResourceException;
27  import org.opensaml.xml.util.DatatypeHelper;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  import org.tmatesoft.svn.core.SVNDepth;
31  import org.tmatesoft.svn.core.SVNException;
32  import org.tmatesoft.svn.core.SVNURL;
33  import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
34  import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
35  import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
36  import org.tmatesoft.svn.core.wc.SVNClientManager;
37  import org.tmatesoft.svn.core.wc.SVNRevision;
38  import org.tmatesoft.svn.core.wc.SVNStatus;
39  import org.tmatesoft.svn.core.wc.SVNStatusClient;
40  import org.tmatesoft.svn.core.wc.SVNStatusType;
41  import org.tmatesoft.svn.core.wc.SVNUpdateClient;
42  
43  /**
44   * A resource representing a file fetch from a Subversion server.
45   * 
46   * This resource will fetch the given resource as follows:
47   * <ul>
48   * <li>If the revision is a positive number the resource will fetch the resource once during construction time and will
49   * never attempt to fetch it again.</li>
50   * <li>If the revision number is zero or less, signaling the HEAD revision, every call this resource will cause the
51   * resource to check to see if the current working copy is the same as the revision in the remote repository. If it is
52   * not the new revision will be retrieved.</li>
53   * </ul>
54   * 
55   * All operations operate on the local working copy.
56   * 
57   * @since 1.1
58   */
59  public class SVNResource extends AbstractFilteredResource {
60  
61      /** Class logger. */
62      private final Logger log = LoggerFactory.getLogger(SVNResource.class);
63  
64      /** SVN Client manager. */
65      private final SVNClientManager clientManager;
66  
67      /** URL to the remote repository. */
68      private SVNURL remoteRepository;
69  
70      /** Directory where the working copy will be kept. */
71      private File workingCopy;
72  
73      /** Revision of the working copy. */
74      private SVNRevision revision;
75  
76      /** File, within the working copy, represented by this resource. */
77      private String resourceFileName;
78  
79      /**
80       * Constructor.
81       * 
82       * @param svnClientMgr manager used to create SVN clients
83       * @param repositoryUrl URL of the remote repository
84       * @param workingCopyDirectory directory that will serve as the root of the local working copy
85       * @param revision revision of the resource to retrieve or -1 for HEAD revision
86       * @param resourceFile file, within the working copy, represented by this resource
87       */
88      public SVNResource(SVNClientManager svnClientMgr, SVNURL repositoryUrl, File workingCopyDirectory, long revision,
89              String resourceFile) throws ResourceException {
90          DAVRepositoryFactory.setup();
91          SVNRepositoryFactoryImpl.setup();
92          FSRepositoryFactory.setup();
93          if (svnClientMgr == null) {
94              log.error("SVN client manager may not be null");
95              throw new IllegalArgumentException("SVN client manager may not be null");
96          }
97          clientManager = svnClientMgr;
98  
99          if(repositoryUrl == null){
100             throw new IllegalArgumentException("SVN repository URL may not be null");
101         }
102         remoteRepository = repositoryUrl;
103         
104         try {
105             checkWorkingCopyDirectory(workingCopyDirectory);
106             workingCopy = workingCopyDirectory;
107         } catch (ResourceException e) {
108             throw new IllegalArgumentException(e.getMessage());
109         }
110 
111         if (revision < 0) {
112             this.revision = SVNRevision.HEAD;
113         } else {
114             this.revision = SVNRevision.create(revision);
115         }
116 
117         resourceFileName = DatatypeHelper.safeTrimOrNullString(resourceFile);
118         if (resourceFileName == null) {
119             log.error("SVN working copy resource file name may not be null or empty");
120             throw new IllegalArgumentException("SVN working copy resource file name may not be null or empty");
121         }
122 
123         checkoutOrUpdateResource();
124         if (!getResourceFile().exists()) {
125             log.error("Resource file " + resourceFile + " does not exist in SVN working copy directory "
126                     + workingCopyDirectory.getAbsolutePath());
127             throw new ResourceException("Resource file " + resourceFile
128                     + " does not exist in SVN working copy directory " + workingCopyDirectory.getAbsolutePath());
129         }
130     }
131 
132     /** {@inheritDoc} */
133     public boolean exists() throws ResourceException {
134         return getResourceFile().exists();
135     }
136 
137     /** {@inheritDoc} */
138     public InputStream getInputStream() throws ResourceException {
139         try {
140             return applyFilter(new FileInputStream(getResourceFile()));
141         } catch (IOException e) {
142             String erroMsg = "Unable to read resource file " + resourceFileName + " from local working copy "
143                     + workingCopy.getAbsolutePath();
144             log.error(erroMsg, e);
145             throw new ResourceException(erroMsg, e);
146         }
147     }
148 
149     /** {@inheritDoc} */
150     public DateTime getLastModifiedTime() throws ResourceException {
151         SVNStatusClient client = clientManager.getStatusClient();
152         client.setIgnoreExternals(false);
153 
154         try {
155             SVNStatus status = client.doStatus(getResourceFile(), false);
156             if (status.getContentsStatus() == SVNStatusType.STATUS_NORMAL) {
157                 return new DateTime(status.getWorkingContentsDate());
158             } else {
159                 String errMsg = "Unable to determine last modified time of resource " + resourceFileName
160                         + " within working directory " + workingCopy.getAbsolutePath();
161                 log.error(errMsg);
162                 throw new ResourceException(errMsg);
163             }
164         } catch (SVNException e) {
165             String errMsg = "Unable to check status of resource " + resourceFileName + " within working directory "
166                     + workingCopy.getAbsolutePath();
167             log.error(errMsg, e);
168             throw new ResourceException(errMsg, e);
169         }
170     }
171 
172     /** {@inheritDoc} */
173     public String getLocation() {
174         return remoteRepository.toDecodedString() + "/" + resourceFileName;
175     }
176 
177     /**
178      * Checks that the given file exists, or can be created, is a directory, and is read/writable by this process.
179      * 
180      * @param directory the directory to check
181      * 
182      * @throws ResourceException thrown if the file is invalid
183      */
184     protected void checkWorkingCopyDirectory(File directory) throws ResourceException {
185         if (directory == null) {
186             log.error("SVN working copy directory may not be null");
187             throw new ResourceException("SVN working copy directory may not be null");
188         }
189 
190         if (!directory.exists()) {
191             boolean created = directory.mkdirs();
192             if (!created) {
193                 log.error("SVN working copy direction " + directory.getAbsolutePath()
194                         + " does not exist and could not be created");
195                 throw new ResourceException("SVN working copy direction " + directory.getAbsolutePath()
196                         + " does not exist and could not be created");
197             }
198         }
199 
200         if (!directory.isDirectory()) {
201             log.error("SVN working copy location " + directory.getAbsolutePath() + " is not a directory");
202             throw new ResourceException("SVN working copy location " + directory.getAbsolutePath()
203                     + " is not a directory");
204         }
205 
206         if (!directory.canRead()) {
207             log.error("SVN working copy directory " + directory.getAbsolutePath() + " can not be read by this process");
208             throw new ResourceException("SVN working copy directory " + directory.getAbsolutePath()
209                     + " can not be read by this process");
210         }
211 
212         if (!directory.canWrite()) {
213             log.error("SVN working copy directory " + directory.getAbsolutePath()
214                     + " can not be written to by this process");
215             throw new ResourceException("SVN working copy directory " + directory.getAbsolutePath()
216                     + " can not be written to by this process");
217         }
218     }
219 
220     /**
221      * Checks out the resource specified by the {@link #remoteRepository} in to the working copy {@link #workingCopy}.
222      * If the working copy is empty than an SVN checkout is performed if the working copy already exists then an SVN
223      * update is performed.
224      * 
225      * @throws ResourceException thrown if there is a problem communicating with the remote repository, the revision
226      *             does not exist, or the working copy is unusable
227      */
228     protected void checkoutOrUpdateResource() throws ResourceException {
229         SVNUpdateClient client = clientManager.getUpdateClient();
230         client.setIgnoreExternals(false);
231 
232         File svnMetadataDir = new File(workingCopy, ".svn");
233         if (!svnMetadataDir.exists()) {
234             try {
235                 client.doCheckout(remoteRepository, workingCopy, revision, revision, SVNDepth.INFINITY, true);
236             } catch (SVNException e) {
237                 String errMsg = "Unable to check out revsion " + revision.toString() + " from remote repository "
238                         + remoteRepository.toDecodedString() + " to local working directory "
239                         + workingCopy.getAbsolutePath();
240                 log.error(errMsg, e);
241                 throw new ResourceException(errMsg, e);
242             }
243         } else {
244             try {
245                 client.doUpdate(workingCopy, revision, SVNDepth.INFINITY, true, true);
246             } catch (SVNException e) {
247                 String errMsg = "Unable to update working copy of resoure " + remoteRepository.toDecodedString()
248                         + " in working copy " + workingCopy.getAbsolutePath() + " to revsion " + revision.toString();
249                 log.error(errMsg, e);
250                 throw new ResourceException(errMsg, e);
251             }
252         }
253     }
254 
255     /**
256      * Gets {@link File} for the resource.
257      * 
258      * @return file for the resource
259      */
260     protected File getResourceFile() {
261         return new File(workingCopy, resourceFileName);
262     }
263 }