Skip to content

Commit 4173288

Browse files
committed
Merge 25.8 to develop
# Conflicts: # api/src/org/labkey/api/reader/DataLoader.java
2 parents 973b32e + 99fab8c commit 4173288

File tree

15 files changed

+148
-85
lines changed

15 files changed

+148
-85
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ static void setInstance(AssayService impl)
8787
AssaySchema createSchema(User user, Container container, @Nullable Container targetStudy);
8888

8989
/**
90-
* @return all the assay protocols that are in scope in the given container
90+
* @return all the assay protocols that are in scope in the given container, which may include those
91+
* defined in other containers
9192
*/
9293
@NotNull List<ExpProtocol> getAssayProtocols(Container container);
9394

api/src/org/labkey/api/dataiterator/AttachmentDataIterator.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,11 @@ public boolean next()
9696

9797
if (null == attachmentValue)
9898
continue;
99-
else if (attachmentValue instanceof String)
99+
else if (attachmentValue instanceof String str)
100100
{
101101
if (null == attachmentDir)
102102
{
103-
errors.addRowError(new ValidationException("Row " + get(0) + ": " + "Can't upload to field " + p.domainProperty.getName() + " with type " + p.domainProperty.getType().getLabel() + "."));
103+
errors.addRowError(new ValidationException("Row " + get(0) + ": " + "Can't upload '" + str + "' to field " + p.domainProperty.getName() + " with type " + p.domainProperty.getType().getLabel() + "."));
104104
return false;
105105
}
106106
filename = (String) attachmentValue;
@@ -124,7 +124,13 @@ else if (attachmentValue instanceof File)
124124
}
125125
else
126126
{
127-
errors.addRowError(new ValidationException("Row " + get(0) + ": " + "Unable to create attachament file."));
127+
errors.addRowError(new ValidationException("Row " + get(0) + ": " + "Unable to create attachment file."));
128+
return false;
129+
}
130+
131+
if (entityIdIndex == 0)
132+
{
133+
errors.addRowError(new ValidationException("Row " + get(0) + ": " + "Unable to create attachment file."));
128134
return false;
129135
}
130136

@@ -139,7 +145,7 @@ else if (attachmentValue instanceof File)
139145
if (null != attachmentFiles && !attachmentFiles.isEmpty())
140146
{
141147
String entityId = String.valueOf(get(entityIdIndex));
142-
AttachmentService.get().addAttachments(getAttachmentParent(entityId, container) , attachmentFiles, user);
148+
AttachmentService.get().addAttachments(getAttachmentParent(entityId, container), attachmentFiles, user);
143149
}
144150
return ret;
145151
}
@@ -217,7 +223,7 @@ public static DataIteratorBuilder getAttachmentDataIteratorBuilder(TableInfo ti,
217223
}
218224
}
219225

220-
if (!attachmentColumns.isEmpty() && 0 != entityIdIndex)
226+
if (!attachmentColumns.isEmpty())
221227
return new AttachmentDataIterator(it, context.getErrors(), user, attachmentDir, entityIdIndex, attachmentColumns, context.getInsertOption(), container, parentFactory );
222228

223229
return it;

api/src/org/labkey/api/dataiterator/StandardDataIteratorBuilder.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,10 @@ public DataIterator getDataIterator(DataIteratorContext context)
277277
PropertyType pt = pd == null ? null : pd.getPropertyType();
278278
boolean isAttachment = pt == PropertyType.ATTACHMENT || pt == PropertyType.FILE_LINK;
279279

280-
if (null == pair.target || isAttachment)
280+
if (null == pair.target)
281281
convert.addColumn(pair.indexFrom);
282+
else if (isAttachment) // Issue 53498: attachment is blank after update from file, if the field name contains underscore
283+
convert.addColumn(pair.target, pair.indexFrom);
282284
else
283285
convert.addConvertColumn(pair.target, pair.indexFrom, pair.indexMv, pd, pt, pair.target.getRemapMissingBehavior(), context.isWithLookupRemapping());
284286
}

api/src/org/labkey/api/exp/api/ExpObject.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public interface ExpObject extends Identifiable, Comparable<ExpObject>
3737
String DEFAULT_CPAS_TYPE = "Object";
3838

3939
/** Prevent edits to this object. Subsequent calls to setters will throw an IllegalStateException */
40-
void lock();
40+
ExpObject lock();
4141

