Coverage Report - org.kuali.maven.mojo.s3.UpdateOriginBucketMojo
 
Classes in this File Line Coverage Branch Coverage Complexity
UpdateOriginBucketMojo
0%
0/184
0%
0/42
1.78
 
 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 above " + 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 + "/" + getProject().getVersion() + "/";
 195  
         }
 196  0
         if (!s.endsWith(getDelimiter())) {
 197  0
             s = s + getDelimiter();
 198  
         }
 199  0
         setPrefix(s);
 200  0
     }
 201  
 
 202  
     protected S3BucketContext getS3BucketContext() throws MojoExecutionException {
 203  0
         updateCredentials();
 204  0
         validateCredentials();
 205  0
         AWSCredentials credentials = getCredentials();
 206  0
         AmazonS3Client client = new AmazonS3Client(credentials);
 207  0
         updatePrefix();
 208  0
         S3BucketContext context = new S3BucketContext();
 209  
         try {
 210  0
             BeanUtils.copyProperties(context, this);
 211  0
         } catch (Exception e) {
 212  0
             throw new MojoExecutionException("Error copying properties", e);
 213  0
         }
 214  0
         context.setClient(client);
 215  0
         context.setLastModifiedDateFormatter(getLastModifiedDateFormatter());
 216  0
         context.setAbout(getAbout());
 217  0
         return context;
 218  
     }
 219  
 
 220  
     /**
 221  
      * Create a PutObjectRequest for some html generated by this mojo. The PutObjectRequest sets the content type to
 222  
      * S3_INDEX_CONTENT_TYPE, sets the ACL to PublicRead, and adds some custom metadata so we can positively identify it
 223  
      * as an object created by this plugin
 224  
      */
 225  
     protected PutObjectRequest getPutIndexObjectRequest(final String html, final String key) throws IOException {
 226  0
         InputStream in = new ByteArrayInputStream(html.getBytes());
 227  0
         ObjectMetadata om = new ObjectMetadata();
 228  0
         om.setCacheControl(getCacheControl());
 229  0
         String contentType = S3_INDEX_CONTENT_TYPE;
 230  0
         om.setContentType(contentType);
 231  0
         om.setContentLength(html.length());
 232  0
         om.addUserMetadata(S3_INDEX_METADATA_KEY, "true");
 233  0
         PutObjectRequest request = new PutObjectRequest(getBucket(), key, in, om);
 234  0
         request.setCannedAcl(CannedAccessControlList.PublicRead);
 235  0
         return request;
 236  
     }
 237  
 
 238  
     /**
 239  
      * Return a SimpleDateFormat object initialized with the date format and timezone supplied to the mojo
 240  
      */
 241  
     protected SimpleDateFormat getLastModifiedDateFormatter() {
 242  0
         SimpleDateFormat sdf = new SimpleDateFormat(getDateFormat());
 243  0
         sdf.setTimeZone(TimeZone.getTimeZone(getTimezone()));
 244  0
         return sdf;
 245  
     }
 246  
 
 247  
     /**
 248  
      * Return true if the Collection is null or contains no entries, false otherwise
 249  
      */
 250  
     protected boolean isEmpty(final Collection<?> c) {
 251  0
         return c == null || c.size() == 0;
 252  
     }
 253  
 
 254  
     /**
 255  
      * Show some text about this plugin
 256  
      */
 257  
     protected String getAbout() {
 258  0
         String date = getLastModifiedDateFormatter().format(new Date());
 259  0
         PluginDescriptor descriptor = (PluginDescriptor) this.getPluginContext().get("pluginDescriptor");
 260  0
         if (descriptor == null) {
 261  
             // Maven 2.2.1 is returning a null descriptor
 262  0
             return "Listing generated by the maven-cloudfront-plugin on " + date;
 263  
         } else {
 264  0
             String name = descriptor.getArtifactId();
 265  0
             String version = descriptor.getVersion();
 266  0
             return "Listing generated by the " + name + " v" + version + " on " + date;
 267  
         }
 268  
     }
 269  
 
 270  
     /**
 271  
      * Create an object in the bucket under a key that lets a normal http request function correctly with CloudFront /
 272  
      * S3.<br>
 273  
      * Either use the client's object or upload some html created by this plugin<br>
 274  
      */
 275  
     protected void updateDirectory(final S3PrefixContext context, final boolean isCopyIfExists, final String copyToKey)
 276  
             throws IOException {
 277  0
         S3BucketContext bucketContext = context.getBucketContext();
 278  0
         AmazonS3Client client = context.getBucketContext().getClient();
 279  0
         String bucket = bucketContext.getBucket();
 280  
 
 281  0
         boolean containsDefaultObject = isExistingObject(context.getObjectListing(), context.getDefaultObjectKey());
 282  0
         if (containsDefaultObject && isCopyIfExists) {
 283  
             // Copy the contents of the clients default object
 284  0
             String sourceKey = context.getDefaultObjectKey();
 285  0
             String destKey = copyToKey;
 286  0
             CopyObjectRequest request = getCopyObjectRequest(bucket, sourceKey, destKey);
 287  0
             getLog().info("Copy: " + sourceKey + " to " + destKey);
 288  0
             client.copyObject(request);
 289  0
         } else {
 290  
             // Upload our custom content
 291  0
             PutObjectRequest request = getPutIndexObjectRequest(context.getHtml(), copyToKey);
 292  0
             getLog().info("Put: " + copyToKey);
 293  0
             client.putObject(request);
 294  
         }
 295  0
     }
 296  
 
 297  
     /**
 298  
      * Update this S3 "directory".
 299  
      */
 300  
     protected void updateDirectory(final S3PrefixContext context) throws IOException {
 301  0
         String trimmedPrefix = converter.getTrimmedPrefix(context.getPrefix(), context.getBucketContext()
 302  
                 .getDelimiter());
 303  
 
 304  
         // Handle "http://www.mybucket.com/foo/bar/"
 305  0
         updateDirectory(context, isCopyDefaultObjectWithDelimiter(), context.getPrefix());
 306  
 
 307  
         // Handle "http://www.mybucket.com/foo/bar"
 308  0
         updateDirectory(context, isCopyDefaultObjectWithoutDelimiter(), trimmedPrefix);
 309  
 
 310  
         // Handle "http://www.mybucket.com/foo/bar/browse.html"
 311  
         // context.getBucketContext().getClient().putObject(getPutIndexObjectRequest(context.getHtml(),
 312  
         // context.getBrowseHtmlKey()));
 313  0
     }
 314  
 
 315  
     /**
 316  
      * If this is the root of the bucket and the default object either does not exist or was created by this plugin,
 317  
      * overwrite the default object with newly generated html. Otherwise, do nothing.
 318  
      */
 319  
     protected void handleRoot(final S3PrefixContext context) throws IOException {
 320  0
         if (!context.isRoot()) {
 321  0
             return;
 322  
         }
 323  
 
 324  0
         AmazonS3Client client = context.getBucketContext().getClient();
 325  
 
 326  
         // Handle "http://www.mybucket.com/browse.html"
 327  0
         PutObjectRequest request1 = getPutIndexObjectRequest(context.getHtml(), context.getBrowseHtmlKey());
 328  0
         getLog().info("Put: " + context.getBrowseHtmlKey());
 329  0
         client.putObject(request1);
 330  
 
 331  0
         boolean isCreateOrUpdateDefaultObject = isCreateOrUpdateDefaultObject(context);
 332  0
         if (!isCreateOrUpdateDefaultObject) {
 333  0
             return;
 334  
         }
 335  
 
 336  
         // Update the default object
 337  0
         PutObjectRequest request2 = getPutIndexObjectRequest(context.getHtml(), context.getDefaultObjectKey());
 338  0
         getLog().info("Put: " + context.getDefaultObjectKey());
 339  0
         client.putObject(request2);
 340  0
     }
 341  
 
 342  
     protected S3PrefixContext getS3PrefixContext(final S3BucketContext context, final String prefix) {
 343  0
         ListObjectsRequest request = new ListObjectsRequest(context.getBucket(), prefix, null, context.getDelimiter(),
 344  
                 1000);
 345  0
         ObjectListing objectListing = context.getClient().listObjects(request);
 346  0
         List<String[]> data = converter.getData(objectListing, prefix, context.getDelimiter());
 347  0
         String html = generator.getHtml(data, prefix, context.getDelimiter());
 348  0
         String defaultObjectKey = StringUtils.isEmpty(prefix) ? getDefaultObject() : prefix + getDefaultObject();
 349  0
         String browseHtmlKey = StringUtils.isEmpty(prefix) ? getBrowseHtml() : prefix + getBrowseHtml();
 350  
         // Is this the root of the bucket?
 351  0
         boolean isRoot = StringUtils.isEmpty(prefix);
 352  
 
 353  0
         S3PrefixContext prefixContext = new S3PrefixContext();
 354  0
         prefixContext.setObjectListing(objectListing);
 355  0
         prefixContext.setHtml(html);
 356  0
         prefixContext.setRoot(isRoot);
 357  0
         prefixContext.setDefaultObjectKey(defaultObjectKey);
 358  0
         prefixContext.setPrefix(prefix);
 359  0
         prefixContext.setBucketContext(context);
 360  0
         prefixContext.setBrowseHtmlKey(browseHtmlKey);
 361  0
         return prefixContext;
 362  
     }
 363  
 
 364  
     /**
 365  
      * Recurse the hierarchy of a bucket starting at "prefix" and create entries in the bucket corresponding to the
 366  
      * directory structure of the hierarchy
 367  
      */
 368  
     protected void recurse(final S3BucketContext context, final String prefix) throws IOException {
 369  0
         S3PrefixContext prefixContext = getS3PrefixContext(context, prefix);
 370  
 
 371  0
         handleRoot(prefixContext);
 372  
 
 373  
         // If this is not the root, there is more to do
 374  0
         if (!prefixContext.isRoot()) {
 375  0
             updateDirectory(prefixContext);
 376  
         }
 377  
 
 378  
         // Recurse down the hierarchy
 379  0
         List<String> commonPrefixes = prefixContext.getObjectListing().getCommonPrefixes();
 380  0
         for (String commonPrefix : commonPrefixes) {
 381  0
             recurse(context, commonPrefix);
 382  
         }
 383  0
     }
 384  
 
 385  
     /**
 386  
      * Return true if the ObjectListing contains an object under "key"
 387  
      */
 388  
     protected boolean isExistingObject(final ObjectListing objectListing, final String key) {
 389  0
         List<S3ObjectSummary> summaries = objectListing.getObjectSummaries();
 390  0
         for (S3ObjectSummary summary : summaries) {
 391  0
             if (key.equals(summary.getKey())) {
 392  0
                 return true;
 393  
             }
 394  
         }
 395  0
         return false;
 396  
     }
 397  
 
 398  
     /**
 399  
      * Return true if there is no object in the ObjectListing under defaultObjectKey.<br>
 400  
      * Return true if the object in the ObjectListing was created by this plugin.<br>
 401  
      * Return false otherwise.<br>
 402  
      */
 403  
     protected boolean isCreateOrUpdateDefaultObject(final S3PrefixContext context) {
 404  0
         if (!isExistingObject(context.getObjectListing(), context.getDefaultObjectKey())) {
 405  
             // There is no default object, we are free to create one
 406  0
             return true;
 407  
         }
 408  0
         S3BucketContext s3Context = context.getBucketContext();
 409  
         // There is a default object, but if it was created by this plugin, we
 410  
         // still need to update it
 411  0
         S3Object s3Object = s3Context.getClient().getObject(s3Context.getBucket(), context.getDefaultObjectKey());
 412  0
         boolean isOurDefaultObject = isOurObject(s3Object);
 413  0
         IOUtils.closeQuietly(s3Object.getObjectContent());
 414  0
         if (isOurDefaultObject) {
 415  0
             return true;
 416  
         } else {
 417  0
             return false;
 418  
         }
 419  
     }
 420  
 
 421  
     /**
 422  
      * Return true if this S3Object was created by this plugin. This is is done by checking the metadata attached to
 423  
      * this object for the presence of a custom value.
 424  
      */
 425  
     protected boolean isOurObject(final S3Object s3Object) {
 426  0
         ObjectMetadata metadata = s3Object.getObjectMetadata();
 427  0
         Map<String, String> userMetadata = metadata.getUserMetadata();
 428  0
         String value = userMetadata.get(S3_INDEX_METADATA_KEY);
 429  0
         boolean isOurObject = "true".equals(value);
 430  0
         return isOurObject;
 431  
     }
 432  
 
 433  
     /**
 434  
      * Create a CopyObjectRequest with an ACL set to PublicRead
 435  
      */
 436  
     protected CopyObjectRequest getCopyObjectRequest(final String bucket, final String sourceKey, final String destKey) {
 437  0
         CopyObjectRequest request = new CopyObjectRequest(bucket, sourceKey, bucket, destKey);
 438  0
         request.setCannedAccessControlList(CannedAccessControlList.PublicRead);
 439  0
         return request;
 440  
     }
 441  
 
 442  
     public String getTimezone() {
 443  0
         return timezone;
 444  
     }
 445  
 
 446  
     public void setTimezone(final String timezone) {
 447  0
         this.timezone = timezone;
 448  0
     }
 449  
 
 450  
     public String getDateFormat() {
 451  0
         return dateFormat;
 452  
     }
 453  
 
 454  
     public void setDateFormat(final String dateFormat) {
 455  0
         this.dateFormat = dateFormat;
 456  0
     }
 457  
 
 458  
     public String getDefaultObject() {
 459  0
         return defaultObject;
 460  
     }
 461  
 
 462  
     public void setDefaultObject(final String defaultCloudFrontObject) {
 463  0
         this.defaultObject = defaultCloudFrontObject;
 464  0
     }
 465  
 
 466  
     public String getFileImage() {
 467  0
         return fileImage;
 468  
     }
 469  
 
 470  
     public void setFileImage(final String fileImage) {
 471  0
         this.fileImage = fileImage;
 472  0
     }
 473  
 
 474  
     public String getDirectoryImage() {
 475  0
         return directoryImage;
 476  
     }
 477  
 
 478  
     public void setDirectoryImage(final String directoryImage) {
 479  0
         this.directoryImage = directoryImage;
 480  0
     }
 481  
 
 482  
     public String getCss() {
 483  0
         return css;
 484  
     }
 485  
 
 486  
     public void setCss(final String css) {
 487  0
         this.css = css;
 488  0
     }
 489  
 
 490  
     public boolean isCopyDefaultObjectWithDelimiter() {
 491  0
         return copyDefaultObjectWithDelimiter;
 492  
     }
 493  
 
 494  
     public void setCopyDefaultObjectWithDelimiter(final boolean copyDefaultObjectWithDelimiter) {
 495  0
         this.copyDefaultObjectWithDelimiter = copyDefaultObjectWithDelimiter;
 496  0
     }
 497  
 
 498  
     public boolean isCopyDefaultObjectWithoutDelimiter() {
 499  0
         return copyDefaultObjectWithoutDelimiter;
 500  
     }
 501  
 
 502  
     public void setCopyDefaultObjectWithoutDelimiter(final boolean copyDefaultObjectWithoutDelimiter) {
 503  0
         this.copyDefaultObjectWithoutDelimiter = copyDefaultObjectWithoutDelimiter;
 504  0
     }
 505  
 
 506  
     public String getCacheControl() {
 507  0
         return cacheControl;
 508  
     }
 509  
 
 510  
     public void setCacheControl(final String cacheControl) {
 511  0
         this.cacheControl = cacheControl;
 512  0
     }
 513  
 
 514  
     public String getBrowseHtml() {
 515  0
         return browseHtml;
 516  
     }
 517  
 
 518  
     public void setBrowseHtml(final String browseHtml) {
 519  0
         this.browseHtml = browseHtml;
 520  0
     }
 521  
 
 522  
     /**
 523  
      * @return the recurse
 524  
      */
 525  
     public boolean isRecurse() {
 526  0
         return recurse;
 527  
     }
 528  
 
 529  
     /**
 530  
      * @param recurse
 531  
      * the recurse to set
 532  
      */
 533  
     public void setRecurse(final boolean recurse) {
 534  0
         this.recurse = recurse;
 535  0
     }
 536  
 
 537  
     /**
 538  
      * @return the organizationGroupId
 539  
      */
 540  
     public String getOrganizationGroupId() {
 541  0
         return organizationGroupId;
 542  
     }
 543  
 
 544  
     /**
 545  
      * @param organizationGroupId
 546  
      * the organizationGroupId to set
 547  
      */
 548  
     public void setOrganizationGroupId(final String organizationGroupId) {
 549  0
         this.organizationGroupId = organizationGroupId;
 550  0
     }
 551  
 
 552  
 }