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