Skip to content

Commit d85f8ca

Browse files
committed
Merge remote-tracking branch 'origin/develop' into fb_domainLsids
2 parents 077c4ea + e873689 commit d85f8ca

36 files changed

Lines changed: 257 additions & 171 deletions

.github/pull_request_template.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,10 @@
66

77
#### Changes
88
- <!-- list of descriptions of changes that are worth noting (replace this comment) -->
9+
10+
<!-- list of standard tasks (remove this comment to enable)
11+
#### Tasks 📍
12+
- [ ] Manual Testing
13+
- [ ] Needs Automation
14+
- [ ] Verify Fix
15+
-->

api/src/org/labkey/api/assay/AssayFileWriter.java

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import org.labkey.api.pipeline.PipeRoot;
3030
import org.labkey.api.pipeline.PipelineService;
3131
import org.labkey.api.query.AbstractQueryUpdateService;
32-
import org.labkey.api.util.DateUtil;
3332
import org.labkey.api.util.FileUtil;
3433
import org.labkey.api.util.NetworkDrive;
3534
import org.labkey.api.view.ViewContext;
@@ -44,7 +43,6 @@
4443
import java.io.IOException;
4544
import java.io.InputStream;
4645
import java.util.ArrayDeque;
47-
import java.util.Date;
4846
import java.util.Deque;
4947
import java.util.HashSet;
5048
import java.util.Iterator;
@@ -138,33 +136,29 @@ public static FileLike ensureUploadDirectoryPath(FileLike dir) throws Experiment
138136
}
139137
}
140138

