Skip to content
1 change: 1 addition & 0 deletions api/src/org/labkey/api/qc/QCStateHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public interface QCStateHandler<FORM extends AbstractManageQCStatesForm>
boolean isQCStateInUse(Container container, QCState state);
boolean isBlankQCStatePublic(Container container);
void updateQcState(Container container, FORM form, User user);
String getHandlerType();
static <T> boolean nullSafeEqual(T first, T second)
{
if (first == null && second == null)
Expand Down
15 changes: 15 additions & 0 deletions api/src/org/labkey/api/qc/QCStateManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
public class QCStateManager
{
private static final QCStateManager _instance = new QCStateManager();
private static Map<String, QCStateHandler> _QCStateHandlers = new HashMap<>();
private static final Cache<Container, QCStateCollections> QC_STATE_DB_CACHE = CacheManager.getBlockingCache(CacheManager.UNLIMITED, CacheManager.DAY, "QCStates", new CacheLoader<Container, QCStateCollections>()
{
@Override
Expand Down Expand Up @@ -100,6 +101,20 @@ public List<QCState> getQCStates(Container container)
return QC_STATE_DB_CACHE.get(container).getQcStates();
}

public void registerQCHandler(QCStateHandler handler)
{
String handlerType = handler.getHandlerType();
if (!_QCStateHandlers.containsKey(handlerType))
_QCStateHandlers.put(handlerType, handler);
else
throw new IllegalArgumentException("QCStateHandler '" + handlerType + "' is already registered.");
}

public Map<String, QCStateHandler> getRegisteredQCHandlers()
{
return _QCStateHandlers;
}

public boolean showQCStates(Container container)
{
return !getQCStates(container).isEmpty();
Expand Down
4 changes: 4 additions & 0 deletions core/src/org/labkey/core/CoreModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import org.labkey.api.portal.ProjectUrls;
import org.labkey.api.premium.PremiumService;
import org.labkey.api.products.ProductRegistry;
import org.labkey.api.qc.QCStateManager;
import org.labkey.api.query.DefaultSchema;
import org.labkey.api.query.QuerySchema;
import org.labkey.api.query.QueryService;
Expand Down Expand Up @@ -198,6 +199,7 @@
import org.labkey.core.portal.UtilController;
import org.labkey.core.products.ProductController;
import org.labkey.core.project.FolderNavigationForm;
import org.labkey.core.qc.CoreQCStateHandler;
import org.labkey.core.qc.QCStateImporter;
import org.labkey.core.qc.QCStateWriter;
import org.labkey.core.query.AttachmentAuditProvider;
Expand Down Expand Up @@ -788,6 +790,8 @@ public void startupAfterSpringConfig(ModuleContext moduleContext)
AuditLogService.get().registerAuditType(new FileSystemBatchAuditProvider());
AuditLogService.get().registerAuditType(new ClientApiAuditProvider());
AuditLogService.get().registerAuditType(new AuthenticationSettingsAuditTypeProvider());

QCStateManager.getInstance().registerQCHandler(new CoreQCStateHandler());
}
ContextListener.addShutdownListener(TempTableTracker.getShutdownListener());
ContextListener.addShutdownListener(DavController.getShutdownListener());
Expand Down
7 changes: 7 additions & 0 deletions core/src/org/labkey/core/qc/CoreQCStateHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@
public class CoreQCStateHandler implements QCStateHandler<CoreController.ManageQCStatesForm>
{
protected List<QCState> _states = null;
public static final String HANDLER_NAME = "CoreQCStateHandler";

@Override
public String getHandlerType()
{
return HANDLER_NAME;
}

@Override
public boolean isBlankQCStatePublic(Container container)
Expand Down
93 changes: 92 additions & 1 deletion core/src/org/labkey/core/query/QCStateTableInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,28 @@
*/
package org.labkey.core.query;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.labkey.api.data.ColumnInfo;
import org.labkey.api.data.Container;
import org.labkey.api.data.CoreSchema;
import org.labkey.api.data.DbScope;
import org.labkey.api.qc.QCState;
import org.labkey.api.qc.QCStateHandler;
import org.labkey.api.qc.QCStateManager;
import org.labkey.api.query.DefaultQueryUpdateService;
import org.labkey.api.query.DuplicateKeyException;
import org.labkey.api.query.FilteredTable;
import org.labkey.api.query.InvalidKeyException;
import org.labkey.api.query.QueryUpdateService;
import org.labkey.api.query.QueryUpdateServiceException;
import org.labkey.api.query.ValidationException;
import org.labkey.api.security.User;
import org.labkey.api.security.UserPrincipal;
import org.labkey.api.security.permissions.Permission;

import java.sql.SQLException;
import java.util.Map;

/**
* Created by marty on 7/25/2017.
Expand All @@ -37,4 +56,76 @@ public QCStateTableInfo(CoreQuerySchema schema)
wrappedColumn.setHidden(true);
}
}
}

@Override
public boolean hasPermission(@NotNull UserPrincipal user, @NotNull Class<? extends Permission> perm)
{
return getContainer().hasPermission(user, perm);
}

static class QCStateService extends DefaultQueryUpdateService
{
public QCStateService(FilteredTable table) { super(table, table.getRealTable()); }

private boolean validateQCStateNotInUse(Map<String, Object> oldRowMap, Container container)
{
Map<String, QCStateHandler> registeredHandlers = QCStateManager.getInstance().getRegisteredQCHandlers();
QCState QCToDelete = QCStateManager.getInstance().getQCStateForRowId(container, (Integer) oldRowMap.get("rowid"));

for (QCStateHandler handler : registeredHandlers.values())
{
if (handler.isQCStateInUse(container, QCToDelete))
return false;
}
return true;
}

@Override
protected Map<String, Object> updateRow(User user, Container container, Map<String, Object> row, @NotNull Map<String, Object> oldRow, boolean allowOwner, boolean retainCreation) throws InvalidKeyException, ValidationException, QueryUpdateServiceException, SQLException
{
Map<String, Object> rowToUpdate;
try (DbScope.Transaction transaction = CoreSchema.getInstance().getSchema().getScope().ensureTransaction())
{
rowToUpdate = super.updateRow(user, container, row, oldRow, allowOwner, retainCreation);
transaction.addCommitTask(() -> QCStateManager.getInstance().clearCache(container), DbScope.CommitTaskOption.IMMEDIATE, DbScope.CommitTaskOption.POSTCOMMIT);
transaction.commit();
}
return rowToUpdate;
}

@Override
protected Map<String, Object> insertRow(User user, Container container, Map<String, Object> row) throws DuplicateKeyException, ValidationException, QueryUpdateServiceException, SQLException
{
Map<String, Object> rowToInsert;
try (DbScope.Transaction transaction = CoreSchema.getInstance().getSchema().getScope().ensureTransaction())
{
rowToInsert = super.insertRow(user, container, row);
transaction.addCommitTask(() -> QCStateManager.getInstance().clearCache(container), DbScope.CommitTaskOption.IMMEDIATE, DbScope.CommitTaskOption.POSTCOMMIT);
transaction.commit();
}
return rowToInsert;
}

@Override
protected Map<String, Object> deleteRow(User user, Container container, Map<String, Object> oldRowMap) throws InvalidKeyException, QueryUpdateServiceException, SQLException
{
if (!validateQCStateNotInUse(oldRowMap, container))
throw new QueryUpdateServiceException("QC state '" + oldRowMap.get("label") + "' cannot be deleted as it is currently in use.");

Map<String, Object> rowToDelete;
try (DbScope.Transaction transaction = CoreSchema.getInstance().getSchema().getScope().ensureTransaction())
{
rowToDelete = super.deleteRow(user, container, oldRowMap);
transaction.addCommitTask(() -> QCStateManager.getInstance().clearCache(container), DbScope.CommitTaskOption.IMMEDIATE, DbScope.CommitTaskOption.POSTCOMMIT);
transaction.commit();
}
return rowToDelete;
}
}

@Override
public @Nullable QueryUpdateService getUpdateService()
{
return new QCStateService(this);
}
}
4 changes: 4 additions & 0 deletions study/src/org/labkey/study/StudyModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import org.labkey.api.module.ModuleLoader;
import org.labkey.api.module.SpringModule;
import org.labkey.api.pipeline.PipelineService;
import org.labkey.api.qc.QCStateManager;
import org.labkey.api.qc.export.QCStateImportExportHelper;
import org.labkey.api.query.DefaultSchema;
import org.labkey.api.query.snapshot.QuerySnapshotService;
Expand Down Expand Up @@ -127,6 +128,7 @@
import org.labkey.study.pipeline.SampleMindedTransformTask;
import org.labkey.study.pipeline.StudyPipeline;
import org.labkey.study.qc.StudyQCImportExportHelper;
import org.labkey.study.qc.StudyQCStateHandler;
import org.labkey.study.query.StudyPersonnelDomainKind;
import org.labkey.study.query.StudyQuerySchema;
import org.labkey.study.query.StudySchemaProvider;
Expand Down Expand Up @@ -367,6 +369,8 @@ protected void startupAfterSpringConfig(ModuleContext moduleContext)
ReportService.get().registerDescriptor(new CrosstabReportDescriptor());
ReportService.get().registerDescriptor(new ParticipantReportDescriptor());

QCStateManager.getInstance().registerQCHandler(new StudyQCStateHandler());

ReportService.get().addUIProvider(new StudyReportUIProvider());
ReportService.get().addGlobalItemFilterType(QueryReport.TYPE);

Expand Down
7 changes: 7 additions & 0 deletions study/src/org/labkey/study/qc/StudyQCStateHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@
public class StudyQCStateHandler implements QCStateHandler<StudyController.ManageQCStatesForm>
{
protected List<QCState> _states = null;
public static final String HANDLER_NAME = "StudyQCStateHandler";

@Override
public String getHandlerType()
{
return HANDLER_NAME;
}

@Override
public List<QCState> getQCStates(Container container)
Expand Down