001/*
002 * Stallion Core: A Modern Web Framework
003 *
004 * Copyright (C) 2015 - 2016 Stallion Software LLC.
005 *
006 * This program is free software: you can redistribute it and/or modify it under the terms of the
007 * GNU General Public License as published by the Free Software Foundation, either version 2 of
008 * the License, or (at your option) any later version. This program is distributed in the hope that
009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
011 * License for more details. You should have received a copy of the GNU General Public License
012 * along with this program.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
013 *
014 *
015 *
016 */
017
018package io.stallion.settings;
019
020import io.stallion.boot.CommandOptionsBase;
021import io.stallion.exceptions.ConfigException;
022import io.stallion.exceptions.UsageException;
023import io.stallion.reflection.PropertyUtils;
024import io.stallion.requests.RouteDefinition;
025import io.stallion.services.Log;
026import io.stallion.settings.childSections.*;
027import io.stallion.utils.GeneralUtils;
028import org.apache.commons.lang3.StringUtils;
029
030import java.io.File;
031import java.time.ZoneId;
032import java.util.*;
033import java.util.regex.Pattern;
034
035import static io.stallion.utils.Literals.empty;
036import static io.stallion.utils.Literals.*;
037
038public class Settings implements ISettings {
039
040
041
042    // Child sections
043    private DbConfig database = null;
044    private UserSettings users = null;
045    private EmailSettings email = null;
046    private CustomSettings custom = null;
047    private CloudStorageSettings cloudStorage = null;
048    private StyleSettings styles = null;
049    private CorsSettings cors = null;
050    private OAuthSettings oAuth;
051    private SecretsSettings secrets;
052
053
054    // Site information
055    @SettingMeta()
056    private String authorName = null;
057    @SettingMeta()
058    private String siteName = null;
059    @SettingMeta(val="A Stallion Web Site")
060    private String defaultTitle = null;
061    @SettingMeta()
062    private String metaDescription = null;
063    @SettingMeta(val="Stallion")
064    private String metaGenerator = null;
065    @SettingMeta(val="", help = "The email address users of the site should contact in case of problems. This will be publicly viewable.")
066    private String supportEmail;
067    @SettingMeta(valLong = 1430510589000L)
068    private Long appCreatedMillis;
069
070    // Tuning
071    @SettingMeta(valLong = 5000000)
072    private Long filterCacheSize;
073
074
075    // File system info
076    @SettingMeta()
077    private String dataDirectory = null;
078    @SettingMeta()
079    private String targetFolder = null;
080
081    // Runtime mode config
082    @SettingMeta()
083    private Boolean localMode;
084    @SettingMeta()
085    private Boolean devMode;
086    @SettingMeta()
087    private String logFile;
088    @SettingMeta()
089    private Boolean logToConsole;
090    @SettingMeta()
091    private Boolean logToFile;
092    @SettingMeta()
093    private Boolean debug;
094    @SettingMeta()
095    private Boolean emailErrors;
096    @SettingMeta()
097    private StrictnessLevel strictnessLevel;
098    @SettingMeta(val="INFO")
099    private String logLevel;
100    private Map<String, String> packageLogLevels = new HashMap<>();
101    private Integer nodeNumber = 1;
102    @SettingMeta(val="x-Real-Ip", help="The HTTP header that contains the original client IP address, by default, with nginx proxying, this is x-Real-Ip")
103    private String ipHeaderName;
104
105
106
107    private String env;
108    @SettingMeta()
109    private Boolean bundleDebug;
110
111    // Routes and rewrites
112    @SettingMeta(val="SAMEORIGIN")
113    private String xFrameOptions;
114    @SettingMeta(cls=ArrayList.class)
115    private List<RouteDefinition> routes = new ArrayList<>();
116    @SettingMeta(cls=HashMap.class)
117    private Map<String, String> redirects = null;
118    @SettingMeta(cls=HashMap.class)
119    private Map<String, String> rewrites = null;
120    @SettingMeta(cls=ArrayList.class)
121    private List<String[]> rewritePatterns = null;
122    @SettingMeta(cls=ArrayList.class)
123    private List<Map.Entry<Pattern, String>> rewriteCompiledPatterns = null;
124    @SettingMeta(cls=ArrayList.class)
125    private List<SecondaryDomain> secondaryDomains;
126    private Map<String, SecondaryDomain> secondaryDomainByDomain  = map();
127    @SettingMeta(cls=ArrayList.class)
128    private List<AssetPreprocessorConfig> assetPreprocessors;
129
130    // Web Serving
131    @SettingMeta(valInt=8090)
132    private Integer port;
133    @SettingMeta(val="http://localhost:{port}")
134    private String cdnUrl;
135    @SettingMeta(val="http://localhost:{port}")
136    private String siteUrl;
137    @SettingMeta(val="25M")
138    private String nginxClientMaxBodySize;
139    @SettingMeta(val="3600")
140    private String nginxProxyReadTimeout;
141
142    // Default code directories and files
143    @SettingMeta(cls=ArrayList.class)
144    private List<ContentFolder> folders;
145    @SettingMeta(val="page.jinja")
146    private String pageTemplate = "page.jinja";
147
148    // Email
149
150    // Publishing
151    //@SettingMeta(cls=ArrayList.class)
152    //private List<PublishingConfig> publishing;
153
154
155    // Time
156    @SettingMeta()
157    private String timeZone;// = ZoneId.systemDefault().toString();
158    @SettingMeta()
159    private ZoneId timeZoneId;// = ZoneId.systemDefault();
160
161
162    // Anti-spam
163    @SettingMeta(val = "")
164    private String antiSpamSecret;
165    @SettingMeta(valBoolean = false)
166    private Boolean disableFormSubmissions;
167
168    // Health
169    @SettingMeta(cls=ArrayList.class)
170    private List<String[]> healthCheckEndpoints = new ArrayList<>();
171    @SettingMeta()
172    private String healthCheckSecret;
173
174    private static Settings _instance;
175
176    public static boolean isNull() {
177        return _instance == null;
178    }
179
180    public static void shutdown() {
181        _instance = null;
182    }
183
184        public static Settings instance() {
185            if (_instance == null) {
186            throw new UsageException("You must call load() before instance().");
187        }
188        return _instance;
189    }
190
191    public static Settings init(String env, CommandOptionsBase options) {
192        _instance = new SettingsLoader().loadSettings(env, options.getTargetPath(), "stallion", Settings.class, options);
193
194        _instance.setCdnUrl(_instance.getCdnUrl().replace("{port}", _instance.getPort().toString()));
195        _instance.setSiteUrl(_instance.getSiteUrl().replace("{port}", _instance.getPort().toString()));
196        return _instance;
197    }
198
199    public void assignDefaults() {
200        if (getLocalMode() == null) {
201            if (empty(System.getenv().getOrDefault("STALLION_DEPLOY_TIME", ""))) {
202                setLocalMode(true);
203            } else {
204                setLocalMode(false);
205            }
206        }
207
208        if (bundleDebug == null) {
209            bundleDebug = getLocalMode();
210        }
211
212
213
214        if (getDebug() == null) {
215            if (getEnv().equals("prod") && !getLocalMode()) {
216                setDebug(false);
217            } else {
218                setDebug(true);
219            }
220        }
221
222        if (timeZone == null) {
223            timeZone = ZoneId.systemDefault().toString();
224        }
225        if (timeZoneId == null) {
226            timeZoneId = ZoneId.of(timeZone);
227        }
228
229
230        if (logFile == null) {
231            logFile = "/tmp/log/stallion/" + StringUtils.strip(GeneralUtils.slugify(targetFolder), "-") + ".log";
232        }
233        if (logToConsole == null) {
234            logToConsole = getLocalMode();
235        }
236        if (logToFile == null) {
237            logToFile = !logToConsole;
238        }
239
240        if (getEmailErrors() == null) {
241            if ((getEnv().equals("prod") || getEnv().equals("qa")) && !getLocalMode()) {
242                setEmailErrors(true);
243            } else {
244                setEmailErrors(false);
245            }
246        }
247
248        if (getStrictnessLevel() == null) {
249            if (getEnv().equals("prod") && !getLocalMode()) {
250                setStrictnessLevel(StrictnessLevel.LAX);
251            } else {
252                setStrictnessLevel(StrictnessLevel.STRICT);
253            }
254        }
255
256        if (!empty(secondaryDomains)) {
257            secondaryDomainByDomain = map();
258            for (SecondaryDomain d: secondaryDomains) {
259                secondaryDomainByDomain.put(d.getDomain(), d);
260            }
261        }
262
263
264        if (!StringUtils.isEmpty(getDataDirectory())) {
265            if (!getDataDirectory().startsWith("/")) {
266                setDataDirectory(targetFolder + "/" + getDataDirectory());
267            }
268        } else {
269            setDataDirectory(targetFolder + "/app-data");
270        }
271        if (getDataDirectory().endsWith("/")) {
272            setDataDirectory(getDataDirectory().substring(0, getDataDirectory().length() - 1));
273        }
274
275        if (getRewrites() != null) {
276            // THe Toml library has a bug whereby if a map key is quoted, it keeps the
277            // quotes as part of the key, rather than using the String inside the quotes
278            Set<String> keys = new HashSet<>(getRewrites().keySet());
279            for(String key: keys) {
280                if (key.startsWith("\"") && key.endsWith("\"")) {
281                    getRewrites().put(key.substring(1, key.length()-1), getRewrites().get(key));
282                }
283            }
284        }
285
286        if (getRedirects() != null) {
287            // THe Toml library has a bug whereby if a map key is quoted, it keeps the
288            // quotes as part of the key, rather than using the String inside the quotes
289            Set<String> keys = new HashSet<>(getRedirects().keySet());
290            for(String key: keys) {
291                if (key.startsWith("\"") && key.endsWith("\"")) {
292                    getRedirects().put(key.substring(1, key.length()-1), getRedirects().get(key));
293                }
294            }
295        }
296
297        if (getRewritePatterns() != null && getRewritePatterns().size() > 0) {
298            if (rewriteCompiledPatterns == null) {
299                rewriteCompiledPatterns = new ArrayList();
300            }
301            for(String[] entry: getRewritePatterns()) {
302                if (entry.length != 2) {
303                    Log.warn("Invalid rewritePatterns entry, size should be 2 but is {0} {1}", entry.length, entry);
304                }
305                Pattern pattern = Pattern.compile(entry[0]);
306                Map.Entry<Pattern, String> kv = new AbstractMap.SimpleEntry<Pattern, String>(pattern, entry[1]);
307                getRewriteCompiledPatterns().add(kv);
308            }
309        }
310
311        if (getEmail() != null) {
312            // By default, in debug mode, we do not want to send real emails to people
313            if (getEmail().getRestrictOutboundEmails() == null) {
314                getEmail().setRestrictOutboundEmails(getDebug());
315            }
316            if (getEmail().getOutboundEmailTestAddress() == null) {
317                if (getEmail().getAdminEmails() != null && getEmail().getAdminEmails().size() > 0) {
318                    getEmail().setOutboundEmailTestAddress(getEmail().getAdminEmails().get(0));
319                }
320            }
321            if (!empty(getEmail().getAllowedTestingOutboundEmailPatterns())) {
322                if (getEmail().getAllowedTestingOutboundEmailCompiledPatterns() == null) {
323                    getEmail().setAllowedTestingOutboundEmailCompiledPatterns(new ArrayList<>());
324                }
325                for (String emailPattern : getEmail().getAllowedTestingOutboundEmailPatterns()) {
326                    getEmail().getAllowedTestingOutboundEmailCompiledPatterns().add(Pattern.compile(emailPattern));
327                }
328            }
329        }
330
331        if (getSecrets() == null) {
332            setSecrets(new SecretsSettings());
333        }
334
335        if (new File(targetFolder + "/pages").isDirectory()) {
336            if (folders == null) {
337                folders = new ArrayList<>();
338            }
339            folders.add(new ContentFolder().setPath(targetFolder + "/pages").setType("markdown").setItemTemplate(getPageTemplate()));
340        }
341
342
343    }
344
345    /**
346     * Gets the scheme (https or http) for a given secondary domain
347     * @return
348     */
349    public String getSchemeForSecondaryDomain(String domain) {
350        SecondaryDomain d = secondaryDomainByDomain.get(domain);
351        return or(d.getScheme(), getSiteUrlScheme());
352    }
353
354    /**
355     * Get siteUrl scheme
356     * @return
357     */
358    public String getSiteUrlScheme() {
359        if (getSiteUrl().startsWith("https:")) {
360            return "https";
361        } else {
362            return "http";
363        }
364    }
365
366
367    public SecondaryDomain getSecondaryDomainByDomain(String domain) {
368        return secondaryDomainByDomain.get(domain);
369    }
370
371    /**
372     * In strict mode, more errors will be exceptions that stop all processing. For instance, missing template variables
373     * will cause exceptions rather than being ignored.
374     * @return
375     */
376    public boolean isStrict() {
377        return StrictnessLevel.STRICT.equals(getStrictnessLevel());
378    }
379
380    /**
381     * ContentFolders are folders in your local site directory that are to be registered with the data access controller.
382     * The .txt will be converted into accessible web pages in your site.
383     * @return
384     */
385    public List<ContentFolder> getFolders() {
386        return folders;
387    }
388
389    public void setFolders(List<ContentFolder> folders) {
390        this.folders = folders;
391    }
392
393    /**
394     * The database configuration
395     * @return
396     */
397    public DbConfig getDatabase() {
398        return database;
399    }
400
401    public void setDatabase(DbConfig database) {
402        this.database = database;
403    }
404
405    /**
406     * The user configuration
407     * @return
408     */
409    public UserSettings getUsers() {
410        return users;
411    }
412
413    public void setUsers(UserSettings users) {
414        this.users = users;
415    }
416
417    /**
418     * Email configuration for Stallion sending emails via SMTP
419     * @return
420     */
421    public EmailSettings getEmail() {
422        return email;
423    }
424
425    public void setEmail(EmailSettings email) {
426        this.email = email;
427    }
428
429
430    /**
431     * Cross-Origin Request configuration -- allow endpoints to respond to cross-origin requests.
432     * @return
433     */
434    public CorsSettings getCors() {
435        return cors;
436    }
437
438    public Settings setCors(CorsSettings cors) {
439        this.cors = cors;
440        return this;
441    }
442
443    /**
444     * Where all data stored to flat-file by the Controllers and Persisters will actually live in the
445     * file system. This will be "app-data" under the site directory by default.
446     * @return
447     */
448    public String getDataDirectory() {
449        return dataDirectory;
450    }
451
452    public void setDataDirectory(String dataDirectory) {
453        this.dataDirectory = dataDirectory;
454    }
455
456    /**
457     * A hashtable of arbitrary user defined settings.
458     * @return
459     */
460    public CustomSettings getCustom() {
461        return custom;
462    }
463
464    public void setCustom(CustomSettings custom) {
465        this.custom = custom;
466    }
467
468    /**
469     * The default template to use to render pages
470     *
471     * @return
472     */
473    public String getPageTemplate() {
474        return pageTemplate;
475    }
476
477    public void setPageTemplate(String pageTemplate) {
478        this.pageTemplate = pageTemplate;
479    }
480
481    /**
482     * The base URL for CDN (Content Delivery Network) that will pass through to your site. This is used
483     * by AssetsController.url() or AssetsController.bundle() to build URL's to your assets. If empty, will use
484     * the site URL instead. Using a CDN can improve performance as it will cache requested assets in data centers
485     * nearer to the end user.
486     *
487     * @return
488     */
489    public String getCdnUrl() {
490        return cdnUrl;
491    }
492
493    public void setCdnUrl(String cdnUrl) {
494        this.cdnUrl = cdnUrl;
495    }
496
497    /**
498     * The base URL at which the site lives. Used to build links internally, also used for checking cross-origin
499     * requests.
500     *
501     * @return
502     */
503    public String getSiteUrl() {
504        return siteUrl;
505    }
506
507    public void setSiteUrl(String siteUrl) {
508        this.siteUrl = siteUrl;
509    }
510
511
512    public List<RouteDefinition> getRoutes() {
513        return routes;
514    }
515
516    public void setRoutes(List<RouteDefinition> routes) {
517        this.routes = routes;
518    }
519
520    /**
521     * Run in debug mode. True by default when env=local. In debug mode, the full stack trace of exceptions
522     * will be rendered to the HTML output.
523     *
524     * @return
525     */
526    public Boolean getDebug() {
527        return debug;
528    }
529
530    public void setDebug(Boolean debug) {
531        this.debug = debug;
532    }
533
534    /**
535     * Should be a time zone parseable by ZoneId.of() Used by the DateUtils.renderLocalDate() method when there
536     * is no time zone for the particular user.
537     *
538     * @return
539     */
540    public String getTimeZone() {
541        return timeZone;
542    }
543
544    public void setTimeZone(String timeZone) {
545        this.timeZone = timeZone;
546    }
547
548    public ZoneId getTimeZoneId() {
549        return timeZoneId;
550    }
551
552    public void setTimeZoneId(ZoneId timeZoneId) {
553        this.timeZoneId = timeZoneId;
554    }
555
556    /**
557     * Usually set by the command line options, rather than the stallion.toml file.
558     *
559     * If true, templates and assets will automatically be checked for changes from the original source
560     * and re-compiled if they have changed.
561     *
562     * @return
563     */
564    public Boolean getDevMode() {
565        return devMode;
566    }
567
568    public void setDevMode(Boolean devMode) {
569        this.devMode = devMode;
570    }
571
572    /**
573     * Set the log level - INFO, FINE, FINER, FINEST
574     *
575     * @return
576     */
577    public String getLogLevel() {
578        return logLevel;
579    }
580
581    public void setLogLevel(String logLevel) {
582        this.logLevel = logLevel;
583    }
584
585    public Map<String, String> getPackageLogLevels() {
586        return packageLogLevels;
587    }
588
589    public void setPackageLogLevels(Map<String, String> packageLogLevels) {
590        this.packageLogLevels = packageLogLevels;
591    }
592
593
594    /**
595     * The current environment name, set by command line options.
596     *
597     * @return
598     */
599    public String getEnv() {
600        return env;
601    }
602
603    public void setEnv(String env) {
604        this.env = env;
605    }
606
607    /**
608     * True by default if in debug mode, false on production. If true,
609     * bundle files will be included on the page as individual source files, rather than as a concatenated,
610     * minified file.
611     *
612     * @return
613     */
614    public Boolean getBundleDebug() {
615        return bundleDebug;
616    }
617
618    public void setBundleDebug(Boolean bundleDebug) {
619        this.bundleDebug = bundleDebug;
620    }
621
622    /**
623     * The name of your site, accessible in templates via {{ site.name }}
624     *
625     * @return
626     */
627    public String getSiteName() {
628        return siteName;
629    }
630
631    public void setSiteName(String siteName) {
632        this.siteName = siteName;
633    }
634
635    /**
636     * The default title of your site, will be used in the HTML title tag in the default base template
637     * if no other title for a page is defined.
638     *
639     *
640     * @return
641     */
642    public String getDefaultTitle() {
643        return defaultTitle;
644    }
645
646    public void setDefaultTitle(String defaultTitle) {
647        this.defaultTitle = defaultTitle;
648    }
649
650    /**
651     * The default meta tag description value. Used if there is no override for the particular page or endpoint.
652     * @return
653     */
654    public String getMetaDescription() {
655        return metaDescription;
656    }
657
658    public void setMetaDescription(String metaDescription) {
659        this.metaDescription = metaDescription;
660    }
661
662    /**
663     * Get the name of the author of the site
664     *
665     * @return
666     */
667    public String getAuthorName() {
668        return authorName;
669    }
670
671    public void setAuthorName(String authorName) {
672        this.authorName = authorName;
673    }
674
675    /**
676     * The port the server should run on. Usually set by the command line options.
677     *
678     * @return
679     */
680    public Integer getPort() {
681        return port;
682    }
683
684    public void setPort(Integer port) {
685        this.port = port;
686    }
687
688    /**
689     * The root site folder that contains the conf directory and the conf/stallion.toml files.
690     * This is always set via the command line options, or defaults to the current working directory.
691     *
692     * @return
693     */
694    public String getTargetFolder() {
695        return targetFolder;
696    }
697
698    public void setTargetFolder(String targetFolder) {
699        this.targetFolder = targetFolder;
700    }
701
702    public StrictnessLevel getStrictnessLevel() {
703        return strictnessLevel;
704    }
705
706    public void setStrictnessLevel(StrictnessLevel strictnessLevel) {
707        this.strictnessLevel = strictnessLevel;
708    }
709
710    /**
711     * A list of paths that will be checked for a 200 response when the health check
712     * is run.
713     *
714     * @return
715     */
716    public List<String[]> getHealthCheckEndpoints() {
717        return healthCheckEndpoints;
718    }
719
720    public void setHealthCheckEndpoints(List<String[]> healthCheckEndpoints) {
721        this.healthCheckEndpoints = healthCheckEndpoints;
722    }
723
724    /**
725     * A secret token that must be passed in when accessing the health endpoints.
726     *
727     * @return
728     */
729    public String getHealthCheckSecret() {
730        return healthCheckSecret;
731    }
732
733    public void setHealthCheckSecret(String healthCheckSecret) {
734        this.healthCheckSecret = healthCheckSecret;
735    }
736
737
738    /**
739     * If true, exceptions will emailed to the admin defined in the email configuration.
740     *
741     * @return
742     */
743    public Boolean getEmailErrors() {
744        return emailErrors;
745    }
746
747    public void setEmailErrors(Boolean emailErrors) {
748        this.emailErrors = emailErrors;
749    }
750
751    /**
752     * A map of 301 redirects in the form of original URL or path to destination URL or path.
753     *
754     * @return
755     */
756    public Map<String, String> getRedirects() {
757        return redirects;
758    }
759
760    public void setRedirects(Map<String, String> redirects) {
761        this.redirects = redirects;
762    }
763
764    /**
765     * The meta tag value for generator. "Stallion" by default.
766     * @return
767     */
768    public String getMetaGenerator() {
769        return metaGenerator;
770    }
771
772    public void setMetaGenerator(String metaGenerator) {
773        this.metaGenerator = metaGenerator;
774    }
775
776    /**
777     * The path to the log file when file-based logging is on.
778     *
779     * @return
780     */
781    public String getLogFile() {
782        return logFile;
783    }
784
785    public void setLogFile(String logFile) {
786        this.logFile = logFile;
787    }
788
789    /**
790     * If true, log to the console instead of file. This defaults to true in local mode.
791     *
792     * @return
793     */
794    public Boolean getLogToConsole() {
795        return logToConsole;
796    }
797
798    public void setLogToConsole(Boolean logToConsole) {
799        this.logToConsole = logToConsole;
800    }
801
802    public Boolean getLogToFile() {
803        return logToFile;
804    }
805
806    public void setLogToFile(Boolean logToFile) {
807        this.logToFile = logToFile;
808    }
809
810    /**
811     * True if this is a developer running locally, false if this is running deployed on a server.
812     *
813     * If localMode is true, production jobs and tasks will not be run and logging will go to the console.
814     *
815     *
816     * @return
817     */
818    public Boolean getLocalMode() {
819        return localMode;
820    }
821
822    public void setLocalMode(Boolean localMode) {
823        this.localMode = localMode;
824    }
825
826
827    /**
828     * A mapping of internal rewrites, from source path to destintion path.
829     * @return
830     */
831    public Map<String, String> getRewrites() {
832        return rewrites;
833    }
834
835    public void setRewrites(Map<String, String> rewrites) {
836        this.rewrites = rewrites;
837    }
838
839    /**
840     * A list of domains that content is also accesible at, and their mapping
841     * to URL paths.
842     *
843     * @return
844     */
845    public List<SecondaryDomain> getSecondaryDomains() {
846        return secondaryDomains;
847    }
848
849    public Settings setSecondaryDomains(List<SecondaryDomain> secondaryDomains) {
850        this.secondaryDomains = secondaryDomains;
851        return this;
852    }
853
854    public List<Map.Entry<Pattern, String>> getRewriteCompiledPatterns() {
855        return rewriteCompiledPatterns;
856    }
857
858    public void setRewriteCompiledPatterns(List<Map.Entry<Pattern, String>> rewriteCompiledPatterns) {
859        this.rewriteCompiledPatterns = rewriteCompiledPatterns;
860    }
861
862    /**
863     * A list of arrays, each array has two values, a source regular expression and a destination path.
864     * @return
865     */
866    public List<String[]> getRewritePatterns() {
867        return rewritePatterns;
868    }
869
870    public void setRewritePatterns(List<String[]> rewritePatterns) {
871        this.rewritePatterns = rewritePatterns;
872    }
873
874    /**
875     * Configuration for integration with a cloud file storage, such as S3.
876     *
877     * @return
878     */
879    public CloudStorageSettings getCloudStorage() {
880        return cloudStorage;
881    }
882
883    public void setCloudStorage(CloudStorageSettings cloudStorage) {
884        this.cloudStorage = cloudStorage;
885    }
886
887    /**
888     * Default CSS styles and colors that will be used in the default templates
889     * for log in, password reset emails, etc.
890     *
891     * @return
892     */
893    public StyleSettings getStyles() {
894        return styles;
895    }
896
897    public void setStyles(StyleSettings style) {
898        this.styles = style;
899    }
900
901    /**
902     * For deployed sites using stablehand, which node this is.
903     *
904     * @return
905     */
906    public Integer getNodeNumber() {
907        return nodeNumber;
908    }
909
910    public Settings setNodeNumber(Integer nodeNumber) {
911        this.nodeNumber = nodeNumber;
912        return this;
913    }
914
915    /**
916     * A random token used for preventing spam in form submissions and comments.
917     *
918     * @return
919     */
920    public String getAntiSpamSecret() {
921        return antiSpamSecret;
922    }
923
924    public Settings setAntiSpamSecret(String antiSpamSecret) {
925        this.antiSpamSecret = antiSpamSecret;
926        return this;
927    }
928
929    /**
930     * If true, the default form submission endpoint will be disabled.
931     * @return
932     */
933    public Boolean getDisableFormSubmissions() {
934        return disableFormSubmissions;
935    }
936
937    public Settings setDisableFormSubmissions(Boolean disableFormSubmissions) {
938        this.disableFormSubmissions = disableFormSubmissions;
939        return this;
940    }
941
942
943    /**
944     * If there is a proxy server in front of Stallion, that proxy server will set
945     * the IP address of the original requester and put it in a header. Add that header
946     * name here so that Stallion can know the IP of the original requester.
947     *
948     * @return
949     */
950    public String getIpHeaderName() {
951        return ipHeaderName;
952    }
953
954    public Settings setIpHeaderName(String ipHeaderName) {
955        this.ipHeaderName = ipHeaderName;
956        return this;
957    }
958
959    /**
960     * Configuration for enabling users to give out OAuth access
961     *
962     * @return
963     */
964    public OAuthSettings getoAuth() {
965        return oAuth;
966    }
967
968    public Settings setoAuth(OAuthSettings oAuth) {
969        this.oAuth = oAuth;
970        return this;
971    }
972
973    public Long getFilterCacheSize() {
974        return filterCacheSize;
975    }
976
977    public Settings setFilterCacheSize(Long filterCacheSize) {
978        this.filterCacheSize = filterCacheSize;
979        return this;
980    }
981
982    public SecretsSettings getSecrets() {
983        return secrets;
984    }
985
986    public Settings setSecrets(SecretsSettings secrets) {
987        this.secrets = secrets;
988        return this;
989    }
990
991    /**
992     * Included in the templates for error pages.
993     *
994     * @return
995     */
996    public String getSupportEmail() {
997        return supportEmail;
998    }
999
1000    public Settings setSupportEmail(String supportEmail) {
1001        this.supportEmail = supportEmail;
1002        return this;
1003    }
1004
1005
1006    /*
1007    public List<PublishingConfig> getPublishing() {
1008        return publishing;
1009    }
1010
1011
1012    public Settings setPublishing(List publishing) {
1013        if (publishing == null) {
1014            this.publishing = null;
1015            return this;
1016        }
1017        if (publishing.size() == 0) {
1018            this.publishing = new ArrayList<>();
1019            return this;
1020        }
1021        this.publishing = new ArrayList<>();
1022        if (publishing.get(0) instanceof Map) {
1023            for(Object o: publishing) {
1024                PublishingConfig config = new PublishingConfig();
1025                Map<String, Object> m = (Map<String, Object>)o;
1026                for(Map.Entry<String, Object> e: m.entrySet()) {
1027                    Object value = e.getValue();
1028                    if (e.getKey().equals("basePort") && e.getValue() instanceof Long) {
1029                        value = Math.toIntExact((Long)value);
1030                    }
1031                    PropertyUtils.setProperty(config, e.getKey(), value);
1032                }
1033                this.publishing.add(config);
1034            }
1035        } else {
1036            this.publishing.addAll(publishing);
1037        }
1038        return this;
1039    }
1040    */
1041
1042    public Long getAppCreatedMillis() {
1043        return appCreatedMillis;
1044    }
1045
1046    public Settings setAppCreatedMillis(Long appCreatedMillis) {
1047        this.appCreatedMillis = appCreatedMillis;
1048        return this;
1049    }
1050
1051    public List<AssetPreprocessorConfig> getAssetPreprocessors() {
1052        return assetPreprocessors;
1053    }
1054
1055    public Settings setAssetPreprocessors(List assetPreProcessors) {
1056        this.assetPreprocessors = convertMapListToObjects(assetPreProcessors, AssetPreprocessorConfig.class);
1057        for (AssetPreprocessorConfig config: this.assetPreprocessors) {
1058            if (empty(config.getCommand()) || empty(config.getName()) || empty(config.getExtension())) {
1059                throw new ConfigException("Asset Pre-processors must have a valid command, name, and extension");
1060            }
1061        }
1062        return this;
1063    }
1064
1065    protected List convertMapListToObjects(List maps, Class targetClass) {
1066        if (maps.size() == 0) {
1067            return maps;
1068        }
1069        if (targetClass.isAssignableFrom(maps.get(0).getClass())) {
1070            return maps;
1071        }
1072        List items = new ArrayList<>();
1073        for (Object o: maps) {
1074            Map<String, Object> map = (Map<String, Object>)o;
1075            try {
1076                Object instance = targetClass.newInstance();
1077                for(Map.Entry<String, Object> e: map.entrySet()) {
1078                    Object value = e.getValue();
1079                    if (e.getKey().equals("basePort") && e.getValue() instanceof Long) {
1080                        value = Math.toIntExact((Long)value);
1081                    }
1082                    PropertyUtils.setProperty(instance, e.getKey(), value);
1083                }
1084                items.add(instance);
1085
1086            } catch (InstantiationException e) {
1087                throw new RuntimeException(e);
1088            } catch (IllegalAccessException e) {
1089                throw new RuntimeException(e);
1090            }
1091        }
1092        return items;
1093    }
1094
1095    public String getxFrameOptions() {
1096        return xFrameOptions;
1097    }
1098
1099    public Settings setxFrameOptions(String xFrameOptions) {
1100        this.xFrameOptions = xFrameOptions;
1101        return this;
1102    }
1103
1104
1105
1106    public String getNginxClientMaxBodySize() {
1107        return nginxClientMaxBodySize;
1108    }
1109
1110    public Settings setNginxClientMaxBodySize(String nginxClientMaxBodySize) {
1111        this.nginxClientMaxBodySize = nginxClientMaxBodySize;
1112        return this;
1113    }
1114
1115
1116    public String getNginxProxyReadTimeout() {
1117        return nginxProxyReadTimeout;
1118    }
1119
1120    public Settings setNginxProxyReadTimeout(String nginxProxyReadTimeout) {
1121        this.nginxProxyReadTimeout = nginxProxyReadTimeout;
1122        return this;
1123    }
1124}