package com.revolsys.swing.map.layer.record;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.CancellationException;
import java.util.function.Consumer;
import java.util.function.Predicate;
import javax.swing.SwingWorker;
import com.revolsys.collection.iterator.Iterators;
import com.revolsys.collection.map.MapEx;
import com.revolsys.collection.map.Maps;
import com.revolsys.geometry.cs.CoordinateSystem;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.identifier.Identifier;
import com.revolsys.io.BaseCloseable;
import com.revolsys.io.PathName;
import com.revolsys.io.Writer;
import com.revolsys.logging.Logs;
import com.revolsys.predicate.Predicates;
import com.revolsys.record.Record;
import com.revolsys.record.RecordFactory;
import com.revolsys.record.RecordState;
import com.revolsys.record.Records;
import com.revolsys.record.code.CodeTable;
import com.revolsys.record.io.RecordReader;
import com.revolsys.record.io.RecordStoreConnectionManager;
import com.revolsys.record.query.Condition;
import com.revolsys.record.query.In;
import com.revolsys.record.query.Q;
import com.revolsys.record.query.Query;
import com.revolsys.record.query.functions.F;
import com.revolsys.record.query.functions.WithinDistance;
import com.revolsys.record.schema.FieldDefinition;
import com.revolsys.record.schema.RecordDefinition;
import com.revolsys.record.schema.RecordStore;
import com.revolsys.swing.SwingUtil;
import com.revolsys.swing.component.BasePanel;
import com.revolsys.swing.component.ValueField;
import com.revolsys.swing.layout.GroupLayouts;
import com.revolsys.swing.map.layer.record.table.model.RecordLayerErrors;
import com.revolsys.swing.parallel.Invoke;
import com.revolsys.transaction.Propagation;
import com.revolsys.transaction.Transaction;
import com.revolsys.util.Label;
import com.revolsys.util.Property;
import com.revolsys.util.count.LabelCountMap;
public class RecordStoreLayer extends AbstractRecordLayer {
private BoundingBox loadedBoundingBox = BoundingBox.empty();
private BoundingBox loadingBoundingBox = BoundingBox.empty();
private SwingWorker<List<LayerRecord>, Void> loadingWorker;
/**
* Caches of sets of {@link Record#getIdentifier()} for different purposes (e.g. selected records, deleted records).
* Each cache has a separate cacheId. The cache id is recommended to be a private variable to prevent modification
* of that cache.
*/
private Map<Label, Set<Identifier>> recordIdentifiersByCacheId = new WeakHashMap<>();
/** Cache of records from {@link Record#getIdentifier()} to {@link Record}. */
private Map<Identifier, RecordStoreLayerRecord> recordsByIdentifier = new WeakHashMap<>();
private RecordStore recordStore;
private PathName typePath;
public RecordStoreLayer() {
this("recordStoreLayer");
}
public RecordStoreLayer(final Map<String, ? extends Object> properties) {
this();
setProperties(properties);
}
public RecordStoreLayer(final RecordStore recordStore, final PathName typePath,
final boolean exists) {
this();
this.recordStore = recordStore;
setExists(exists);
setTypePath(typePath);
}
protected RecordStoreLayer(final String type) {
super(type);
}
protected void addCachedRecord(final Identifier identifier, final LayerRecord record) {
synchronized (getSync()) {
this.recordsByIdentifier.put(identifier, (RecordStoreLayerRecord)record);
}
}
@Override
protected boolean addRecordToCache(final Label cacheId, final LayerRecord record) {
if (isLayerRecord(record)) {
if (record.getState() == RecordState.DELETED && !isDeleted(record)) {
} else {
final Identifier identifier = record.getIdentifier();
if (identifier == null) {
return super.addRecordToCache(cacheId, record);
} else {
synchronized (getSync()) {
if (!(record instanceof AbstractProxyLayerRecord)) {
getCachedRecord(identifier, record, false);
}
return Maps.addToSet(this.recordIdentifiersByCacheId, cacheId, identifier);
}
}
}
}
return false;
}
protected void cancelLoading(final BoundingBox loadedBoundingBox) {
synchronized (getSync()) {
if (loadedBoundingBox == this.loadingBoundingBox) {
firePropertyChange("loaded", false, true);
this.loadedBoundingBox = BoundingBox.empty();
this.loadingBoundingBox = BoundingBox.empty();
this.loadingWorker = null;
}
}
}
protected Set<Identifier> cleanCachedRecordIds() {
final Set<Identifier> identifiers = new HashSet<>();
for (final Set<Identifier> recordIds : this.recordIdentifiersByCacheId.values()) {
if (recordIds != null) {
identifiers.addAll(recordIds);
}
}
addProxiedRecordIdsToCollection(identifiers);
return identifiers;
}
/**
* Remove any cached records that are currently not used.
*/
@Override
protected void cleanCachedRecords() {
synchronized (getSync()) {
super.cleanCachedRecords();
final Set<Identifier> identifiers = cleanCachedRecordIds();
synchronized (getSync()) {
this.recordsByIdentifier.keySet().retainAll(identifiers);
}
}
}
@Override
public void clearCachedRecords(final Label cacheId) {
synchronized (getSync()) {
super.clearCachedRecords(cacheId);
this.recordIdentifiersByCacheId.remove(cacheId);
}
}
@Override
public RecordStoreLayer clone() {
final RecordStoreLayer clone = (RecordStoreLayer)super.clone();
clone.recordIdentifiersByCacheId = new WeakHashMap<>();
clone.loadedBoundingBox = BoundingBox.empty();
clone.loadingBoundingBox = BoundingBox.empty();
clone.loadingWorker = null;
clone.recordsByIdentifier = new WeakHashMap<>();
return clone;
}
@Override
public void delete() {
super.delete();
if (this.recordStore != null) {
final Map<String, String> connectionProperties = getProperty("connection");
if (connectionProperties != null) {
final Map<String, Object> config = new HashMap<>();
config.put("connection", connectionProperties);
RecordStoreConnectionManager.releaseRecordStore(config);
}
this.recordStore = null;
}
final SwingWorker<List<LayerRecord>, Void> loadingWorker = this.loadingWorker;
this.recordIdentifiersByCacheId.clear();
this.loadedBoundingBox = BoundingBox.empty();
this.loadingBoundingBox = BoundingBox.empty();
this.loadingWorker = null;
this.recordsByIdentifier.clear();
if (loadingWorker != null) {
loadingWorker.cancel(true);
}
}
@Override
protected boolean deleteRecordDo(final LayerRecord record) {
final Identifier identifier = record.getIdentifier();
final boolean result = super.deleteRecordDo(record);
removeFromRecordIdToRecordMap(identifier);
return result;
}
@Override
protected void forEachRecordInternal(Query query, final Consumer<? super LayerRecord> consumer) {
if (isExists()) {
try {
final RecordStore recordStore = getRecordStore();
if (recordStore != null && query != null) {
final Predicate<Record> filter = query.getWhereCondition();
final Map<String, Boolean> orderBy = query.getOrderBy();
final List<LayerRecord> changedRecords = new ArrayList<>();
changedRecords.addAll(getRecordsNew());
changedRecords.addAll(getRecordsModified());
Records.filterAndSort(changedRecords, filter, orderBy);
final Iterator<LayerRecord> changedIterator = changedRecords.iterator();
LayerRecord currentChangedRecord = Iterators.next(changedIterator);
final RecordDefinition internalRecordDefinition = getInternalRecordDefinition();
query = query.newQuery(internalRecordDefinition);
final Comparator<Record> comparator = Records.newComparatorOrderBy(orderBy);
try (
final BaseCloseable booleanValueCloseable = eventsDisabled();
Transaction transaction = recordStore.newTransaction(Propagation.REQUIRES_NEW);
final RecordReader reader = newRecordStoreRecordReader(query);) {
for (LayerRecord record : reader.<LayerRecord> i()) {
boolean write = true;
final Identifier identifier = getId(record);
if (identifier != null) {
final LayerRecord cachedRecord = this.recordsByIdentifier.get(identifier);
if (cachedRecord != null) {
record = cachedRecord;
if (record.isChanged() || isDeleted(record)) {
write = false;
}
}
}
if (!isDeleted(record) && write) {
while (currentChangedRecord != null
&& comparator.compare(currentChangedRecord, record) < 0) {
consumer.accept(currentChangedRecord);
currentChangedRecord = Iterators.next(changedIterator);
}
consumer.accept(record);
}
}
while (currentChangedRecord != null) {
consumer.accept(currentChangedRecord);
currentChangedRecord = Iterators.next(changedIterator);
}
}
}
} catch (final CancellationException e) {
} catch (final RuntimeException e) {
Logs.error(this, "Error executing query: " + query, e);
throw e;
}
}
}
public <R extends LayerRecord> void forEachRecordsPersisted(final Query query,
final Consumer<? super R> consumer) {
if (query != null && isExists()) {
final RecordStore recordStore = getRecordStore();
if (recordStore != null) {
try (
final BaseCloseable booleanValueCloseable = eventsDisabled();
Transaction transaction = recordStore.newTransaction(Propagation.REQUIRES_NEW);
final RecordReader reader = newRecordStoreRecordReader(query);) {
final LabelCountMap labelCountMap = query.getProperty("statistics");
for (final LayerRecord record : reader.<LayerRecord> i()) {
final Identifier identifier = getId(record);
R proxyRecord = null;
if (identifier == null) {
proxyRecord = newProxyLayerRecord(record);
} else {
synchronized (getSync()) {
final LayerRecord cachedRecord = getCachedRecord(identifier, record, true);
if (!cachedRecord.isDeleted()) {
proxyRecord = newProxyLayerRecord(identifier);
}
}
}
if (proxyRecord != null) {
consumer.accept(proxyRecord);
if (labelCountMap != null) {
labelCountMap.addCount(record);
}
}
}
}
}
}
}
@Override
public BoundingBox getBoundingBox() {
if (hasGeometryField()) {
final CoordinateSystem coordinateSystem = getCoordinateSystem();
if (coordinateSystem != null) {
return coordinateSystem.getAreaBoundingBox();
}
}
return BoundingBox.empty();
}
@SuppressWarnings("unchecked")
@Override
protected <R extends LayerRecord> R getCachedRecord(final Identifier identifier) {
final RecordDefinition recordDefinition = getInternalRecordDefinition();
final List<String> idFieldNames = recordDefinition.getIdFieldNames();
if (idFieldNames.isEmpty()) {
Logs.error(this, this.typePath + " does not have a primary key");
return null;
} else {
synchronized (getSync()) {
LayerRecord record = this.recordsByIdentifier.get(identifier);
if (record == null) {
final Condition where = getCachedRecordQuery(idFieldNames, identifier);
final Query query = new Query(recordDefinition, where);
final RecordStore recordStore = this.recordStore;
if (recordStore != null) {
try (
Transaction transaction = recordStore.newTransaction(Propagation.REQUIRED);
RecordReader reader = newRecordStoreRecordReader(query)) {
record = reader.getFirst();
if (record != null) {
addCachedRecord(identifier, record);
}
}
}
}
return (R)record;
}
}
}
/**
* Get the record from the cache if it exists, otherwise add this record to the cache
*
* @param identifier
* @param record
*/
private LayerRecord getCachedRecord(final Identifier identifier, final LayerRecord record,
final boolean updateRecord) {
assert !(record instanceof AbstractProxyLayerRecord);
synchronized (getSync()) {
final RecordStoreLayerRecord cachedRecord = this.recordsByIdentifier.get(identifier);
if (cachedRecord == null) {
addCachedRecord(identifier, record);
return record;
} else {
if (updateRecord) {
cachedRecord.refreshFromRecordStore(record);
}
return cachedRecord;
}
}
}
protected Condition getCachedRecordQuery(final List<String> idFieldNames,
final Identifier identifier) {
return Q.equalId(idFieldNames, identifier);
}
@Override
protected Set<Label> getCacheIdsDo(final LayerRecord record) {
final Set<Label> cacheIds = super.getCacheIdsDo(record);
final Identifier identifier = record.getIdentifier();
if (identifier != null) {
for (final Entry<Label, Set<Identifier>> entry : this.recordIdentifiersByCacheId.entrySet()) {
final Label cacheId = entry.getKey();
if (!cacheIds.contains(cacheId)) {
final Collection<Identifier> identifiers = entry.getValue();
if (identifiers.contains(identifier)) {
cacheIds.add(cacheId);
}
}
}
}
return cacheIds;
}
public FieldDefinition getGeometryField() {
final RecordDefinition recordDefinition = getRecordDefinition();
if (recordDefinition == null) {
return null;
} else {
return recordDefinition.getGeometryField();
}
}
protected Identifier getId(final LayerRecord record) {
if (isLayerRecord(record)) {
return record.getIdentifier();
} else {
return null;
}
}
protected RecordDefinition getInternalRecordDefinition() {
return getRecordDefinition();
}
public BoundingBox getLoadingBoundingBox() {
return this.loadingBoundingBox;
}
@Override
public PathName getPathName() {
return this.typePath;
}
@Override
public LayerRecord getRecordById(final Identifier identifier) {
final LayerRecord record = getCachedRecord(identifier);
if (record == null) {
return record;
} else {
return newProxyLayerRecord(identifier);
}
}
@Override
public int getRecordCount(final Query query) {
int count = 0;
count += Predicates.count(getRecordsNew(), query.getWhereCondition());
count += getRecordCountChangeModified(query);
count += getRecordCountPersisted(query);
count -= Predicates.count(getRecordsDeleted(), query.getWhereCondition());
return count;
}
@Override
protected int getRecordCountCached(final Label cacheId) {
int count = super.getRecordCountCached(cacheId);
final Set<Identifier> identifiers = this.recordIdentifiersByCacheId.get(cacheId);
if (identifiers != null) {
count += identifiers.size();
}
return count;
}
/**
* Get the count of the modified records where the original record did not match the filter but
* the modified record does.
* @param query
* @return
*/
protected int getRecordCountChangeModified(final Query query) {
final Condition filter = query.getWhereCondition();
if (filter.isEmpty()) {
return 0;
} else {
int count = 0;
for (final LayerRecord record : getRecordsModified()) {
final Record originalRecord = record.getOriginalRecord();
final boolean modifiedMatches = filter.test(record);
final boolean originalMatches = filter.test(originalRecord);
if (modifiedMatches) {
if (!originalMatches) {
count++;
}
} else {
if (originalMatches) {
count--;
}
}
}
return count;
}
}
@Override
public int getRecordCountPersisted() {
if (isExists()) {
final RecordDefinition recordDefinition = getRecordDefinition();
final Query query = new Query(recordDefinition);
return getRecordCountPersisted(query);
}
return 0;
}
@Override
public int getRecordCountPersisted(final Query query) {
if (isExists()) {
final RecordStore recordStore = getRecordStore();
if (recordStore != null) {
try (
Transaction transaction = recordStore.newTransaction(Propagation.REQUIRES_NEW)) {
return recordStore.getRecordCount(query);
}
}
}
return 0;
}
protected RecordDefinition getRecordDefinition(final PathName typePath) {
if (typePath != null) {
final RecordStore recordStore = getRecordStore();
if (recordStore != null) {
return recordStore.getRecordDefinition(typePath);
}
}
return null;
}
@SuppressWarnings({
"unchecked", "rawtypes"
})
@Override
public <R extends LayerRecord> List<R> getRecords(BoundingBox boundingBox) {
if (hasGeometryField()) {
boundingBox = convertBoundingBox(boundingBox);
if (Property.hasValue(boundingBox)) {
try (
final BaseCloseable booleanValueCloseable = eventsDisabled()) {
final BoundingBox queryBoundingBox = convertBoundingBox(boundingBox);
boolean covers;
synchronized (getSync()) {
covers = this.loadedBoundingBox.covers(queryBoundingBox);
}
if (covers) {
return getRecordsIndex(queryBoundingBox);
} else {
final List<R> records = (List)getRecordsPersisted(queryBoundingBox);
return records;
}
}
}
}
return Collections.emptyList();
}
@Override
public <R extends LayerRecord> List<R> getRecords(final Geometry geometry,
final double distance) {
if (Property.isEmpty(geometry) || !hasGeometryField()) {
return Collections.emptyList();
} else {
final RecordDefinition recordDefinition = getRecordDefinition();
final FieldDefinition geometryField = getGeometryField();
final WithinDistance where = F.dWithin(geometryField, geometry, distance);
final Query query = new Query(recordDefinition, where);
return getRecords(query);
}
}
@Override
public List<LayerRecord> getRecordsBackground(BoundingBox boundingBox) {
if (hasGeometryField()) {
boundingBox = convertBoundingBox(boundingBox);
if (Property.hasValue(boundingBox)) {
synchronized (getSync()) {
final BoundingBox loadBoundingBox = boundingBox.expandPercent(0.2);
if (!this.loadedBoundingBox.covers(boundingBox)
&& !this.loadingBoundingBox.covers(boundingBox)) {
if (this.loadingWorker != null) {
this.loadingWorker.cancel(true);
}
this.loadingBoundingBox = loadBoundingBox;
this.loadingWorker = newLoadingWorker(loadBoundingBox);
Invoke.worker(this.loadingWorker);
}
}
final List<LayerRecord> records = getRecordsIndex(boundingBox);
return records;
}
}
return Collections.emptyList();
}
@Override
public List<LayerRecord> getRecordsCached(final Label cacheId) {
synchronized (getSync()) {
final List<LayerRecord> records = super.getRecordsCached(cacheId);
final Set<Identifier> recordIds = this.recordIdentifiersByCacheId.get(cacheId);
if (recordIds != null) {
for (final Identifier recordId : recordIds) {
final LayerRecord record = getRecordById(recordId);
if (record != null) {
records.add(record);
}
}
}
return records;
}
}
protected List<LayerRecord> getRecordsPersisted(final BoundingBox boundingBox) {
final RecordDefinition recordDefinition = getInternalRecordDefinition();
final Query query = Query.intersects(recordDefinition, boundingBox);
return getRecords(query);
}
@Override
public <R extends LayerRecord> List<R> getRecordsPersisted(final Query query) {
final List<R> records = new ArrayList<>();
final Consumer<R> consumer = records::add;
forEachRecordsPersisted(query, consumer);
return records;
}
@Override
public RecordStore getRecordStore() {
return this.recordStore;
}
@Override
protected boolean initializeDo() {
RecordStore recordStore = this.recordStore;
if (recordStore == null) {
final Map<String, String> connectionProperties = getProperty("connection");
if (connectionProperties == null) {
Logs.error(this,
"A record store layer requires a connection entry with a name or url, username, and password: "
+ getPath());
return false;
} else {
final Map<String, Object> config = new HashMap<>();
config.put("connection", connectionProperties);
recordStore = RecordStoreConnectionManager.getRecordStore(config);
if (recordStore == null) {
Logs.error(this, "Unable to create record store for layer: " + getPath());
return false;
} else {
try {
recordStore.initialize();
} catch (final Throwable e) {
throw new RuntimeException("Unable to iniaitlize record store for layer " + getPath(),
e);
}
setRecordStore(recordStore);
}
}
}
final PathName typePath = getPathName();
RecordDefinition recordDefinition = getRecordDefinition();
if (recordDefinition == null) {
recordDefinition = getRecordDefinition(typePath);
if (recordDefinition == null) {
Logs.error(this, "Cannot find table " + typePath + " for layer " + getPath());
return false;
} else {
setRecordDefinition(recordDefinition);
}
}
initRecordMenu();
return true;
}
@Override
public boolean isLayerRecord(final Record record) {
if (record instanceof LayerRecord) {
final LayerRecord layerRecord = (LayerRecord)record;
if (layerRecord.getLayer() == this) {
return true;
}
}
return false;
}
@Override
public boolean isRecordCached(final Label cacheId, final LayerRecord record) {
if (isLayerRecord(record)) {
synchronized (getSync()) {
final Identifier identifier = record.getIdentifier();
if (identifier != null) {
if (Maps.collectionContains(this.recordIdentifiersByCacheId, cacheId, identifier)) {
return true;
}
}
return super.isRecordCached(cacheId, record);
}
}
return false;
}
protected Query newBoundingBoxQuery(BoundingBox boundingBox) {
final RecordDefinition recordDefinition = getInternalRecordDefinition();
final FieldDefinition geometryField = recordDefinition.getGeometryField();
boundingBox = convertBoundingBox(boundingBox);
if (geometryField == null || Property.isEmpty(boundingBox)) {
return null;
} else {
Query query = getQuery();
query = query.newQuery(recordDefinition);
query.and(F.envelopeIntersects(geometryField, boundingBox));
return query;
}
}
@Override
public LayerRecord newLayerRecord(final Map<String, ? extends Object> values) {
if (!isReadOnly() && isEditable() && isCanAddRecords()) {
final RecordDefinition recordDefinition = getRecordDefinition();
final ArrayLayerRecord newRecord = new RecordStoreLayerRecord(this);
if (values != null) {
newRecord.setState(RecordState.INITIALIZING);
final List<FieldDefinition> idFields = recordDefinition.getIdFields();
for (final FieldDefinition fieldDefinition : recordDefinition.getFields()) {
if (!idFields.contains(fieldDefinition)) {
final String fieldName = fieldDefinition.getName();
final Object value = values.get(fieldName);
fieldDefinition.setValue(newRecord, value);
}
}
newRecord.setState(RecordState.NEW);
}
addRecordToCache(getCacheIdNew(), newRecord);
if (isEventsEnabled()) {
cleanCachedRecords();
}
final LayerRecord proxyRecord = new NewProxyLayerRecord(this, newRecord);
fireRecordInserted(proxyRecord);
return proxyRecord;
} else {
return null;
}
}
@Override
protected LayerRecord newLayerRecord(final RecordDefinition recordDefinition) {
final PathName layerTypePath = getPathName();
if (recordDefinition.getPathName().equals(layerTypePath)) {
return new RecordStoreLayerRecord(this);
} else {
throw new IllegalArgumentException("Cannot create records for " + recordDefinition);
}
}
protected LoadingWorker newLoadingWorker(final BoundingBox boundingBox) {
return new LoadingWorker(this, boundingBox);
}
@Override
protected ValueField newPropertiesTabGeneralPanelSource(final BasePanel parent) {
final ValueField panel = super.newPropertiesTabGeneralPanelSource(parent);
final Map<String, String> connectionProperties = getProperty("connection");
String connectionName = null;
String url = null;
String user = null;
if (isExists()) {
final RecordStore recordStore = getRecordStore();
url = recordStore.getUrl();
user = recordStore.getUsername();
}
if (connectionProperties != null) {
connectionName = connectionProperties.get("name");
if (!isExists()) {
url = connectionProperties.get("url");
user = connectionProperties.get("user");
}
}
if (connectionName != null) {
SwingUtil.addLabelledReadOnlyTextField(panel, "Record Store Name", connectionName);
}
if (url != null) {
SwingUtil.addLabelledReadOnlyTextField(panel, "Record Store URL", url);
}
if (user != null) {
SwingUtil.addLabelledReadOnlyTextField(panel, "Record Store Username", user);
}
SwingUtil.addLabelledReadOnlyTextField(panel, "Type Path", this.typePath);
GroupLayouts.makeColumns(panel, 2, true);
return panel;
}
@SuppressWarnings("unchecked")
protected <V extends LayerRecord> V newProxyLayerRecord(final Identifier identifier) {
return (V)new IdentifierProxyLayerRecord(this, identifier);
}
@Override
@SuppressWarnings("unchecked")
protected <R extends LayerRecord> R newProxyLayerRecord(LayerRecord record) {
if (record instanceof AbstractProxyLayerRecord) {
// Already a proxy
} else if (RecordState.NEW.equals(record.getState())) {
record = new NewProxyLayerRecord(this, record);
} else {
final Identifier identifier = record.getIdentifier();
addCachedRecord(identifier, record);
record = newProxyLayerRecord(identifier);
}
return (R)record;
}
protected RecordReader newRecordStoreRecordReader(final Query query) {
final RecordStore recordStore = getRecordStore();
if (recordStore == null) {
return RecordReader.empty();
} else {
final RecordFactory<LayerRecord> recordFactory = getRecordFactory();
query.setRecordFactory(recordFactory);
return recordStore.getRecords(query);
}
}
@Override
protected boolean postSaveDeletedRecord(final LayerRecord record) {
final boolean deleted = super.postSaveDeletedRecord(record);
if (deleted) {
removeRecordFromCache(this.getCacheIdDeleted(), record);
}
return deleted;
}
protected void preDeleteRecord(final LayerRecord record) {
}
@Override
protected void refreshDo() {
synchronized (getSync()) {
if (this.loadingWorker != null) {
this.loadingWorker.cancel(true);
}
this.loadedBoundingBox = BoundingBox.empty();
this.loadingBoundingBox = this.loadedBoundingBox;
super.refreshDo();
}
final RecordStore recordStore = getRecordStore();
final PathName pathName = getPathName();
final CodeTable codeTable = recordStore.getCodeTable(pathName);
if (codeTable != null) {
codeTable.refresh();
}
final List<Identifier> identifiers = new ArrayList<>();
synchronized (getSync()) {
identifiers.addAll(this.recordsByIdentifier.keySet());
}
if (!identifiers.isEmpty()) {
identifiers.sort(Identifier.comparator());
final RecordDefinition recordDefinition = recordStore.getRecordDefinition(pathName);
final String idFieldName = recordDefinition.getIdFieldName();
final int pageSize = 999;
final int identifierCount = identifiers.size();
for (int i = 0; i < identifiers.size(); i += pageSize) {
final List<Identifier> queryIdentifiers = identifiers.subList(i,
Math.min(identifierCount, i + pageSize));
final In in = Q.in(idFieldName, queryIdentifiers);
final Query query = new Query(recordDefinition, in);
try (
Transaction transaction = recordStore.newTransaction(Propagation.REQUIRES_NEW);
RecordReader reader = recordStore.getRecords(query)) {
for (final Record record : reader) {
final Identifier identifier = record.getIdentifier();
final RecordStoreLayerRecord cachedRecord = this.recordsByIdentifier.get(identifier);
if (cachedRecord != null) {
cachedRecord.refreshFromRecordStore(record);
}
}
}
}
}
}
private void removeFromRecordIdToRecordMap(final Identifier identifier) {
synchronized (getSync()) {
this.recordsByIdentifier.remove(identifier);
}
}
@Override
protected boolean removeRecordFromCache(final Label cacheId, final LayerRecord record) {
boolean removed = false;
if (isLayerRecord(record)) {
final Identifier identifier = record.getIdentifier();
if (identifier != null) {
synchronized (getSync()) {
removed = Maps.removeFromSet(this.recordIdentifiersByCacheId, cacheId, identifier);
}
}
}
removed |= super.removeRecordFromCache(cacheId, record);
return removed;
}
@Override
protected boolean removeRecordFromCache(final LayerRecord record) {
boolean removed = false;
if (isLayerRecord(record)) {
synchronized (getSync()) {
final Identifier identifier = record.getIdentifier();
if (identifier != null) {
for (final Iterator<Set<Identifier>> iterator = this.recordIdentifiersByCacheId.values()
.iterator(); iterator.hasNext();) {
final Set<Identifier> identifiers = iterator.next();
identifiers.remove(identifier);
if (identifiers.isEmpty()) {
iterator.remove();
}
}
}
removed |= super.removeRecordFromCache(record);
}
}
return removed;
}
@Override
public void revertChanges(final LayerRecord record) {
removeRecordFromCache(this.getCacheIdDeleted(), record);
super.revertChanges(record);
}
@Override
protected boolean saveChangesDo(final RecordLayerErrors errors, final LayerRecord record) {
boolean deleted = super.isDeleted(record);
if (isExists()) {
final RecordStore recordStore = getRecordStore();
if (recordStore == null) {
return true;
} else {
try (
Transaction transaction = recordStore.newTransaction(Propagation.REQUIRES_NEW)) {
try {
Identifier identifier = record.getIdentifier();
try (
final Writer<Record> writer = recordStore.newRecordWriter()) {
if (isRecordCached(getCacheIdDeleted(), record) || super.isDeleted(record)) {
preDeleteRecord(record);
record.setState(RecordState.DELETED);
writeDelete(writer, record);
deleted = true;
} else {
final RecordDefinition recordDefinition = getRecordDefinition();
if (super.isNew(record)) {
final List<String> idFieldNames = recordDefinition.getIdFieldNames();
if (identifier == null && !idFieldNames.isEmpty()) {
identifier = recordStore.newPrimaryIdentifier(this.typePath);
if (identifier != null) {
identifier.setIdentifier(record, idFieldNames);
}
}
}
final int fieldCount = recordDefinition.getFieldCount();
for (int fieldIndex = 0; fieldIndex < fieldCount; fieldIndex++) {
record.validateField(fieldIndex);
}
if (super.isModified(record)) {
writeUpdate(writer, record);
} else if (super.isNew(record)) {
writer.write(record);
}
}
}
if (!deleted) {
record.setState(RecordState.PERSISTED);
}
removeFromRecordIdToRecordMap(identifier);
return true;
} catch (final Throwable e) {
throw transaction.setRollbackOnly(e);
}
}
}
} else {
return true;
}
}
protected void setIndexRecords(final BoundingBox loadedBoundingBox,
final List<LayerRecord> records) {
synchronized (getSync()) {
if (loadedBoundingBox == this.loadingBoundingBox) {
setIndexRecords(records);
firePropertyChange("loaded", false, true);
this.loadedBoundingBox = this.loadingBoundingBox;
this.loadingBoundingBox = BoundingBox.empty();
this.loadingWorker = null;
}
}
firePropertyChange("refresh", false, true);
}
@Override
public void setProperty(final String name, final Object value) {
if ("typePath".equals(name)) {
super.setProperty(name, PathName.newPathName(value));
} else {
super.setProperty(name, value);
}
}
public void setRecordsToCache(final Label cacheId,
final Iterable<? extends LayerRecord> records) {
synchronized (getSync()) {
this.recordIdentifiersByCacheId.put(cacheId, new HashSet<>());
addRecordsToCache(cacheId, records);
}
}
protected void setRecordStore(final RecordStore recordStore) {
this.recordStore = recordStore;
}
public void setTypePath(final PathName typePath) {
this.typePath = typePath;
if (this.typePath != null) {
if (!Property.hasValue(getName())) {
setName(this.typePath.getName());
}
}
if (isExists()) {
final RecordDefinition recordDefinition = getRecordDefinition(typePath);
setRecordDefinition(recordDefinition);
}
}
@Override
public void showForm(final LayerRecord record, final String fieldName) {
if (record != null) {
final Identifier identifier = getId(record);
if (identifier != null) {
addRecordToCache(getCacheIdForm(), record);
}
final LayerRecord proxyRecord = record.newRecordProxy();
super.showForm(proxyRecord, fieldName);
}
}
@Override
public MapEx toMap() {
final MapEx map = super.toMap();
addToMap(map, "typePath", this.typePath);
return map;
}
protected void writeDelete(final Writer<Record> writer, final LayerRecord record) {
writer.write(record);
}
protected void writeUpdate(final Writer<Record> writer, final LayerRecord record) {
writer.write(record);
}
}