Package org.apache.sling.jackrabbit.usermanager.impl.post

Source Code of org.apache.sling.jackrabbit.usermanager.impl.post.AbstractAuthorizablePostServlet$SlingUnsupportedEncodingException

/*
* 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.jackrabbit.usermanager.impl.post;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.servlet.ServletException;

import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.sling.api.SlingIOException;
import org.apache.sling.api.request.RequestParameter;
import org.apache.sling.commons.osgi.OsgiUtil;
import org.apache.sling.jackrabbit.usermanager.impl.resource.AuthorizableResourceProvider;
import org.apache.sling.servlets.post.Modification;
import org.apache.sling.servlets.post.SlingPostConstants;
import org.apache.sling.servlets.post.impl.helper.DateParser;
import org.apache.sling.servlets.post.impl.helper.RequestProperty;
import org.osgi.service.component.ComponentContext;

/**
* Base class for all the POST servlets for the UserManager operations
*/
public abstract class AbstractAuthorizablePostServlet extends
        AbstractPostServlet {
    private static final long serialVersionUID = -5918670409789895333L;

    public static final String PROP_DATE_FORMAT = "servlet.post.dateFormats";

    private DateParser dateParser;

    // ---------- SCR Integration ----------------------------------------------

    protected void activate(ComponentContext context) {
        Dictionary<?, ?> props = context.getProperties();

        dateParser = new DateParser();
        String[] dateFormats = OsgiUtil.toStringArray(props.get(PROP_DATE_FORMAT));
        for (String dateFormat : dateFormats) {
            dateParser.register(dateFormat);
        }
    }

    protected void deactivate(ComponentContext context) {
        dateParser = null;
    }

    // ------ The methods below are based on the private methods from the
    // ModifyOperation class -----

    /**
     * Collects the properties that form the content to be written back to the
     * repository. NOTE: In the returned map, the key is the property name not a
     * path.
     *
     * @throws RepositoryException if a repository error occurs
     * @throws ServletException if an internal error occurs
     */
    protected Map<String, RequestProperty> collectContent(
            Map<String, ?> properties,
            String authorizablePath) {

        boolean requireItemPrefix = requireItemPathPrefix(properties);

        // walk the request parameters and collect the properties
        Map<String, RequestProperty> reqProperties = new HashMap<String, RequestProperty>();
        for (Map.Entry<String, ?> e : properties.entrySet()) {
            final String paramName = e.getKey();

            // do not store parameters with names starting with sling:post
            if (paramName.startsWith(SlingPostConstants.RP_PREFIX)) {
                continue;
            }
            // SLING-298: skip form encoding parameter
            if (paramName.equals("_charset_")) {
                continue;
            }
            // skip parameters that do not start with the save prefix
            if (requireItemPrefix && !hasItemPathPrefix(paramName)) {
                continue;
            }

            // ensure the paramName is an absolute property name
            String propPath;
            if (paramName.startsWith("./")) {
                propPath = paramName.substring(2);
            } else {
                propPath = paramName;
            }
            if (propPath.indexOf('/') != -1) {
                // only one path segment is valid here, so this paramter can't
                // be used.
                continue; // skip it.
            }

            propPath = authorizablePath + "/" + propPath;

            // @TypeHint example
            // <input type="text" name="./age" />
            // <input type="hidden" name="./age@TypeHint" value="long" />
            // causes the setProperty using the 'long' property type
            if (propPath.endsWith(SlingPostConstants.TYPE_HINT_SUFFIX)) {
                RequestProperty prop = getOrCreateRequestProperty(
                    reqProperties, propPath,
                    SlingPostConstants.TYPE_HINT_SUFFIX);

                String typeHintValue = convertToString(e.getValue());
                if (typeHintValue != null) {
                    prop.setTypeHintValue(typeHintValue);
                }

                continue;
            }

            // @DefaultValue
            if (propPath.endsWith(SlingPostConstants.DEFAULT_VALUE_SUFFIX)) {
                RequestProperty prop = getOrCreateRequestProperty(
                    reqProperties, propPath,
                    SlingPostConstants.DEFAULT_VALUE_SUFFIX);

                prop.setDefaultValues(convertToRequestParameterArray(e.getValue()));

                continue;
            }

            // SLING-130: VALUE_FROM_SUFFIX means take the value of this
            // property from a different field
            // @ValueFrom example:
            // <input name="./Text@ValueFrom" type="hidden" value="fulltext" />
            // causes the JCR Text property to be set to the value of the
            // fulltext form field.
            if (propPath.endsWith(SlingPostConstants.VALUE_FROM_SUFFIX)) {
                RequestProperty prop = getOrCreateRequestProperty(
                    reqProperties, propPath,
                    SlingPostConstants.VALUE_FROM_SUFFIX);

                // @ValueFrom params must have exactly one value, else ignored
                String [] valueFrom = convertToStringArray(e.getValue());
                if (valueFrom.length == 1) {
                    String refName = valueFrom[0];
                    RequestParameter[] refValues = convertToRequestParameterArray(refName);
                    if (refValues != null) {
                        prop.setValues(refValues);
                    }
                }

                continue;
            }

            // SLING-458: Allow Removal of properties prior to update
            // @Delete example:
            // <input name="./Text@Delete" type="hidden" />
            // causes the JCR Text property to be deleted before update
            if (propPath.endsWith(SlingPostConstants.SUFFIX_DELETE)) {
                RequestProperty prop = getOrCreateRequestProperty(
                    reqProperties, propPath, SlingPostConstants.SUFFIX_DELETE);

                prop.setDelete(true);

                continue;
            }

            // SLING-455: @MoveFrom means moving content to another location
            // @MoveFrom example:
            // <input name="./Text@MoveFrom" type="hidden" value="/tmp/path" />
            // causes the JCR Text property to be set by moving the /tmp/path
            // property to Text.
            if (propPath.endsWith(SlingPostConstants.SUFFIX_MOVE_FROM)) {
                // don't support @MoveFrom here
                continue;
            }

            // SLING-455: @CopyFrom means moving content to another location
            // @CopyFrom example:
            // <input name="./Text@CopyFrom" type="hidden" value="/tmp/path" />
            // causes the JCR Text property to be set by copying the /tmp/path
            // property to Text.
            if (propPath.endsWith(SlingPostConstants.SUFFIX_COPY_FROM)) {
                // don't support @CopyFrom here
                continue;
            }

            // plain property, create from values
            RequestProperty prop = getOrCreateRequestProperty(reqProperties,
                propPath, null);
            prop.setValues(convertToRequestParameterArray(e.getValue()));
        }

        return reqProperties;
    }

    /**
     * Returns the request property for the given property path. If such a
     * request property does not exist yet it is created and stored in the
     * <code>props</code>.
     *
     * @param props The map of already seen request properties.
     * @param paramPath The absolute path of the property including the
     *            <code>suffix</code> to be looked up.
     * @param suffix The (optional) suffix to remove from the
     *            <code>paramName</code> before looking it up.
     * @return The {@link RequestProperty} for the <code>paramName</code>.
     */
    private RequestProperty getOrCreateRequestProperty(
            Map<String, RequestProperty> props, String paramPath, String suffix) {
        if (suffix != null && paramPath.endsWith(suffix)) {
            paramPath = paramPath.substring(0, paramPath.length()
                - suffix.length());
        }

        RequestProperty prop = props.get(paramPath);
        if (prop == null) {
            prop = new RequestProperty(paramPath);
            props.put(paramPath, prop);
        }

        return prop;
    }

    /**
     * Removes all properties listed as {@link RequestProperty#isDelete()} from
     * the authorizable.
     *
     * @param authorizable The
     *            <code>org.apache.jackrabbit.api.security.user.Authorizable</code>
     *            that should have properties deleted.
     * @param reqProperties The map of request properties to check for
     *            properties to be removed.
     * @param changes The <code>List</code> to be updated with
     *            information on deleted properties.
     * @throws RepositoryException Is thrown if an error occurrs checking or
     *             removing properties.
     */
    protected void processDeletes(Authorizable authorizable,
            Map<String, RequestProperty> reqProperties,
            List<Modification> changes) throws RepositoryException {

        for (RequestProperty property : reqProperties.values()) {
            if (property.isDelete()) {
                if (authorizable.hasProperty(property.getName())) {
                    authorizable.removeProperty(property.getName());
                    changes.add(Modification.onDeleted(property.getPath()));
                }
            }
        }
    }

    /**
     * Writes back the content
     *
     * @throws RepositoryException if a repository error occurs
     * @throws ServletException if an internal error occurs
     */
    protected void writeContent(Session session, Authorizable authorizable,
            Map<String, RequestProperty> reqProperties,
            List<Modification> changes) throws RepositoryException {

        for (RequestProperty prop : reqProperties.values()) {
            if (prop.hasValues()) {
                // skip jcr special properties
                if (prop.getName().equals("jcr:primaryType")
                    || prop.getName().equals("jcr:mixinTypes")) {
                    continue;
                }
                if (authorizable.isGroup()) {
                    if (prop.getName().equals("groupId")) {
                        // skip these
                        continue;
                    }
                } else {
                    if (prop.getName().equals("userId")
                        || prop.getName().equals("pwd")
                        || prop.getName().equals("pwdConfirm")) {
                        // skip these
                        continue;
                    }
                }
                if (prop.isFileUpload()) {
                    // don't handle files for user properties for now.
                    continue;
                    // uploadHandler.setFile(parent, prop, changes);
                } else {
                    setPropertyAsIs(session, authorizable, prop, changes);
                }
            }
        }
    }

    /**
     * set property without processing, except for type hints
     *
     * @param parent the parent node
     * @param prop the request property
     * @throws RepositoryException if a repository error occurs.
     */
    private void setPropertyAsIs(Session session, Authorizable parent,
            RequestProperty prop, List<Modification> changes)
            throws RepositoryException {

        String parentPath;
        if (parent.isGroup()) {
            parentPath = AuthorizableResourceProvider.SYSTEM_USER_MANAGER_GROUP_PREFIX
                + parent.getID();
        } else {
            parentPath = AuthorizableResourceProvider.SYSTEM_USER_MANAGER_USER_PREFIX
                + parent.getID();
        }

        // no explicit typehint
        int type = PropertyType.UNDEFINED;
        if (prop.getTypeHint() != null) {
            try {
                type = PropertyType.valueFromName(prop.getTypeHint());
            } catch (Exception e) {
                // ignore
            }
        }

        String[] values = prop.getStringValues();
        if (values == null) {
            // remove property
            boolean removedProp = removePropertyIfExists(parent, prop.getName());
            if (removedProp) {
                changes.add(Modification.onDeleted(parentPath + "/"
                    + prop.getName()));
            }
        } else if (values.length == 0) {
            // do not create new prop here, but clear existing
            if (parent.hasProperty(prop.getName())) {
                Value val = session.getValueFactory().createValue("");
                parent.setProperty(prop.getName(), val);
                changes.add(Modification.onModified(parentPath + "/"
                    + prop.getName()));
            }
        } else if (values.length == 1) {
            boolean removedProp = removePropertyIfExists(parent, prop.getName());
            // if the provided value is the empty string, we don't have to do
            // anything.
            if (values[0].length() == 0) {
                if (removedProp) {
                    changes.add(Modification.onDeleted(parentPath + "/"
                        + prop.getName()));
                }
            } else {
                // modify property
                if (type == PropertyType.DATE) {
                    // try conversion
                    Calendar c = dateParser.parse(values[0]);
                    if (c != null) {
                        if (prop.hasMultiValueTypeHint()) {
                            final Value[] array = new Value[1];
                            array[0] = session.getValueFactory().createValue(c);
                            parent.setProperty(prop.getName(), array);
                            changes.add(Modification.onModified(parentPath
                                + "/" + prop.getName()));
                        } else {
                            Value cVal = session.getValueFactory().createValue(
                                c);
                            parent.setProperty(prop.getName(), cVal);
                            changes.add(Modification.onModified(parentPath
                                + "/" + prop.getName()));
                        }
                        return;
                    }
                    // fall back to default behaviour
                }
                if (type == PropertyType.UNDEFINED) {
                    Value val = session.getValueFactory().createValue(
                        values[0], PropertyType.STRING);
                    parent.setProperty(prop.getName(), val);
                } else {
                    if (prop.hasMultiValueTypeHint()) {
                        final Value[] array = new Value[1];
                        array[0] = session.getValueFactory().createValue(
                            values[0], type);
                        parent.setProperty(prop.getName(), array);
                    } else {
                        Value val = session.getValueFactory().createValue(
                            values[0], type);
                        parent.setProperty(prop.getName(), val);
                    }
                }
                changes.add(Modification.onModified(parentPath + "/"
                    + prop.getName()));
            }
        } else {
            removePropertyIfExists(parent, prop.getName());
            if (type == PropertyType.DATE) {
                // try conversion
                ValueFactory valFac = session.getValueFactory();
                Value[] c = dateParser.parse(values, valFac);
                if (c != null) {
                    parent.setProperty(prop.getName(), c);
                    changes.add(Modification.onModified(parentPath + "/"
                        + prop.getName()));
                    return;
                }
                // fall back to default behaviour
            }

            Value[] vals = new Value[values.length];
            if (type == PropertyType.UNDEFINED) {
                for (int i = 0; i < values.length; i++) {
                    vals[i] = session.getValueFactory().createValue(values[i]);
                }
            } else {
                for (int i = 0; i < values.length; i++) {
                    vals[i] = session.getValueFactory().createValue(values[i],
                        type);
                }
            }
            parent.setProperty(prop.getName(), vals);
            changes.add(Modification.onModified(parentPath + "/"
                + prop.getName()));
        }

    }

    /**
     * Removes the property with the given name from the authorizable if it
     * exists.
     *
     * @param authorizable the <code>org.apache.jackrabbit.api.security.user.Authorizable</code>
     *         that should have properties deleted.
     * @param name the name of the property to remove
     * @return path of the property that was removed or <code>null</code> if it
     *         was not removed
     * @throws RepositoryException if a repository error occurs.
     */
    private boolean removePropertyIfExists(Authorizable authorizable, String name)
            throws RepositoryException {
        if (authorizable.getProperty(name) != null) {
            authorizable.removeProperty(name);
            return true;
        }
        return false;
    }

    // ------ These methods were copied from AbstractSlingPostOperation ------

    /**
     * Returns <code>true</code> if the <code>name</code> starts with either of
     * the prefixes {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_CURRENT
     * <code>./</code>}, {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_PARENT
     * <code>../</code>} and {@link SlingPostConstants#ITEM_PREFIX_ABSOLUTE
     * <code>/</code>}.
     */
    protected boolean hasItemPathPrefix(String name) {
        return name.startsWith(SlingPostConstants.ITEM_PREFIX_ABSOLUTE)
            || name.startsWith(SlingPostConstants.ITEM_PREFIX_RELATIVE_CURRENT)
            || name.startsWith(SlingPostConstants.ITEM_PREFIX_RELATIVE_PARENT);
    }

    /**
     * Returns true if any of the request parameters starts with
     * {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_CURRENT <code>./</code>}.
     * In this case only parameters starting with either of the prefixes
     * {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_CURRENT <code>./</code>},
     * {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_PARENT <code>../</code>}
     * and {@link SlingPostConstants#ITEM_PREFIX_ABSOLUTE <code>/</code>} are
     * considered as providing content to be stored. Otherwise all parameters
     * not starting with the command prefix <code>:</code> are considered as
     * parameters to be stored.
     */
    protected final boolean requireItemPathPrefix(
            Map<String, ?> properties) {

        boolean requirePrefix = false;

        Iterator<String> iterator = properties.keySet().iterator();
        while (iterator.hasNext() && !requirePrefix) {
            String name = iterator.next();
            requirePrefix = name.startsWith(SlingPostConstants.ITEM_PREFIX_RELATIVE_CURRENT);
        }

        return requirePrefix;
    }


    protected String convertToString(Object obj) {
        if (obj == null) {
            return null;
        }

        if (obj instanceof String) {
            return (String)obj;
        } else if (obj instanceof String[]) {
            String [] values = (String[])obj;
            if (values.length > 0) {
                return values[0];
            }
            return null;
        } else if (obj instanceof RequestParameter) {
            ((RequestParameter)obj).getString();
        } else if (obj instanceof RequestParameter[]) {
            RequestParameter[] values = (RequestParameter[])obj;
            if (values.length > 0) {
                return values[0].getString();
            }
            return null;
        }
        return null;
    }

    protected String[] convertToStringArray(Object obj) {
        if (obj == null) {
            return null;
        }

        if (obj instanceof String) {
            return new String[] {(String)obj};
        } else if (obj instanceof String[]) {
            return (String[])obj;
        } else if (obj instanceof RequestParameter) {
            return new String[] {((RequestParameter)obj).getString()};
        } else if (obj instanceof RequestParameter[]) {
            RequestParameter[] values = (RequestParameter[])obj;
            String [] strValues = new String[values.length];
            for (int i=0; i < values.length; i++) {
                strValues[i] = values[i].getString();
            }
            return strValues;
        }
        return null;
    }

    protected RequestParameter[] convertToRequestParameterArray(Object obj) {
        if (obj == null) {
            return null;
        }

        if (obj instanceof String) {
            return new RequestParameter[] {
                new RequestParameterImpl((String)obj, null)
            };
        } else if (obj instanceof String[]) {
            String [] strValues = (String[])obj;
            RequestParameter [] values = new RequestParameter[strValues.length];
            for (int i=0; i < strValues.length; i++) {
                values[i] = new RequestParameterImpl(strValues[i], null);
            }
            return values;
        } else if (obj instanceof RequestParameter) {
            return new RequestParameter[] {(RequestParameter)obj};
        } else if (obj instanceof RequestParameter[]) {
            return (RequestParameter[])obj;
        }
        return null;
    }

    static class RequestParameterImpl implements RequestParameter {

        private String value;
        private String encoding;

        private byte[] content;

        RequestParameterImpl(String value, String encoding) {
            this.encoding = encoding;
            this.value = value;
            this.content = null;
        }

        String getEncoding() {
            return this.encoding;
        }

        void setEncoding(String encoding) {
            // recode this parameter by encoding the string with the current
            // encoding and decode the bytes with the encoding
            try {
                this.value = getString(encoding);
            } catch (UnsupportedEncodingException uee) {
                throw new SlingUnsupportedEncodingException(uee);
            }
            this.encoding = encoding;
        }

        /**
         * @see org.apache.sling.api.request.RequestParameter#get()
         */
        public byte[] get() {
            if (content == null) {
                try {
                    content = getString().getBytes(getEncoding());
                } catch (Exception e) {
                    // UnsupportedEncodingException, IllegalArgumentException
                    content = getString().getBytes();
                }
            }
            return content;
        }

        /**
         * @see org.apache.sling.api.request.RequestParameter#getContentType()
         */
        public String getContentType() {
            // none known for www-form-encoded parameters
            return null;
        }

        /**
         * @see org.apache.sling.api.request.RequestParameter#getInputStream()
         */
        public InputStream getInputStream() {
            return new ByteArrayInputStream(this.get());
        }

        /**
         * @see org.apache.sling.api.request.RequestParameter#getFileName()
         */
        public String getFileName() {
            // no original file name
            return null;
        }

        /**
         * @see org.apache.sling.api.request.RequestParameter#getSize()
         */
        public long getSize() {
            return this.get().length;
        }

        /**
         * @see org.apache.sling.api.request.RequestParameter#getString()
         */
        public String getString() {
            return value;
        }

        /**
         * @see org.apache.sling.api.request.RequestParameter#getString(java.lang.String)
         */
        public String getString(String encoding)
                throws UnsupportedEncodingException {
            return new String(this.get(), encoding);
        }

        /**
         * @see org.apache.sling.api.request.RequestParameter#isFormField()
         */
        public boolean isFormField() {
            // www-form-encoded are always form fields
            return true;
        }

        @Override
        public String toString() {
            return this.getString();
        }
    }

    static class SlingUnsupportedEncodingException extends SlingIOException {

        private static final long serialVersionUID = -4482276105859280247L;

        SlingUnsupportedEncodingException(UnsupportedEncodingException uee) {
            super(uee);
        }

    }

}
TOP

Related Classes of org.apache.sling.jackrabbit.usermanager.impl.post.AbstractAuthorizablePostServlet$SlingUnsupportedEncodingException

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.