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