139+
public static String generateFileName(ExpProtocol protocol, boolean shouldEncode)
140+
{
141+
String protocolName = protocol.getName();
142+
if (shouldEncode)
143+
return FileUtil.makeFileNameWithTimestamp(protocolName);
144+
145+
return protocolName + "_" + FileUtil.getTimestamp(); // Issue 52075
146+
}
147+
141148
/**
142149
* Create file name based upon the assay protocol's name and the current time.
143150
* e.g., <code>assayname-2020-04-14-1602345</code>
144151
*/
145152
public static FileLike createFile(ExpProtocol protocol, FileLike dir, String extension)
146153
{
147-
Date dateCreated = new Date();
148-
String dateString = DateUtil.formatDateTime(dateCreated, "yyy-MM-dd-HHmmss-SSS");
154+
String fileNamePrefix = generateFileName(protocol, true);
149155
int id = 0;
150156

151-
String protocolName = protocol.getName();
152-
char[] characters = protocolName.toCharArray();
153-
154-
for (int i = 0; i < characters.length; i++)
155-
{
156-
char character = characters[i];
157-
boolean isAtoZchar = character >= 'A' && character <= 'z';
158-
if (!Character.isDigit(character) && !isAtoZchar)
159-
characters[i] = '_';
160-
}
161-
protocolName = new String(characters);
162-
163157
FileLike file;
164158
do
165159
{
166160
String extra = id++ == 0 ? "" : String.valueOf(id);
167-
String fileName = protocolName + "-" + dateString + extra + "." + extension;
161+
String fileName = fileNamePrefix + extra + "." + extension;
168162
fileName = fileName.replace('\\', '_').replace('/', '_').replace(':', '_');
169163
file = dir.resolveChild(fileName);
170164
}

api/src/org/labkey/api/cache/CacheManager.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public class CacheManager
6060
public static final int DEFAULT_CACHE_SIZE = 5000;
6161

6262
// Set useCache = false to completely disable all caching... and slow your server to a near halt. Possibly useful for
63-
// reproducing CacheLoader re-entrancy problems, but not much else.
63+
// reproducing CacheLoader reentrancy problems, but not much else.
6464
private static final boolean useCache = true;
6565
private static final CacheProvider PROVIDER = useCache ? EhCacheProvider.getInstance() : new NoopCacheProvider();
6666

@@ -176,7 +176,7 @@ public static void shutdown()
176176

177177
private static final Set<Class<?>> CLASSES = new HashSet<>();
178178

179-
// Validate a cached value. For now, just log warnings for mutable collections.
179+
// Validate a cached value. Log errors for mutable collections/arrays and for values holding Container or User objects.
180180
public static <V> void validate(String debugName, @Nullable V value)
181181
{
182182
if (value instanceof Wrapper<?>)
@@ -186,14 +186,11 @@ public static <V> void validate(String debugName, @Nullable V value)
186186

187187
if (null != description)
188188
{
189-
LOG.warn("{} attempted to cache {}, which could be mutated by callers!", debugName, description);
189+
LOG.error("{} attempted to cache {}, which could be mutated by callers!", debugName, description);
190190
}
191191

192-
// Log questionable members, but don't do the work if we're not going to log it
193-
if (LOG.isDebugEnabled())
194-
{
195-
analyzeValue(value, debugName, null, 1);
196-
}
192+
// Flag values that hold a Container or User object
193+
analyzeValue(value, debugName, null, 1);
197194
}
198195

199196
private static final int MAX_DEPTH = 4;
@@ -236,9 +233,7 @@ else if (CLASSES.add(clazz))
236233

237234
if (Container.class.isAssignableFrom(type) || User.class.isAssignableFrom(type) || Project.class.isAssignableFrom(type))
238235
{
239-
// String message = cacheName + ": " + clazz.getName() + " field " + newFieldPath + " (" + field.getType().getName() + ")";
240-
// throw new IllegalStateException(message);
241-
LOG.debug("{}: {} field {} ({})", cacheName, clazz.getName(), newFieldPath, field.getType().getName());
236+
LOG.error("Cached value holds an unsafe object - {}: {} field {} ({})", cacheName, clazz.getName(), newFieldPath, field.getType().getName());
242237
}
243238
else
244239
{

api/src/org/labkey/api/data/SimpleDisplayColumn.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public void renderDetailsCellContents(RenderContext ctx, HtmlWriter out)
107107
{
108108
Object value = getValue(ctx);
109109
if (value != null)
110-
out.write(value.toString());
110+
out.write(value);
111111
}
112112

113113
@Override

api/src/org/labkey/api/data/dialect/MutatingSqlDetector.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ State getNextState(char c, StringBuilder firstWord, String sql)
129129
// Needed for PostgreSQL
130130
WORD_MUTATING_MAP.putAll(Map.of(
131131
"ANALYZE", true, // Typically executed after UPDATE, CREATE INDEX, et al
132+
"LOCK", true, // Not technically mutating. However, it is currently only used in mutating TX.
132133
"VACUUM", true, // VACUUM is mutating
133134
"{call", true // Execute a stored procedure, which is likely to be mutating
134135
));

api/src/org/labkey/api/exp/property/DomainUtil.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,14 @@ public static ValidationException updateDomainDescriptor(GWTDomain<? extends GWT
982982
return validationException;
983983
}
984984

985+
// Issue 51321: check reserved domain name: First, All
986+
public static @Nullable String validateReservedName(@NotNull String domainName, @NotNull String kindName)
987+
{
988+
if ("First".equalsIgnoreCase(domainName) || "All".equalsIgnoreCase(domainName))
989+
return kindName + " name '" + domainName + "' is a reserved name.";
990+
return null;
991+
}
992+
985993
public static @Nullable String validateDomainName(@NotNull String domainName, String kindName, boolean supportsNamingPattern)
986994
{
987995
String prefix = "Invalid " + kindName + " name '" + domainName + "'. ";

api/src/org/labkey/api/query/AbstractQueryImportAction.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,7 @@ else if (!dataFileDir.exists())
604604
QueryImportPipelineJob job = new QueryImportPipelineJob(getQueryImportProviderName(), info, root, importContextBuilder);
605605
PipelineService.get().queueJob(job, getQueryImportJobNotificationProviderName());
606606

607+
SimpleMetricsService.get().increment("query", "fileBackgroundImports", getMetricPrefix(_target));
607608
JSONObject response = new JSONObject();
608609
response.put("success", true);
609610
response.put("jobId", PipelineService.get().getJobId(user, getContainer(), job.getJobGUID()));
@@ -635,7 +636,10 @@ else if (!dataFileDir.exists())
635636
int rowCount = importData(loader, file, originalName, ve, behaviorType, auditEvent, _auditUserComment);
636637

637638
if (ve.hasErrors())
639+
{
640+
addImportValidationErrorMetric(_insertOption, _target, (form instanceof QueryForm qf) ? qf.getQueryName() : null);
638641
throw ve;
642+
}
639643

640644
JSONObject response = createSuccessResponse(rowCount);
641645
if (auditEvent != null)
@@ -879,9 +883,7 @@ public static int importData(DataLoader dl, TableInfo target, QueryUpdateService
879883
if (auditEvent != null)
880884
auditEvent.addComment(auditAction, count);
881885

882-
String metricPrefix = target.getUserSchema() == null ? target.getSchema().getName() : target.getUserSchema().getSchemaName();
883-
metricPrefix = metricPrefix.replace("exp.", "");
884-
incrementRowCountMetric(count, context.getInsertOption(), metricPrefix);
886+
incrementRowCountMetric(count, context.getInsertOption(), getMetricPrefix(target));
885887
transaction.commit();
886888

887889
return count;
@@ -903,6 +905,24 @@ else if (!context.isCrossTypeImport())
903905
return 0;
904906
}
905907

908+
public static void addImportValidationErrorMetric(QueryUpdateService.InsertOption insertOption, @Nullable TableInfo target, @Nullable String queryName)
909+
{
910+
String featureArea = insertOption.toString().toLowerCase() + "FailWithValidationError";
911+
String targetType = null;
912+
if (target != null)
913+
targetType = getMetricPrefix(target);
914+
else if (queryName != null)
915+
targetType = queryName.equalsIgnoreCase("materials") ? "samples" : queryName;
916+
if (targetType != null)
917+
SimpleMetricsService.get().increment("query", featureArea, targetType);
918+
}
919+
920+
public static String getMetricPrefix(TableInfo target)
921+
{
922+
String metricPrefix = target.getUserSchema() == null ? target.getSchema().getName() : target.getUserSchema().getSchemaName();
923+
return metricPrefix.replace("exp.", "");
924+
}
925+
906926
public static void incrementRowCountMetric(int count, QueryUpdateService.InsertOption insertOption, String prefix)
907927
{
908928
String featureArea = "file" + StringUtils.capitalize(insertOption.toString().toLowerCase()) + "Counts";

api/src/org/labkey/api/query/QueryImportPipelineJob.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,10 @@ public void run()
305305
int importedCount = AbstractQueryImportAction.importData(loader, target, updateService, diContext, auditEvent, getInfo().getUser(), getInfo().getContainer());
306306

307307
if (ve.hasErrors())
308+
{
309+
AbstractQueryImportAction.addImportValidationErrorMetric(_importContextBuilder.getInsertOption(), target, null);
308310
throw ve;
311+
}
309312

310313
if (auditEvent != null)
311314
_transactionAuditId = auditEvent.getRowId();

api/src/org/labkey/api/security/ElevatedUser.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import org.labkey.api.audit.permissions.CanSeeAuditLogPermission;
44
import org.labkey.api.data.Container;
5+
import org.labkey.api.security.impersonation.ImpersonationContext;
56
import org.labkey.api.security.impersonation.WrappedImpersonationContext;
67
import org.labkey.api.security.permissions.Permission;
78
import org.labkey.api.security.roles.CanSeeAuditLogRole;
@@ -26,6 +27,11 @@ private ElevatedUser(User user, Set<Role> rolesToAdd)
2627
super(user, new WrappedImpersonationContext(user.getImpersonationContext(), rolesToAdd));
2728
}
2829

30+
private ElevatedUser(User user, ImpersonationContext ctx)
31+
{
32+
super(user, ctx);
33+
}
34+
2935
/**
3036
* Wrap the supplied user and unconditionally add the supplied role(s). Always returns an ElevatedUser.
3137
*/
@@ -43,6 +49,14 @@ public static ElevatedUser getElevatedUser(User user, Collection<Class<? extends
4349
return new ElevatedUser(user, getRoles(roleClassesToAdd));
4450
}
4551

52+
/**
53+
* Used to reconstitute an ElevatedUser from its component parts
54+
*/
55+
public static ElevatedUser getElevatedUser(User user, ImpersonationContext ctx)
56+
{
57+
return new ElevatedUser(user, ctx);
58+
}
59+
4660
/**
4761
* Ensure the supplied user has the supplied permissions. If so, return that user. If not, wrap the user with
4862
* ElevatedUser and, for each pair of permission + role, add the role if the user doesn't have the corresponding

api/src/org/labkey/api/security/permissions/AdminOperationsPermission.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
package org.labkey.api.security.permissions;
1717

1818
/**
19-
* Describes the ability to manage operational site administration settings.
19+
* Describes the ability to manage operational site administration settings, such as arbitrary paths on the server's
20+
* underlying file system. Use {@see org.labkey.api.security.permissions.ApplicationAdminPermission} for gating
21+
* access to server-wide settings that don't have the potential to access these underlying server resources.
2022
*/
2123
public class AdminOperationsPermission extends AdminPermission
2224
{

0 commit comments

Comments
 (0)