4242
int getRowId();
4343
/** Get the exp.object objectId */

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,16 +1237,25 @@ private static void addDefaultUserLoginDelay(HttpServletRequest request, String
12371237

12381238
// Throws UnauthorizedException if credentials are incorrect, password is expired, password is not complex enough,
12391239
// user doesn't exist, user is inactive, or secondary auth is enabled and an API key hasn't been used.
1240-
public static @Nullable User authenticate(HttpServletRequest request, String id, String password) throws InvalidEmailException
1240+
public static @NotNull User authenticate(HttpServletRequest request, String id, String password) throws InvalidEmailException
12411241
{
12421242
PrimaryAuthenticationResult primaryResult = authenticate(request, id, password, null, true);
1243+
String message;
12431244

1244-
// If primary authentication is successful then look for secondary authentication. handleAuthentication() will
1245+
// If primary authentication is successful, then look for secondary authentication. handleAuthentication() will
12451246
// always return a failure result (i.e., null user) if secondary authentication is enabled. #22944
12461247
if (primaryResult.getStatus() == AuthenticationStatus.Success)
12471248
{
12481249
AuthenticationManager.setPrimaryAuthenticationResult(request, primaryResult);
1249-
return handleAuthentication(request, ContainerManager.getRoot()).getUser();
1250+
User user = handleAuthentication(request, ContainerManager.getRoot()).getUser();
1251+
if (user != null)
1252+
return user;
1253+
1254+
message = "Authentication failed because secondary authentication is enabled on this server. Consider using an API key instead.";
1255+
}
1256+
else
1257+
{
1258+
message = Objects.requireNonNullElse(primaryResult.getMessage(), primaryResult.getStatusErrorMessage(DisplayLocation.API));
12501259
}
12511260

12521261
// Basic auth has failed so send error response in a format that APIs can consume (JSON unless request specifies
@@ -1255,9 +1264,8 @@ private static void addDefaultUserLoginDelay(HttpServletRequest request, String
12551264
// SpringActionController.handleRequest() will need to call this again since actions can specify the default
12561265
// format.
12571266
SpringActionController.setResponseFormat(request, Format.JSON);
1258-
String message = primaryResult.getMessage();
12591267

1260-
throw new UnauthorizedException(message != null ? message : primaryResult.getStatusErrorMessage(DisplayLocation.API));
1268+
throw new UnauthorizedException(message);
12611269
}
12621270

12631271
public static URLHelper logout(@NotNull User user, @NotNull HttpServletRequest request, URLHelper returnUrl)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public class SecurityPolicyManager
8585
Selector selector = new TableSelector(core.getTableInfoRoleAssignments(), filter, new Sort("UserId"));
8686
Collection<RoleAssignment> assignments = selector.getCollection(RoleAssignment.class);
8787

88-
return new SecurityPolicy(policyBean.getResourceId(), policyBean.getResourceClass(), policyBean.getContainer().getId(), assignments, policyBean.getModified());
88+
return new SecurityPolicy(policyBean.getResourceId(), policyBean.getResourceClass(), policyBean.getContainer().getId(), assignments.isEmpty() ? Collections.emptyList() : assignments, policyBean.getModified());
8989
}
9090

9191
return null;

api/src/org/labkey/api/util/JsonUtil.java

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@
2424
import com.fasterxml.jackson.core.json.JsonWriteFeature;
2525
import com.fasterxml.jackson.databind.*;
2626
import org.apache.commons.lang3.StringUtils;
27+
import org.apache.commons.lang3.mutable.MutableBoolean;
2728
import org.apache.logging.log4j.Logger;
2829
import org.json.JSONArray;
2930
import org.json.JSONException;
3031
import org.json.JSONObject;
3132
import org.junit.Assert;
3233
import org.junit.Test;
3334
import org.labkey.api.action.ApiUsageException;
35+
import org.labkey.api.collections.CaseInsensitiveHashMap;
3436
import org.labkey.api.data.Container;
3537
import org.labkey.api.security.User;
3638
import org.labkey.api.settings.AppProps;
@@ -202,15 +204,20 @@ public static List<JSONObject> toJSONObjectList(JSONArray array)
202204
return result;
203205
}
204206

205-
// The new JSONObject.toMap() translates all JSONObjects into Maps and JSONArrays into Lists. In many cases, this is
206-
// fine, but some existing code paths want to maintain the contained JSONObjects and JSONArrays. This method does
207-
// that, acting more like the old JSONObject.toMap().
208-
public static void fillMapShallow(JSONObject json, Map<String, Object> map)
207+
/**
208+
* The new JSONObject.toMap() translates all JSONObjects into Maps and JSONArrays into Lists. In many cases, this is
209+
* fine, but some existing code paths want to maintain the contained JSONObjects and JSONArrays. This method does
210+
* that, acting more like the old JSONObject.toMap().
211+
* @return whether there were duplicate values in the map (useful when filling a case-insensitive map)
212+
*/
213+
public static boolean fillMapShallow(JSONObject json, Map<String, Object> map)
209214
{
215+
MutableBoolean hadDuplicates = new MutableBoolean(false);
210216
json.keySet().forEach(key -> {
211217
Object value = json.get(key);
212-
map.put(key, JSONObject.NULL == value ? null : value);
218+
hadDuplicates.setValue(map.put(key, JSONObject.NULL == value ? null : value) != null || hadDuplicates.booleanValue());
213219
});
220+
return hadDuplicates.booleanValue();
214221
}
215222

216223
// New JSONObject discards all properties with null values. This returns a JSONObject containing all Map values,
@@ -461,6 +468,20 @@ public void testJsonString()
461468
assertEquals(u.getUserId(), roundTripJson.get("user"));
462469
}
463470

