Skip to content

Commit ae00ec7

Browse files
Merge branch 'develop' into fb_bugFixes25.10X
2 parents f07e586 + 1626c0c commit ae00ec7

83 files changed

Lines changed: 785 additions & 710 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitattributes

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,6 @@ api/src/org/labkey/api/assay/AssaySaveHandler.java -text
253253
api/src/org/labkey/api/assay/AssaySchema.java -text
254254
api/src/org/labkey/api/assay/AssayService.java -text
255255
api/src/org/labkey/api/assay/AssayTableMetadata.java -text
256-
api/src/org/labkey/api/assay/AssayUploadFileResolver.java -text
257256
api/src/org/labkey/api/assay/AssayUploadXarContext.java -text
258257
api/src/org/labkey/api/assay/AssayUrls.java -text
259258
api/src/org/labkey/api/assay/AssayView.java -text
@@ -618,7 +617,6 @@ api/src/org/labkey/api/exceptions/TableNotFoundException.java -text
618617
api/src/org/labkey/api/exp/AbstractParameter.java -text
619618
api/src/org/labkey/api/exp/api/AbstractExperimentDataHandler.java -text
620619
api/src/org/labkey/api/exp/api/AbstractProtocolInputCriteria.java -text
621-
api/src/org/labkey/api/exp/api/AssayDomainType.java -text
622620
api/src/org/labkey/api/exp/api/AssayJSONConverter.java -text
623621
api/src/org/labkey/api/exp/api/DataType.java -text
624622
api/src/org/labkey/api/exp/api/DefaultExperimentDataHandler.java -text

api/schemas/query.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<xsd:attribute name="name" type="xsd:string"/>
1313
<xsd:attribute name="schemaName" type="xsd:string"/>
1414
<xsd:attribute name="hidden" type="xsd:boolean" default="false"/>
15+
<xsd:attribute name="inherit" type="xsd:boolean" default="false"/>
1516
<xsd:attribute name="schemaVersion" type="xsd:double"/>
1617
</xsd:complexType>
1718
</xsd:schema>

api/src/org/labkey/api/ApiModule.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.labkey.api.attachments.ImageServlet;
3333
import org.labkey.api.attachments.LookAndFeelResourceType;
3434
import org.labkey.api.attachments.SecureDocumentType;
35+
import org.labkey.api.audit.query.AbstractAuditDomainKind;
3536
import org.labkey.api.cache.BlockingCache;
3637
import org.labkey.api.collections.ArrayListMap;
3738
import org.labkey.api.collections.CaseInsensitiveHashMap;
@@ -463,6 +464,7 @@ public void registerServlets(ServletContext servletCtx)
463464
public @NotNull Set<Class> getIntegrationTests()
464465
{
465466
return Set.of(
467+
AbstractAuditDomainKind.TestCase.class,
466468
AbstractForeignKey.TestCase.class,
467469
AbstractQueryUpdateService.TestCase.class,
468470
ActionURL.TestCase.class,

api/src/org/labkey/api/action/ConfirmAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public final ModelAndView handleRequest() throws Exception
7777
ModelAndView mv = getSuccessView(form);
7878
if (null != mv)
7979
return mv;
80-
throw new RedirectException(getSuccessURL(form));
80+
throw new RedirectException(getSuccessURL(form), false);
8181
}
8282
}
8383
else

api/src/org/labkey/api/audit/AbstractAuditTypeProvider.java

Lines changed: 18 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,15 @@
2323
import org.labkey.api.audit.query.DefaultAuditTypeTable;
2424
import org.labkey.api.collections.CaseInsensitiveHashMap;
2525
import org.labkey.api.data.AbstractTableInfo;
26-
import org.labkey.api.data.ColumnInfo;
2726
import org.labkey.api.data.Container;
2827
import org.labkey.api.data.ContainerFilter;
2928
import org.labkey.api.data.ContainerManager;
3029
import org.labkey.api.data.DbSchema;
3130
import org.labkey.api.data.DbSchemaType;
3231
import org.labkey.api.data.DbScope;
3332
import org.labkey.api.data.MutableColumnInfo;
34-
import org.labkey.api.data.PropertyStorageSpec;
3533
import org.labkey.api.data.SQLFragment;
36-
import org.labkey.api.data.SchemaTableInfo;
3734
import org.labkey.api.data.SqlExecutor;
38-
import org.labkey.api.data.TableChange;
3935
import org.labkey.api.data.TableInfo;
4036
import org.labkey.api.dataiterator.DataIterator;
4137
import org.labkey.api.dataiterator.ExistingRecordDataIterator;
@@ -44,7 +40,6 @@
4440
import org.labkey.api.exp.api.ExperimentService;
4541
import org.labkey.api.exp.api.StorageProvisioner;
4642
import org.labkey.api.exp.property.Domain;
47-
import org.labkey.api.exp.property.DomainKind;
4843
import org.labkey.api.exp.property.DomainProperty;
4944
import org.labkey.api.exp.property.PropertyService;
5045
import org.labkey.api.gwt.client.DefaultValueType;
@@ -55,14 +50,12 @@
5550
import org.labkey.api.security.User;
5651
import org.labkey.api.util.DateUtil;
5752
import org.labkey.api.util.PageFlowUtil;
58-
import org.labkey.api.util.Pair;
5953
import org.labkey.api.view.ActionURL;
6054

