blob: 1134db01a72a47d380afe46a94b008561f7c47c5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.jackrabbit.oak.upgrade.security;
import java.util.Set;
import java.util.TreeSet;
import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.plugins.memory.PropertyBuilder;
import org.apache.jackrabbit.oak.security.user.MembershipWriter;
import org.apache.jackrabbit.oak.spi.commit.DefaultEditor;
import org.apache.jackrabbit.oak.spi.commit.Editor;
import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.apache.jackrabbit.util.Text;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
import static org.apache.jackrabbit.oak.api.CommitFailedException.CONSTRAINT;
import static org.apache.jackrabbit.oak.api.Type.NAME;
class GroupEditor extends DefaultEditor {
private static final Logger log = LoggerFactory.getLogger(GroupEditor.class);
private final String[] groupsRoot;
private State state;
private EditorGroup currentGroup;
private final UpgradeMembershipWriter writer = new UpgradeMembershipWriter();
GroupEditor(@NotNull NodeBuilder builder, @NotNull String groupsPath) {
this.state = new State(builder);
this.groupsRoot = Text.explode(groupsPath, '/', false);
// writer.setMembershipSizeThreshold(10); // uncomment to test different split sizes
}
private boolean descend(String name) {
if (state.depth < groupsRoot.length && !name.equals(groupsRoot[state.depth])) {
return false;
}
state = state.push(name);
return true;
}
private void ascend() {
state = state.pop();
}
@Override
public void leave(NodeState before, NodeState after) throws CommitFailedException {
if (currentGroup != null && currentGroup.path.equals(state.path)) {
log.info("scanned group {}, {} members", currentGroup.path, currentGroup.members.size());
writer.setMembers(state.builder, currentGroup.members);
currentGroup = null;
}
ascend();
}
@SuppressWarnings("deprecation")
@Override
public Editor childNodeAdded(String name, NodeState after) throws CommitFailedException {
if (!descend(name)) {
return null;
}
String nt = after.getName(JCR_PRIMARYTYPE);
if (nt == null) {
throw new CommitFailedException(
CONSTRAINT, 34, JCR_PRIMARYTYPE + " missing at " + state.path);
}
if (UserConstants.NT_REP_GROUP.equals(nt)) {
if (currentGroup != null) {
log.error("rep:Group within rep:Group not supported during upgrade. current group: {}, overwriting group: {}",
currentGroup.path, state.path);
}
currentGroup = new EditorGroup(state.path);
currentGroup.addMembers(after.getProperty(UserConstants.REP_MEMBERS));
} else if (UserConstants.NT_REP_MEMBERS.equals(nt)) {
if (currentGroup == null) {
log.warn("rep:Members detected outside of a rep:Group. ignoring {}", state.path);
} else {
currentGroup.addMembers(after);
}
}
return this;
}
@Override
public Editor childNodeChanged(String name, NodeState before, NodeState after) {
return null;
}
@Override
public Editor childNodeDeleted(String name, NodeState before) {
throw new IllegalStateException("deleted node during upgrade copy not expected: " + state.path + "/" + name);
}
private static class State {
private final State parent;
private final String path;
private final NodeBuilder builder;
private final int depth;
private State(State parent, String name) {
this.parent = parent;
this.path = parent.path + "/" + name;
this.builder = parent.builder.child(name);
this.depth = parent.depth + 1;
log.debug("{} --> {}", depth, path);
}
private State(NodeBuilder builder) {
this.parent = null;
this.path = "";
this.builder = builder;
this.depth = 0;
}
private State push(String name) {
return new State(this, name);
}
private State pop() {
log.debug("{} <-- {}", depth, path);
return parent;
}
}
private static class EditorGroup {
private final String path;
private final Set<String> members = new TreeSet<String>();
private EditorGroup(String path) {
this.path = path;
}
private void addMembers(PropertyState repMembers) {
if (repMembers != null) {
for (String ref: repMembers.getValue(Type.WEAKREFERENCES)) {
members.add(ref);
}
}
}
private void addMembers(NodeState node) {
for (PropertyState prop: node.getProperties()) {
if (prop.getType() == Type.WEAKREFERENCE) {
members.add(prop.getValue(Type.WEAKREFERENCE));
}
}
}
}
private static class UpgradeMembershipWriter {
/**
* Sets the given set of members to the specified group. this method is only used by the migration code.
*
* @param group node builder of group
* @param members set of content ids to set
*/
public void setMembers(@NotNull NodeBuilder group, @NotNull Set<String> members) {
group.removeProperty(UserConstants.REP_MEMBERS);
if (group.hasChildNode(UserConstants.REP_MEMBERS)) {
group.getChildNode(UserConstants.REP_MEMBERS).remove();
}
PropertyBuilder<String> prop = null;
NodeBuilder refList = null;
NodeBuilder node = group;
int count = 0;
int numNodes = 0;
for (String ref : members) {
if (prop == null) {
prop = PropertyBuilder.array(Type.WEAKREFERENCE, UserConstants.REP_MEMBERS);
}
prop.addValue(ref);
count++;
if (count > MembershipWriter.DEFAULT_MEMBERSHIP_THRESHOLD) {
node.setProperty(prop.getPropertyState());
prop = null;
if (refList == null) {
// create intermediate structure
refList = group.child(UserConstants.REP_MEMBERS_LIST);
refList.setProperty(JCR_PRIMARYTYPE, UserConstants.NT_REP_MEMBER_REFERENCES_LIST, NAME);
}
node = refList.child(String.valueOf(numNodes++));
node.setProperty(JCR_PRIMARYTYPE, UserConstants.NT_REP_MEMBER_REFERENCES, NAME);
}
}
if (prop != null) {
node.setProperty(prop.getPropertyState());
}
}
}
}