Skip to content

Commit

Permalink
Move calendar initialization to server startup
Browse files Browse the repository at this point in the history
  • Loading branch information
devinrsmith committed Jan 12, 2024
1 parent 76b96ce commit 74ac3e1
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 95 deletions.
1 change: 1 addition & 0 deletions engine/time/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dependencies {
implementation project(':Configuration')
implementation project(':log-factory')
implementation depJdom2
Classpaths.inheritDagger(project)

testImplementation TestTools.projectDependency(project, 'Base')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
import org.jdom2.input.SAXBuilder;
import org.jetbrains.annotations.NotNull;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.time.*;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -89,38 +91,78 @@ public static BusinessCalendar loadBusinessCalendar(@NotNull final String file)
public static BusinessCalendar loadBusinessCalendar(@NotNull final File file) {
Require.neqNull(file, "file");
final BusinessCalendarInputs in = parseBusinessCalendarInputs(file);
return new BusinessCalendar(in.calendarName, in.description,
in.timeZone, in.firstValidDate, in.lastValidDate,
in.standardBusinessDay, in.weekendDays, in.holidays);
}

/**
* Loads a business calendar from an XML input stream.
*
* @param inputStream XML input stream
* @return business calendar.
* @throws RequirementFailure if the input is null
*/
public static BusinessCalendar loadBusinessCalendar(@NotNull final InputStream inputStream) {
Require.neqNull(inputStream, "inputStream");
final BusinessCalendarInputs in = parseBusinessCalendarInputs(inputStream);
return new BusinessCalendar(in.calendarName, in.description,
in.timeZone, in.firstValidDate, in.lastValidDate,
in.standardBusinessDay, in.weekendDays, in.holidays);
}

/**
* Loads a business calendar from an XML resource.
*
* @param resource XML input stream
* @return business calendar.
*/
public static BusinessCalendar loadBusinessCalendarFromResource(String resource) throws IOException {
final InputStream in = Calendars.class.getResourceAsStream(resource);
if (in == null) {
throw new RuntimeException("Could not open resource " + resource + " from classpath");
}
try (final InputStream bin = new BufferedInputStream(in)) {
return BusinessCalendarXMLParser.loadBusinessCalendar(bin);
}
}

private static BusinessCalendarInputs parseBusinessCalendarInputs(@NotNull final File file) {
Require.neqNull(file, "file");
try {
final BusinessCalendarInputs calendarElements = new BusinessCalendarInputs();

Element root = loadXMLRootElement(file);
calendarElements.calendarName = getText(getRequiredChild(root, "name"));
calendarElements.timeZone = TimeZoneAliases.zoneId(getText(getRequiredChild(root, "timeZone")));
calendarElements.description = getText(getRequiredChild(root, "description"));
calendarElements.firstValidDate =
DateTimeUtils.parseLocalDate(getText(getRequiredChild(root, "firstValidDate")));
calendarElements.lastValidDate =
DateTimeUtils.parseLocalDate(getText(getRequiredChild(root, "lastValidDate")));
calendarElements.holidays = parseHolidays(root, calendarElements.timeZone);

// Set the default values
final Element defaultElement = getRequiredChild(root, "default");
calendarElements.weekendDays = parseWeekendDays(defaultElement);
calendarElements.standardBusinessDay = parseCalendarDaySchedule(defaultElement);

return calendarElements;
return fill(loadXMLRootElement(file));
} catch (Exception e) {
throw new RuntimeException("Unable to load calendar file: file=" + file.getPath(), e);
}
}

private static BusinessCalendarInputs parseBusinessCalendarInputs(@NotNull final InputStream in) {
Require.neqNull(in, "in");
try {
return fill(loadXMLRootElement(in));
} catch (Exception e) {
throw new RuntimeException("Unable to load calendar file: inputStream=" + in, e);
}
}

private static BusinessCalendarInputs fill(Element root) throws Exception {
final BusinessCalendarInputs calendarElements = new BusinessCalendarInputs();
calendarElements.calendarName = getText(getRequiredChild(root, "name"));
calendarElements.timeZone = TimeZoneAliases.zoneId(getText(getRequiredChild(root, "timeZone")));
calendarElements.description = getText(getRequiredChild(root, "description"));
calendarElements.firstValidDate =
DateTimeUtils.parseLocalDate(getText(getRequiredChild(root, "firstValidDate")));
calendarElements.lastValidDate =
DateTimeUtils.parseLocalDate(getText(getRequiredChild(root, "lastValidDate")));
calendarElements.holidays = parseHolidays(root, calendarElements.timeZone);

// Set the default values
final Element defaultElement = getRequiredChild(root, "default");
calendarElements.weekendDays = parseWeekendDays(defaultElement);
calendarElements.standardBusinessDay = parseCalendarDaySchedule(defaultElement);
return calendarElements;
}

private static Element loadXMLRootElement(File calendarFile) throws Exception {
final Document doc;

Expand All @@ -136,6 +178,21 @@ private static Element loadXMLRootElement(File calendarFile) throws Exception {
return doc.getRootElement();
}

private static Element loadXMLRootElement(InputStream in) throws Exception {
final Document doc;

try {
final SAXBuilder builder = new SAXBuilder();
doc = builder.build(in);
} catch (JDOMException e) {
throw new Exception("Error parsing business calendar: inputStream=" + in, e);
} catch (IOException e) {
throw new Exception("Error loading business calendar: inputStream=" + in, e);
}

return doc.getRootElement();
}

private static Element getRequiredChild(@NotNull final Element root, final String child) throws Exception {
Element element = root.getChild(child);
if (element != null) {
Expand Down
115 changes: 39 additions & 76 deletions engine/time/src/main/java/io/deephaven/time/calendar/Calendars.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
import org.jetbrains.annotations.NotNull;

import java.io.*;
import java.nio.file.NoSuchFileException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Consumer;

/**
* A collection of business calendars.
Expand All @@ -26,84 +27,47 @@ public class Calendars {
private static final String BUSINESS_CALENDAR_PROP_INTERNAL = "Calendar.importPath";
private static final String BUSINESS_CALENDAR_PROP_USER = "Calendar.userImportPath";
private static String defaultName = Configuration.getInstance().getProperty("Calendar.default");
private static final Map<String, BusinessCalendar> calMap = new TreeMap<>();

// Variable should only be accessed through getMap()
private static volatile Map<String, BusinessCalendar> calMap;

private Calendars() {}

// region Load

// Get the initialized map of calendars.
// Initilization is deferred to so that errors are easier for users to find.
private static Map<String, BusinessCalendar> getMap() {
if (calMap != null) {
return calMap;
}

synchronized (Calendars.class) {
if (calMap != null) {
return calMap;
}

calMap = new TreeMap<>();
final Configuration configuration = Configuration.getInstance();

loadProperty(configuration, BUSINESS_CALENDAR_PROP_INTERNAL);

if (configuration.hasProperty(BUSINESS_CALENDAR_PROP_USER)) {
loadProperty(configuration, BUSINESS_CALENDAR_PROP_USER);
}

return calMap;
/**
* Loads the line-separated calendar resources from the resource file configuration value
* {@value BUSINESS_CALENDAR_PROP_INTERNAL}. If the resource file configuration value
* {@value BUSINESS_CALENDAR_PROP_USER} exists, those line-separated calendar resources will be returned as well.
*
* @return the calendars
*/
public static List<BusinessCalendar> calendarsFromConfiguration() {
final Configuration configuration = Configuration.getInstance();
final List<BusinessCalendar> configurationCalendars = new ArrayList<>(
loadCalendarsFromResourceList(configuration.getProperty(BUSINESS_CALENDAR_PROP_INTERNAL)));
if (configuration.hasProperty(BUSINESS_CALENDAR_PROP_USER)) {
configurationCalendars.addAll(
loadCalendarsFromResourceList(configuration.getProperty(BUSINESS_CALENDAR_PROP_USER)));
}
return configurationCalendars;
}

private static void loadProperty(final Configuration configuration, final String property) {
final String location = configuration.getProperty(property);
try {
load(location);
} catch (Exception e) {
logger.warn().append("Problem loading calendars. property=").append(property)
.append(" importPath=").append(location).append(e).endl();
throw new RuntimeException("Problem loading calendars. property=" + property +
" importPath=" + location, e);
}
}
private Calendars() {}

private static void load(final String businessCalendarConfig) throws NoSuchFileException {
final InputStream configToLoad = Calendars.class.getResourceAsStream(businessCalendarConfig);
// region Load

if (configToLoad == null) {
logger.warn("Could not find " + businessCalendarConfig + " on classpath");
throw new RuntimeException("Could not open " + businessCalendarConfig + " from classpath");
private static List<BusinessCalendar> loadCalendarsFromResourceList(String resource) {
final InputStream in = Calendars.class.getResourceAsStream(resource);
if (in == null) {
logger.warn("Could not find resource " + resource + " on classpath");
throw new RuntimeException("Could not open resource " + resource + " from classpath");
}

final Consumer<String> consumer = (filePath) -> {
try {
final InputStream inputStream = Calendars.class.getResourceAsStream(filePath);
if (inputStream != null) {
final File calendarFile = inputStreamToFile(inputStream);
final BusinessCalendar businessCalendar =
BusinessCalendarXMLParser.loadBusinessCalendar(calendarFile);
addCalendar(businessCalendar);
// noinspection ResultOfMethodCallIgnored
calendarFile.delete();
} else {
logger.warn("Could not open " + filePath + " from classpath");
throw new RuntimeException("Could not open " + filePath + " from classpath");
}
} catch (Exception e) {
logger.warn("Problem loading calendar: location=" + businessCalendarConfig, e);
throw new RuntimeException("Problem loading calendar: location=" + businessCalendarConfig, e);
final List<BusinessCalendar> calendars = new ArrayList<>();
try (final BufferedReader config = new BufferedReader(new InputStreamReader(in))) {
final Iterator<String> it = config.lines().iterator();
while (it.hasNext()) {
final String calendarResource = it.next();
calendars.add(BusinessCalendarXMLParser.loadBusinessCalendarFromResource(calendarResource));
}
};

try (final BufferedReader config = new BufferedReader(new InputStreamReader(configToLoad))) {
config.lines().forEach(consumer);
return calendars;
} catch (Exception e) {
logger.warn("Problem loading calendar: location=" + businessCalendarConfig, e);
throw new RuntimeException("Problem loading calendar: location=" + businessCalendarConfig, e);
logger.warn("Problem loading calendar: location=" + resource, e);
throw new RuntimeException("Problem loading calendar: location=" + resource, e);
}
}

Expand All @@ -115,7 +79,7 @@ private static void load(final String businessCalendarConfig) throws NoSuchFileE
*/
public synchronized static void removeCalendar(final String name) {
Require.neqNull(name, "name");
getMap().remove(name);
calMap.remove(name);
}

/**
Expand All @@ -131,8 +95,7 @@ public synchronized static void addCalendar(final BusinessCalendar cal) {
if (!NameValidator.isValidQueryParameterName(name)) {
throw new IllegalArgumentException("Invalid name for calendar: name='" + name + "'");
}

final Map<String, BusinessCalendar> map = getMap();
final Map<String, BusinessCalendar> map = calMap;

if (map.containsKey(name)) {
final Calendar oldCalendar = map.get(name);
Expand Down Expand Up @@ -219,7 +182,7 @@ public synchronized static BusinessCalendar calendar(final String name) {
}

final String n = name.toUpperCase();
final Map<String, BusinessCalendar> map = getMap();
final Map<String, BusinessCalendar> map = calMap;

if (!map.containsKey(n)) {
throw new IllegalArgumentException("No such calendar: " + name);
Expand Down Expand Up @@ -258,7 +221,7 @@ public synchronized static String calendarName() {
* @return names of all available calendars
*/
public synchronized static String[] calendarNames() {
return getMap().keySet().toArray(String[]::new);
return calMap.keySet().toArray(String[]::new);
}

// endregion
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2016-2023 Deephaven Data Labs and Patent Pending
*/
package io.deephaven.time.calendar;

import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ElementsIntoSet;

import java.util.HashSet;
import java.util.Set;

/**
* Provides the {@link BusinessCalendar business calendars} fromm {@link Calendars#calendarsFromConfiguration()}.
*/
@Module
public interface CalendarsFromConfigurationModule {

@Provides
@ElementsIntoSet
static Set<BusinessCalendar> providesCalendarsFromConfiguration() {
return new HashSet<>(Calendars.calendarsFromConfiguration());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@

public class TestCalendars extends BaseArrayTestCase {

static {
for (BusinessCalendar calendar : Calendars.calendarsFromConfiguration()) {
Calendars.addCalendar(calendar);
}
}

public void testDefault() {
final BusinessCalendar calendar = Calendars.calendar();
assertEquals(Configuration.getInstance().getProperty("Calendar.default"), Calendars.calendarName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
*/
public class TestStaticCalendarMethods extends BaseArrayTestCase {

static {
for (BusinessCalendar calendar : Calendars.calendarsFromConfiguration()) {
Calendars.addCalendar(calendar);
}
}

private final Map<Class<?>, Object[]> data = new HashMap<>();

{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.deephaven.io.logger.LogBuffer;
import io.deephaven.io.logger.LogBufferOutputStream;
import io.deephaven.server.auth.CommunityAuthorizationModule;
import io.deephaven.time.calendar.CalendarsFromConfigurationModule;
import io.deephaven.server.console.ExecutionContextModule;
import io.deephaven.server.console.groovy.GroovyConsoleSessionModule;
import io.deephaven.server.console.python.PythonConsoleSessionModule;
Expand Down Expand Up @@ -56,6 +57,7 @@ public class EmbeddedServer {
CommunityAuthorizationModule.class,
ClientDefaultsModule.class,
ObfuscatingErrorTransformerModule.class,
CalendarsFromConfigurationModule.class,
})
public interface PythonServerComponent extends JettyServerComponent {
@Component.Builder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import dagger.Module;
import io.deephaven.client.ClientDefaultsModule;
import io.deephaven.time.calendar.CalendarsFromConfigurationModule;
import io.deephaven.server.console.ExecutionContextModule;
import io.deephaven.server.console.groovy.GroovyConsoleSessionModule;
import io.deephaven.server.console.python.PythonConsoleSessionModule;
Expand Down Expand Up @@ -36,6 +37,7 @@
* @see ExecutionContextModule
* @see ClientDefaultsModule
* @see ObfuscatingErrorTransformerModule
* @see CalendarsFromConfigurationModule
*/
@Module(includes = {
DeephavenApiServerModule.class,
Expand All @@ -49,6 +51,7 @@
ExecutionContextModule.class,
ClientDefaultsModule.class,
ObfuscatingErrorTransformerModule.class,
CalendarsFromConfigurationModule.class,
})
public interface CommunityDefaultsModule {
}
Loading

0 comments on commit 74ac3e1

Please sign in to comment.