6155
import java.sql.Time;
6256
import java.util.Collection;
6357
import java.util.Collections;
6458
import java.util.Date;
65-
import java.util.HashSet;
6659
import java.util.LinkedHashMap;
6760
import java.util.LinkedHashSet;
6861
import java.util.List;
@@ -90,18 +83,17 @@ public abstract class AbstractAuditTypeProvider implements AuditTypeProvider
9083
public static final String COLUMN_NAME_TRANSACTION_ID = "TransactionID";
9184
public static final String COLUMN_NAME_DATA_CHANGES = "DataChanges";
9285

93-
final AbstractAuditDomainKind _domainKind;
94-
86+
private final AbstractAuditDomainKind _domainKind;
9587

88+
@Deprecated // Call the other constructor and stop overriding getDomainKind()
9689
public AbstractAuditTypeProvider()
9790
{
9891
this(null);
9992
}
10093

10194
public AbstractAuditTypeProvider(@NotNull AbstractAuditDomainKind domainKind)
10295
{
103-
// TODO : consolidate domain kind initialization to either this constructor or to override
104-
// getDomainKind.
96+
// TODO: consolidate domain kind initialization to this constructor and stop overriding getDomainKind()
10597
_domainKind = domainKind;
10698
// Register the DomainKind
10799
PropertyService.get().registerDomainKind(getDomainKind());
@@ -115,10 +107,18 @@ protected AbstractAuditDomainKind getDomainKind()
115107
return _domainKind;
116108
}
117109

110+
// Expose the domain kind to AbstractAuditDomainKind$TestCase without touching every subclass
111+
public AbstractAuditDomainKind getAuditDomainKind()
112+
{
113+
return getDomainKind();
114+
}
115+
118116
@Override
119-
public void initializeProvider(User user)
117+
public final void initializeProvider(User user)
120118
{
121119
AbstractAuditDomainKind domainKind = getDomainKind();
120+
domainKind.validate();
121+
122122
Domain domain = getDomain(true);
123123

124124
// if the domain doesn't exist, create it
@@ -145,52 +145,6 @@ public void initializeProvider(User user)
145145
ensureProperties(user, domain);
146146
}
147147

148-
private void updateIndices(Domain domain, AbstractAuditDomainKind domainKind)
149-
{
150-
if (domain.getStorageTableName() == null)
151-
return;
152-
153-
// Issue 50059, acquiring the schema table info this way ensures that the domain fields are properly fixed up. See ProvisionedSchemaOptions.
154-
SchemaTableInfo schemaTableInfo = StorageProvisioner.get().getSchemaTableInfo(domain);
155-
if (schemaTableInfo != null)
156-
{
157-
Map<String, Pair<TableInfo.IndexType, List<ColumnInfo>>> existingIndices = schemaTableInfo.getAllIndices();
158-
Set<PropertyStorageSpec.Index> newIndices = new HashSet<>(domainKind.getPropertyIndices(domain));
159-
Set<String> toRemove = new HashSet<>();
160-
for (String name : existingIndices.keySet())
161-
{
162-
if (existingIndices.get(name).first == TableInfo.IndexType.Primary)
163-
continue;
164-
Pair<TableInfo.IndexType, List<ColumnInfo>> columnIndex = existingIndices.get(name);
165-
String[] columnNames = new String[columnIndex.second.size()];
166-
for (int i = 0; i < columnIndex.second.size(); i++)
167-
{
168-
columnNames[i] = columnIndex.second.get(i).getColumnName();
169-
}
170-
PropertyStorageSpec.Index existingIndex = new PropertyStorageSpec.Index(columnIndex.first == TableInfo.IndexType.Unique, columnNames);
171-
boolean foundIt = false;
172-
for (PropertyStorageSpec.Index propertyIndex : newIndices)
173-
{
174-
if (PropertyStorageSpec.Index.isSameIndex(propertyIndex, existingIndex))
175-
{
176-
foundIt = true;
177-
newIndices.remove(propertyIndex);
178-
break;
179-
}
180-
}
181-
182-
if (!foundIt)
183-
toRemove.add(name);
184-
}
185-
186-
if (!toRemove.isEmpty())
187-
StorageProvisioner.get().dropTableIndices(domain, toRemove);
188-
if (!newIndices.isEmpty())
189-
StorageProvisioner.get().addTableIndices(domain, newIndices, TableChange.IndexSizeMode.Normal);
190-
}
191-
}
192-
193-
194148
// NOTE: Changing the name of an existing PropertyDescriptor will lose data!
195149
private void ensureProperties(User user, Domain domain)
196150
{
@@ -254,7 +208,11 @@ private void ensureProperties(User user, Domain domain)
254208
domain.save(user);
255209
}
256210

