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