| /* |
| * 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.sling.jcr.repoinit.impl; |
| |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| import java.util.List; |
| |
| import javax.jcr.Node; |
| import javax.jcr.PathNotFoundException; |
| import javax.jcr.PropertyType; |
| import javax.jcr.RepositoryException; |
| import javax.jcr.Session; |
| import javax.jcr.Value; |
| |
| import org.apache.jackrabbit.api.security.user.Authorizable; |
| import org.apache.jackrabbit.util.Text; |
| import org.apache.jackrabbit.value.BooleanValue; |
| import org.apache.jackrabbit.value.DateValue; |
| import org.apache.jackrabbit.value.DoubleValue; |
| import org.apache.jackrabbit.value.LongValue; |
| import org.apache.jackrabbit.value.StringValue; |
| import org.apache.sling.repoinit.parser.operations.PropertyLine; |
| import org.apache.sling.repoinit.parser.operations.SetProperties; |
| import org.jetbrains.annotations.NotNull; |
| |
| /** |
| * OperationVisitor which processes only operations related to setting node |
| * properties. Having several such specialized visitors makes it easy to control |
| * the execution order. |
| */ |
| class NodePropertiesVisitor extends DoNothingVisitor { |
| /** |
| * The repoinit.parser transforms the authorizable(ids)[/relative_path] path |
| * syntax from the original source into ":authorizable:ids#/relative_path" in the |
| * values provided from {@link SetProperties#getPaths()} |
| * |
| * These constants are used to unwind those values into the parts for processing |
| */ |
| private static final String PATH_AUTHORIZABLE = ":authorizable:"; |
| private static final char ID_DELIMINATOR = ','; |
| private static final char SUBTREE_DELIMINATOR = '#'; |
| |
| /** |
| * Create a visitor using the supplied JCR Session. |
| * |
| * @param s must have sufficient rights to set properties on a path. |
| */ |
| public NodePropertiesVisitor(Session s) { |
| super(s); |
| } |
| |
| /** |
| * True if the property needs to be set - if false, it is not touched. This |
| * handles the "default" repoinit instruction, which means "do not change the |
| * property if already set" |
| * |
| * @throws RepositoryException |
| * @throws PathNotFoundException |
| */ |
| private static boolean needToSetProperty(Node n, PropertyLine line) throws RepositoryException { |
| if(!line.isDefault()) { |
| // It's a "set" line -> overwrite existing value if any |
| return true; |
| } |
| |
| // Otherwise set the property only if not set yet |
| final String name = line.getPropertyName(); |
| return(!n.hasProperty(name) || n.getProperty(name) == null); |
| } |
| |
| /** |
| * True if the property needs to be set - if false, it is not touched. This |
| * handles the "default" repoinit instruction, which means "do not change the |
| * property if already set" |
| * |
| * @throws RepositoryException |
| * @throws PathNotFoundException |
| */ |
| private static boolean needToSetProperty(Authorizable a, String pRelPath, boolean isDefault) throws RepositoryException { |
| if (!isDefault) { |
| // It's a "set" line -> overwrite existing value if any |
| return true; |
| } |
| |
| // Otherwise set the property only if not set yet |
| return(!a.hasProperty(pRelPath) || a.getProperty(pRelPath) == null); |
| } |
| |
| /** |
| * Build relative property path from a subtree path and a property name |
| * @param subTreePath the subtree path (may be null or empty) |
| * @param name the property name |
| * @return the relative path of the property |
| */ |
| private static String toRelPath(String subTreePath, final String name) { |
| final String pRelPath; |
| if (subTreePath == null || subTreePath.isEmpty()) { |
| pRelPath = name; |
| } else { |
| if (subTreePath.startsWith("/")) { |
| subTreePath = subTreePath.substring(1); |
| } |
| pRelPath = String.format("%s/%s", subTreePath, name); |
| } |
| return pRelPath; |
| } |
| |
| /** |
| * Lookup the authorizables for the given ids |
| * @param session the jcr session |
| * @param ids delimited list of authorizable ids |
| * @return iterator over the found authorizables |
| */ |
| @NotNull |
| private static Iterable<Authorizable> getAuthorizables(@NotNull Session session, @NotNull String ids) throws RepositoryException { |
| List<Authorizable> authorizables = new ArrayList<>(); |
| for (String id : Text.explode(ids, ID_DELIMINATOR)) { |
| Authorizable a = UserUtil.getAuthorizable(session, id); |
| if (a == null) { |
| throw new PathNotFoundException("Cannot resolve path of authorizable with id '" + id + "'."); |
| } |
| authorizables.add(a); |
| } |
| return authorizables; |
| } |
| |
| /** |
| * Set properties on a user or group |
| * |
| * @param nodePath the target path |
| * @param propertyLines the property lines to process to set the properties |
| */ |
| private void setAuthorizableProperties(String nodePath, List<PropertyLine> propertyLines) throws RepositoryException { |
| int lastHashIndex = nodePath.lastIndexOf(SUBTREE_DELIMINATOR); |
| if (lastHashIndex == -1) { |
| throw new IllegalStateException("Invalid format of authorizable path: # deliminator expected."); |
| } |
| String ids = nodePath.substring(PATH_AUTHORIZABLE.length(), lastHashIndex); |
| String subTreePath = nodePath.substring(lastHashIndex + 1); |
| for (Authorizable a : getAuthorizables(session, ids)) { |
| log.info("Setting properties on authorizable '{}'", a.getID()); |
| for (PropertyLine pl : propertyLines) { |
| final String pName = pl.getPropertyName(); |
| final String pRelPath = toRelPath(subTreePath, pName); |
| if (needToSetProperty(a, pRelPath, pl.isDefault())) { |
| final List<Object> values = pl.getPropertyValues(); |
| if (values.size() > 1) { |
| Value[] pValues = convertToValues(values); |
| a.setProperty(pRelPath, pValues); |
| } else { |
| Value pValue = convertToValue(values.get(0)); |
| a.setProperty(pRelPath, pValue); |
| } |
| } else { |
| log.info("Property '{}' already set on authorizable '{}', existing value will not be overwritten in 'default' mode", |
| pRelPath, a.getID()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Set properties on a JCR node |
| * |
| * @param nodePath the target path |
| * @param propertyLines the property lines to process to set the properties |
| */ |
| private void setNodeProperties(String nodePath, List<PropertyLine> propertyLines) throws RepositoryException { |
| log.info("Setting properties on nodePath '{}'", nodePath); |
| Node n = session.getNode(nodePath); |
| for (PropertyLine pl : propertyLines) { |
| final String pName = pl.getPropertyName(); |
| if (needToSetProperty(n, pl)) { |
| final PropertyLine.PropertyType pType = pl.getPropertyType(); |
| final int type = PropertyType.valueFromName(pType.name()); |
| final List<Object> values = pl.getPropertyValues(); |
| if (values.size() > 1) { |
| Value[] pValues = convertToValues(values); |
| n.setProperty(pName, pValues, type); |
| } else { |
| Value pValue = convertToValue(values.get(0)); |
| n.setProperty(pName, pValue, type); |
| } |
| } else { |
| log.info("Property '{}' already set on path '{}', existing value will not be overwritten in 'default' mode", |
| pName, nodePath); |
| } |
| } |
| } |
| |
| @Override |
| public void visitSetProperties(SetProperties sp) { |
| for (String nodePath : sp.getPaths()) { |
| try { |
| if (nodePath.startsWith(PATH_AUTHORIZABLE)) { |
| // special case for setting properties on authorizable |
| setAuthorizableProperties(nodePath, sp.getPropertyLines()); |
| } else { |
| setNodeProperties(nodePath, sp.getPropertyLines()); |
| } |
| } catch (RepositoryException e) { |
| report(e, "Unable to set properties on path [" + nodePath + "]:" + e); |
| } |
| } |
| } |
| |
| private Value[] convertToValues(List<Object> values) { |
| int size = values.size(); |
| Value[] valueArray = new Value[size]; |
| for(int i = 0; i < size; i++) { |
| valueArray[i] = convertToValue(values.get(i)); |
| } |
| return valueArray; |
| } |
| |
| private Value convertToValue(Object value) { |
| Value convertedValue = null; |
| if (value instanceof String) { |
| convertedValue = new StringValue((String) value); |
| } else if (value instanceof Double) { |
| convertedValue = new DoubleValue((Double) value); |
| } else if (value instanceof Long) { |
| convertedValue = new LongValue((Long) value); |
| } else if (value instanceof Boolean) { |
| convertedValue = new BooleanValue((Boolean) value); |
| } else if (value instanceof Calendar) { |
| convertedValue = new DateValue((Calendar) value); |
| } else { |
| throw new RuntimeException("Unable to convert " + value + " to jcr Value"); |
| } |
| return convertedValue; |
| } |
| } |