diff --git a/.gitattributes b/.gitattributes index 3a672b7ae37..34eb4ac7c18 100644 --- a/.gitattributes +++ b/.gitattributes @@ -78,27 +78,17 @@ api/gwtsrc/org/labkey/api/gwt/client/model/GWTIndex.java -text api/gwtsrc/org/labkey/api/gwt/client/model/GWTPropertyDescriptor.java -text api/gwtsrc/org/labkey/api/gwt/client/model/GWTPropertyValidator.java -text api/gwtsrc/org/labkey/api/gwt/client/model/PropertyValidatorType.java -text -api/gwtsrc/org/labkey/api/gwt/client/pipeline/GWTPipelineConfig.java -text -api/gwtsrc/org/labkey/api/gwt/client/pipeline/GWTPipelineTask.java -text -api/gwtsrc/org/labkey/api/gwt/client/pipeline/PipelineGWTService.java -text -api/gwtsrc/org/labkey/api/gwt/client/pipeline/PipelineGWTServiceAsync.java -text api/gwtsrc/org/labkey/api/gwt/client/ui/BoundTextBox.java -text api/gwtsrc/org/labkey/api/gwt/client/ui/DirtyCallback.java -text api/gwtsrc/org/labkey/api/gwt/client/ui/domain/CancellationException.java -text -api/gwtsrc/org/labkey/api/gwt/client/ui/domain/ImportStatus.java -text -api/gwtsrc/org/labkey/api/gwt/client/ui/FontButton.java -text api/gwtsrc/org/labkey/api/gwt/client/ui/HelpPopup.java -text api/gwtsrc/org/labkey/api/gwt/client/ui/ImageButton.java -text -api/gwtsrc/org/labkey/api/gwt/client/ui/LinkButton.java -text -api/gwtsrc/org/labkey/api/gwt/client/ui/StringListBox.java -text api/gwtsrc/org/labkey/api/gwt/client/ui/TextBoxDialogBox.java -text api/gwtsrc/org/labkey/api/gwt/client/ui/WebPartPanel.java -text api/gwtsrc/org/labkey/api/gwt/client/ui/WidgetUpdatable.java -text api/gwtsrc/org/labkey/api/gwt/client/ui/WindowUtil.java -text api/gwtsrc/org/labkey/api/gwt/client/util/BooleanProperty.java -text api/gwtsrc/org/labkey/api/gwt/client/util/ColorGenerator.java -text -api/gwtsrc/org/labkey/api/gwt/client/util/DateProperty.java -text -api/gwtsrc/org/labkey/api/gwt/client/util/DoubleProperty.java -text api/gwtsrc/org/labkey/api/gwt/client/util/ErrorDialogAsyncCallback.java -text api/gwtsrc/org/labkey/api/gwt/client/util/IntegerProperty.java -text api/gwtsrc/org/labkey/api/gwt/client/util/IPropertyWrapper.java -text @@ -197,7 +187,6 @@ api/src/org/labkey/api/admin/AdminConsoleService.java -text api/src/org/labkey/api/admin/AdminUrls.java -text api/src/org/labkey/api/admin/BaseFolderWriter.java -text api/src/org/labkey/api/admin/CoreUrls.java -text -api/src/org/labkey/api/admin/DiagnosticsLink.java -text api/src/org/labkey/api/admin/FolderArchiveDataTypes.java -text api/src/org/labkey/api/admin/FolderExportContext.java -text api/src/org/labkey/api/admin/FolderExportPermission.java -text @@ -466,7 +455,6 @@ api/src/org/labkey/api/data/dialect/StandardJdbcMetaDataLocator.java -text api/src/org/labkey/api/data/dialect/StandardTableResolver.java -text api/src/org/labkey/api/data/dialect/StatementWrapper.java -text api/src/org/labkey/api/data/dialect/TableResolver.java -text -api/src/org/labkey/api/data/dialect/TestUpgradeCode.java -text api/src/org/labkey/api/data/DimensionColumnInfo.java -text api/src/org/labkey/api/data/DisplayColumnDecorator.java -text api/src/org/labkey/api/data/DisplayColumnFactory.java -text @@ -1137,7 +1125,6 @@ api/src/org/labkey/api/security/ActionNames.java -text api/src/org/labkey/api/security/AdminConsoleAction.java -text api/src/org/labkey/api/security/ApiKeyManager.java -text api/src/org/labkey/api/security/AuthenticatedRequest.java -text -api/src/org/labkey/api/security/AuthenticatedResponse.java -text api/src/org/labkey/api/security/AuthenticationLogoAttachmentParent.java -text api/src/org/labkey/api/security/AuthenticationLogoType.java -text api/src/org/labkey/api/security/AuthenticationManager.java -text @@ -2352,7 +2339,6 @@ pipeline/src/org/labkey/pipeline/permission.jsp -text pipeline/src/org/labkey/pipeline/PipelineCommandTestCase.java -text pipeline/src/org/labkey/pipeline/PipelineController.java -text pipeline/src/org/labkey/pipeline/PipelineDirectoryImpl.java -text -pipeline/src/org/labkey/pipeline/PipelineGWTServiceImpl.java -text pipeline/src/org/labkey/pipeline/PipelineModule.java -text pipeline/src/org/labkey/pipeline/PipelineWebdavProvider.java -text pipeline/src/org/labkey/pipeline/PipelineWebPart.java -text @@ -2906,7 +2892,6 @@ study/test/src/org/labkey/test/tests/study/StudyDemoModeTest.java -text study/test/src/org/labkey/test/tests/study/StudyExportTest.java -text study/test/src/org/labkey/test/tests/study/StudyLotsOfParticipantsTest.java -text study/test/src/org/labkey/test/tests/study/StudyManualTest.java -text -study/test/src/org/labkey/test/tests/study/StudyMergeParticipantsTest.java -text study/test/src/org/labkey/test/tests/study/StudyProtocolDesignerTest.java -text study/test/src/org/labkey/test/tests/study/StudyPublishTest.java -text study/test/src/org/labkey/test/tests/study/StudyReloadColumnInferenceTest.java -text diff --git a/api/src/org/labkey/api/ApiModule.java b/api/src/org/labkey/api/ApiModule.java index 7faf690a140..3736a5bca48 100644 --- a/api/src/org/labkey/api/ApiModule.java +++ b/api/src/org/labkey/api/ApiModule.java @@ -138,16 +138,14 @@ import org.labkey.api.security.SecurityManager; import org.labkey.api.security.UserManager; import org.labkey.api.security.ValidEmail; -import org.labkey.api.settings.AdminConsole; -import org.labkey.api.settings.AdminConsole.OptionalFeatureFlag; import org.labkey.api.settings.AppProps; import org.labkey.api.settings.AppPropsTestCase; import org.labkey.api.settings.BaseServerProperties; import org.labkey.api.settings.LookAndFeelFolderPropertiesTest; import org.labkey.api.settings.LookAndFeelProperties; +import org.labkey.api.settings.OptionalFeatureFlag; import org.labkey.api.settings.OptionalFeatureService; import org.labkey.api.settings.OptionalFeatureService.FeatureType; -import org.labkey.api.settings.OptionalFeatureStartupListener; import org.labkey.api.settings.WriteableLookAndFeelProperties; import org.labkey.api.util.ChecksumUtil; import org.labkey.api.util.Compress; @@ -232,11 +230,11 @@ protected void init() LabKeyManagement.register(new StandardMBean(new OperationsMXBeanImpl(), OperationsMXBean.class, true), "Operations"); - AdminConsole.addOptionalFeatureFlag(new OptionalFeatureFlag(FileStream.STAGE_FILE_TRANSFERS, + OptionalFeatureService.get().addFeatureFlag(new OptionalFeatureFlag(FileStream.STAGE_FILE_TRANSFERS, "Stage file uploads and downloads to temporary local file", "When using a non-local file system, using a specific API that requires a locally staged copy of the file as the source can sometimes be significantly faster than streaming the file directly to/from storage", false, false, FeatureType.Optional)); - AdminConsole.addOptionalFeatureFlag(new OptionalFeatureFlag(MothershipReport.FEATURE_FLAG_EXTENDED_METRICS, + OptionalFeatureService.get().addFeatureFlag(new OptionalFeatureFlag(MothershipReport.FEATURE_FLAG_EXTENDED_METRICS, "Send extended metrics information to LabKey", "Send additional usage information to https://www.labkey.org along with standard usage metrics. This " + "information includes a list of unique, active users registered on the server. Providing this information " + @@ -263,9 +261,7 @@ protected void doStartup(ModuleContext moduleContext) ContentSecurityPolicyFilter.registerMetricsProvider(); ApiKeyManager.get().handleStartupProperties(); MailHelper.init(); - // Handle optional feature and system maintenance startup properties as late as possible; we want all optional - // features and system maintenance tasks to be registered first - ContextListener.addStartupListener(new OptionalFeatureStartupListener()); + // Handle system maintenance startup properties as late as possible; we want all system maintenance tasks to be registered first ContextListener.addStartupListener(new SystemMaintenanceStartupListener()); ContextListener.addStartupListener(new StartupPropertyStartupListener()); } diff --git a/api/src/org/labkey/api/module/CodeOnlyModule.java b/api/src/org/labkey/api/module/CodeOnlyModule.java index 19c884b7eb7..25575b7b57e 100644 --- a/api/src/org/labkey/api/module/CodeOnlyModule.java +++ b/api/src/org/labkey/api/module/CodeOnlyModule.java @@ -25,13 +25,9 @@ import java.util.Set; /** - * Bit of a misnomer, but I couldn't think of a better name. These modules provide code and resources, but don't manage - * any schemas, don't run SQL scripts, and don't need to do anything at upgrade time. This simplifies the implementation - * of such modules. - * - * Perhaps this should implement Module instead of extending DefaultModule. - * - * Created by adam on 6/29/2016. + * This is a bit of a misnomer, but I couldn't think of a better name. These modules provide code and resources, but + * don't manage any schemas, don't run SQL scripts, and don't need to do anything at upgrade time. This simplifies the + * implementation of such modules. Perhaps this should implement Module instead of extending DefaultModule. */ public abstract class CodeOnlyModule extends DefaultModule { diff --git a/api/src/org/labkey/api/query/AbstractQueryUpdateService.java b/api/src/org/labkey/api/query/AbstractQueryUpdateService.java index 4a1ce0f0744..efd4e9f0545 100644 --- a/api/src/org/labkey/api/query/AbstractQueryUpdateService.java +++ b/api/src/org/labkey/api/query/AbstractQueryUpdateService.java @@ -125,7 +125,8 @@ public abstract class AbstractQueryUpdateService implements QueryUpdateService { - private TableInfo _queryTable; + private final TableInfo _queryTable; + private boolean _bulkLoad = false; private CaseInsensitiveHashMap _columnImportMap = null; private VirtualFile _att = null; @@ -159,7 +160,7 @@ protected TableInfo getQueryTable() } @Override - public boolean hasPermission(@NotNull UserPrincipal user, Class acl) + public boolean hasPermission(@NotNull UserPrincipal user, @NotNull Class acl) { return getQueryTable().hasPermission(user, acl); } @@ -261,8 +262,8 @@ protected final DataIteratorContext getDataIteratorContext(BatchValidationExcept } /** - * if QUS want to use something other than PKs to select existing rows for merge it can override this method - * Used only for generating ExistingRecordDataIterator at the moment + * If QUS wants to use something other than PKs to select existing rows for merge, it can override this method. + * Used only for generating ExistingRecordDataIterator at the moment. */ protected Set getSelectKeys(DataIteratorContext context) { @@ -295,7 +296,7 @@ public DataIteratorBuilder createImportDIB(User user, Container container, DataI /** * Implementation to use insertRows() while we migrate to using DIB for all code paths *

- * DataIterator should/must use same error collection as passed in + * DataIterator should/must use the same error collection as passed in */ @Deprecated protected int _importRowsUsingInsertRows(User user, Container container, DataIterator rows, BatchValidationException errors, Map extraScriptContext) diff --git a/api/src/org/labkey/api/query/QueryUpdateService.java b/api/src/org/labkey/api/query/QueryUpdateService.java index eacf1c70d36..1aea04e48fa 100644 --- a/api/src/org/labkey/api/query/QueryUpdateService.java +++ b/api/src/org/labkey/api/query/QueryUpdateService.java @@ -319,6 +319,6 @@ int truncateRows(User user, Container container, @Nullable Map con */ boolean isBulkLoad(); - /** Setup the data iterator for any special behavior needed for the target table */ + /** Set up the data iterator for any special behavior needed for the target table */ default void configureDataIteratorContext(DataIteratorContext context) {} } diff --git a/api/src/org/labkey/api/settings/AdminConsole.java b/api/src/org/labkey/api/settings/AdminConsole.java index f20057fae03..ffd1dba46a6 100644 --- a/api/src/org/labkey/api/settings/AdminConsole.java +++ b/api/src/org/labkey/api/settings/AdminConsole.java @@ -15,27 +15,19 @@ */ package org.labkey.api.settings; -import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; import org.labkey.api.security.User; import org.labkey.api.security.permissions.Permission; -import org.labkey.api.settings.OptionalFeatureService.FeatureType; -import org.labkey.api.util.StringUtilsLabKey; -import org.labkey.api.util.logging.LogHelper; import org.labkey.api.view.ActionURL; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.TreeSet; -import java.util.concurrent.ConcurrentSkipListSet; /** * Manages registration for links to be shown in the Admin Console, as well as experimental features that can @@ -63,9 +55,7 @@ public String getCaption() } } - private static final Logger LOG = LogHelper.getLogger(AdminConsole.class, "Warnings about optional feature flag names"); private static final Map> _links = new HashMap<>(); - private static final Set _optionalFlags = new ConcurrentSkipListSet<>(); static { @@ -142,116 +132,4 @@ public int compareTo(@NotNull AdminLink o) return getText().compareToIgnoreCase(o.getText()); } } - - /** - * @param flag must be unique. Can be used as a startup property to enable/disable the task, but only if it follows - * the Java identifier rules (e.g., alphanumeric plus _, start with a letter, no spaces). - */ - public static void addExperimentalFeatureFlag(String flag, String title, String description, boolean requiresRestart) - { - addOptionalFeatureFlag(new OptionalFeatureFlag(flag, title, description, requiresRestart, false, FeatureType.Experimental)); - } - - public static void addOptionalFeatureFlag(OptionalFeatureFlag optionalFeatureFlag) - { - _optionalFlags.add(optionalFeatureFlag); - } - - // Return all optional features, regardless of type - public static Collection getOptionalFeatureFlags() - { - return Collections.unmodifiableSet(_optionalFlags); - } - - // Return all optional features having the specified type - public static Collection getOptionalFeatureFlags(FeatureType type) - { - return _optionalFlags.stream() - .filter(flag -> flag.getType() == type) - .toList(); - } - - public static class OptionalFeatureFlag implements Comparable, StartupProperty - { - private final String _flag; - private final String _title; - private final String _description; - private final boolean _requiresRestart; - private final boolean _hidden; - private final FeatureType _type; - - /** - * @param flag must be unique. Can be used as a startup property to enable/disable the task, but only if it follows - * the Java identifier rules (e.g., alphanumeric plus _, start with a letter, no spaces). - */ - public OptionalFeatureFlag(String flag, String title, String description, boolean requiresRestart, boolean hidden, FeatureType type) - { - _flag = flag; - _title = title; - _description = description; - _requiresRestart = requiresRestart; - _hidden = hidden; - _type = type; - } - - public String getFlag() - { - return _flag; - } - - public String getTitle() - { - return _title; - } - - @Override - public String getDescription() - { - return _description; - } - - public boolean isRequiresRestart() - { - return _requiresRestart; - } - - @Override - public int compareTo(@NotNull AdminConsole.OptionalFeatureFlag o) - { - return getTitle().compareToIgnoreCase(o.getTitle()); - } - - public boolean isEnabled() - { - return AppProps.getInstance().isOptionalFeatureEnabled(getFlag()); - } - - public boolean isHidden() - { - return _hidden; - } - - public FeatureType getType() - { - return _type; - } - - // StartupProperty implementation - - /** - * Returns {@code null} if {@code getFlag()} does not conform to the property name rules. - */ - @Nullable - @Override - public String getPropertyName() - { - String name = getFlag(); - if (!StringUtilsLabKey.isValidJavaIdentifier(name)) - { - LOG.debug("Feature flag name doesn't conform to the property name rules so it won't be available as a startup property: {}", name); - name = null; - } - return name; - } - } } diff --git a/api/src/org/labkey/api/settings/AppProps.java b/api/src/org/labkey/api/settings/AppProps.java index 5eb3a6d52d8..c7c7cc781fb 100644 --- a/api/src/org/labkey/api/settings/AppProps.java +++ b/api/src/org/labkey/api/settings/AppProps.java @@ -41,7 +41,8 @@ public interface AppProps String SCOPE_SITE_SETTINGS = "SiteSettings"; - String OPTIONAL_FEATURE = "experimentalFeature"; // Used for all optional features; "experimental" for historical reasons. + // Used for all optional features; "experimental" for historical reasons. + String OPTIONAL_FEATURE_PREFIX = "experimentalFeature."; String SCOPE_OPTIONAL_FEATURE = "ExperimentalFeature"; // Startup property prefix for all optional features; "Experimental" for historical reasons. String EXPERIMENTAL_NO_GUESTS = "disableGuestAccount"; String EXPERIMENTAL_BLOCKER = "blockMaliciousClients"; diff --git a/api/src/org/labkey/api/settings/AppPropsImpl.java b/api/src/org/labkey/api/settings/AppPropsImpl.java index d424085b871..80d9b622895 100644 --- a/api/src/org/labkey/api/settings/AppPropsImpl.java +++ b/api/src/org/labkey/api/settings/AppPropsImpl.java @@ -75,7 +75,6 @@ class AppPropsImpl extends AbstractWriteableSettingsGroup implements AppProps static final String LOOK_AND_FEEL_REVISION = "logoRevision"; static final String DEFAULT_LSID_AUTHORITY_PROP = "defaultLsidAuthority"; - static final String OPTIONAL_FEATURE_PREFIX = OPTIONAL_FEATURE + "."; static final String ALLOW_LIST_DELIMITER = "\n"; private static final String SITE_CONFIG_NAME = "SiteConfig"; diff --git a/api/src/org/labkey/api/settings/OptionalFeatureFlag.java b/api/src/org/labkey/api/settings/OptionalFeatureFlag.java new file mode 100644 index 00000000000..baf0c447ed5 --- /dev/null +++ b/api/src/org/labkey/api/settings/OptionalFeatureFlag.java @@ -0,0 +1,94 @@ +package org.labkey.api.settings; + +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.labkey.api.settings.OptionalFeatureService.FeatureType; +import org.labkey.api.util.StringUtilsLabKey; +import org.labkey.api.util.logging.LogHelper; + +public class OptionalFeatureFlag implements Comparable, StartupProperty +{ + private static final Logger LOG = LogHelper.getLogger(OptionalFeatureFlag.class, "Warnings about optional feature flag names"); + + private final String _flag; + private final String _title; + private final String _description; + private final boolean _requiresRestart; + private final boolean _hidden; + private final FeatureType _type; + + /** + * @param flag must be unique. Can be used as a startup property to enable/disable the task, but only if it follows + * the Java identifier rules (e.g., alphanumeric plus _, start with a letter, no spaces). + */ + public OptionalFeatureFlag(String flag, String title, String description, boolean requiresRestart, boolean hidden, FeatureType type) + { + _flag = flag; + _title = title; + _description = description; + _requiresRestart = requiresRestart; + _hidden = hidden; + _type = type; + } + + public String getFlag() + { + return _flag; + } + + public String getTitle() + { + return _title; + } + + @Override + public String getDescription() + { + return _description; + } + + public boolean isRequiresRestart() + { + return _requiresRestart; + } + + @Override + public int compareTo(@NotNull OptionalFeatureFlag o) + { + return getTitle().compareToIgnoreCase(o.getTitle()); + } + + public boolean isEnabled() + { + return AppProps.getInstance().isOptionalFeatureEnabled(getFlag()); + } + + public boolean isHidden() + { + return _hidden; + } + + public FeatureType getType() + { + return _type; + } + + // StartupProperty implementation + + /** + * Returns {@code null} if {@code getFlag()} does not conform to the property name rules. + */ + @Nullable + @Override + public String getPropertyName() + { + String name = getFlag(); + if (!StringUtilsLabKey.isValidJavaIdentifier(name)) + { + LOG.warn("Feature flag name doesn't conform to the property name rules so it won't be available as a startup property: {}", name); + name = null; + } + return name; + } +} diff --git a/api/src/org/labkey/api/settings/OptionalFeatureService.java b/api/src/org/labkey/api/settings/OptionalFeatureService.java index b70eaa94613..4244f5c5713 100644 --- a/api/src/org/labkey/api/settings/OptionalFeatureService.java +++ b/api/src/org/labkey/api/settings/OptionalFeatureService.java @@ -15,17 +15,12 @@ */ package org.labkey.api.settings; +import org.jetbrains.annotations.NotNull; import org.labkey.api.security.User; import org.labkey.api.services.ServiceRegistry; import org.labkey.api.util.HtmlString; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CopyOnWriteArrayList; - -import static org.labkey.api.settings.AppPropsImpl.OPTIONAL_FEATURE_PREFIX; +import java.util.Collection; /** * Manages the optional features that can be enabled or disabled within a given deployment, and their current state. @@ -34,9 +29,12 @@ */ public interface OptionalFeatureService { - static OptionalFeatureService get() + static @NotNull OptionalFeatureService get() { - return ServiceRegistry.get().getService(OptionalFeatureService.class); + OptionalFeatureService svc = ServiceRegistry.get().getService(OptionalFeatureService.class); + if (null == svc) + throw new IllegalStateException("OptionalFeatureService not found"); + return svc; } static void setInstance(OptionalFeatureService impl) @@ -44,6 +42,23 @@ static void setInstance(OptionalFeatureService impl) ServiceRegistry.get().registerService(OptionalFeatureService.class, impl); } + /** + * @param flag must be unique. Can be used as a startup property to enable/disable the task, but only if it follows + * the Java identifier rules (e.g., alphanumeric plus _, start with a letter, no spaces). + */ + default void addExperimentalFeatureFlag(String flag, String title, String description, boolean requiresRestart) + { + addFeatureFlag(new OptionalFeatureFlag(flag, title, description, requiresRestart, false, FeatureType.Experimental)); + } + + void addFeatureFlag(OptionalFeatureFlag optionalFeatureFlag); + + // Return all optional features, regardless of type + Collection getOptionalFeatureFlags(); + + // Return all optional features having the specified type + Collection getOptionalFeatureFlags(FeatureType type); + void addFeatureListener(String feature, OptionalFeatureListener listener); boolean isFeatureEnabled(String feature); @@ -57,10 +72,10 @@ interface OptionalFeatureListener void featureChanged(String feature, boolean enabled); } - // FeatureType is an optional feature flag property that determines the admin page on which the feature appears. - // The property is used at run-time registration only; it is not persisted. All optional properties are persisted - // and retrieved the same way, and can be populated using the "ExperimentalFeature" startup property prefix. This - // means features can be switched to a different FeatureType at any time. + // FeatureType determines the admin page on which an optional feature appears. The property is used at run-time + // registration only; it is not persisted. All optional properties are persisted and retrieved the same way, and can + // be populated using the "ExperimentalFeature" startup property prefix. This means features can be switched to a + // different FeatureType at any time. enum FeatureType { Deprecated @@ -107,61 +122,4 @@ public HtmlString getAdminGuidance() public abstract HtmlString getAdminGuidance(); } - - class OptionalFeatureServiceImpl implements OptionalFeatureService - { - private Map> _listeners; - - public OptionalFeatureServiceImpl() - { - } - - @Override - public void addFeatureListener(String feature, OptionalFeatureListener listener) - { - if (_listeners == null) - _listeners = Collections.synchronizedMap(new HashMap<>()); - - if (!_listeners.containsKey(feature)) - _listeners.put(feature, new CopyOnWriteArrayList<>()); - - _listeners.get(feature).add(listener); - } - - @Override - public boolean isFeatureEnabled(String feature) - { - return AppProps.getInstance().isOptionalFeatureEnabled(feature); - } - - @Override - public void removeFeatureListener(String feature, OptionalFeatureListener listener) - { - if (_listeners != null && _listeners.containsKey(feature)) - { - _listeners.get(feature).remove(listener); - } - } - - @Override - public void setFeatureEnabled(String feature, boolean enabled, User user) - { - WriteableAppProps props = AppProps.getWriteableInstance(); - setFeatureEnabled(feature, enabled, props); - props.save(user); - } - - private void setFeatureEnabled(String feature, boolean enabled, WriteableAppProps props) - { - props.storeBooleanValue(OPTIONAL_FEATURE_PREFIX + feature, enabled); - - if (_listeners != null && _listeners.containsKey(feature)) - { - for (OptionalFeatureListener listener : _listeners.get(feature)) - { - listener.featureChanged(feature, enabled); - } - } - } - } } diff --git a/api/src/org/labkey/api/settings/WriteableAppProps.java b/api/src/org/labkey/api/settings/WriteableAppProps.java index 32933476119..b806b0249f4 100644 --- a/api/src/org/labkey/api/settings/WriteableAppProps.java +++ b/api/src/org/labkey/api/settings/WriteableAppProps.java @@ -247,4 +247,9 @@ public void setAllowedExternalResourceHosts(String jsonArray) { storeStringValue(ALLOWED_EXTERNAL_RESOURCES, jsonArray); } + + public void setFeatureEnabled(String feature, boolean enabled) + { + storeBooleanValue(OPTIONAL_FEATURE_PREFIX + feature, enabled); + } } diff --git a/api/src/org/labkey/api/view/Portal.java b/api/src/org/labkey/api/view/Portal.java index e4a6dc2e8e4..3d5fd35b5f7 100644 --- a/api/src/org/labkey/api/view/Portal.java +++ b/api/src/org/labkey/api/view/Portal.java @@ -1590,7 +1590,7 @@ public static WebPartFactory getPortalPart(String name) } // The maintained view map is now case-insensitive, so this is identical to the above method - @Deprecated // Call getPortalPart() instead + @Deprecated // Call getPortalPart() instead - TODO: DELETE, unused public static WebPartFactory getPortalPartCaseInsensitive(String name) { return getPortalPart(name); diff --git a/core/src/org/labkey/core/CoreModule.java b/core/src/org/labkey/core/CoreModule.java index 76db398883d..d3c559450b5 100644 --- a/core/src/org/labkey/core/CoreModule.java +++ b/core/src/org/labkey/core/CoreModule.java @@ -143,8 +143,6 @@ import org.labkey.api.security.roles.ReaderRole; import org.labkey.api.security.roles.Role; import org.labkey.api.security.roles.RoleManager; -import org.labkey.api.settings.AdminConsole; -import org.labkey.api.settings.AdminConsole.OptionalFeatureFlag; import org.labkey.api.settings.AppProps; import org.labkey.api.settings.CustomLabelService; import org.labkey.api.settings.CustomLabelService.CustomLabelServiceImpl; @@ -153,9 +151,9 @@ import org.labkey.api.settings.LookAndFeelPropertiesManager; import org.labkey.api.settings.LookAndFeelPropertiesManager.ResourceType; import org.labkey.api.settings.LookAndFeelPropertiesManager.SiteResourceHandler; +import org.labkey.api.settings.OptionalFeatureFlag; import org.labkey.api.settings.OptionalFeatureService; import org.labkey.api.settings.OptionalFeatureService.FeatureType; -import org.labkey.api.settings.OptionalFeatureService.OptionalFeatureServiceImpl; import org.labkey.api.settings.ProductConfiguration; import org.labkey.api.settings.StandardStartupPropertyHandler; import org.labkey.api.settings.StartupPropertyEntry; @@ -216,6 +214,8 @@ import org.labkey.core.admin.DisplayFormatValidationProviderFactory; import org.labkey.core.admin.FilesSiteSettingsAction; import org.labkey.core.admin.MenuViewFactory; +import org.labkey.core.admin.OptionalFeatureServiceImpl; +import org.labkey.core.admin.OptionalFeatureStartupListener; import org.labkey.core.admin.importer.FolderTypeImporterFactory; import org.labkey.core.admin.importer.MissingValueImporterFactory; import org.labkey.core.admin.importer.ModulePropertiesImporterFactory; @@ -521,28 +521,28 @@ public QuerySchema createSchema(DefaultSchema schema, Module module) }); } - AdminConsole.addExperimentalFeatureFlag(NotificationMenuView.EXPERIMENTAL_NOTIFICATION_MENU, "Notifications Menu", - "Notifications 'inbox' count display in the header bar with click to show the notifications panel of unread notifications.", false); - AdminConsole.addExperimentalFeatureFlag(DataColumn.EXPERIMENTAL_USE_QUERYSELECT_COMPONENT, "Use QuerySelect for row insert/update form", - "This feature will switch the query based select inputs on the row insert/update form to use the React QuerySelect" + - "component. This will allow for a user to view the first 100 options in the select but then use type ahead" + - "search to find the other select values.", false); - AdminConsole.addExperimentalFeatureFlag(SQLFragment.FEATUREFLAG_DISABLE_STRICT_CHECKS, "Disable SQLFragment strict checks", - "SQLFragment now has very strict usage validation, these checks may cause errors in code that has not been updated. Turn on this feature to disable checks.", false); - AdminConsole.addExperimentalFeatureFlag(LoginController.FEATUREFLAG_DISABLE_LOGIN_XFRAME, "Disable Login X-FRAME-OPTIONS=DENY", - "By default LabKey disables all framing of login related actions. Disabling this feature will revert to using the standard site settings.", false); - AdminConsole.addExperimentalFeatureFlag(PageTemplate.EXPERIMENTAL_SHORT_CIRCUIT_ROBOTS, - "Short-circuit robots", - "Save resources by not rendering pages marked as 'noindex' for robots. This is experimental as not all robots are search engines.", - false); - AdminConsole.addOptionalFeatureFlag(new OptionalFeatureFlag(AppProps.DEPRECATED_OBJECT_LEVEL_DISCUSSIONS, - "Restore Object-Level Discussions", - "This option and all support for Object-Level Discussions will be removed in LabKey Server v25.11.", - false, false, FeatureType.Deprecated)); - AdminConsole.addOptionalFeatureFlag(new OptionalFeatureFlag(SimpleTranslator.DEPRECATED_NULL_MISSING_VALUE_RESOLUTION, - "Resolve Missing Lookup Values to Null", - "When Lookup Validation for a field is not selected and lookup by alternate key is enabled, resolves missing lookup values to null instead of throwing an error. This option will be removed in LabKey Server v25.11.", - false, false, OptionalFeatureService.FeatureType.Deprecated)); + OptionalFeatureService.get().addExperimentalFeatureFlag(NotificationMenuView.EXPERIMENTAL_NOTIFICATION_MENU, "Notifications Menu", + "Notifications 'inbox' count display in the header bar with click to show the notifications panel of unread notifications.", false); + OptionalFeatureService.get().addExperimentalFeatureFlag(DataColumn.EXPERIMENTAL_USE_QUERYSELECT_COMPONENT, "Use QuerySelect for row insert/update form", + "This feature will switch the query based select inputs on the row insert/update form to use the React QuerySelect" + + "component. This will allow for a user to view the first 100 options in the select but then use type ahead" + + "search to find the other select values.", false); + OptionalFeatureService.get().addExperimentalFeatureFlag(SQLFragment.FEATUREFLAG_DISABLE_STRICT_CHECKS, "Disable SQLFragment strict checks", + "SQLFragment now has very strict usage validation, these checks may cause errors in code that has not been updated. Turn on this feature to disable checks.", false); + OptionalFeatureService.get().addExperimentalFeatureFlag(LoginController.FEATUREFLAG_DISABLE_LOGIN_XFRAME, "Disable Login X-FRAME-OPTIONS=DENY", + "By default LabKey disables all framing of login related actions. Disabling this feature will revert to using the standard site settings.", false); + OptionalFeatureService.get().addExperimentalFeatureFlag(PageTemplate.EXPERIMENTAL_SHORT_CIRCUIT_ROBOTS, + "Short-circuit robots", + "Save resources by not rendering pages marked as 'noindex' for robots. This is experimental as not all robots are search engines.", + false); + OptionalFeatureService.get().addFeatureFlag(new OptionalFeatureFlag(AppProps.DEPRECATED_OBJECT_LEVEL_DISCUSSIONS, + "Restore Object-Level Discussions", + "This option and all support for Object-Level Discussions will be removed in LabKey Server v25.11.", + false, false, FeatureType.Deprecated)); + OptionalFeatureService.get().addFeatureFlag(new OptionalFeatureFlag(SimpleTranslator.DEPRECATED_NULL_MISSING_VALUE_RESOLUTION, + "Resolve Missing Lookup Values to Null", + "When Lookup Validation for a field is not selected and lookup by alternate key is enabled, resolves missing lookup values to null instead of throwing an error. This option will be removed in LabKey Server v25.11.", + false, false, OptionalFeatureService.FeatureType.Deprecated)); SiteValidationService svc = SiteValidationService.get(); if (null != svc) @@ -1066,6 +1066,9 @@ public void moduleStartupComplete(ServletContext servletContext) } }); + // Handle optional feature startup properties as late as possible; we want all optional features to be registered first + ContextListener.addStartupListener(new OptionalFeatureStartupListener()); + LabKeyScriptEngineManager svc = LabKeyScriptEngineManager.get(); // populate script engine definitions values read from startup properties if (svc instanceof ScriptEngineManagerImpl) @@ -1114,27 +1117,27 @@ public void moduleStartupComplete(ServletContext servletContext) .forEach(ss::addDocumentParser); } - AdminConsole.addExperimentalFeatureFlag(AppProps.EXPERIMENTAL_NO_GUESTS, + OptionalFeatureService.get().addExperimentalFeatureFlag(AppProps.EXPERIMENTAL_NO_GUESTS, "No Guest Account", "Disable the guest account", false); - AdminConsole.addExperimentalFeatureFlag(AppProps.EXPERIMENTAL_BLOCKER, + OptionalFeatureService.get().addExperimentalFeatureFlag(AppProps.EXPERIMENTAL_BLOCKER, "Block malicious clients", "Reject requests from clients that appear malicious. Turn this feature off if you want to run a security scanner.", false); - AdminConsole.addExperimentalFeatureFlag(FEATURE_FLAG_DISABLE_ENFORCE_CSP, + OptionalFeatureService.get().addExperimentalFeatureFlag(FEATURE_FLAG_DISABLE_ENFORCE_CSP, "Disable enforce Content Security Policy", "Stop sending the " + ContentSecurityPolicyFilter.ContentSecurityPolicyType.Enforce.getHeaderName() + " header to browsers, " + "but continue sending the " + ContentSecurityPolicyFilter.ContentSecurityPolicyType.Report.getHeaderName() + " header. " + "This turns off an important layer of security for the entire site, so use it as a last resort only on a temporary basis " + "(e.g., if an enforce CSP breaks critical functionality).", false); - AdminConsole.addExperimentalFeatureFlag(DataRegion.EXPERIMENTAL_DATA_REGION_ASYNC_TOTAL_ROWS, + OptionalFeatureService.get().addExperimentalFeatureFlag(DataRegion.EXPERIMENTAL_DATA_REGION_ASYNC_TOTAL_ROWS, "Data Region Async Total Rows", "Enable asynchronous calculation of total rows for data regions. This can improve performance for large datasets.", false); - AdminConsole.addOptionalFeatureFlag(new OptionalFeatureFlag(EXPERIMENTAL_LOCAL_MARKETING_UPDATE, + OptionalFeatureService.get().addFeatureFlag(new OptionalFeatureFlag(EXPERIMENTAL_LOCAL_MARKETING_UPDATE, "Self test marketing updates", "Test marketing updates from this local server (requires the mothership module).", false, true, FeatureType.Experimental)); OptionalFeatureService.get().addFeatureListener(EXPERIMENTAL_LOCAL_MARKETING_UPDATE, (feature, enabled) -> { // update the timer task when this setting changes @@ -1165,7 +1168,7 @@ public void moduleStartupComplete(ServletContext servletContext) results.put("javaRuntime", javaInfo); results.put("distributionFilename", AppProps.getInstance().getDistributionFilename()); results.put("applicationMenuDisplayMode", LookAndFeelProperties.getInstance(ContainerManager.getRoot()).getApplicationMenuDisplayMode()); - results.put("optionalFeatures", AdminConsole.getOptionalFeatureFlags().stream() + results.put("optionalFeatures", OptionalFeatureService.get().getOptionalFeatureFlags().stream() .collect(Collectors.groupingBy(optionalFeatureFlag -> optionalFeatureFlag.getType().name().toLowerCase(), Collectors.mapping(flag -> flag, Collectors.toMap(OptionalFeatureFlag::getFlag, OptionalFeatureFlag::isEnabled)) )) @@ -1572,15 +1575,17 @@ public void enumerateDocuments(final SearchService.IndexTask task, @NotNull fina properties.put(SearchService.PROPERTY.categories.toString(), SearchService.navigationCategory.getName()); ActionURL startURL = PageFlowUtil.urlProvider(ProjectUrls.class).getStartURL(c); startURL.setExtraPath(c.getId()); - WebdavResource doc = new SimpleDocumentResource(c.getParsedPath(), - "link:" + c.getId(), - c.getEntityId(), - "text/plain", - body, - startURL, - UserManager.getUser(c.getCreatedBy()), c.getCreated(), - null, null, - properties); + WebdavResource doc = new SimpleDocumentResource( + c.getParsedPath(), + "link:" + c.getId(), + c.getEntityId(), + "text/plain", + body, + startURL, + UserManager.getUser(c.getCreatedBy()), c.getCreated(), + null, null, + properties + ); (null==task?ss.defaultTask():task).addResource(doc, SearchService.PRIORITY.item); }; // running this asynchronously seems to expose race conditions in domain checking/creation @@ -1692,11 +1697,11 @@ private boolean setSiteResource(SiteResourceHandler resourceHandler, StartupProp } catch(Exception e) { - LOG.error(String.format("Exception setting %1$s during server startup.", prop.getName()), e); + LOG.error("Exception setting {} during server startup.", prop.getName(), e); } } - LOG.error(String.format("Unable to find %1$s resource during server startup: %2$s", prop.getName(), prop.getValue())); + LOG.error("Unable to find {} resource during server startup: {}", prop.getName(), prop.getValue()); return false; } diff --git a/core/src/org/labkey/core/admin/OptionalFeatureServiceImpl.java b/core/src/org/labkey/core/admin/OptionalFeatureServiceImpl.java new file mode 100644 index 00000000000..6e0308e8f44 --- /dev/null +++ b/core/src/org/labkey/core/admin/OptionalFeatureServiceImpl.java @@ -0,0 +1,91 @@ +package org.labkey.core.admin; + +import org.labkey.api.security.User; +import org.labkey.api.settings.AppProps; +import org.labkey.api.settings.OptionalFeatureFlag; +import org.labkey.api.settings.OptionalFeatureService; +import org.labkey.api.settings.WriteableAppProps; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.CopyOnWriteArrayList; + +public class OptionalFeatureServiceImpl implements OptionalFeatureService +{ + private final Set _optionalFlags = new ConcurrentSkipListSet<>(); + private final Map> _listeners = Collections.synchronizedMap(new HashMap<>()); + + public OptionalFeatureServiceImpl() + { + } + + @Override + public void addFeatureFlag(OptionalFeatureFlag optionalFeatureFlag) + { + _optionalFlags.add(optionalFeatureFlag); + } + + @Override + public Collection getOptionalFeatureFlags() + { + return Collections.unmodifiableSet(_optionalFlags); + } + + @Override + public Collection getOptionalFeatureFlags(FeatureType type) + { + return _optionalFlags.stream() + .filter(flag -> flag.getType() == type) + .toList(); + } + + @Override + public void addFeatureListener(String feature, OptionalFeatureListener listener) + { + if (!_listeners.containsKey(feature)) + _listeners.put(feature, new CopyOnWriteArrayList<>()); + + _listeners.get(feature).add(listener); + } + + @Override + public boolean isFeatureEnabled(String feature) + { + return AppProps.getInstance().isOptionalFeatureEnabled(feature); + } + + @Override + public void removeFeatureListener(String feature, OptionalFeatureListener listener) + { + if (_listeners.containsKey(feature)) + { + _listeners.get(feature).remove(listener); + } + } + + @Override + public void setFeatureEnabled(String feature, boolean enabled, User user) + { + WriteableAppProps props = AppProps.getWriteableInstance(); + setFeatureEnabled(feature, enabled, props); + props.save(user); + } + + private void setFeatureEnabled(String feature, boolean enabled, WriteableAppProps props) + { + props.setFeatureEnabled(feature, enabled); + + if (_listeners.containsKey(feature)) + { + for (OptionalFeatureListener listener : _listeners.get(feature)) + { + listener.featureChanged(feature, enabled); + } + } + } +} diff --git a/api/src/org/labkey/api/settings/OptionalFeatureStartupListener.java b/core/src/org/labkey/core/admin/OptionalFeatureStartupListener.java similarity index 84% rename from api/src/org/labkey/api/settings/OptionalFeatureStartupListener.java rename to core/src/org/labkey/core/admin/OptionalFeatureStartupListener.java index cf42886ef2a..6f9ab6b03e9 100644 --- a/api/src/org/labkey/api/settings/OptionalFeatureStartupListener.java +++ b/core/src/org/labkey/core/admin/OptionalFeatureStartupListener.java @@ -1,9 +1,12 @@ -package org.labkey.api.settings; +package org.labkey.core.admin; import jakarta.servlet.ServletContext; import org.apache.commons.lang3.BooleanUtils; import org.labkey.api.module.ModuleLoader; -import org.labkey.api.settings.AdminConsole.OptionalFeatureFlag; +import org.labkey.api.settings.MapBasedStartupPropertyHandler; +import org.labkey.api.settings.OptionalFeatureFlag; +import org.labkey.api.settings.OptionalFeatureService; +import org.labkey.api.settings.StartupPropertyEntry; import org.labkey.api.util.DOM; import org.labkey.api.util.StartupListener; @@ -35,7 +38,7 @@ public OptionalFeatureStartupPropertyHandler() super( SCOPE_OPTIONAL_FEATURE, OptionalFeatureFlag.class.getName(), - AdminConsole.getOptionalFeatureFlags().stream() + OptionalFeatureService.get().getOptionalFeatureFlags().stream() .filter(flag -> flag.getPropertyName() != null) .sorted(Comparator.comparing(OptionalFeatureFlag::getPropertyName, String.CASE_INSENSITIVE_ORDER)) ); diff --git a/core/src/org/labkey/core/admin/optionalFeatures.jsp b/core/src/org/labkey/core/admin/optionalFeatures.jsp index 4a40b1c506e..64cc65dd0db 100644 --- a/core/src/org/labkey/core/admin/optionalFeatures.jsp +++ b/core/src/org/labkey/core/admin/optionalFeatures.jsp @@ -16,8 +16,8 @@ */ %> <%@ page import="org.labkey.api.security.permissions.AdminOperationsPermission" %> -<%@ page import="org.labkey.api.settings.AdminConsole" %> -<%@ page import="org.labkey.api.settings.AdminConsole.OptionalFeatureFlag" %> +<%@ page import="org.labkey.api.settings.OptionalFeatureFlag" %> +<%@ page import="org.labkey.api.settings.OptionalFeatureService" %> <%@ page import="org.labkey.api.settings.OptionalFeatureService.FeatureType" %> <%@ page import="org.labkey.api.util.HtmlString" %> <%@ page import="org.labkey.core.admin.AdminController.OptionalFeaturesForm" %> @@ -48,7 +48,7 @@ <%=getTroubleshooterWarning(hasAdminOpsPerms, HtmlString.EMPTY_STRING, HtmlString.unsafe("
"))%>

<% - for (OptionalFeatureFlag flag : AdminConsole.getOptionalFeatureFlags(type)) + for (OptionalFeatureFlag flag : OptionalFeatureService.get().getOptionalFeatureFlags(type)) { if (!showHidden && flag.isHidden()) continue; diff --git a/core/src/org/labkey/core/query/PostgresStatActivityTable.java b/core/src/org/labkey/core/query/PostgresStatActivityTable.java index b15e039d8e7..fabdd64ae7d 100644 --- a/core/src/org/labkey/core/query/PostgresStatActivityTable.java +++ b/core/src/org/labkey/core/query/PostgresStatActivityTable.java @@ -136,7 +136,7 @@ protected ConnectionUpdateService(TableInfo queryTable) } @Override - public boolean hasPermission(@NotNull UserPrincipal user, Class acl) + public boolean hasPermission(@NotNull UserPrincipal user, @NotNull Class acl) { return PostgresStatActivityTable.this.hasPermission(user, acl); } diff --git a/core/src/org/labkey/core/thumbnail/ThumbnailServiceImpl.java b/core/src/org/labkey/core/thumbnail/ThumbnailServiceImpl.java index eba689678dd..a353e53b54b 100644 --- a/core/src/org/labkey/core/thumbnail/ThumbnailServiceImpl.java +++ b/core/src/org/labkey/core/thumbnail/ThumbnailServiceImpl.java @@ -15,8 +15,8 @@ */ package org.labkey.core.thumbnail; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.Nullable; import org.labkey.api.attachments.AttachmentFile; import org.labkey.api.attachments.AttachmentService; @@ -37,11 +37,6 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; -/** - * User: adam - * Date: 10/8/11 - * Time: 9:22 AM - */ public class ThumbnailServiceImpl implements ThumbnailService { private static final Logger LOG = LogManager.getLogger(ThumbnailServiceImpl.class); diff --git a/core/src/org/labkey/core/view/template/bootstrap/CoreWarningProvider.java b/core/src/org/labkey/core/view/template/bootstrap/CoreWarningProvider.java index a1c8d1f3e7b..ce0c405fe77 100644 --- a/core/src/org/labkey/core/view/template/bootstrap/CoreWarningProvider.java +++ b/core/src/org/labkey/core/view/template/bootstrap/CoreWarningProvider.java @@ -33,9 +33,8 @@ import org.labkey.api.security.impersonation.AbstractImpersonationContextFactory; import org.labkey.api.security.permissions.SiteAdminPermission; import org.labkey.api.security.permissions.TroubleshooterPermission; -import org.labkey.api.settings.AdminConsole; -import org.labkey.api.settings.AdminConsole.OptionalFeatureFlag; import org.labkey.api.settings.AppProps; +import org.labkey.api.settings.OptionalFeatureFlag; import org.labkey.api.settings.OptionalFeatureService; import org.labkey.api.util.HelpTopic; import org.labkey.api.util.HtmlString; @@ -381,7 +380,7 @@ private void getProbableLeakCountWarnings(Warnings warnings, boolean showAllWarn private void getDeprecatedFeatureWarnings(Warnings warnings, boolean showAllWarnings) { - Collection flags = AdminConsole.getOptionalFeatureFlags(OptionalFeatureService.FeatureType.Deprecated); + Collection flags = OptionalFeatureService.get().getOptionalFeatureFlags(OptionalFeatureService.FeatureType.Deprecated); List deprecated = flags.stream() .filter(flag -> showAllWarnings || flag.isEnabled()) .map(flag -> "\"" + flag.getTitle() + "\"") diff --git a/core/src/org/labkey/core/view/template/bootstrap/WarningServiceImpl.java b/core/src/org/labkey/core/view/template/bootstrap/WarningServiceImpl.java index 1d0756f7e26..c005543005d 100644 --- a/core/src/org/labkey/core/view/template/bootstrap/WarningServiceImpl.java +++ b/core/src/org/labkey/core/view/template/bootstrap/WarningServiceImpl.java @@ -19,7 +19,6 @@ import org.labkey.api.admin.CoreUrls; import org.labkey.api.module.ModuleLoader; import org.labkey.api.security.permissions.TroubleshooterPermission; -import org.labkey.api.settings.AdminConsole; import org.labkey.api.settings.AppProps; import org.labkey.api.settings.OptionalFeatureService; import org.labkey.api.util.HtmlString; @@ -55,7 +54,7 @@ private static class LazyInitializer { if (AppProps.getInstance().isDevMode()) { - AdminConsole.addExperimentalFeatureFlag(EXPERIMENTAL_SHOW_ALL_WARNINGS, "Show all admin warnings", + OptionalFeatureService.get().addExperimentalFeatureFlag(EXPERIMENTAL_SHOW_ALL_WARNINGS, "Show all admin warnings", "Enable to test the admin warning messages generated by most of the warning providers.", false); SHOW_ALL_WARNINGS = OptionalFeatureService.get().isFeatureEnabled(EXPERIMENTAL_SHOW_ALL_WARNINGS); OptionalFeatureService.get().addFeatureListener(EXPERIMENTAL_SHOW_ALL_WARNINGS, (feature, enabled) -> { diff --git a/core/src/org/labkey/core/workbook/WorkbooksTableInfo.java b/core/src/org/labkey/core/workbook/WorkbooksTableInfo.java index f66d951a092..7d0e96539a4 100644 --- a/core/src/org/labkey/core/workbook/WorkbooksTableInfo.java +++ b/core/src/org/labkey/core/workbook/WorkbooksTableInfo.java @@ -72,11 +72,6 @@ import java.util.Set; import java.util.concurrent.Callable; -/** - * User: labkey - * Date: Jan 6, 2010 - * Time: 2:18:59 PM - */ public class WorkbooksTableInfo extends ContainerTable implements UpdateableTableInfo { public WorkbooksTableInfo(CoreQuerySchema coreSchema, ContainerFilter cf) @@ -179,12 +174,6 @@ else if (keys.get("Container") != null) } } - @Override - public boolean hasPermission(@NotNull UserPrincipal user, Class acl) - { - return getQueryTable().hasPermission(user, acl); - } - @Override public List> insertRows(User user, Container container, List> rows, BatchValidationException errors, @Nullable Map configParameters, Map extraScriptContext) { diff --git a/devtools/src/org/labkey/devtools/DevtoolsModule.java b/devtools/src/org/labkey/devtools/DevtoolsModule.java index 053d7cece3c..057033145c1 100644 --- a/devtools/src/org/labkey/devtools/DevtoolsModule.java +++ b/devtools/src/org/labkey/devtools/DevtoolsModule.java @@ -22,7 +22,7 @@ import org.labkey.api.module.CodeOnlyModule; import org.labkey.api.module.ModuleContext; import org.labkey.api.security.AuthenticationManager; -import org.labkey.api.settings.AdminConsole; +import org.labkey.api.settings.OptionalFeatureFlag; import org.labkey.api.settings.OptionalFeatureService; import org.labkey.api.util.JspTestCase; import org.labkey.api.view.WebPartFactory; @@ -63,10 +63,10 @@ protected void init() addController("testsso", TestSsoController.class); AuthenticationManager.registerProvider(new TestSsoProvider()); - AdminConsole.addOptionalFeatureFlag(new AdminConsole.OptionalFeatureFlag(Domain.EXPERIMENTAL_FUZZ_STORAGE_NAME, - "'fuzz' name of database columns used to back domain properties", - "This is dev/test feature and not intended for any production usage.", - false, false, OptionalFeatureService.FeatureType.Experimental)); + OptionalFeatureService.get().addFeatureFlag(new OptionalFeatureFlag(Domain.EXPERIMENTAL_FUZZ_STORAGE_NAME, + "'fuzz' name of database columns used to back domain properties", + "This is dev/test feature and not intended for any production usage.", + false, false, OptionalFeatureService.FeatureType.Experimental)); } @Override diff --git a/devtools/src/org/labkey/devtools/ToolsController.java b/devtools/src/org/labkey/devtools/ToolsController.java index d2039eb1b3e..e2a8289ae21 100644 --- a/devtools/src/org/labkey/devtools/ToolsController.java +++ b/devtools/src/org/labkey/devtools/ToolsController.java @@ -10,12 +10,14 @@ import org.labkey.api.data.DbScope; import org.labkey.api.module.Module; import org.labkey.api.module.ModuleLoader; +import org.labkey.api.module.SupportedDatabase; import org.labkey.api.reader.Readers; import org.labkey.api.security.RequiresPermission; import org.labkey.api.security.permissions.AdminPermission; import org.labkey.api.util.BaseScanner.Handler; import org.labkey.api.util.ButtonBuilder; import org.labkey.api.util.FileUtil; +import org.labkey.api.util.HtmlString; import org.labkey.api.util.HtmlStringBuilder; import org.labkey.api.util.PageFlowUtil; import org.labkey.api.util.StringUtilsLabKey; @@ -109,7 +111,7 @@ public ModelAndView getView(Object o, BindException errors) ); } - private class GitAttributesView extends HttpView + private class GitAttributesView extends HttpView { private final String _moduleName; @@ -420,7 +422,7 @@ private Collection findJspReferences(Module module, PrintWriter out) Files.walkFileTree(root, new SimpleFileVisitor<>() { @Override - public @NotNull FileVisitResult visitFile(Path file, @NotNull BasicFileAttributes attrs) + public @NotNull FileVisitResult visitFile(@NotNull Path file, @NotNull BasicFileAttributes attrs) { String filePath = file.toString(); if (filePath.endsWith(".java")) @@ -467,25 +469,25 @@ private Collection findJspFiles(Module module, PrintWriter out) Files.walkFileTree(root, new SimpleFileVisitor<>() { @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + public @NotNull FileVisitResult visitFile(@NotNull Path file, @NotNull BasicFileAttributes attrs) { - String filePath = file.toString().replaceAll("\\\\", "/"); - if (filePath.endsWith(".jsp") && !JSPS_TO_IGNORE.contains(filePath)) - { - // Accommodates /org/labkey, /org/scharp, and /com/hphc - int idx = StringUtils.indexOfAny(filePath, "/org/", "/com/"); - - if (-1 != idx) + String filePath = file.toString().replaceAll("\\\\", "/"); + if (filePath.endsWith(".jsp") && !JSPS_TO_IGNORE.contains(filePath)) { - ret.add(filePath.substring(idx)); + // Accommodates /org/labkey, /org/scharp, and /com/hphc + int idx = StringUtils.indexOfAny(filePath, "/org/", "/com/"); + + if (-1 != idx) + { + ret.add(filePath.substring(idx)); + } + else + { + out.println(filter("Can't find \"/org/\" or \"/com/\": " + filePath)); + } } - else - { - out.println(filter("Can't find \"/org/\" or \"/com/\": " + filePath)); - } - } - return FileVisitResult.CONTINUE; + return FileVisitResult.CONTINUE; } }); } @@ -508,7 +510,7 @@ public void addNavTrail(NavTree root) } @RequiresPermission(AdminPermission.class) - public class CheckCrawlerActionsAction extends SimpleViewAction + public class CheckCrawlerActionsAction extends SimpleViewAction { @Override public ModelAndView getView(Object o, BindException errors) throws IOException @@ -672,4 +674,28 @@ public int compareTo(@NotNull ControllerActionId o) } } } + + @RequiresPermission(AdminPermission.class) + public class PostgreSqlOnlyModulesThatHaveSqlServerScriptsAction extends SimpleViewAction + { + @Override + public ModelAndView getView(Object o, BindException errors) + { + List names = ModuleLoader.getInstance().getModules().stream() + .filter(m -> !m.getSupportedDatabasesSet().contains(SupportedDatabase.mssql)) + .filter(m -> !StringUtils.isBlank(m.getSourcePath())) + .filter(m -> new File(m.getSourcePath(), "resources/schemas/dbscripts/sqlserver").exists()) + .map(Module::getName) + .toList(); + + return new HtmlView(HtmlString.of(names.toString())); + } + + @Override + public void addNavTrail(NavTree root) + { + addBeginNavTrail(root); + root.addChild("PostgreSQL-Only Modules That Still Have SQL Server Scripts"); + } + } } diff --git a/experiment/src/org/labkey/experiment/ExperimentModule.java b/experiment/src/org/labkey/experiment/ExperimentModule.java index 7dfb0c6dbd6..a84e2de0f8b 100644 --- a/experiment/src/org/labkey/experiment/ExperimentModule.java +++ b/experiment/src/org/labkey/experiment/ExperimentModule.java @@ -81,8 +81,8 @@ import org.labkey.api.search.SearchService; import org.labkey.api.security.User; import org.labkey.api.security.roles.RoleManager; -import org.labkey.api.settings.AdminConsole; import org.labkey.api.settings.AppProps; +import org.labkey.api.settings.OptionalFeatureService; import org.labkey.api.usageMetrics.UsageMetricsService; import org.labkey.api.util.JspTestCase; import org.labkey.api.util.PageFlowUtil; @@ -239,17 +239,17 @@ protected void init() ExperimentService.get().registerNameExpressionType("aliquots", "exp", "MaterialSource", "aliquotnameexpression"); ExperimentService.get().registerNameExpressionType("dataclass", "exp", "DataClass", "nameexpression"); - AdminConsole.addExperimentalFeatureFlag(AppProps.EXPERIMENTAL_RESOLVE_PROPERTY_URI_COLUMNS, "Resolve property URIs as columns on experiment tables", - "If a column is not found on an experiment table, attempt to resolve the column name as a Property URI and add it as a property column", false); + OptionalFeatureService.get().addExperimentalFeatureFlag(AppProps.EXPERIMENTAL_RESOLVE_PROPERTY_URI_COLUMNS, "Resolve property URIs as columns on experiment tables", + "If a column is not found on an experiment table, attempt to resolve the column name as a Property URI and add it as a property column", false); if (CoreSchema.getInstance().getSqlDialect().isSqlServer()) { - AdminConsole.addExperimentalFeatureFlag(NameGenerator.EXPERIMENTAL_WITH_COUNTER, "Use strict incremental withCounter and rootSampleCount expression", - "When withCounter or rootSampleCount is used in name expression, make sure the count increments one-by-one and does not jump.", true); + OptionalFeatureService.get().addExperimentalFeatureFlag(NameGenerator.EXPERIMENTAL_WITH_COUNTER, "Use strict incremental withCounter and rootSampleCount expression", + "When withCounter or rootSampleCount is used in name expression, make sure the count increments one-by-one and does not jump.", true); } else { - AdminConsole.addExperimentalFeatureFlag(NameGenerator.EXPERIMENTAL_ALLOW_GAP_COUNTER, "Allow gap with withCounter and rootSampleCount expression", - "Check this option if gaps in the count generated by withCounter or rootSampleCount name expression are allowed.", true); + OptionalFeatureService.get().addExperimentalFeatureFlag(NameGenerator.EXPERIMENTAL_ALLOW_GAP_COUNTER, "Allow gap with withCounter and rootSampleCount expression", + "Check this option if gaps in the count generated by withCounter or rootSampleCount name expression are allowed.", true); } diff --git a/filecontent/src/org/labkey/filecontent/FileQueryUpdateService.java b/filecontent/src/org/labkey/filecontent/FileQueryUpdateService.java index 999a0c49e18..7c3d1a6ab75 100644 --- a/filecontent/src/org/labkey/filecontent/FileQueryUpdateService.java +++ b/filecontent/src/org/labkey/filecontent/FileQueryUpdateService.java @@ -280,7 +280,7 @@ private Domain getFileProperties(Container container) } @Override - public boolean hasPermission(@NotNull UserPrincipal user, Class acl) + public boolean hasPermission(@NotNull UserPrincipal user, @NotNull Class acl) { return _container.hasPermission(user, acl); } diff --git a/pipeline/src/org/labkey/pipeline/PipelineModule.java b/pipeline/src/org/labkey/pipeline/PipelineModule.java index 2c692ed9485..d96856a7918 100644 --- a/pipeline/src/org/labkey/pipeline/PipelineModule.java +++ b/pipeline/src/org/labkey/pipeline/PipelineModule.java @@ -47,7 +47,7 @@ import org.labkey.api.pipeline.trigger.PipelineTriggerRegistry; import org.labkey.api.pipeline.trigger.PipelineTriggerType; import org.labkey.api.security.User; -import org.labkey.api.settings.AdminConsole; +import org.labkey.api.settings.OptionalFeatureFlag; import org.labkey.api.settings.OptionalFeatureService; import org.labkey.api.usageMetrics.UsageMetricsService; import org.labkey.api.util.ContextListener; @@ -218,11 +218,14 @@ protected void startupAfterSpringConfig(ModuleContext moduleContext) AuditLogService.get().registerAuditType(new ProtocolManagementAuditProvider()); - AdminConsole.addOptionalFeatureFlag(new AdminConsole.OptionalFeatureFlag(ADVANCED_IMPORT_FLAG, + OptionalFeatureService.get().addFeatureFlag( + new OptionalFeatureFlag( + ADVANCED_IMPORT_FLAG, "Restore 'Advanced Import Options' during Folder import", "This option will be removed in LabKey Server v25.11.", false, false, OptionalFeatureService.FeatureType.Deprecated - )); + ) + ); UsageMetricsService.get().registerUsageMetrics(getName(), () -> { DbSchema pipelineSchema = PipelineSchema.getInstance().getSchema(); diff --git a/query/src/org/labkey/query/QueryModule.java b/query/src/org/labkey/query/QueryModule.java index 5bd0265144f..c0851c0f1b1 100644 --- a/query/src/org/labkey/query/QueryModule.java +++ b/query/src/org/labkey/query/QueryModule.java @@ -70,7 +70,7 @@ import org.labkey.api.security.roles.PlatformDeveloperRole; import org.labkey.api.security.roles.Role; import org.labkey.api.security.roles.RoleManager; -import org.labkey.api.settings.AdminConsole; +import org.labkey.api.settings.OptionalFeatureService; import org.labkey.api.stats.AnalyticsProviderRegistry; import org.labkey.api.stats.SummaryStatisticRegistry; import org.labkey.api.util.JspTestCase; @@ -224,16 +224,17 @@ public QuerySchema createSchema(DefaultSchema schema, Module module) DataViewService.get().registerProvider(QueryDataViewProvider.TYPE, new QueryDataViewProvider()); DataViewService.get().registerProvider(InheritedQueryDataViewProvider.TYPE, new InheritedQueryDataViewProvider()); - AdminConsole.addExperimentalFeatureFlag(QueryView.EXPERIMENTAL_GENERIC_DETAILS_URL, "Generic [details] link in grids/queries", - "This feature will turn on generating a generic [details] URL link in most grids.", false); - AdminConsole.addExperimentalFeatureFlag(QueryServiceImpl.EXPERIMENTAL_LAST_MODIFIED, "Include Last-Modified header on query metadata requests", - "For schema, query, and view metadata requests include a Last-Modified header such that the browser can cache the response. " + - "The metadata is invalidated when performing actions such as creating a new List or modifying the columns on a custom view", false); - AdminConsole.addExperimentalFeatureFlag(USE_ROW_BY_ROW_UPDATE, "Use row-by-row update", "For Query.updateRows api, do row-by-row update, instead of using a prepared statement that updates rows in batches.", false); - AdminConsole.addExperimentalFeatureFlag(QueryServiceImpl.EXPERIMENTAL_PRODUCT_ALL_FOLDER_LOOKUPS, "Less restrictive product folder lookups", - "Allow for lookup fields in product folders to query across all folders within the top-level folder.", false); - AdminConsole.addExperimentalFeatureFlag(QueryServiceImpl.EXPERIMENTAL_PRODUCT_PROJECT_DATA_LISTING_SCOPED, "Product folders display folder-specific data", - "Only list folder-specific data within product folders.", false); + OptionalFeatureService.get().addExperimentalFeatureFlag(QueryView.EXPERIMENTAL_GENERIC_DETAILS_URL, "Generic [details] link in grids/queries", + "This feature will turn on generating a generic [details] URL link in most grids.", false); + OptionalFeatureService.get().addExperimentalFeatureFlag(QueryServiceImpl.EXPERIMENTAL_LAST_MODIFIED, "Include Last-Modified header on query metadata requests", + "For schema, query, and view metadata requests include a Last-Modified header such that the browser can cache the response. " + + "The metadata is invalidated when performing actions such as creating a new List or modifying the columns on a custom view", false); + OptionalFeatureService.get().addExperimentalFeatureFlag(USE_ROW_BY_ROW_UPDATE, "Use row-by-row update", + "For Query.updateRows api, do row-by-row update, instead of using a prepared statement that updates rows in batches.", false); + OptionalFeatureService.get().addExperimentalFeatureFlag(QueryServiceImpl.EXPERIMENTAL_PRODUCT_ALL_FOLDER_LOOKUPS, "Less restrictive product folder lookups", + "Allow for lookup fields in product folders to query across all folders within the top-level folder.", false); + OptionalFeatureService.get().addExperimentalFeatureFlag(QueryServiceImpl.EXPERIMENTAL_PRODUCT_PROJECT_DATA_LISTING_SCOPED, "Product folders display folder-specific data", + "Only list folder-specific data within product folders.", false); } diff --git a/study/src/org/labkey/study/StudyModule.java b/study/src/org/labkey/study/StudyModule.java index c5a62276bfa..e1a7c0208e4 100644 --- a/study/src/org/labkey/study/StudyModule.java +++ b/study/src/org/labkey/study/StudyModule.java @@ -66,7 +66,7 @@ import org.labkey.api.security.roles.RoleManager; import org.labkey.api.services.ServiceRegistry; import org.labkey.api.settings.AdminConsole; -import org.labkey.api.settings.AdminConsole.OptionalFeatureFlag; +import org.labkey.api.settings.OptionalFeatureFlag; import org.labkey.api.settings.OptionalFeatureService; import org.labkey.api.specimen.SpecimenManager; import org.labkey.api.specimen.SpecimenSampleTypeDomainKind; @@ -391,27 +391,27 @@ protected void startupAfterSpringConfig(ModuleContext moduleContext) DatasetDefinition.cleanupOrphanedDatasetDomains(); - AdminConsole.addExperimentalFeatureFlag(StudyQuerySchema.EXPERIMENTAL_STUDY_SUBSCHEMAS, "Use sub-schemas in Study", + OptionalFeatureService.get().addExperimentalFeatureFlag(StudyQuerySchema.EXPERIMENTAL_STUDY_SUBSCHEMAS, "Use sub-schemas in Study", "Separate study tables into three groups 'datasets', 'specimens', and 'design'", false); - AdminConsole.addExperimentalFeatureFlag(DatasetQueryView.EXPERIMENTAL_LINKED_DATASET_CHECK, + OptionalFeatureService.get().addExperimentalFeatureFlag(DatasetQueryView.EXPERIMENTAL_LINKED_DATASET_CHECK, "Assay linked to study consistency check", "Flags rows in assay linked datasets where the subject and timepoint may be different from the source assay.", false); - AdminConsole.addExperimentalFeatureFlag(DatasetQueryView.EXPERIMENTAL_ALLOW_MERGE_WITH_MANAGED_KEYS, + OptionalFeatureService.get().addExperimentalFeatureFlag(DatasetQueryView.EXPERIMENTAL_ALLOW_MERGE_WITH_MANAGED_KEYS, "Allow merge of study dataset that uses server-managed additional key fields", "Merging of dataset that uses server-managed third key (such as GUID or auto RowId) is not officially supported. Unexpected outcome might be experienced when merge is performed.", false); - AdminConsole.addExperimentalFeatureFlag(DatasetQueryView.EXPERIMENTAL_QUERY_DATASETS, + OptionalFeatureService.get().addExperimentalFeatureFlag(DatasetQueryView.EXPERIMENTAL_QUERY_DATASETS, "Allow query based dataset snapshots", "Allow unprovisioned, query-based dataset snapshots to be created.", false); if (SpecimenService.get() == null) { - AdminConsole.addOptionalFeatureFlag(new OptionalFeatureFlag(SpecimenManager.VIEW_SPECIMEN_TABLES_FLAG, + OptionalFeatureService.get().addFeatureFlag(new OptionalFeatureFlag(SpecimenManager.VIEW_SPECIMEN_TABLES_FLAG, "Show read-only specimen tables in the study schema", "Provides a read-only view of specimen data when the specimen module is not present.", false, false, OptionalFeatureService.FeatureType.Optional diff --git a/study/src/org/labkey/study/query/DatasetUpdateService.java b/study/src/org/labkey/study/query/DatasetUpdateService.java index 74087264a23..8c88fc1077e 100644 --- a/study/src/org/labkey/study/query/DatasetUpdateService.java +++ b/study/src/org/labkey/study/query/DatasetUpdateService.java @@ -151,7 +151,7 @@ public DatasetUpdateService(DatasetTableImpl table) } @Override - public boolean hasPermission(@NotNull UserPrincipal user, Class acl) + public boolean hasPermission(@NotNull UserPrincipal user, @NotNull Class acl) { if (StudySecurityEscalator.isEscalated()) { return true;