471+
@Test
472+
public void testConflictingCase()
473+
{
474+
JSONObject json = new JSONObject();
475+
json.put("foo", "bar");
476+
json.put("x", "y");
477+
assertFalse(fillMapShallow(json, new HashMap<>()));
478+
assertFalse(fillMapShallow(json, new CaseInsensitiveHashMap<>()));
479+
480+
json.put("FOO", "BAR");
481+
assertFalse(fillMapShallow(json, new HashMap<>()));
482+
assertTrue(fillMapShallow(json, new CaseInsensitiveHashMap<>()));
483+
}
484+
464485
@Test
465486
public void testSanitize()
466487
{

assay/src/org/labkey/assay/AssayManager.java

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ public Set<String> getPermittedContainerIds(User user, Map<String, Container> co
138138
}
139139
};
140140

141+
/** Cache the protocols defined in a given container, which we can quickly compose to get the protocols in scope */
141142
private static final Cache<Container, List<ExpProtocol>> PROTOCOL_CACHE = CacheManager.getCache(CacheManager.UNLIMITED, TimeUnit.HOURS.toMillis(1), "Assay protocols");
142143

143144
private static final List<AssayListener> _listeners = new CopyOnWriteArrayList<>();
@@ -287,7 +288,7 @@ private class ModuleAssayLsidHandlerFinder implements LsidHandlerFinder
287288

288289
@Nullable
289290
@Override
290-
public LsidHandler findHandler(String authority, String namespacePrefix)
291+
public LsidHandler<?> findHandler(String authority, String namespacePrefix)
291292
{
292293
if (AppProps.getInstance().getDefaultLsidAuthority().equals(authority))
293294
{
@@ -360,29 +361,32 @@ public AssaySchema createSchema(User user, Container container, @Nullable Contai
360361
@Override
361362
public @NotNull List<ExpProtocol> getAssayProtocols(Container container)
362363
{
363-
return PROTOCOL_CACHE.get(container, null, (c, argument) ->
364-
{
365-
// Build up a set of containers so that we can query them all at once
366-
Set<Container> containers = c.getContainersFor(ContainerType.DataType.protocol);
367-
368-
List<? extends ExpProtocol> protocols = ExperimentService.get().getExpProtocols(containers.toArray(new Container[0]));
369-
List<ExpProtocol> result = new ArrayList<>();
364+
List<ExpProtocol> allProtocols = new ArrayList<>();
370365

371-
// Filter to just the ones that have an AssayProvider associated with them
372-
for (ExpProtocol protocol : protocols)
366+
for (Container containerInScope : container.getContainersFor(ContainerType.DataType.protocol))
367+
{
368+
List<ExpProtocol> ids = PROTOCOL_CACHE.get(containerInScope, null, (c, argument) ->
373369
{
374-
AssayProvider p = getProvider(protocol);
375-
if (p != null)
370+
List<ExpProtocol> result = new ArrayList<>();
371+
372+
// Filter to just the ones that have an AssayProvider associated with them
373+
for (ExpProtocol protocol : ExperimentService.get().getExpProtocols(c))
376374
{
377-
// We don't want anyone editing our cached object
378-
protocol.lock();
379-
result.add(protocol);
375+
AssayProvider p = getProvider(protocol);
376+
if (p != null)
377+
{
378+
// We don't want anyone editing our cached object
379+
protocol.lock();
380+
result.add(protocol);
381+
}
380382
}
381-
}
382-
// Sort them, just to be nice
383-
Collections.sort(result);
384-
return Collections.unmodifiableList(result);
385-
});
383+
return result.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(result);
384+
});
385+
allProtocols.addAll(ids);
386+
}
387+
388+
Collections.sort(allProtocols);
389+
return Collections.unmodifiableList(allProtocols);
386390
}
387391

388392
@Override

experiment/src/org/labkey/experiment/api/ExpObjectImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,10 @@ abstract public class ExpObjectImpl implements ExpObject, Serializable
4343
protected ExpObjectImpl() {}
4444

4545
@Override
46-
public void lock()
46+
public ExpObjectImpl lock()
4747
{
4848
_locked = true;
49+
return this;
4950
}
5051

5152
protected void ensureUnlocked()

experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ public class ExperimentServiceImpl implements ExperimentService, ObjectReference
322322

323323
private final Cache<String, ExpProtocolImpl> PROTOCOL_LSID_CACHE = DatabaseCache.get(getExpSchema().getScope(), CacheManager.UNLIMITED, CacheManager.HOUR, "Protocol by LSID",
324324
(key, argument) -> getExpProtocol(new SimpleFilter(FieldKey.fromParts("LSID"), key)));
325+
325326
private final Cache<String, ExperimentRun> EXPERIMENT_RUN_CACHE = DatabaseCache.get(getExpSchema().getScope(), getTinfoExperimentRun().getCacheSize(), "Experiment Run by LSID", new ExperimentRunCacheLoader());
326327

327328
private final Cache<String, SortedSet<DataClass>> dataClassCache = CacheManager.getBlockingStringKeyCache(CacheManager.UNLIMITED, CacheManager.DAY, "Data classes", (containerId, argument) ->

0 commit comments

Comments
 (0)