/**
* PermissionsEx
* Copyright (C) zml and PermissionsEx contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ninja.leaping.permissionsex.backend.sql;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import ninja.leaping.permissionsex.data.ImmutableSubjectData;
import ninja.leaping.permissionsex.util.ThrowingBiConsumer;
import ninja.leaping.permissionsex.util.Util;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
/**
* Data for SQL-backed subjects
*/
class SqlSubjectData implements ImmutableSubjectData {
private final SubjectRef subject;
private final Map<Set<Entry<String, String>>, Segment> segments;
private final AtomicReference<ImmutableList<ThrowingBiConsumer<SqlDao, SqlSubjectData, SQLException>>> updatesToPerform = new AtomicReference<>();
SqlSubjectData(SubjectRef subject) {
this(subject, ImmutableMap.of(), null);
}
SqlSubjectData(SubjectRef subject, Map<Set<Entry<String, String>>, Segment> segments, ImmutableList<ThrowingBiConsumer<SqlDao, SqlSubjectData, SQLException>> updates) {
this.subject = subject;
this.segments = segments;
this.updatesToPerform.set(updates);
}
protected final SqlSubjectData newWithUpdate(Map<Set<Entry<String, String>>, Segment> segments, ThrowingBiConsumer<SqlDao, SqlSubjectData, SQLException> updateFunc) {
return new SqlSubjectData(subject, segments, Util.appendImmutable(this.updatesToPerform.get(), updateFunc));
}
protected final SqlSubjectData newWithUpdated(Set<Entry<String, String>> key, Segment val) {
ThrowingBiConsumer<SqlDao, SqlSubjectData, SQLException> updateFunc;
if (val.isEmpty()) { // then remove segment
if (val.isUnallocated()) {
updateFunc = (dao, data) -> {};
} else {
updateFunc = (dao, data) -> dao.removeSegment(val);
}
} else if (val.isUnallocated()) { // create new segment
updateFunc = (dao, data) -> {
Segment seg = data.segments.get(key);
if (seg != null) {
if (seg.isUnallocated()) {
seg.popUpdates();
dao.updateFullSegment(data.subject, seg);
} else {
seg.doUpdates(dao);
}
}
};
} else { // just run updates
updateFunc = (dao, data) -> {
val.doUpdates(dao);
};
}
return newWithUpdate(Util.updateImmutable(segments, immutSet(key), val), updateFunc);
}
private Segment getSegmentOrNew(Set<Entry<String, String>> segments) {
Segment res = this.segments.get(segments);
if (res == null) {
res = Segment.unallocated();
}
return res;
}
private <E> ImmutableSet<E> immutSet(Set<E> set) {
return ImmutableSet.copyOf(set);
}
@Override
public Map<Set<Entry<String, String>>, Map<String, String>> getAllOptions() {
return Maps.filterValues(Maps.transformValues(segments,
dataEntry -> dataEntry == null ? null : dataEntry.getOptions()), el -> el != null);
}
@Override
public Map<String, String> getOptions(Set<Entry<String, String>> segments) {
final Segment entry = this.segments.get(segments);
return entry == null || entry.getOptions() == null ? Collections.emptyMap() : entry.getOptions();
}
@Override
public ImmutableSubjectData setOption(Set<Entry<String, String>> segments, String key, String value) {
if (value == null) {
return newWithUpdated(segments, getSegmentOrNew(segments).withoutOption(key));
} else {
return newWithUpdated(segments, getSegmentOrNew(segments).withOption(key, value));
}
}
@Override
public ImmutableSubjectData setOptions(Set<Entry<String, String>> segments, Map<String, String> values) {
return newWithUpdated(segments, getSegmentOrNew(segments).withOptions(values));
}
@Override
public ImmutableSubjectData clearOptions(Set<Entry<String, String>> segments) {
if (!this.segments.containsKey(segments)) {
return this;
}
return newWithUpdated(segments, getSegmentOrNew(segments).withoutOptions());
}
@Override
public ImmutableSubjectData clearOptions() {
if (this.segments.isEmpty()) {
return this;
}
Map<Set<Entry<String, String>>, Segment> newValue = Maps.transformValues(this.segments,
dataEntry -> dataEntry == null ? null : dataEntry.withoutOptions());
return newWithUpdate(newValue, createBulkUpdateFunc(newValue.keySet()));
}
private ThrowingBiConsumer<SqlDao, SqlSubjectData, SQLException> createBulkUpdateFunc(Collection<Set<Entry<String, String>>> keys) {
return (dao, data) -> {
for (Set<Entry<String, String>> key : keys) {
Segment seg = data.segments.get(key);
if (seg != null) {
if (seg.isEmpty()) {
dao.removeSegment(seg);
} else {
seg.doUpdates(dao);
}
}
}
};
}
@Override
public Map<Set<Entry<String, String>>, Map<String, Integer>> getAllPermissions() {
return Maps.filterValues(Maps.transformValues(segments,
dataEntry -> dataEntry == null ? null : dataEntry.getPermissions()), o -> o != null);
}
@Override
public Map<String, Integer> getPermissions(Set<Entry<String, String>> set) {
final Segment entry = this.segments.get(set);
return entry == null || entry.getPermissions()== null ? Collections.emptyMap() : entry.getPermissions();
}
@Override
public ImmutableSubjectData setPermission(Set<Entry<String, String>> segments, String permission, int value) {
if (value == 0) {
return newWithUpdated(segments, getSegmentOrNew(segments).withoutPermission(permission));
} else {
return newWithUpdated(segments, getSegmentOrNew(segments).withPermission(permission, value));
}
}
@Override
public ImmutableSubjectData setPermissions(Set<Entry<String, String>> segments, Map<String, Integer> values) {
return newWithUpdated(segments, getSegmentOrNew(segments).withPermissions(values));
}
@Override
public ImmutableSubjectData clearPermissions() {
if (this.segments.isEmpty()) {
return this;
}
Map<Set<Entry<String, String>>, Segment> newValue = Maps.transformValues(this.segments,
dataEntry -> dataEntry == null ? null : dataEntry.withoutPermissions());
return newWithUpdate(newValue, createBulkUpdateFunc(newValue.keySet()));
}
@Override
public ImmutableSubjectData clearPermissions(Set<Entry<String, String>> segments) {
if (!this.segments.containsKey(segments)) {
return this;
}
return newWithUpdated(segments, getSegmentOrNew(segments).withoutPermissions());
}
@Override
public Map<Set<Entry<String, String>>, List<Entry<String, String>>> getAllParents() {
return Maps.filterValues(Maps.transformValues(segments,
dataEntry -> dataEntry == null ? null : dataEntry.getParents() == null ? null : ImmutableList.copyOf(dataEntry.getParents())), v -> v != null);
}
@Override
public List<Entry<String, String>> getParents(Set<Entry<String, String>> segments) {
Segment ent = this.segments.get(segments);
return ent == null || ent.getParents() == null ? Collections.emptyList() : ImmutableList.copyOf(ent.getParents());
}
@Override
public ImmutableSubjectData addParent(Set<Entry<String, String>> segments, String type, String ident) {
Segment entry = getSegmentOrNew(segments);
final SubjectRef parentIdent = SubjectRef.unresolved(type, ident);
if (entry.getParents() != null && entry.getParents().contains(parentIdent)) {
return this;
}
return newWithUpdated(segments, entry.withAddedParent(parentIdent));
}
@Override
public ImmutableSubjectData removeParent(Set<Entry<String, String>> segments, String type, String identifier) {
Segment ent = this.segments.get(segments);
if (ent == null) {
return this;
}
final SubjectRef parentIdent = SubjectRef.unresolved(type, identifier);
if (ent.getParents() == null || !ent.getParents().contains(parentIdent)) {
return this;
}
return newWithUpdated(segments, ent.withRemovedParent(parentIdent));
}
@Override
public ImmutableSubjectData setParents(Set<Entry<String, String>> segments, List<Entry<String, String>> parents) {
Segment entry = getSegmentOrNew(segments);
return newWithUpdated(segments, entry.withParents(Lists.transform(parents, ent -> ent instanceof SubjectRef ? (SubjectRef) ent : SubjectRef.unresolved(ent.getKey(), ent.getValue()))));
}
@Override
public ImmutableSubjectData clearParents() {
if (this.segments.isEmpty()) {
return this;
}
Map<Set<Entry<String, String>>, Segment> newValue = Maps.transformValues(this.segments,
dataEntry -> dataEntry == null ? null : dataEntry.withoutParents());
return newWithUpdate(newValue, createBulkUpdateFunc(newValue.keySet()));
}
@Override
public ImmutableSubjectData clearParents(Set<Entry<String, String>> segments) {
if (!this.segments.containsKey(segments)) {
return this;
}
return newWithUpdated(segments, getSegmentOrNew(segments).withoutParents());
}
@Override
public int getDefaultValue(Set<Entry<String, String>> segments) {
Segment ent = this.segments.get(segments);
return ent == null || ent.getPermissionDefault() == null ? 0 : ent.getPermissionDefault();
}
@Override
public ImmutableSubjectData setDefaultValue(Set<Entry<String, String>> segments, int defaultValue) {
return newWithUpdated(segments, getSegmentOrNew(segments).withDefaultValue(defaultValue));
}
@Override
public Iterable<Set<Entry<String, String>>> getActiveContexts() {
return segments.keySet();
}
@Override
public Map<Set<Entry<String, String>>, Integer> getAllDefaultValues() {
return Maps.filterValues(Maps.transformValues(segments,
dataEntry -> dataEntry == null ? null : dataEntry.getPermissionDefault()), v -> v != null);
}
public void doUpdates(SqlDao dao) throws SQLException {
dao.executeInTransaction(() -> {
List<ThrowingBiConsumer<SqlDao, SqlSubjectData, SQLException>> updates = this.updatesToPerform.getAndSet(null);
if (updates != null) {
for (ThrowingBiConsumer<SqlDao, SqlSubjectData, SQLException> func : updates) {
func.accept(dao, this);
}
}
return null;
});
}
}