257-
updateIndices(domain, domainKind);
211+
assert domain.getStorageTableName() != null;
212+
assert domain.getDomainKind() != null;
213+
assert domain.getDomainKind().getClass().equals(domainKind.getClass());
214+
215+
StorageProvisioner.get().ensureTableIndices(domain);
258216
transaction.commit();
259217
}
260218
catch (ChangePropertyDescriptorException e)
@@ -301,7 +259,7 @@ public final Domain getDomain()
301259
@Override
302260
public final Domain getDomain(boolean forUpdate)
303261
{
304-
DomainKind domainKind = getDomainKind();
262+
AbstractAuditDomainKind domainKind = getDomainKind();
305263

306264
String domainURI = domainKind.generateDomainURI(QUERY_SCHEMA_NAME, getEventName(), getDomainContainer(), null);
307265

api/src/org/labkey/api/audit/query/AbstractAuditDomainKind.java

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@
1818
import org.jetbrains.annotations.NotNull;
1919
import org.jetbrains.annotations.Nullable;
2020
import org.json.JSONObject;
21+
import org.junit.Assert;
22+
import org.junit.Test;
2123
import org.labkey.api.audit.AbstractAuditTypeProvider;
24+
import org.labkey.api.audit.AuditLogService;
2225
import org.labkey.api.collections.CaseInsensitiveHashSet;
26+
import org.labkey.api.collections.LabKeyCollectors;
2327
import org.labkey.api.data.Container;
2428
import org.labkey.api.data.ContainerManager;
2529
import org.labkey.api.data.DbSchema;
@@ -45,23 +49,27 @@
4549
import org.labkey.api.query.QueryService;
4650
import org.labkey.api.query.ValidationException;
4751
import org.labkey.api.security.User;
52+
import org.labkey.api.settings.OptionalFeatureService;
4853
import org.labkey.api.util.PageFlowUtil;
4954
import org.labkey.api.view.ActionURL;
5055
import org.labkey.api.view.NavTree;
5156
import org.labkey.api.writer.ContainerUser;
5257

58+
import java.util.Collection;
5359
import java.util.Collections;
5460
import java.util.HashSet;
5561
import java.util.LinkedHashSet;
62+
import java.util.LinkedList;
63+
import java.util.List;
64+
import java.util.Map;
65+
import java.util.Objects;
5666
import java.util.Set;
67+
import java.util.stream.Collectors;
68+
69+
import static org.labkey.api.audit.query.DefaultAuditTypeTable.LEGACY_UNION_AUDIT_TABLE;
5770

58-
/**
59-
* User: klum
60-
* Date: 7/8/13
61-
*/
6271
public abstract class AbstractAuditDomainKind extends DomainKind<JSONObject>
6372
{
64-
6573
private static final String XAR_SUBSTITUTION_SCHEMA_NAME = "SchemaName";
6674
private static final String XAR_SUBSTITUTION_TABLE_NAME = "TableName";
6775

@@ -393,7 +401,9 @@ public Set<String> getNonProvisionedTableNames()
393401
// omit the legacy auditlog table, this can be removed once the
394402
// table is dropped after migration
395403
Set<String> tables = new CaseInsensitiveHashSet();
396-
tables.add("auditlog");
404+
405+
if (OptionalFeatureService.get().isFeatureEnabled(LEGACY_UNION_AUDIT_TABLE))
406+
tables.add("auditlog");
397407

398408
return tables;
399409
}
@@ -418,4 +428,61 @@ public boolean isUserCreatedType()
418428
{
419429
return false;
420430
}
431+
432+
private static final Set<String> PREFIXES = new HashSet<>();
433+
434+
// Called at audit provider initialization time to forbid domain kinds with shared and overlapping namespace prefixes
435+
public void validate()
436+
{
437+
String prefix = getNamespacePrefix();
438+
// This check for length >= 12 is arbitrary, meant to prevent "Audit-" and other trivial namespace prefixes that
439+
// are likely to overlap and cause domain URIs to resolve to the wrong domain kinds.
440+
if (prefix.length() < 12)
441+
throw new IllegalStateException("Namespace prefix must be unique and longer than this: " + prefix);
442+
443+
List<String> overlappers = PREFIXES.stream()
444+
.filter(p -> p.startsWith(prefix) || prefix.startsWith(p))
445+
.map(p -> "\"" + p + "\"")
446+
.toList();
447+
448+
if (overlappers.isEmpty())
449+
PREFIXES.add(prefix);
450+
else
451+
throw new IllegalStateException("Namespace prefix \"" + prefix + "\" overlaps with " + overlappers);
452+
}
453+
454+
public static class TestCase extends Assert
455+
{
456+
// Also see AuditDomainUriTest which tests the DomainURIs in the database
457+
@Test
458+
public void flagDuplicateNamespacePrefixes()
459+
{
460+
Map<String, Collection<String>> map = AuditLogService.get().getAuditProviders().stream()
461+
.filter(AbstractAuditTypeProvider.class::isInstance)
462+
.map(p -> ((AbstractAuditTypeProvider)p).getAuditDomainKind())
463+
.collect(LabKeyCollectors.toMultiValuedMap(AbstractAuditDomainKind::getNamespacePrefix, dk -> dk.getClass().getSimpleName()))
464+
.asMap();
465+
466+
List<String> failures = map.entrySet().stream()
467+
.filter(e -> e.getValue().size() > 1)
468+
.map(e -> e.getValue() + " share the same namespace prefix: \"" + e.getKey() + "\"!")
469+
.collect(Collectors.toCollection(LinkedList::new));
470+
471+
failures.addAll(map.entrySet().stream()
472+
.map(e1 -> {
473+
String key1 = e1.getKey();
474+
List<String> overlappers = map.entrySet().stream()
475+
.filter(e2 -> !e1.equals(e2) && e2.getKey().startsWith(key1))
476+
.map(e2 -> "\"" + e2.getKey() + "\"")
477+
.toList();
478+
479+
return overlappers.isEmpty() ? null : "Prefix " + key1 + " (" + e1.getValue() + ") overlaps with prefixes " + overlappers + ")!";
480+
})
481+
.filter(Objects::nonNull)
482+
.toList());
483+
484+
if (!failures.isEmpty())
485+
Assert.fail(failures.toString());
486+
}
487+
}
421488
}

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.labkey.api.data.dialect.SqlDialect;
2626
import org.labkey.api.query.ExprColumn;
2727
import org.labkey.api.query.FieldKey;
28+
import org.labkey.api.query.QueryService;
2829
import org.labkey.api.security.SecurityLogger;
2930
import org.labkey.api.security.User;
3031
import org.labkey.api.security.permissions.Permission;
@@ -1205,6 +1206,46 @@ public Type getType()
12051206
}
12061207
}
12071208

