Coverage Report - org.kuali.maven.mojo.s3.UpdateOriginBucketMojo
 
Classes in this File Line Coverage Branch Coverage Complexity
UpdateOriginBucketMojo
0%
0/185
0%
0/44
1.829
 
 1  
 package org.kuali.maven.mojo.s3;
 2  
 
 3  
 import java.io.ByteArrayInputStream;
 4  
 import java.io.IOException;
 5  
 import java.io.InputStream;
 6  
 import java.text.SimpleDateFormat;
 7  
 import java.util.Collection;
 8  
 import java.util.Date;
 9  
 import java.util.List;
 10  
 import java.util.Map;
 11  
 import java.util.TimeZone;
 12  
 
 13  
 import org.apache.commons.beanutils.BeanUtils;
 14  
 import org.apache.commons.io.IOUtils;
 15  
 import org.apache.commons.lang.StringUtils;
 16  
 import org.apache.maven.plugin.MojoExecutionException;
 17  
 import org.apache.maven.plugin.MojoFailureException;
 18  
 import org.apache.maven.plugin.descriptor.PluginDescriptor;
 19  
 import org.kuali.maven.common.UrlBuilder;
 20  
 
 21  
 import com.amazonaws.auth.AWSCredentials;
 22  
 import com.amazonaws.services.s3.AmazonS3Client;
 23  
 import com.amazonaws.services.s3.model.CannedAccessControlList;
 24  
 import com.amazonaws.services.s3.model.CopyObjectRequest;
 25  
 import com.amazonaws.services.s3.model.ListObjectsRequest;
 26  
 import com.amazonaws.services.s3.model.ObjectListing;
 27  
 import com.amazonaws.services.s3.model.ObjectMetadata;
 28  
 import com.amazonaws.services.s3.model.PutObjectRequest;
 29  
 import com.amazonaws.services.s3.model.S3Object;
 30  
 import com.amazonaws.services.s3.model.S3ObjectSummary;
 31  
 
 32  
 /**
 33  
  * <p>
 34  
  * This mojo updates a bucket serving as an origin for a Cloud Front distribution. It generates an html directory
 35  
  * listing for each "directory" in the bucket and stores the html under a key in the bucket such that a regular http
 36  
  * request for a directory returns the html instead of the XML for "object does not exist" Amazon would normally return.
 37  
  * For example: The url "http://www.mybucket.com/foo/bar" returns an html page containing a listing of all the files and
 38  
  * directories under "foo/bar" in the bucket.
 39  
  * </p>
 40  
  * <p>
 41  
  * If a directory contains an object with a key that is the same as the default object, the plugin copies the object to
 42  
  * a key representing the directory structure. For example, the url "http://www.mybucket.com/foo/bar/index.html"
 43  
  * represents an object in an S3 bucket under the key "foo/bar/index.html". This plugin will copy the object from the
 44  
  * key "foo/bar/index.html" to the key "foo/bar/". This causes the url "http://www.mybucket.com/foo/bar/" to return the
 45  
  * same content as the url "http://www.mybucket.com/foo/bar/index.html"
 46  
  * </p>
 47  
  * <p>
 48  
  * It also generates an html directory listing at the root of the bucket hierarchy and places that html into the bucket
 49  
  * as the default object, unless a default object already exists.
 50  
  * </p>
 51  
  *
 52  
  * @goal updateoriginbucket
 53  
  * @aggregator
 54  
  */
 55  0
 public class UpdateOriginBucketMojo extends S3Mojo {
 56  
 
 57  
     private static final String S3_INDEX_METADATA_KEY = "maven-cloudfront-plugin-index";
 58  
     private static final String S3_INDEX_CONTENT_TYPE = "text/html";
 59  
     CloudFrontHtmlGenerator generator;
 60  
     S3DataConverter converter;
 61  
 
 62  
     /**
 63  
      * The groupId for the organization
 64  
      *
 65  
      * @parameter expression="${organizationGroupId}" default-value="org.kuali"
 66  
      */
 67  
     private String organizationGroupId;
 68  
 
 69  
     /**
 70  
      * This controls the caching behavior for CloudFront. By default, CloudFront edge locations cache content from an S3
 71  
      * bucket for 24 hours. That interval is shortened to 1 hour for the html indexes generated by this plugin.
 72  
      *
 73  
      * @parameter expression="${cacheControl}" default-value="max-age=3600, must-revalidate"
 74  
      */
 75  
     private String cacheControl;
 76  
 
 77  
     /**
 78  
      * If true, the complete hierarchy underneath <code>prefix</code> will be recursively updated. If false, only the
 79  
      * directory corresponding to the prefix will be updated along with the path back to the root of the bucket
 80  
      *
 81  
      * @parameter expression="${recurse}" default-value="true"
 82  
      */
 83  
     private boolean recurse;
 84  
 
 85  
     /**
 86  
      * If true, "foo/bar/index.html" will get copied to "foo/bar/"
 87  
      *
 88  
      * @parameter expression="${copyDefaultObjectWithDelimiter}" default-value="true"
 89  
      */
 90  
     private boolean copyDefaultObjectWithDelimiter;
 91  
 
 92  
     /**
 93  
      * If true, "foo/bar/index.html" will get copied to "foo/bar". This is defaulted to false because the relative
 94  
      * pathing in the html generated by the maven-site-plugin does not render correctly from a url without the trailing
 95  
      * slash.
 96  
      *
 97  
      * @parameter expression="${copyDefaultObjectWithoutDelimiter}" default-value="false"
 98  
      */
 99  
     private boolean copyDefaultObjectWithoutDelimiter;
 100  
 
 101  
     /**
 102  
      * The stylesheet to use for the directory listing
 103  
      *
 104  
      * @parameter expression="${css}" default-value="http://s3browse.ks.kuali.org/css/style.css"
 105  
      */
 106  
     private String css;
 107  
 
 108  
     /**
 109  
      * Image representing a file
 110  
      *
 111  
      * @parameter expression="${fileImage}" default-value="http://s3browse.ks.kuali.org/images/page_white.png"
 112  
      */
 113  
     private String fileImage;
 114  
 
 115  
     /**
 116  
      * Image representing a directory
 117  
      *
 118  
      * @parameter expression="${directoryImage}" default-value="http://s3browse.ks.kuali.org/images/folder.png"
 119  
      */
 120  
     private String directoryImage;
 121  
 
 122  
     /**
 123  
      * When displaying the last modified timestamp, use this timezone
 124  
      *
 125  
      * @parameter expression="${timezone}" default-value="GMT"
 126  
      */
 127  
     private String timezone;
 128  
 
 129  
     /**
 130  
      * When displaying the last modified timestamp use this format
 131  
      *
 132  
      * @parameter expression="${dateFormat}" default-value="EEE, dd MMM yyyy HH:mm:ss z"
 133  
      */
 134  
     private String dateFormat;
 135  
 
 136  
     /**
 137  
      * The key containing the default object for the Cloud Front distribution. If this object already exists, the plugin
 138  
      * will not modify it. If it does not exist, this plugin will generate an html directory listing and place it into
 139  
      * the bucket under this key.
 140  
      *
 141  
      * @parameter expression="${defaultObject}" default-value="index.html";
 142  
      */
 143  
     private String defaultObject;
 144  
 
 145  
     /**
 146  
      * The html for browsing a directory will be created under this key
 147  
      *
 148  
      * @parameter expression="${browseHtml}" default-value="browse.html";
 149  
      */
 150  
     private String browseHtml;
 151  
 
 152  
     @Override
 153  
     public void executeMojo() throws MojoExecutionException, MojoFailureException {
 154  
         try {
 155  0
             getLog().info("Updating S3 bucket - " + getBucket());
 156  0
             S3BucketContext context = getS3BucketContext();
 157  0
             generator = new CloudFrontHtmlGenerator(context);
 158  0
             converter = new S3DataConverter(context);
 159  0
             converter.setBrowseHtml(getBrowseHtml());
 160  0
             if (isRecurse()) {
 161  0
                 getLog().info("Recursing into " + getPrefix());
 162  0
                 recurse(context, getPrefix());
 163  
             }
 164  0
             getLog().info("Updating hierarchy underneath " + getPrefix());
 165  0
             goUpTheChain(context, getPrefix());
 166  0
         } catch (Exception e) {
 167  0
             throw new MojoExecutionException("Unexpected error: ", e);
 168  0
         }
 169  0
     }
 170  
 
 171  
     protected void goUpTheChain(final S3BucketContext context, final String startingPrefix) throws IOException {
 172  0
         handleRoot(getS3PrefixContext(context, null));
 173  
 
 174  0
         if (StringUtils.isEmpty(startingPrefix)) {
 175  0
             return;
 176  
         }
 177  
 
 178  0
         String[] prefixes = StringUtils.splitByWholeSeparator(startingPrefix, context.getDelimiter());
 179  0
         if (prefixes.length == 1) {
 180  0
             return;
 181  
         }
 182  0
         String newPrefix = "";
 183  0
         for (int i = 0; i < prefixes.length - 2; i++) {
 184  0
             newPrefix += prefixes[i] + context.getDelimiter();
 185  0
             updateDirectory(getS3PrefixContext(context, newPrefix));
 186  
         }
 187  0
     }
 188  
 
 189  
     protected void updatePrefix() {
 190  0
         UrlBuilder builder = new UrlBuilder();
 191  0
         String sitePath = builder.getSitePath(getProject(), getOrganizationGroupId());
 192  0
         String s = getPrefix();
 193  0
         if (StringUtils.isEmpty(s)) {
 194  0
             s = sitePath;
 195  
         }
 196  0
         if (s == null) {
 197  0
             return;
 198  
         }
 199  0
         if (!s.endsWith(getDelimiter())) {
 200  0
             setPrefix(s + getDelimiter());
 201  
         }
 202  0
     }
 203  
 
 204  
     protected S3BucketContext getS3BucketContext() throws MojoExecutionException {
 205  0
         updateCredentials();
 206  0
         validateCredentials();
 207  0
         AWSCredentials credentials = getCredentials();
 208  0
         AmazonS3Client client = new AmazonS3Client(credentials);
 209  0
         updatePrefix();
 210  0
         S3BucketContext context = new S3BucketContext();
 211  
         try {
 212  0
             BeanUtils.copyProperties(context, this);
 213  0
         } catch (Exception e) {
 214  0
             throw new MojoExecutionException("Error copying properties", e);
 215  0
         }
 216  0
         context.setClient(client);
 217  0
         context.setLastModifiedDateFormatter(getLastModifiedDateFormatter());
 218  0
         context.setAbout(getAbout());
 219  0
         return context;
 220  
     }
 221  
 
 222  
     /**
 223  
      * Create a PutObjectRequest for some html generated by this mojo. The PutObjectRequest sets the content type to
 224  
      * S3_INDEX_CONTENT_TYPE, sets the ACL to PublicRead, and adds some custom metadata so we can positively identify it
 225  
      * as an object created by this plugin
 226  
      */
 227  
     protected PutObjectRequest getPutIndexObjectRequest(final String html, final String key) throws IOException {
 228  0
         InputStream in = new ByteArrayInputStream(html.getBytes());
 229  0
         ObjectMetadata om = new ObjectMetadata();
 230  0
         om.setCacheControl(getCacheControl());
 231  0
         String contentType = S3_INDEX_CONTENT_TYPE;
 232  0
         om.setContentType(contentType);
 233  0
         om.setContentLength(html.length());
 234  0
         om.addUserMetadata(S3_INDEX_METADATA_KEY, "true");
 235  0
         PutObjectRequest request = new PutObjectRequest(getBucket(), key, in, om);
 236  0
         request.setCannedAcl(CannedAccessControlList.PublicRead);
 237  0
         return request;
 238  
     }
 239  
 
 240  
     /**
 241  
      * Return a SimpleDateFormat object initialized with the date format and timezone supplied to the mojo
 242  
      */
 243  
     protected SimpleDateFormat getLastModifiedDateFormatter() {
 244  0
         SimpleDateFormat sdf = new SimpleDateFormat(getDateFormat());
 245  0
         sdf.setTimeZone(TimeZone.getTimeZone(getTimezone()));
 246  0
         return sdf;
 247  
     }
 248  
 
 249  
     /**
 250  
      * Return true if the Collection is null or contains no entries, false otherwise
 251  
      */
 252  
     protected boolean isEmpty(final Collection<?> c) {
 253  0
         return c == null || c.size() == 0;
 254  
     }
 255  
 
 256  
     /**
 257  
      * Show some text about this plugin
 258  
      */
 259  
     protected String getAbout() {
 260  0
         String date = getLastModifiedDateFormatter().format(new Date());
 261  0
         PluginDescriptor descriptor = (PluginDescriptor) this.getPluginContext().get("pluginDescriptor");
 262  0
         if (descriptor == null) {
 263  
             // Maven 2.2.1 is returning a null descriptor
 264  0
             return "Listing generated by the maven-cloudfront-plugin on " + date;
 265  
         } else {
 266  0
             String name = descriptor.getArtifactId();
 267  0
             String version = descriptor.getVersion();
 268  0
             return "Listing generated by the " + name + " v" + version + " on " + date;
 269  
         }
 270  
     }
 271  
 
 272  
     /**
 273  
      * Create an object in the bucket under a key that lets a normal http request function correctly with CloudFront /
 274  
      * S3.<br>
 275  
      * Either use the client's object or upload some html created by this plugin<br>
 276  
      */
 277  
     protected void updateDirectory(final S3PrefixContext context, final boolean isCopyIfExists, final String copyToKey)
 278  
             throws IOException {
 279  0
         S3BucketContext bucketContext = context.getBucketContext();
 280  0
         AmazonS3Client client = context.getBucketContext().getClient();
 281  0
         String bucket = bucketContext.getBucket();
 282  
 
 283  0
         boolean containsDefaultObject = isExistingObject(context.getObjectListing(), context.getDefaultObjectKey());
 284  0
         if (containsDefaultObject && isCopyIfExists) {
 285  
             // Copy the contents of the clients default object
 286  0
             String sourceKey = context.getDefaultObjectKey();
 287  0
             String destKey = copyToKey;
 288  0
             CopyObjectRequest request = getCopyObjectRequest(bucket, sourceKey, destKey);
 289  0
             getLog().info("Copy: " + sourceKey + " to " + destKey);
 290  0
             client.copyObject(request);
 291  0
         } else {
 292  
             // Upload our custom content
 293  0
             PutObjectRequest request = getPutIndexObjectRequest(context.getHtml(), copyToKey);
 294  0
             getLog().info("Put: " + copyToKey);
 295  0
             client.putObject(request);
 296  
         }
 297  0
     }
 298  
 
 299  
     /**
 300  
      * Update this S3 "directory".
 301  
      */
 302  
     protected void updateDirectory(final S3PrefixContext context) throws IOException {
 303  0
         String trimmedPrefix = converter.getTrimmedPrefix(context.getPrefix(), context.getBucketContext()
 304  
                 .getDelimiter());
 305  
 
 306  
         // Handle "http://www.mybucket.com/foo/bar/"
 307  0
         updateDirectory(context, isCopyDefaultObjectWithDelimiter(), context.getPrefix());
 308  
 
 309  
         // Handle "http://www.mybucket.com/foo/bar"
 310  0
         updateDirectory(context, isCopyDefaultObjectWithoutDelimiter(), trimmedPrefix);
 311  
 
 312  
         // Handle "http://www.mybucket.com/foo/bar/browse.html"
 313  
         // context.getBucketContext().getClient().putObject(getPutIndexObjectRequest(context.getHtml(),
 314  
         // context.getBrowseHtmlKey()));
 315  0
     }
 316  
 
 317  
     /**
 318  
      * If this is the root of the bucket and the default object either does not exist or was created by this plugin,
 319  
      * overwrite the default object with newly generated html. Otherwise, do nothing.
 320  
      */
 321  
     protected void handleRoot(final S3PrefixContext context) throws IOException {
 322  0
         if (!context.isRoot()) {
 323  0
             return;
 324  
         }
 325  
 
 326  0
         AmazonS3Client client = context.getBucketContext().getClient();
 327  
 
 328  
         // Handle "http://www.mybucket.com/browse.html"
 329  0
         PutObjectRequest request1 = getPutIndexObjectRequest(context.getHtml(), context.getBrowseHtmlKey());
 330  0
         getLog().info("Put: " + context.getBrowseHtmlKey());
 331  0
         client.putObject(request1);
 332  
 
 333  0
         boolean isCreateOrUpdateDefaultObject = isCreateOrUpdateDefaultObject(context);
 334  0
         if (!isCreateOrUpdateDefaultObject) {
 335  0
             return;
 336  
         }
 337  
 
 338  
         // Update the default object
 339  0
         PutObjectRequest request2 = getPutIndexObjectRequest(context.getHtml(), context.getDefaultObjectKey());
 340  0
         getLog().info("Put: " + context.getDefaultObjectKey());
 341  0
         client.putObject(request2);
 342  0
     }
 343  
 
 344  
     protected S3PrefixContext getS3PrefixContext(final S3BucketContext context, final String prefix) {
 345  0
         ListObjectsRequest request = new ListObjectsRequest(context.getBucket(), prefix, null, context.getDelimiter(),
 346  
                 1000);
 347  0
         ObjectListing objectListing = context.getClient().listObjects(request);
 348  0
         List<String[]> data = converter.getData(objectListing, prefix, context.getDelimiter());
 349  0
         String html = generator.getHtml(data, prefix, context.getDelimiter());
 350  0
         String defaultObjectKey = StringUtils.isEmpty(prefix) ? getDefaultObject() : prefix + getDefaultObject();
 351  0
         String browseHtmlKey = StringUtils.isEmpty(prefix) ? getBrowseHtml() : prefix + getBrowseHtml();
 352  
         // Is this the root of the bucket?
 353  0
         boolean isRoot = StringUtils.isEmpty(prefix);
 354  
 
 355  0
         S3PrefixContext prefixContext = new S3PrefixContext();
 356  0
         prefixContext.setObjectListing(objectListing);
 357  0
         prefixContext.setHtml(html);
 358  0
         prefixContext.setRoot(isRoot);
 359  0
         prefixContext.setDefaultObjectKey(defaultObjectKey);
 360  0
         prefixContext.setPrefix(prefix);
 361  0
         prefixContext.setBucketContext(context);
 362  0
         prefixContext.setBrowseHtmlKey(browseHtmlKey);
 363  0
         return prefixContext;
 364  
     }
 365  
 
 366  
     /**
 367  
      * Recurse the hierarchy of a bucket starting at "prefix" and create entries in the bucket corresponding to the
 368  
      * directory structure of the hierarchy
 369  
      */
 370  
     protected void recurse(final S3BucketContext context, final String prefix) throws IOException {
 371  0
         S3PrefixContext prefixContext = getS3PrefixContext(context, prefix);
 372  
 
 373  0
         handleRoot(prefixContext);
 374  
 
 375  
         // If this is not the root, there is more to do
 376  0
         if (!prefixContext.isRoot()) {
 377  0
             updateDirectory(prefixContext);
 378  
         }
 379  
 
 380  
         // Recurse down the hierarchy
 381  0
         List<String> commonPrefixes = prefixContext.getObjectListing().getCommonPrefixes();
 382  0
         for (String commonPrefix : commonPrefixes) {
 383  0
             recurse(context, commonPrefix);
 384  
         }
 385  0
     }
 386  
 
 387  
     /**
 388  
      * Return true if the ObjectListing contains an object under "key"
 389  
      */
 390  
     protected boolean isExistingObject(final ObjectListing objectListing, final String key) {
 391  0
         List<S3ObjectSummary> summaries = objectListing.getObjectSummaries();
 392  0
         for (S3ObjectSummary summary : summaries) {
 393  0
             if (key.equals(summary.getKey())) {
 394  0
                 return true;
 395  
             }
 396  
         }
 397  0
         return false;
 398  
     }
 399  
 
 400  
     /**
 401  
      * Return true if there is no object in the ObjectListing under defaultObjectKey.<br>
 402  
      * Return true if the object in the ObjectListing was created by this plugin.<br>
 403  
      * Return false otherwise.<br>
 404  
      */
 405  
     protected boolean isCreateOrUpdateDefaultObject(final S3PrefixContext context) {
 406  0
         if (!isExistingObject(context.getObjectListing(), context.getDefaultObjectKey())) {
 407  
             // There is no default object, we are free to create one
 408  0
             return true;
 409  
         }
 410  0
         S3BucketContext s3Context = context.getBucketContext();
 411  
         // There is a default object, but if it was created by this plugin, we
 412  
         // still need to update it
 413  0
         S3Object s3Object = s3Context.getClient().getObject(s3Context.getBucket(), context.getDefaultObjectKey());
 414  0
         boolean isOurDefaultObject = isOurObject(s3Object);
 415  0
         IOUtils.closeQuietly(s3Object.getObjectContent());
 416  0
         if (isOurDefaultObject) {
 417  0
             return true;
 418  
         } else {
 419  0
             return false;
 420  
         }
 421  
     }
 422  
 
 423  
     /**
 424  
      * Return true if this S3Object was created by this plugin. This is is done by checking the metadata attached to
 425  
      * this object for the presence of a custom value.
 426  
      */
 427  
     protected boolean isOurObject(final S3Object s3Object) {
 428  0
         ObjectMetadata metadata = s3Object.getObjectMetadata();
 429  0
         Map<String, String> userMetadata = metadata.getUserMetadata();
 430  0
         String value = userMetadata.get(S3_INDEX_METADATA_KEY);
 431  0
         boolean isOurObject = "true".equals(value);
 432  0
         return isOurObject;
 433  
     }
 434  
 
 435  
     /**
 436  
      * Create a CopyObjectRequest with an ACL set to PublicRead
 437  
      */
 438  
     protected CopyObjectRequest getCopyObjectRequest(final String bucket, final String sourceKey, final String destKey) {
 439  0
         CopyObjectRequest request = new CopyObjectRequest(bucket, sourceKey, bucket, destKey);
 440  0
         request.setCannedAccessControlList(CannedAccessControlList.PublicRead);
 441  0
         return request;
 442  
     }
 443  
 
 444  
     public String getTimezone() {
 445  0
         return timezone;
 446  
     }
 447  
 
 448  
     public void setTimezone(final String timezone) {
 449  0
         this.timezone = timezone;
 450  0
     }
 451  
 
 452  
     public String getDateFormat() {
 453  0
         return dateFormat;
 454  
     }
 455  
 
 456  
     public void setDateFormat(final String dateFormat) {
 457  0
         this.dateFormat = dateFormat;
 458  0
     }
 459  
 
 460  
     public String getDefaultObject() {
 461  0
         return defaultObject;
 462  
     }
 463  
 
 464  
     public void setDefaultObject(final String defaultCloudFrontObject) {
 465  0
         this.defaultObject = defaultCloudFrontObject;
 466  0
     }
 467  
 
 468  
     public String getFileImage() {
 469  0
         return fileImage;
 470  
     }
 471  
 
 472  
     public void setFileImage(final String fileImage) {
 473  0
         this.fileImage = fileImage;
 474  0
     }
 475  
 
 476  
     public String getDirectoryImage() {
 477  0
         return directoryImage;
 478  
     }
 479  
 
 480  
     public void setDirectoryImage(final String directoryImage) {
 481  0
         this.directoryImage = directoryImage;
 482  0
     }
 483  
 
 484  
     public String getCss() {
 485  0
         return css;
 486  
     }
 487  
 
 488  
     public void setCss(final String css) {
 489  0
         this.css = css;
 490  0
     }
 491  
 
 492  
     public boolean isCopyDefaultObjectWithDelimiter() {
 493  0
         return copyDefaultObjectWithDelimiter;
 494  
     }
 495  
 
 496  
     public void setCopyDefaultObjectWithDelimiter(final boolean copyDefaultObjectWithDelimiter) {
 497  0
         this.copyDefaultObjectWithDelimiter = copyDefaultObjectWithDelimiter;
 498  0
     }
 499  
 
 500  
     public boolean isCopyDefaultObjectWithoutDelimiter() {
 501  0
         return copyDefaultObjectWithoutDelimiter;
 502  
     }
 503  
 
 504  
     public void setCopyDefaultObjectWithoutDelimiter(final boolean copyDefaultObjectWithoutDelimiter) {
 505  0
         this.copyDefaultObjectWithoutDelimiter = copyDefaultObjectWithoutDelimiter;
 506  0
     }
 507  
 
 508  
     public String getCacheControl() {
 509  0
         return cacheControl;
 510  
     }
 511  
 
 512  
     public void setCacheControl(final String cacheControl) {
 513  0
         this.cacheControl = cacheControl;
 514  0
     }
 515  
 
 516  
     public String getBrowseHtml() {
 517  0
         return browseHtml;
 518  
     }
 519  
 
 520  
     public void setBrowseHtml(final String browseHtml) {
 521  0
         this.browseHtml = browseHtml;
 522  0
     }
 523  
 
 524  
     /**
 525  
      * @return the recurse
 526  
      */
 527  
     public boolean isRecurse() {
 528  0
         return recurse;
 529  
     }
 530  
 
 531  
     /**
 532  
      * @param recurse
 533  
      * the recurse to set
 534  
      */
 535  
     public void setRecurse(final boolean recurse) {
 536  0
         this.recurse = recurse;
 537  0
     }
 538  
 
 539  
     /**
 540  
      * @return the organizationGroupId
 541  
      */
 542  
     public String getOrganizationGroupId() {
 543  0
         return organizationGroupId;
 544  
     }
 545  
 
 546  
     /**
 547  
      * @param organizationGroupId
 548  
      * the organizationGroupId to set
 549  
      */
 550  
     public void setOrganizationGroupId(final String organizationGroupId) {
 551  0
         this.organizationGroupId = organizationGroupId;
 552  0
     }
 553  
 
 554  
 }