001/* 002 * Copyright 2012 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.newsletter.auto; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.Date; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025 026import org.apache.avalon.framework.configuration.Configuration; 027import org.apache.avalon.framework.configuration.ConfigurationException; 028import org.apache.avalon.framework.context.Context; 029import org.apache.avalon.framework.context.ContextException; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.cocoon.Constants; 033import org.apache.cocoon.components.ContextHelper; 034import org.apache.cocoon.environment.ObjectModelHelper; 035import org.apache.cocoon.environment.Request; 036import org.apache.cocoon.environment.background.BackgroundEnvironment; 037import org.apache.cocoon.util.log.SLF4JLoggerAdapter; 038import org.apache.commons.lang.StringUtils; 039import org.joda.time.DateTime; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042 043import org.ametys.cms.filter.ContentFilter; 044import org.ametys.cms.filter.ContentFilterExtensionPoint; 045import org.ametys.cms.repository.Content; 046import org.ametys.cms.repository.RequestAttributeWorkspaceSelector; 047import org.ametys.cms.repository.WorkflowAwareContent; 048import org.ametys.cms.workflow.AbstractContentWorkflowComponent; 049import org.ametys.cms.workflow.CreateContentFunction; 050import org.ametys.cms.workflow.SendMailFunction; 051import org.ametys.core.authentication.AuthenticateAction; 052import org.ametys.core.engine.BackgroundEngineHelper; 053import org.ametys.core.util.I18nUtils; 054import org.ametys.plugins.newsletter.category.Category; 055import org.ametys.plugins.newsletter.category.CategoryProvider; 056import org.ametys.plugins.newsletter.category.CategoryProviderExtensionPoint; 057import org.ametys.plugins.newsletter.workflow.CreateNewsletterFunction; 058import org.ametys.plugins.repository.AmetysObjectIterable; 059import org.ametys.plugins.repository.AmetysObjectResolver; 060import org.ametys.plugins.workflow.AbstractWorkflowComponent; 061import org.ametys.plugins.workflow.support.WorkflowProvider; 062import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow; 063import org.ametys.runtime.i18n.I18nizableText; 064import org.ametys.web.WebConstants; 065import org.ametys.web.filter.ContentFilterHelper; 066import org.ametys.web.filter.WebContentFilter; 067import org.ametys.web.repository.site.Site; 068import org.ametys.web.repository.site.SiteManager; 069import org.ametys.web.repository.sitemap.Sitemap; 070 071import com.opensymphony.workflow.WorkflowException; 072 073/** 074 * Runnable engine that creates the automatic newsletter contents. 075 */ 076public class AutomaticNewslettersEngine implements Runnable 077{ 078 079 /** The logger. */ 080 protected static final Logger _LOGGER = LoggerFactory.getLogger(AutomaticNewslettersEngine.class); 081 082 /** The newsletter content type. */ 083 protected static final String _NEWSLETTER_CONTENT_TYPE = "org.ametys.plugins.newsletter.Content.newsletter"; 084 085 /** The avalon context. */ 086 protected Context _context; 087 088 /** The service manager. */ 089 protected ServiceManager _manager; 090 091 /** Is the engine initialized ? */ 092 protected boolean _initialized; 093 094 /** The instant the engine was started. */ 095 protected Date _runDate; 096 097 /** The newsletter workflow name. */ 098 protected String _workflowName; 099 100 /** The workflow initial action ID. */ 101 protected int _wfInitialActionId; 102 103 /** A list of action IDs to validate a newsletter from initial step. */ 104 protected List<Integer> _wfValidateActionIds; 105 106 /** A map of the content IDs by filter, reset on each run. */ 107 protected Map<String, List<String>> _filterContentIdCache; 108 109 /** The cocoon environment context. */ 110 protected org.apache.cocoon.environment.Context _environmentContext; 111 112 /** The ametys object resolver. */ 113 protected AmetysObjectResolver _resolver; 114 115 /** The site manager. */ 116 protected SiteManager _siteManager; 117 118 /** The workflow provider. */ 119 protected WorkflowProvider _workflowProvider; 120 121 /** The automatic newsletter extension point. */ 122 protected AutomaticNewsletterExtensionPoint _autoNewsletterEP; 123 124 /** The newsletter category provider extension point. */ 125 protected CategoryProviderExtensionPoint _categoryEP; 126 127 /** The content filter extension point. */ 128 protected ContentFilterExtensionPoint _contentFilterEP; 129 130 /** The content filter helper. */ 131 protected ContentFilterHelper _contentFilterHelper; 132 133 /** The i18n utils. */ 134 protected I18nUtils _i18nUtils; 135 136 /** 137 * Initialize the engine. 138 * @param manager the avalon service manager. 139 * @param context the avalon context. 140 * @throws ContextException if an error occurred 141 * @throws ServiceException if an error occurred 142 */ 143 public void initialize(ServiceManager manager, Context context) throws ContextException, ServiceException 144 { 145 _manager = manager; 146 _context = context; 147 _environmentContext = (org.apache.cocoon.environment.Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 148 149 // Lookup the needed components. 150 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 151 _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE); 152 _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE); 153 _autoNewsletterEP = (AutomaticNewsletterExtensionPoint) manager.lookup(AutomaticNewsletterExtensionPoint.ROLE); 154 _categoryEP = (CategoryProviderExtensionPoint) manager.lookup(CategoryProviderExtensionPoint.ROLE); 155 _contentFilterEP = (ContentFilterExtensionPoint) manager.lookup(ContentFilterExtensionPoint.ROLE); 156 _contentFilterHelper = (ContentFilterHelper) manager.lookup(ContentFilterHelper.ROLE); 157 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 158 159 _filterContentIdCache = new HashMap<>(); 160 161 _initialized = true; 162 } 163 164 /** 165 * Configure the engine (to be called by the scheduler or the action). 166 * @param configuration the component configuration. 167 * @throws ConfigurationException if an error occurs in the configuration. 168 */ 169 public void configure(Configuration configuration) throws ConfigurationException 170 { 171 Configuration workflowConf = configuration.getChild("workflow"); 172 _workflowName = workflowConf.getAttribute("name"); 173 _wfInitialActionId = workflowConf.getAttributeAsInteger("initialActionId"); 174 175 String[] validateActionIds = StringUtils.split(workflowConf.getAttribute("validateActionIds"), ", "); 176 _wfValidateActionIds = new ArrayList<>(validateActionIds.length); 177 for (String actionId : validateActionIds) 178 { 179 try 180 { 181 _wfValidateActionIds.add(Integer.valueOf(actionId)); 182 } 183 catch (NumberFormatException e) 184 { 185 throw new ConfigurationException("Invalid validation action ID.", e); 186 } 187 } 188 } 189 190 /** 191 * Check the initialization and throw an exception if not initialized. 192 */ 193 protected void checkInitialization() 194 { 195 if (!_initialized) 196 { 197 String message = "The automatic newsletter engine must be properly initialized before it's run."; 198 _LOGGER.error(message); 199 throw new IllegalStateException(message); 200 } 201 } 202 203 @Override 204 public void run() 205 { 206 Map<String, Object> environmentInformation = null; 207 long duration = 0; 208 try 209 { 210 if (_LOGGER.isInfoEnabled()) 211 { 212 _LOGGER.info("Preparing to create the automatic newsletter contents..."); 213 } 214 215 checkInitialization(); 216 217 // Store the date and time. 218 _runDate = new Date(); 219 220 // Create the environment. 221 environmentInformation = BackgroundEngineHelper.createAndEnterEngineEnvironment(_manager, _environmentContext, new SLF4JLoggerAdapter(_LOGGER)); 222 223 BackgroundEnvironment environment = (BackgroundEnvironment) environmentInformation.get("environment"); 224 225 // Authorize workflow actions and "check-auth" CMS action, from this background environment. 226 Request request = (Request) environment.getObjectModel().get(ObjectModelHelper.REQUEST_OBJECT); 227 request.setAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_INTERNAL_ALLOWED, true); 228 229 long start = System.currentTimeMillis(); 230 231 // Get all the contents and purge the old versions. 232 createAutomaticNewsletters(); 233 234 long end = System.currentTimeMillis(); 235 236 duration = (end - start) / 1000; 237 } 238 catch (Exception e) 239 { 240 _LOGGER.error("An error occurred creating the automatic newsletter contents.", e); 241 } 242 finally 243 { 244 // Leave the environment. 245 if (environmentInformation != null) 246 { 247 BackgroundEngineHelper.leaveEngineEnvironment(environmentInformation); 248 } 249 250 // Dispose of the resources. 251 dispose(); 252 253 if (_LOGGER.isInfoEnabled()) 254 { 255 _LOGGER.info("Automatic newsletter creation ended after " + duration + " seconds."); 256 } 257 } 258 } 259 260 /** 261 * Dispose of the resources and looked-up components. 262 */ 263 protected void dispose() 264 { 265 // Release the components. 266 if (_manager != null) 267 { 268 _manager.release(_resolver); 269 } 270 271 _resolver = null; 272 273 _environmentContext = null; 274 _context = null; 275 _manager = null; 276 277 _runDate = null; 278 279 _initialized = false; 280 } 281 282 /** 283 * Launch the creation process for each site and sitemap. 284 */ 285 protected void createAutomaticNewsletters() 286 { 287 // Reset the cache. 288 _filterContentIdCache.clear(); 289 290 try (AmetysObjectIterable<Site> sites = _siteManager.getSites();) 291 { 292 for (Site site : sites) 293 { 294 try (AmetysObjectIterable<Sitemap> sitemaps = site.getSitemaps();) 295 { 296 for (Sitemap sitemap : sitemaps) 297 { 298 createAutomaticNewsletters(site.getName(), sitemap.getName()); 299 } 300 } 301 } 302 } 303 } 304 305 /** 306 * Test each category in a site and sitemap and launch the newsletter creation if needed. 307 * @param siteName the site name. 308 * @param sitemapName the sitemap name. 309 */ 310 protected void createAutomaticNewsletters(String siteName, String sitemapName) 311 { 312 Request request = ContextHelper.getRequest(_context); 313 request.setAttribute("siteName", siteName); 314 315 for (String providerId : _categoryEP.getExtensionsIds()) 316 { 317 CategoryProvider provider = _categoryEP.getExtension(providerId); 318 319 // Browse all categories for this site and sitemap. 320 for (Category category : provider.getAllCategories(siteName, sitemapName)) 321 { 322 // Get the automatic newsletter assigned to this category. 323 Collection<String> automaticIds = provider.getAutomaticIds(category.getId()); 324 325 for (String autoNewsletterId : automaticIds) 326 { 327 AutomaticNewsletter autoNewsletter = _autoNewsletterEP.getExtension(autoNewsletterId); 328 329 // Test if an automatic newsletter content has to be created today. 330 if (autoNewsletter != null && createNow(autoNewsletter)) 331 { 332 createAndValidateAutomaticNewsletter(siteName, sitemapName, category, provider, autoNewsletter); 333 } 334 } 335 } 336 337 } 338 } 339 340 /** 341 * Create an automatic newsletter content in a category. 342 * @param sitemapName the sitemap name. 343 * @param siteName the site name. 344 * @param category the newsletter category. 345 * @param provider the category provider. 346 * @param autoNewsletter the associated automatic newsletter. 347 */ 348 protected void createAndValidateAutomaticNewsletter(String siteName, String sitemapName, Category category, CategoryProvider provider, AutomaticNewsletter autoNewsletter) 349 { 350 if (_LOGGER.isInfoEnabled()) 351 { 352 _LOGGER.info("Preparing to create an automatic newsletter for category " + category.getId() + " in " + siteName + " and sitemap " + sitemapName); 353 } 354 355 // Get the list of content IDs by filter name. 356 Map<String, AutomaticNewsletterFilterResult> contentsByFilter = getFilterResults(siteName, sitemapName, autoNewsletter); 357 358 try 359 { 360 if (hasResults(contentsByFilter.values())) 361 { 362 // Compute the next newsletter number in this category. 363 long nextNumber = getNextNumber(category, provider, siteName, sitemapName); 364 365 // Create newsletter content. 366 WorkflowAwareContent content = createNewsletterContent(siteName, sitemapName, category, autoNewsletter, nextNumber, contentsByFilter); 367 368 // Validate and send. 369 validateNewsletter(content); 370 } 371 else 372 { 373 if (_LOGGER.isInfoEnabled()) 374 { 375 _LOGGER.info("No content has been returned by the filters for the automatic newsletter in category " + category.getId() + " in site " + siteName + " and sitemap " + sitemapName + ": no newsletter has been created."); 376 } 377 } 378 } 379 catch (WorkflowException e) 380 { 381 _LOGGER.error("Unable to create and validate an automatic newsletter for category " + category.getId() + " in site " + siteName + " and sitemap " + sitemapName, e); 382 } 383 } 384 385 /** 386 * Get the list of contents for the automatic newsletter filters. 387 * @param siteName the site name. 388 * @param sitemapName the sitemap name. 389 * @param autoNewsletter the automatic newsletter. 390 * @return the results, indexed by filter name (in the auto newsletter). 391 */ 392 protected Map<String, AutomaticNewsletterFilterResult> getFilterResults(String siteName, String sitemapName, AutomaticNewsletter autoNewsletter) 393 { 394 Map<String, AutomaticNewsletterFilterResult> contentsByFilter = new HashMap<>(); 395 396 Request request = ContextHelper.getRequest(_context); 397 398 Map<String, String> filters = autoNewsletter.getFilters(); 399 400 for (String name : filters.keySet()) 401 { 402 String filterId = filters.get(name); 403 404 AutomaticNewsletterFilterResult result = new AutomaticNewsletterFilterResult(); 405 contentsByFilter.put(name, result); 406 407 List<String> contentIds = new ArrayList<>(); 408 409 ContentFilter filter = _contentFilterEP.getExtension(filterId); 410 411 if (filter != null && filter instanceof WebContentFilter) 412 { 413 WebContentFilter webFilter = (WebContentFilter) filter; 414 result.setMetadataSetName(filter.getView()); 415 416 String cacheKey = siteName + "/" + sitemapName + "/" + filterId; 417 418 if (_filterContentIdCache.containsKey(cacheKey)) 419 { 420 contentIds = _filterContentIdCache.get(cacheKey); 421 } 422 else 423 { 424 // Get the contents in the live workspace. 425 String originalWorkspace = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 426 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, WebConstants.LIVE_WORKSPACE); 427 428 contentIds = _contentFilterHelper.getMatchingContentIds(webFilter, siteName, sitemapName, null); 429 430 // Set the workspace back to its original value. 431 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, originalWorkspace); 432 433 // Cache the results. 434 _filterContentIdCache.put(cacheKey, contentIds); 435 } 436 } 437 438 result.setContentIds(contentIds); 439 } 440 441 return contentsByFilter; 442 } 443 444 /** 445 * Create the newsletter content. 446 * @param siteName the site name. 447 * @param language the language. 448 * @param category the category. 449 * @param autoNewsletter the automatic newsletter. 450 * @param newsletterNumber the newsletter number. 451 * @param filterResults the filter results (content IDs for each filter). 452 * @return The newly created newsletter content. 453 * @throws WorkflowException if a workflow error occurs. 454 */ 455 protected WorkflowAwareContent createNewsletterContent(String siteName, String language, Category category, AutomaticNewsletter autoNewsletter, long newsletterNumber, Map<String, AutomaticNewsletterFilterResult> filterResults) throws WorkflowException 456 { 457 String contentName = category.getName() + "-" + newsletterNumber; 458 459 String title = getNewsletterTitle(language, category, autoNewsletter, newsletterNumber); 460 461 Map<String, Object> params = new HashMap<>(); 462 463 // Workflow result. 464 Map<String, Object> workflowResult = new HashMap<>(); 465 params.put(AbstractWorkflowComponent.RESULT_MAP_KEY, workflowResult); 466 467 // Workflow parameters. 468 params.put("workflowName", _workflowName); 469 params.put(org.ametys.web.workflow.CreateContentFunction.SITE_KEY, siteName); 470 params.put(CreateContentFunction.CONTENT_NAME_KEY, contentName); 471 params.put(CreateContentFunction.CONTENT_TITLE_KEY, title); 472 params.put(CreateContentFunction.CONTENT_TYPES_KEY, new String[]{_NEWSLETTER_CONTENT_TYPE}); 473 params.put(CreateContentFunction.CONTENT_LANGUAGE_KEY, language); 474 params.put(CreateNewsletterFunction.NEWSLETTER_CATEGORY_KEY, category.getId()); 475 params.put(CreateNewsletterFunction.NEWSLETTER_NUMBER_KEY, new Long(newsletterNumber)); 476 params.put(CreateNewsletterFunction.NEWSLETTER_DATE_KEY, _runDate); 477 params.put(CreateNewsletterFunction.NEWSLETTER_IS_AUTOMATIC_KEY, "true"); 478 params.put(CreateNewsletterFunction.NEWSLETTER_PROCESS_AUTO_SECTIONS_KEY, "true"); 479 params.put(CreateNewsletterFunction.NEWSLETTER_CONTENT_ID_MAP_KEY, filterResults); 480 481 // Trigger the creation. 482 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(); 483 workflow.initialize(_workflowName, _wfInitialActionId, params); 484 485 // Get the content in the results and return it. 486 WorkflowAwareContent content = (WorkflowAwareContent) workflowResult.get(AbstractContentWorkflowComponent.CONTENT_KEY); 487 488 return content; 489 } 490 491 /** 492 * Validate the newly created newsletter. 493 * @param newsletterContent the newsletter content, must be in draft state. 494 * @throws WorkflowException if a workflow error occurs. 495 */ 496 protected void validateNewsletter(WorkflowAwareContent newsletterContent) throws WorkflowException 497 { 498 long workflowId = newsletterContent.getWorkflowId(); 499 500 Map<String, Object> inputs = new HashMap<>(); 501 502 inputs.put(AbstractContentWorkflowComponent.CONTENT_KEY, newsletterContent); 503 // Do not send workflow mail notifications. 504 inputs.put(SendMailFunction.SEND_MAIL, "false"); 505 506 // Without this attribute, the newsletter is not sent to subscribers. 507 Request request = ContextHelper.getRequest(_context); 508 request.setAttribute("send", "true"); 509 510 // Successively execute all the configured actions. 511 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(newsletterContent); 512 for (Integer actionId : _wfValidateActionIds) 513 { 514 workflow.doAction(workflowId, actionId, inputs); 515 } 516 } 517 518 /** 519 * Compute the newsletter title. 520 * @param language the language. 521 * @param category the newsletter category. 522 * @param autoNewsletter the automatic newsletter. 523 * @param newsletterNumber the newsletter number. 524 * @return the newsletter title. 525 */ 526 protected String getNewsletterTitle(String language, Category category, AutomaticNewsletter autoNewsletter, long newsletterNumber) 527 { 528 String title = ""; 529 530 I18nizableText newsletterTitle = autoNewsletter.getNewsletterTitle(); 531 if (newsletterTitle == null || StringUtils.isEmpty(newsletterTitle.toString())) 532 { 533 // The newsletter title is not set in the auto newsletter: 534 // create the newsletter title from the category title. 535 String categoryTitle = _i18nUtils.translate(category.getTitle(), language); 536 title = categoryTitle + " " + newsletterNumber; 537 } 538 else if (newsletterTitle.isI18n()) 539 { 540 // The newsletter title is set as a parametrizable I18nizableText in the auto newsletter. 541 Map<String, I18nizableText> params = Collections.singletonMap("number", new I18nizableText(Long.toString(newsletterNumber))); 542 I18nizableText titleI18n = new I18nizableText(newsletterTitle.getCatalogue(), newsletterTitle.getKey(), params); 543 title = _i18nUtils.translate(titleI18n, language); 544 } 545 else 546 { 547 // The newsletter title is set as a non-18n I18nizableText. 548 title = newsletterTitle.getLabel() + " " + newsletterNumber; 549 } 550 551 return title; 552 } 553 554 /** 555 * Compute the newsletter number. 556 * @param category the newsletter category. 557 * @param provider the category provider. 558 * @param siteName the site name. 559 * @param language the language. 560 * @return the newsletter number. 561 */ 562 protected long getNextNumber(Category category, CategoryProvider provider, String siteName, String language) 563 { 564 long number = 0; 565 566 // Browse all existing numbers to get the highest number. 567 try (AmetysObjectIterable<Content> newsletters = provider.getNewsletters(category.getId(), siteName, language);) 568 { 569 for (Content newsletterContent : newsletters) 570 { 571 long contentNumber = newsletterContent.getMetadataHolder().getLong("newsletter-number", 0); 572 573 // Keep the number if it's higher. 574 number = Math.max(number, contentNumber); 575 } 576 577 // Return the next newsletter number. 578 return number + 1; 579 } 580 } 581 582 /** 583 * Test if there is at least one content in a collection of filter results. 584 * @param results a collection of filter results. 585 * @return true if at least one filter yielded a result, false otherwise. 586 */ 587 protected boolean hasResults(Collection<AutomaticNewsletterFilterResult> results) 588 { 589 boolean hasResults = false; 590 591 for (AutomaticNewsletterFilterResult result : results) 592 { 593 if (result.hasResults()) 594 { 595 hasResults = true; 596 } 597 } 598 599 return hasResults; 600 } 601 602 /** 603 * Test if an automatic newsletter content has to be created now. 604 * @param autoNewsletter the automatic newsletter. 605 * @return true if an automatic newsletter content has to be created now, false otherwise. 606 */ 607 protected boolean createNow(AutomaticNewsletter autoNewsletter) 608 { 609 boolean createToday = false; 610 611 // The time the engine was launched. 612 DateTime runDate = new DateTime(_runDate.getTime()); 613 614 // The days at which the newsletter has to be created. 615 Collection<Integer> dayNumbers = autoNewsletter.getDayNumbers(); 616 617 switch(autoNewsletter.getFrequencyType()) 618 { 619 case MONTH: 620 // Test with a month frequency. 621 createToday = testMonth(dayNumbers, runDate); 622 break; 623 case WEEK: 624 // Test with a week frequency. 625 createToday = testWeek(dayNumbers, runDate); 626 break; 627 default: 628 break; 629 } 630 631 return createToday; 632 } 633 634 /** 635 * Test if we are in the configured month creation period. 636 * @param dayNumbers the days in the month on which a newsletter is to be created. 637 * @param runDate the instant the engine was launched. 638 * @return true if we are in the configured month creation period, false otherwise. 639 */ 640 protected boolean testMonth(Collection<Integer> dayNumbers, DateTime runDate) 641 { 642 boolean createToday = false; 643 644 int dayOfMonth = runDate.getDayOfMonth(); 645 int lastDayOfMonth = runDate.dayOfMonth().withMaximumValue().getDayOfMonth(); 646 647 for (Integer dayNumber : dayNumbers) 648 { 649 if (dayNumber.intValue() == dayOfMonth) 650 { 651 createToday = true; 652 } 653 else if (dayNumber.intValue() > lastDayOfMonth && dayOfMonth == lastDayOfMonth) 654 { 655 // If the configured day is outside the current month (for instance, if "31" is configured), 656 // run the last day of the current month. 657 createToday = true; 658 } 659 } 660 661 return createToday; 662 } 663 664 /** 665 * Test if we are in the configured week creation period. 666 * @param dayNumbers the days in the month on which a newsletter is to be created. 667 * @param runDate the instant the engine was launched. 668 * @return true if we are in the configured week creation period, false otherwise. 669 */ 670 protected boolean testWeek(Collection<Integer> dayNumbers, DateTime runDate) 671 { 672 boolean createToday = false; 673 674 int dayOfWeek = runDate.getDayOfWeek(); 675 676 for (Integer dayNumber : dayNumbers) 677 { 678 if (dayNumber.intValue() == dayOfWeek) 679 { 680 createToday = true; 681 } 682 } 683 684 return createToday; 685 } 686 687}