1209+
/**
1210+
* This container filter type is uniquely specified for the scenario of importing data cross-folder.
1211+
* - When importing in a project, the scope of the data is the project, and all subfolders plus shared.
1212+
* - When importing into a subfolder, the scope of the data is the subfolder, and the project plus shared.
1213+
* - However, when resolving the scope of a lookup within that data during an import, we retain the behavior from
1214+
* {@link QueryService#getContainerFilterForLookups}.
1215+
* See Issue 52504, Issue 53803
1216+
*/
1217+
public static class ProductFolderImport extends ContainerFilterWithPermission
1218+
{
1219+
ContainerFilterWithPermission _cf;
1220+
1221+
public ProductFolderImport(Container c, User user)
1222+
{
1223+
super(c, user);
1224+
1225+
if (c.isProject())
1226+
_cf = new AllInProjectPlusShared(c, user);
1227+
else
1228+
_cf = new CurrentPlusProjectAndShared(c, user);
1229+
}
1230+
1231+
@Override
1232+
public @Nullable Collection<GUID> generateIds(Container currentContainer, Class<? extends Permission> permission, Set<Role> roles)
1233+
{
1234+
return _cf.generateIds(currentContainer, permission, roles);
1235+
}
1236+
1237+
public @NotNull ContainerFilter getContainerFilterForLookups(Container container, User user)
1238+
{
1239+
var cf = QueryService.get().getContainerFilterForLookups(container, user);
1240+
return cf == null ? this : cf;
1241+
}
1242+
1243+
@Override
1244+
public Type getType()
1245+
{
1246+
return null;
1247+
}
1248+
}
12081249

12091250
public static class InternalNoContainerFilter extends ContainerFilter
12101251
{

0 commit comments

Comments
 (0)