/*
 * 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.spi.commons;

import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.jcr.Credentials;
import javax.jcr.ItemNotFoundException;
import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.RepositoryException;
import javax.jcr.UnsupportedRepositoryOperationException;

import org.apache.jackrabbit.spi.ItemId;
import org.apache.jackrabbit.spi.NodeId;
import org.apache.jackrabbit.spi.NodeInfo;
import org.apache.jackrabbit.spi.QValue;
import org.apache.jackrabbit.spi.SessionInfo;
import org.apache.jackrabbit.spi.commons.nodetype.compact.ParseException;

/**
 * <code>AbstractReadableRepositoryService</code> provides an abstract base
 * class where all methods that attempt to write throw an
 * {@link UnsupportedRepositoryOperationException}. This class useful for
 * repository service implementation that only provide read access to the
 * underlying content.
 */
public abstract class AbstractReadableRepositoryService extends AbstractRepositoryService {

    protected static final Set<String> WRITE_ACTIONS = new HashSet<String>(
            Arrays.asList("add_node", "set_property", "remove"));

    /**
     * The list of workspaces that this repository service exposes.
     */
    protected final List<String> wspNames;

    /**
     * The name of the default workspace
     */
    protected final String defaulWsp;

    /**
     * Creates a new <code>AbstractReadableRepositoryService</code>.
     *
     * @param descriptors the repository descriptors. Maps descriptor keys to
     *                    descriptor values.
     * @param namespaces  the namespaces. Maps namespace prefixes to namespace
     *                    URIs.
     * @param cnd         a reader on the compact node type definition.
     * @param wspNames    a list of workspace names.
     * @param defaultWsp  name of the default workspace
     * @throws RepositoryException       if the namespace mappings are invalid.
     * @throws ParseException            if an error occurs while parsing the CND.
     * @throws IllegalArgumentException  if <code>defaultWsp</code> is <code>null</code>
     */
    public AbstractReadableRepositoryService(Map<String, QValue[]> descriptors,
                                             Map<String, String> namespaces,
                                             Reader cnd,
                                             List<String> wspNames,
                                             String defaultWsp)
            throws RepositoryException, ParseException, IllegalArgumentException {

        super(descriptors, namespaces, cnd);

        if (defaultWsp == null) {
            throw new IllegalArgumentException("Default workspace is null");
        }

        this.wspNames = Collections.unmodifiableList(new ArrayList<String>(wspNames));
        this.defaulWsp = defaultWsp;
    }

    //---------------------< may be overwritten by subclasses>------------------

    /**
     * Checks whether the <code>workspaceName</code> is valid.
     * @param workspaceName  name of the workspace to check
     * @throws NoSuchWorkspaceException  if <code>workspaceName</code> is neither in the
     *   list of workspaces nor null (i.e. default workspace).
     */
    @Override
    protected void checkWorkspace(String workspaceName) throws NoSuchWorkspaceException {
        if (workspaceName != null && !wspNames.contains(workspaceName)) {
            throw new NoSuchWorkspaceException(workspaceName);
        }
    }

    @Override
    protected SessionInfo createSessionInfo(Credentials credentials, String workspaceName)
            throws RepositoryException {

        return super.createSessionInfo(credentials, workspaceName == null? defaulWsp : workspaceName);
    }

    //-----------------------------< reading >----------------------------------

    /**
     * This default implementation returns the first item returned by the call to
     * {@link #getItemInfos(SessionInfo, NodeId)}. The underlying assumption here is that
     * the implementation and the persistence layer are optimized for batch reading. That is,
     * a call to <code>getItemInfos</code> is no more expensive than retrieving the single
     * <code>NodeInfo</code> only. If this assumption does not hold, subclasses should override
     * this method.
     */
    public NodeInfo getNodeInfo(SessionInfo sessionInfo, NodeId nodeId) throws ItemNotFoundException,
            RepositoryException {

        return (NodeInfo) getItemInfos(sessionInfo, nodeId).next();
    }

    //-------------------------< workspace names >------------------------------

    /**
     * This default implementation first calls {@link #checkSessionInfo(SessionInfo)}
     * with the <code>sessionInfo</code>, then returns the workspaces that were
     * passed to the constructor of this repository service.
     */
    public String[] getWorkspaceNames(SessionInfo sessionInfo) throws RepositoryException {
        checkSessionInfo(sessionInfo);
        return wspNames.toArray(new String[wspNames.size()]);
    }

    //-------------------------< access control >-------------------------------

    /**
     * This default implementation first calls {@link #checkSessionInfo(SessionInfo)}
     * with the <code>sessionInfo</code>, then returns <code>false</code> if
     * the any of the <code>actions</code> are in {@link #WRITE_ACTIONS};
     * otherwise returns <code>true</code>.
     */
    public boolean isGranted(SessionInfo sessionInfo,
                             ItemId itemId,
                             String[] actions) throws RepositoryException {
        checkSessionInfo(sessionInfo);
        // deny all but read
        for (String action : actions) {
            if (WRITE_ACTIONS.contains(action)) {
                return false;
            }
        }
        return true;
    }
}

@@ -972,16 +972,43 @@
 ashSet;%0A
+import java.util.Iterator;%0A
 import j
@@ -1314,24 +1314,67 @@
 spi.ItemId;%0A
+import org.apache.jackrabbit.spi.ItemInfo;%0A
 import org.a
@@ -5476,67 +5476,236 @@
     
-return (NodeInfo) getItemInfos(sessionInfo, nodeId).next();
+Iterator%3C? extends ItemInfo%3E infos = getItemInfos(sessionInfo, nodeId);%0A        if (infos.hasNext()) %7B%0A            return (NodeInfo) infos.next();%0A        %7D%0A        else %7B%0A            throw new ItemNotFoundException();%0A        %7D
 %0A   

Improve reusability of AbstractRepositoryService and AbstractReadableRepositoryService