Skip to content

Commit 95a650a

Browse files
committed
Merge remote-tracking branch 'origin/develop' into fb_amountsAndUnits
2 parents 6064ef7 + 23ce916 commit 95a650a

File tree

26 files changed

+325
-368
lines changed

26 files changed

+325
-368
lines changed

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;
@@ -460,6 +461,7 @@ public void registerServlets(ServletContext servletCtx)
460461
public @NotNull Set<Class> getIntegrationTests()
461462
{
462463
return Set.of(
464+
AbstractAuditDomainKind.TestCase.class,
463465
AbstractForeignKey.TestCase.class,
464466
AbstractQueryUpdateService.TestCase.class,
465467
ActionURL.TestCase.class,

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/DbSchemaCache.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,6 @@
2626
import org.labkey.api.cache.CacheTimeChooser;
2727
import org.labkey.api.module.ModuleLoader;
2828

29-
/*
30-
* User: adam
31-
* Date: Mar 20, 2011
32-
* Time: 2:53:51 PM
33-
*/
34-
3529
// Every scope has its own cache of DbSchemas
3630
public class DbSchemaCache
3731
{
@@ -68,7 +62,7 @@ public DbSchemaCache(DbScope scope)
6862

6963
void remove(String schemaName, DbSchemaType type)
7064
{
71-
LOG.debug("remove " + type + " schema: " + schemaName);
65+
LOG.debug("remove {} schema: {}", type, schemaName);
7266
_cache.removeUsingFilter(new Cache.StringPrefixFilter(getKey(schemaName, type)));
7367
}
7468

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

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,9 @@
3131
import java.util.Set;
3232

3333
/**
34-
* User: newton
35-
* Date: Aug 24, 2010
36-
* Time: 4:08:39 PM
37-
*
3834
* The reason that we have this class, instead of doing something like reusing PropertyDescriptor, is that we also need
3935
* a storage spec like this when there is not a full property descriptor such as for the base properties of DomainKinds.
4036
* ColumnInfo and SqlColumn are also mismatched for various reasons.
41-
*
4237
*/
4338
public class PropertyStorageSpec
4439
{
@@ -470,7 +465,7 @@ public Index(boolean unique, boolean clustered, String... columnNames)
470465

471466
/**
472467
* Determines if two indices are the same modulo the isClustered setting. This is useful for updating indices
473-
* when an audit domain type changes, for example.
468+
* when a domain changes, for example.
474469
*/
475470
public static boolean isSameIndex(Index propertyIndex, Index tableIndex)
476471
{

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ public void addColumn(PropertyDescriptor prop)
234234
addColumn(new PropertyStorageSpec(prop));
235235
}
236236

237-
/** Add the property to the set of columns tracked in this change along with it's old scale. */
237+
/** Add the property to the set of columns tracked in this change along with its old scale. */
238238
public void addColumnResize(PropertyDescriptor prop, Integer oldScale)
239239
{
240240
addColumn(prop);
@@ -248,7 +248,7 @@ public void addColumnRename(String oldName, String newName)
248248

249249
/**
250250
* Index will be renamed using the columns listed in the Index.
251-
* The columns used by the index won't be changed. We need to
251+
* The columns used by the index won't be changed. We need to
252252
* pass the list of columns since the index name is created by the dialect.
253253
*
254254
* @param oldIndex Old index to be renamed.
@@ -309,7 +309,8 @@ public void setIndexedColumns(Domain domain, Collection<Index> indices)
309309
_indices = indices.stream().map(i -> i.translateToStorageNames(domain)).toList();
310310
}
311311

312-
public Set<String> getIndicesToBeDroppedByName(){
312+
public Set<String> getIndicesToBeDroppedByName()
313+
{
313314
return _indicesToBeDroppedByName;
314315
}
315316

0 commit comments

Comments
 (0)