All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.sling.servlets.post.AbstractPostOperation Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*
 * 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.servlets.post;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

import javax.jcr.Item;
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.servlet.http.HttpServletResponse;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.wrappers.SlingRequestPaths;
import org.apache.sling.servlets.post.exceptions.PreconditionViolatedPersistenceException;
import org.apache.sling.servlets.post.exceptions.TemporaryPersistenceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The AbstractPostOperation class is a base implementation of the
 * {@link PostOperation} service interface providing actual implementations with
 * useful tooling and common functionality like preparing the change logs or
 * saving or refreshing the JCR Session.
 *
 * @deprecated (SLING-6722): this class mixes Sling and JCR APIs which is not
 *  optimal as nowadays we favor the Sling APIs. There's no intention to remove
 *  it however, if you're using JCR APIs anyways in your project it's fine to
 *  use it. Theres no public replacement for it as I write this.
 */
@Deprecated
public abstract class AbstractPostOperation implements PostOperation {

    /**
     * default log
     */
    protected final Logger log = LoggerFactory.getLogger(getClass());

    /**
     * Prepares and finalizes the actual operation. Preparation encompasses
     * getting the absolute path of the item to operate on by calling the
     * {@link #getItemPath(SlingHttpServletRequest)} method and setting the
     * location and parent location on the response. After the operation has
     * been done in the {@link #doRun(SlingHttpServletRequest, PostResponse, List)}
     * method the session is saved if there are unsaved modifications. In case
     * of errorrs, the unsaved changes in the session are rolled back.
     *
     * @param request the request to operate on
     * @param response The PostResponse to record execution
     *            progress.
     * @param processors The array of processors
     */
    @Override
    public void run(final SlingHttpServletRequest request,
                    final PostResponse response,
                    final SlingPostProcessor[] processors) throws PreconditionViolatedPersistenceException, TemporaryPersistenceException {
        final Session session = request.getResourceResolver().adaptTo(Session.class);

        final VersioningConfiguration versionableConfiguration = getVersioningConfiguration(request);

        try {
            // calculate the paths
            String path = getItemPath(request);
            path = removeAndValidateWorkspace(path, session);
            response.setPath(path);

            // location
            response.setLocation(externalizePath(request, path));

            // parent location
            path = ResourceUtil.getParent(path);
            if (path != null) {
                response.setParentLocation(externalizePath(request, path));
            }

            final List changes = new ArrayList<>();

            doRun(request, response, changes);

            // invoke processors
            if (processors != null) {
                for (SlingPostProcessor processor : processors) {
                    processor.process(request, changes);
                }
            }

            // check modifications for remaining postfix and store the base path
            final Map modificationSourcesContainingPostfix = new HashMap<>();
            final Set allModificationSources = new HashSet<>(changes.size());
            for (final Modification modification : changes) {
                final String source = modification.getSource();
                if (source != null) {
                    allModificationSources.add(source);
                    final int atIndex = source.indexOf('@');
                    if (atIndex > 0) {
                        modificationSourcesContainingPostfix.put(source.substring(0, atIndex), source);
                    }
                }
            }

            // fail if any of the base paths (before the postfix) which had a postfix are contained in the modification set
            if (modificationSourcesContainingPostfix.size() > 0) {
                for (final Map.Entry sourceToCheck : modificationSourcesContainingPostfix.entrySet()) {
                    if (allModificationSources.contains(sourceToCheck.getKey())) {
                        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                                "Postfix-containing path " + sourceToCheck.getValue() +
                                " contained in the modification list. Check configuration.");
                        return;
                    }
                }
            }

            final Set nodesToCheckin = new LinkedHashSet<>();

            // set changes on html response
            for(Modification change : changes) {
                switch ( change.getType() ) {
                    case MODIFY : response.onModified(change.getSource()); break;
                    case DELETE : response.onDeleted(change.getSource()); break;
                    case MOVE   : response.onMoved(change.getSource(), change.getDestination()); break;
                    case COPY   : response.onCopied(change.getSource(), change.getDestination()); break;
                    case CREATE :
                        response.onCreated(change.getSource());
                        if (versionableConfiguration.isCheckinOnNewVersionableNode()) {
                            nodesToCheckin.add(change.getSource());
                        }
                        break;
                    case ORDER  : response.onChange("ordered", change.getSource(), change.getDestination()); break;
                    case CHECKOUT :
                        response.onChange("checkout", change.getSource());
                        nodesToCheckin.add(change.getSource());
                        break;
                    case CHECKIN :
                        response.onChange("checkin", change.getSource());
                        nodesToCheckin.remove(change.getSource());
                        break;
                }
            }

            if (isSessionSaveRequired(session, request)) {
                request.getResourceResolver().commit();
            }

            if (!isSkipCheckin(request)) {
                // now do the checkins
                for(String checkinPath : nodesToCheckin) {
                    if (checkin(request.getResourceResolver(), checkinPath)) {
                        response.onChange("checkin", checkinPath);
                    }
                }
            }

        } catch (Exception e) {

            log.error("Exception during response processing.", e);
            response.setError(e);

        } finally {
            try {
                if (isSessionSaveRequired(session, request)) {
                    request.getResourceResolver().revert();
                }
            } catch (RepositoryException e) {
                log.warn("RepositoryException in finally block: {}",
                    e.getMessage(), e);
            }
        }

    }

    /**
     * Actually performs the desired operation filling progress into the
     * changes list and preparing and further information in the
     * response.
     * 

* The response comes prepared with the path, location and * parent location set. Other properties are expected to be set by this * implementation. * * @param request The SlingHttpServletRequest providing the * input, mostly in terms of request parameters, to the * operation. * @param response The {@link PostResponse} to fill with response * information * @param changes A container to add {@link Modification} instances * representing the operations done. * @throws RepositoryException Maybe thrown if any error occurrs while * accessing the repository. * @throws TemporaryPersistenceException if a retry could help * @throws PreconditionViolatedPersistenceException if a retry doesn't make sense * and some preconditions should be changed. */ protected abstract void doRun(SlingHttpServletRequest request, PostResponse response, List changes) throws RepositoryException, PreconditionViolatedPersistenceException, TemporaryPersistenceException; /** * Get the versioning configuration. * @param request The http request * @return The versioning configuration */ protected VersioningConfiguration getVersioningConfiguration(SlingHttpServletRequest request) { VersioningConfiguration versionableConfiguration = (VersioningConfiguration) request.getAttribute(VersioningConfiguration.class.getName()); return versionableConfiguration != null ? versionableConfiguration : new VersioningConfiguration(); } /** * Check if checkin should be skipped * @param request The http request * @return {@code true} if checkin should be skipped */ protected boolean isSkipCheckin(SlingHttpServletRequest request) { return !getVersioningConfiguration(request).isAutoCheckin(); } /** * Check whether changes should be written back * @param request The http request * @return {@code true} If session handling should be skipped */ protected boolean isSkipSessionHandling(SlingHttpServletRequest request) { return Boolean.parseBoolean((String) request.getAttribute(SlingPostConstants.ATTR_SKIP_SESSION_HANDLING)) == true; } /** * Check whether commit to the resource resolver should be called. * @param session The JCR session * @param request The http request * @return {@code true} if a save is required. * @throws RepositoryException a repository exception */ protected boolean isSessionSaveRequired(Session session, SlingHttpServletRequest request) throws RepositoryException { return !isSkipSessionHandling(request) && request.getResourceResolver().hasChanges(); } /** * Remove the workspace name, if any, from the start of the path and validate that the * session's workspace name matches the path workspace name. * @param path The path * @param session The JCR session * @return The path without the workspace * @throws RepositoryException a repository exception */ protected String removeAndValidateWorkspace(String path, Session session) throws RepositoryException { final int wsSepPos = path.indexOf(":/"); if (wsSepPos != -1) { final String workspaceName = path.substring(0, wsSepPos); if (!workspaceName.equals(session.getWorkspace().getName())) { throw new RepositoryException("Incorrect workspace. Expecting " + workspaceName + ". Received " + session.getWorkspace().getName()); } return path.substring(wsSepPos + 1); } return path; } /** * Returns the path of the resource of the request as the item path. *

* This method may be overwritten by extension if the operation has * different requirements on path processing. * @param request The http request * @return The item path */ protected String getItemPath(SlingHttpServletRequest request) { return request.getResource().getPath(); } /** * Returns an iterator on Resource instances addressed in the * {@link SlingPostConstants#RP_APPLY_TO} request parameter. If the request * parameter is not set, null is returned. If the parameter * is set with valid resources an empty iterator is returned. Any resources * addressed in the {@link SlingPostConstants#RP_APPLY_TO} parameter is * ignored. * * @param request The SlingHttpServletRequest object used to * get the {@link SlingPostConstants#RP_APPLY_TO} parameter. * @return The iterator of resources listed in the parameter or * null if the parameter is not set in the request. */ protected Iterator getApplyToResources( SlingHttpServletRequest request) { final String[] applyTo = request.getParameterValues(SlingPostConstants.RP_APPLY_TO); if (applyTo == null) { return null; } return new ApplyToIterator(request, applyTo); } /** * Returns an external form of the given path prepending the context path * and appending a display extension. * * @param request The http request * @param path the path to externalize * @return the url */ protected final String externalizePath(SlingHttpServletRequest request, String path) { StringBuilder ret = new StringBuilder(); ret.append(SlingRequestPaths.getContextPath(request)); ret.append(request.getResourceResolver().map(path)); // append optional extension String ext = request.getParameter(SlingPostConstants.RP_DISPLAY_EXTENSION); if (ext != null && ext.length() > 0) { if (ext.charAt(0) != '.') { ret.append('.'); } ret.append(ext); } return ret.toString(); } /** * Resolves the given path with respect to the current root path. * * @param absPath The absolute base path * @param relPath the path to resolve * @return the given path if it starts with a '/'; a resolved path * otherwise. */ protected final String resolvePath(String absPath, String relPath) { if (relPath.startsWith("/")) { return relPath; } return absPath + "/" + relPath; } /** * Returns true if any of the request parameters starts with * {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_CURRENT ./}. * In this case only parameters starting with either of the prefixes * {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_CURRENT ./}, * {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_PARENT ../} * and {@link SlingPostConstants#ITEM_PREFIX_ABSOLUTE /} are * considered as providing content to be stored. Otherwise all parameters * not starting with the command prefix : are considered as * parameters to be stored. * * @param request The http request * @return If a prefix is required. */ protected final boolean requireItemPathPrefix( SlingHttpServletRequest request) { boolean requirePrefix = false; Enumeration names = request.getParameterNames(); while (names.hasMoreElements() && !requirePrefix) { String name = (String) names.nextElement(); requirePrefix = name.startsWith(SlingPostConstants.ITEM_PREFIX_RELATIVE_CURRENT); } return requirePrefix; } /** * Returns true if the name starts with either * of the prefixes * {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_CURRENT ./}, * {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_PARENT ../} * and {@link SlingPostConstants#ITEM_PREFIX_ABSOLUTE /}. * * @param name The name * @return {@code true} if the name has a prefix */ 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); } /** * Orders the given node according to the specified command. The following * syntax is supported: <xmp> | first | before all child nodes | before A | * before child node A | after A | after child node A | last | after all * nodes | N | at a specific position, N being an integer </xmp> * * @param request The http request * @param item node to order * @param changes The list of modifications * @throws RepositoryException if an error occurs */ protected void orderNode(SlingHttpServletRequest request, Item item, List changes) throws RepositoryException { String command = request.getParameter(SlingPostConstants.RP_ORDER); if (command == null || command.length() == 0) { // nothing to do return; } if (!item.isNode()) { return; } Node parent = item.getParent(); String next = null; if (command.equals(SlingPostConstants.ORDER_FIRST)) { next = parent.getNodes().nextNode().getName(); } else if (command.equals(SlingPostConstants.ORDER_LAST)) { next = ""; } else if (command.startsWith(SlingPostConstants.ORDER_BEFORE)) { next = command.substring(SlingPostConstants.ORDER_BEFORE.length()); } else if (command.startsWith(SlingPostConstants.ORDER_AFTER)) { String name = command.substring(SlingPostConstants.ORDER_AFTER.length()); NodeIterator iter = parent.getNodes(); while (iter.hasNext()) { Node n = iter.nextNode(); if (n.getName().equals(name)) { if (iter.hasNext()) { next = iter.nextNode().getName(); } else { next = ""; } } } } else { // check for integer try { // 01234 // abcde move a -> 2 (above 3) // bcade move a -> 1 (above 1) // bacde int newPos = Integer.parseInt(command); next = ""; NodeIterator iter = parent.getNodes(); while (iter.hasNext() && newPos >= 0) { Node n = iter.nextNode(); if (n.getName().equals(item.getName())) { // if old node is found before index, need to // inc index newPos++; } if (newPos == 0) { next = n.getName(); break; } newPos--; } } catch (NumberFormatException e) { throw new IllegalArgumentException( "provided node ordering command is invalid: " + command); } } if (next != null) { if (next.equals("")) { next = null; } parent.orderBefore(item.getName(), next); changes.add(Modification.onOrder(item.getPath(), next)); if (log.isDebugEnabled()) { log.debug("Node {} moved '{}'", item.getPath(), command); } } else { throw new IllegalArgumentException( "provided node ordering command is invalid: " + command); } } protected Node findVersionableAncestor(Node node) throws RepositoryException { if (isVersionable(node)) { return node; } try { node = node.getParent(); return findVersionableAncestor(node); } catch (ItemNotFoundException e) { // top-level return null; } } protected boolean isVersionable(Node node) throws RepositoryException { return node.isNodeType("mix:versionable"); } protected void checkoutIfNecessary(Node node, List changes, VersioningConfiguration versioningConfiguration) throws RepositoryException { if (versioningConfiguration.isAutoCheckout()) { Node versionableNode = findVersionableAncestor(node); if (versionableNode != null) { if (!versionableNode.isCheckedOut()) { versionableNode.checkout(); changes.add(Modification.onCheckout(versionableNode.getPath())); } } } } private boolean checkin(final ResourceResolver resolver, final String path) throws RepositoryException { final Resource rsrc = resolver.getResource(path); final Node node = (rsrc == null ? null : rsrc.adaptTo(Node.class)); if (node != null) { if (node.isCheckedOut() && isVersionable(node)) { node.checkin(); return true; } } return false; } private static class ApplyToIterator implements Iterator { private final ResourceResolver resolver; private final Resource baseResource; private final String[] paths; private int pathIndex; private Resource nextResource; private Iterator resourceIterator = null; ApplyToIterator(SlingHttpServletRequest request, String[] paths) { this.resolver = request.getResourceResolver(); this.baseResource = request.getResource(); this.paths = paths; this.pathIndex = 0; nextResource = seek(); } @Override public boolean hasNext() { return nextResource != null; } @Override public Resource next() { if (!hasNext()) { throw new NoSuchElementException(); } Resource result = nextResource; nextResource = seek(); return result; } @Override public void remove() { throw new UnsupportedOperationException(); } private Resource seek() { if (resourceIterator != null) { if (resourceIterator.hasNext()) { //return the next resource in the iterator Resource res = resourceIterator.next(); return res; } resourceIterator = null; } while (pathIndex < paths.length) { String path = paths[pathIndex]; pathIndex++; //SLING-2415 - support wildcard as the last segment of the applyTo path if (path.endsWith("*")) { if (path.length() == 1) { resourceIterator = baseResource.listChildren(); } else if (path.endsWith("/*")) { path = path.substring(0, path.length() - 2); if (path.length() == 0) { resourceIterator = baseResource.listChildren(); } else { Resource res = resolver.getResource(baseResource, path); if (res != null) { resourceIterator = res.listChildren(); } } } if (resourceIterator != null) { //return the first resource in the iterator if (resourceIterator.hasNext()) { Resource res = resourceIterator.next(); return res; } resourceIterator = null; } } else { Resource res = resolver.getResource(baseResource, path); if (res != null) { return res; } } } // no more elements in the array return null; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy