/**
 *
 * Copyright 2003-2007 Jive Software.
 *
 * All rights reserved. Licensed 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.jivesoftware.smack;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.FromContainsFilter;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.ThreadFilter;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.collections.ReferenceMap;
import org.xmpp.packet.Message;
import org.xmpp.packet.Message.Type;
import org.xmpp.packet.Packet;

/**
 * The chat manager keeps track of references to all current chats. It will not
 * hold any references in memory on its own so it is necessary to keep a
 * reference to the chat object itself. To be made aware of new chats, register
 * a listener by calling {@link #addChatListener(ChatManagerListener)}.
 *
 * @author Alexander Wenckus
 */
public class ChatManager {
    /*
     * Sets the default behaviour for allowing 'normal' messages to be used in
     * chats. As some clients don't set the message type to chat, the type
     * normal has to be accepted to allow chats with these clients.
     */
    private static boolean defaultIsNormalInclude = true;

    /*
     * Sets the default behaviour for how to match chats when there is NO thread
     * id in the incoming message.
     */
    private static MatchMode defaultMatchMode = MatchMode.BARE_JID;

    /**
     * Defines the different modes under which a match will be attempted with an
     * existing chat when the incoming message does not have a thread id.
     */
    public enum MatchMode {
        /**
         * Will not attempt to match, always creates a new chat.
         */
        NONE,
        /**
         * Will match on the JID in the from field of the message.
         */
        SUPPLIED_JID,
        /**
         * Will attempt to match on the JID in the from field, and then attempt
         * the base JID if no match was found. This is the most lenient
         * matching.
         */
        BARE_JID;
    }

    /*
     * Determines whether incoming messages of type normal can create chats.
     */
    private boolean normalIncluded = defaultIsNormalInclude;

    /*
     * Determines how incoming message with no thread will be matched to
     * existing chats.
     */
    private MatchMode matchMode = defaultMatchMode;

    /**
     * Maps thread ID to chat.
     */
    private Map<String, Chat> threadChats = Collections
            .synchronizedMap(new ReferenceMap<String, Chat>(ReferenceMap.HARD,
                    ReferenceMap.WEAK));

    /**
     * Maps jids to chats
     */
    private Map<String, Chat> jidChats = Collections
            .synchronizedMap(new ReferenceMap<String, Chat>(ReferenceMap.HARD,
                    ReferenceMap.WEAK));

    /**
     * Maps base jids to chats
     */
    private Map<String, Chat> baseJidChats = Collections
            .synchronizedMap(new ReferenceMap<String, Chat>(ReferenceMap.HARD,
                    ReferenceMap.WEAK));

    private Set<ChatManagerListener> chatManagerListeners = new CopyOnWriteArraySet<ChatManagerListener>();

    private Map<PacketInterceptor, PacketFilter> interceptors = new WeakHashMap<PacketInterceptor, PacketFilter>();

    private Connection connection;

    ChatManager(Connection connection) {
        this.connection = connection;

        PacketFilter filter = new PacketFilter() {
            public boolean accept(Packet packet) {
                if (!(packet instanceof Message)) {
                    return false;
                }
                Message.Type messageType = ((Message) packet).getType();
                return (messageType == Type.chat)
                        || (normalIncluded ? messageType == Type.normal : false);
            }
        };

        // Add a listener for all message packets so that we can deliver
        // messages to the best Chat instance available.
        connection.addPacketListener(new PacketListener() {
            public void processPacket(Packet packet) {
                Message message = (Message) packet;
                Chat chat;
                if (message.getThread() == null) {
                    chat = getUserChat(message.getFrom().toFullJID());
                } else {
                    chat = getThreadChat(message.getThread());
                }

                if (chat == null) {
                    chat = createChat(message);
                }
                deliverMessage(chat, message);
            }
        }, filter);
    }

    /**
     * Determines whether incoming messages of type normal will be used
     * for creating new chats or matching a message to existing ones.
     *
     * @return true if normal is allowed, false otherwise.
     */
    public boolean isNormalIncluded() {
        return normalIncluded;
    }

    /**
     * Sets whether to allow incoming messages of type normal to be used
     * for creating new chats or matching a message to an existing one.
     *
     * @param normalIncluded
     *            true to allow normal, false otherwise.
     */
    public void setNormalIncluded(boolean normalIncluded) {
        this.normalIncluded = normalIncluded;
    }

    /**
     * Gets the current mode for matching messages with NO thread id to
     * existing chats.
     *
     * @return The current mode.
     */
    public MatchMode getMatchMode() {
        return matchMode;
    }

    /**
     * Sets the mode for matching messages with NO thread id to existing
     * chats.
     *
     * @param matchMode
     *            The mode to set.
     */
    public void setMatchMode(MatchMode matchMode) {
        this.matchMode = matchMode;
    }

    /**
     * Creates a new chat and returns it.
     *
     * @param userJID
     *            the user this chat is with.
     * @param listener
     *            the listener which will listen for new messages from this
     *            chat.
     * @return the created chat.
     */
    public Chat createChat(String userJID, MessageListener listener) {
        return createChat(userJID, null, listener);
    }

    /**
     * Creates a new chat using the specified thread ID, then returns it.
     *
     * @param userJID
     *            the jid of the user this chat is with
     * @param thread
     *            the thread of the created chat.
     * @param listener
     *            the listener to add to the chat
     * @return the created chat.
     */
    public Chat createChat(String userJID, String thread,
            MessageListener listener) {
        if (thread == null) {
            thread = nextID();
        }
        Chat chat = threadChats.get(thread);
        if (chat != null) {
            throw new IllegalArgumentException("ThreadID is already used");
        }
        chat = createChat(userJID, thread, true);
        chat.addMessageListener(listener);
        return chat;
    }

    private Chat createChat(String userJID, String threadID,
            boolean createdLocally) {
        Chat chat = new Chat(this, userJID, threadID);
        threadChats.put(threadID, chat);
        jidChats.put(userJID, chat);
        baseJidChats.put(StringUtils.parseBareAddress(userJID), chat);

        for (ChatManagerListener listener : chatManagerListeners) {
            listener.chatCreated(chat, createdLocally);
        }

        return chat;
    }

    private Chat createChat(Message message) {
        String threadID = message.getThread();
        if (threadID == null) {
            threadID = nextID();
        }
        String userJID = message.getFrom().toFullJID();

        return createChat(userJID, threadID, false);
    }

    /**
     * Try to get a matching chat for the given user JID, based on the
     * {@link MatchMode}.
NONE - return null
SUPPLIED_JID - match the
     * jid in the from field of the message exactly.
BARE_JID - if not match
     * for from field, try the bare jid.
     *
     * @param userJID
     *            jid in the from field of message.
     * @return Matching chat, or null if no match found.
     */
    private Chat getUserChat(String userJID) {
        if (matchMode == MatchMode.NONE) {
            return null;
        }

        Chat match = jidChats.get(userJID);

        if (match == null && (matchMode == MatchMode.BARE_JID)) {
            match = baseJidChats.get(StringUtils.parseBareAddress(userJID));
        }
        return match;
    }

    public Chat getThreadChat(String thread) {
        return threadChats.get(thread);
    }

    /**
     * Register a new listener with the ChatManager to recieve events related to
     * chats.
     *
     * @param listener
     *            the listener.
     */
    public void addChatListener(ChatManagerListener listener) {
        chatManagerListeners.add(listener);
    }

    /**
     * Removes a listener, it will no longer be notified of new events related
     * to chats.
     *
     * @param listener
     *            the listener that is being removed
     */
    public void removeChatListener(ChatManagerListener listener) {
        chatManagerListeners.remove(listener);
    }

    /**
     * Returns an unmodifiable collection of all chat listeners currently
     * registered with this manager.
     *
     * @return an unmodifiable collection of all chat listeners currently
     *         registered with this manager.
     */
    public Collection<ChatManagerListener> getChatListeners() {
        return Collections.unmodifiableCollection(chatManagerListeners);
    }

    private void deliverMessage(Chat chat, Message message) {
        // Here we will run any interceptors
        chat.deliver(message);
    }

    void sendMessage(Chat chat, Message message) {
        for (Map.Entry<PacketInterceptor, PacketFilter> interceptor : interceptors
                .entrySet()) {
            PacketFilter filter = interceptor.getValue();
            if (filter != null && filter.accept(message)) {
                interceptor.getKey().interceptPacket(message);
            }
        }
        // Ensure that messages being sent have a proper FROM value
        if (message.getFrom() == null) {
            message.setFrom(connection.getUser());
        }
        connection.sendPacket(message);
    }

    PacketCollector createPacketCollector(Chat chat) {
        return connection.createPacketCollector(new AndFilter(new ThreadFilter(
                chat.getThreadID()), new FromContainsFilter(chat
                .getParticipant())));
    }

    /**
     * Adds an interceptor which intercepts any messages sent through chats.
     *
     * @param packetInterceptor
     *            the interceptor.
     */
    public void addOutgoingMessageInterceptor(
            PacketInterceptor packetInterceptor) {
        addOutgoingMessageInterceptor(packetInterceptor, null);
    }

    public void addOutgoingMessageInterceptor(
            PacketInterceptor packetInterceptor, PacketFilter filter) {
        if (packetInterceptor != null) {
            interceptors.put(packetInterceptor, filter);
        }
    }

    /**
     * Returns a unique id.
     *
     * @return the next id.
     */
    private static String nextID() {
        return UUID.randomUUID().toString();
    }

    public static void setDefaultMatchMode(MatchMode mode) {
        defaultMatchMode = mode;
    }

    public static void setDefaultIsNormalIncluded(boolean allowNormal) {
        defaultIsNormalInclude = allowNormal;
    }
}


/**
 *
 * Copyright 2003-2005 Jive Software.
 *
 * All rights reserved. Licensed 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.jivesoftware.smack.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import org.jivesoftware.smack.util.dns.DNSResolver;
import org.jivesoftware.smack.util.dns.HostAddress;
import org.jivesoftware.smack.util.dns.SRVRecord;

/**
 * Utility class to perform DNS lookups for XMPP services.
 *
 * @author Matt Tucker
 */
public class DNSUtil {

    /**
     * Create a cache to hold the 100 most recently accessed DNS lookups for a
     * period of 10 minutes.
     */
    private static Map<String, List<HostAddress>> cache = new Cache<String, List<HostAddress>>(
            100, 1000 * 60 * 10);

    private static DNSResolver dnsResolver = null;

    /**
     * Set the DNS resolver that should be used to perform DNS lookups.
     *
     * @param resolver
     */
    public static void setDNSResolver(DNSResolver resolver) {
        dnsResolver = resolver;
    }

    /**
     * Returns the current DNS resolved used to perform DNS lookups.
     *
     * @return
     */
    public static DNSResolver getDNSResolver() {
        return dnsResolver;
    }

    /**
     * Returns a list of HostAddresses under which the specified XMPP server can
     * be reached at for client-to-server communication. A DNS lookup for a SRV
     * record in the form "_xmpp-client._tcp.example.com" is attempted,
     * according to section 14.4 of RFC 3920. If that lookup fails, a lookup in
     * the older form of "_jabber._tcp.example.com" is attempted since servers
     * that implement an older version of the protocol may be listed using that
     * notation. If that lookup fails as well, it's assumed that the XMPP server
     * lives at the host resolved by a DNS lookup at the specified domain on the
     * default port of 5222.
     *

     *
     * As an example, a lookup for "example.com" may return
     * "im.example.com:5269".
     *
     * @param domain
     *            the domain.
     * @return List of HostAddress, which encompasses the hostname and port that
     *         the XMPP server can be reached at for the specified domain.
     */
    public static List<HostAddress> resolveXMPPDomain(final String domain) {
        if (dnsResolver == null) {
            List<HostAddress> addresses = new ArrayList<HostAddress>(1);
            addresses.add(new HostAddress(domain, 5222));
            return addresses;
        }
        return resolveDomain(domain, 'c');
    }

    /**
     * Returns a list of HostAddresses under which the specified XMPP server can
     * be reached at for server-to-server communication. A DNS lookup for a SRV
     * record in the form "_xmpp-server._tcp.example.com" is attempted,
     * according to section 14.4 of RFC 3920. If that lookup fails, a lookup in
     * the older form of "_jabber._tcp.example.com" is attempted since servers
     * that implement an older version of the protocol may be listed using that
     * notation. If that lookup fails as well, it's assumed that the XMPP server
     * lives at the host resolved by a DNS lookup at the specified domain on the
     * default port of 5269.
     *

     *
     * As an example, a lookup for "example.com" may return
     * "im.example.com:5269".
     *
     * @param domain
     *            the domain.
     * @return List of HostAddress, which encompasses the hostname and port that
     *         the XMPP server can be reached at for the specified domain.
     */
    public static List<HostAddress> resolveXMPPServerDomain(final String domain) {
        if (dnsResolver == null) {
            List<HostAddress> addresses = new ArrayList<HostAddress>(1);
            addresses.add(new HostAddress(domain, 5269));
            return addresses;
        }
        return resolveDomain(domain, 's');
    }

    private static List<HostAddress> resolveDomain(String domain, char keyPrefix) {
        // Prefix the key with 's' to distinguish him from the client domain
        // lookups
        String key = keyPrefix + domain;
        // Return item from cache if it exists.
        if (cache.containsKey(key)) {
            List<HostAddress> addresses = cache.get(key);
            if (addresses != null) {
                return addresses;
            }
        }

        List<HostAddress> addresses = new ArrayList<HostAddress>();

        // Step one: Do SRV lookups
        String srvDomain;
        if (keyPrefix == 's') {
            srvDomain = "_xmpp-server._tcp." + domain;
        } else if (keyPrefix == 'c') {
            srvDomain = "_xmpp-client._tcp." + domain;
        } else {
            srvDomain = domain;
        }
        List<SRVRecord> srvRecords = dnsResolver.lookupSRVRecords(srvDomain);
        List<HostAddress> sortedRecords = sortSRVRecords(srvRecords);
        if (sortedRecords != null)
            addresses.addAll(sortedRecords);

        // Step two: Add the hostname to the end of the list
        addresses.add(new HostAddress(domain));

        // Add item to cache.
        cache.put(key, addresses);

        return addresses;
    }

    /**
     * Sort a given list of SRVRecords as described in RFC 2782 Note that we
     * follow the RFC with one exception. In a group of the same priority, only
     * the first entry is calculated by random. The others are ore simply
     * ordered by their priority.
     *
     * @param records
     * @return
     */
    protected static List<HostAddress> sortSRVRecords(List<SRVRecord> records) {
        // RFC 2782, Usage rules:
        // "If there is precisely one SRV RR, and its Target is "."
        // (the root domain), abort."
        if (records.size() == 1 && records.get(0).getFQDN().equals("."))
            return null;

        // sorting the records improves the performance of the bisection later
        Collections.sort(records);

        // create the priority buckets
        SortedMap<Integer, List<SRVRecord>> buckets = new TreeMap<Integer, List<SRVRecord>>();
        for (SRVRecord r : records) {
            Integer priority = r.getPriority();
            List<SRVRecord> bucket = buckets.get(priority);
            // create the list of SRVRecords if it doesn't exist
            if (bucket == null) {
                bucket = new LinkedList<SRVRecord>();
                buckets.put(priority, bucket);
            }
            bucket.add(r);
        }

        List<HostAddress> res = new ArrayList<HostAddress>(records.size());

        for (Integer priority : buckets.keySet()) {
            List<SRVRecord> bucket = buckets.get(priority);
            int bucketSize;
            while ((bucketSize = bucket.size()) > 0) {
                int[] totals = new int[bucket.size()];
                int running_total = 0;
                int count = 0;
                int zeroWeight = 1;

                for (SRVRecord r : bucket) {
                    if (r.getWeight() > 0)
                        zeroWeight = 0;
                }

                for (SRVRecord r : bucket) {
                    running_total += (r.getWeight() + zeroWeight);
                    totals[count] = running_total;
                    count++;
                }
                int selectedPos;
                if (running_total == 0) {
                    // If running total is 0, then all weights in this priority
                    // group are 0. So we simply select one of the weights
                    // randomly
                    // as the other 'normal' algorithm is unable to handle this
                    // case
                    selectedPos = (int) (Math.random() * bucketSize);
                } else {
                    double rnd = Math.random() * running_total;
                    selectedPos = bisect(totals, rnd);
                }
                // add the SRVRecord that was randomly chosen on it's weight
                // to the start of the result list
                SRVRecord chosenSRVRecord = bucket.remove(selectedPos);
                res.add(chosenSRVRecord);
            }
        }

        return res;
    }

    // TODO this is not yet really bisection just a stupid linear search
    private static int bisect(int[] array, double value) {
        int pos = 0;
        for (int element : array) {
            if (value < element)
                break;
            pos++;
        }
        return pos;
    }
}


/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF 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.sakaiproject.nakamura.lite.content;

import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.sakaiproject.nakamura.api.lite.RemoveProperty;
import org.sakaiproject.nakamura.api.lite.Repository;
import org.sakaiproject.nakamura.api.lite.StorageClientException;
import org.sakaiproject.nakamura.api.lite.StorageClientUtils;
import org.sakaiproject.nakamura.api.lite.content.Content;
import org.sakaiproject.nakamura.lite.storage.spi.StorageClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * Internal Content Object for holding sparse Content objects. Has a protected
 * constructor for internal use.
 */
public class InternalContent {

    private static final Logger LOGGER = LoggerFactory.getLogger(Content.class);

    public static final String INTERNAL_FIELD_PREFIX = Repository.SYSTEM_PROP_PREFIX + ":";
    /**
     * The ID of a content item
     */
    public static String UUID_FIELD = Repository.SYSTEM_PROP_PREFIX+ "id";

    static boolean idFieldIsSet = false;
    /**
     * The path of the content item (used in structure row)
     */
    public static final String PATH_FIELD = Repository.SYSTEM_PROP_PREFIX + "path";
    /**
     * The parent path (
     */
    public static final String PARENT_HASH_FIELD = INTERNAL_FIELD_PREFIX + "parenthash";

    /**
     * content item ID referenced by a Structure item
     */
    public static final String STRUCTURE_UUID_FIELD = INTERNAL_FIELD_PREFIX + "cid";
    /**
     * Where a structure object is a link, this field contains the location of
     * the target of the link
     */
    public static final String LINKED_PATH_FIELD = INTERNAL_FIELD_PREFIX + "link";
    /**
     * BlockID where the body of this content item is stored, if there is a body
     * (content row)
     */
    public static final String BLOCKID_FIELD = Repository.SYSTEM_PROP_PREFIX + "blockId";
    /**
     * ID of the previous version (content row)
     */
    public static final String PREVIOUS_VERSION_UUID_FIELD = Repository.SYSTEM_PROP_PREFIX
            + "previousVersion";
    /**
     * Previous Block ID. (content row)
     */
    public static final String PREVIOUS_BLOCKID_FIELD = Repository.SYSTEM_PROP_PREFIX
            + "previousBlockId";
    /**
     * The ID of the next version (content row)
     */
    public static final String NEXT_VERSION_FIELD = Repository.SYSTEM_PROP_PREFIX + "nextVersion";
    /**
     * Set to "Y" if the content item is read only. (content row)
     */
    public static final String READONLY_FIELD = Repository.SYSTEM_PROP_PREFIX + "readOnly";
    /**
     * set to "Y" if deleted. (content row)
     */
    public static final String DELETED_FIELD = StorageClient.DELETED_FIELD;

    /**
     * The block size in bytes in each block in a block set, if body store uses
     * blocking (content row)
     */
    public static final String BLOCKSIZE_FIELD = Repository.SYSTEM_PROP_PREFIX + "blocksize";
    /**
     * Total length of the content body (content row)
     */
    public static final String LENGTH_FIELD = Repository.SYSTEM_PROP_PREFIX + "length";
    /**
     * The number of block sets in a body (content row)
     */
    public static final String NBLOCKS_FIELD = Repository.SYSTEM_PROP_PREFIX + "nblocks";
    /**
     * Yes, True, etc
     */
    public static final String TRUE = StorageClient.TRUE;

    /**
     * The date (stored as GMT epoch long) the body was last modified. (content
     * row)
     */
    public static final String BODY_LAST_MODIFIED_FIELD = Repository.SYSTEM_PROP_PREFIX
            + "bodyLastModified";

    /**
     * The user ID that last modified the body. (content row)
     */
    public static final String BODY_LAST_MODIFIED_BY_FIELD = Repository.SYSTEM_PROP_PREFIX
            + "bodyLastModifiedBy";

    /**
     * The date the body was created (GMT epoch long) (content row)
     */
    public static final String BODY_CREATED_FIELD = Repository.SYSTEM_PROP_PREFIX + "bodyCreated";

    /**
     * The user that created the body. (content row)
     */
    public static final String BODY_CREATED_BY_FIELD = Repository.SYSTEM_PROP_PREFIX
            + "bodyCreatedBy";

    /**
     * The time the item was created. (content row)
     */
    public static final String CREATED_FIELD = Repository.SYSTEM_PROP_PREFIX + "created";

    /**
     * The user that created the item. (content row)
     */
    public static final String CREATED_BY_FIELD = Repository.SYSTEM_PROP_PREFIX + "createdBy";

    /**
     * The time the item was last modified. (content row)
     */
    public static final String LASTMODIFIED_FIELD = Repository.SYSTEM_PROP_PREFIX + "lastModified";

    /**
     * The user that lastModified the item. (content row)
     */
    public static final String LASTMODIFIED_BY_FIELD = Repository.SYSTEM_PROP_PREFIX
            + "lastModifiedBy";

    /**
     * The path the content object was copied from if it was copied
     */
    public static final String COPIED_FROM_PATH_FIELD = Repository.SYSTEM_PROP_PREFIX
            + "copiedFrom";

    /**
     * The ID the content object was copied from.
     */
    public static final String COPIED_FROM_ID_FIELD = Repository.SYSTEM_PROP_PREFIX
            + "copiedFromId";

    /**
     * If the copy was deep, then true
     */
    public static final String COPIED_DEEP_FIELD = Repository.SYSTEM_PROP_PREFIX + "copiedDeep";

    /**
     * Mime type
     */
    public static final String MIMETYPE_FIELD = Repository.SYSTEM_PROP_PREFIX + "mimeType";

    /**
     * The sling resource type field.
     */
    public static final String SLING_RESOURCE_TYPE_FIELD = "sling:resourceType";

    /**
     * Alternative resource type field.
     */
    public static final String RESOURCE_TYPE_FIELD = "resourceType";

    /**
     * Charset encoding if char based.
     */
    public static final String ENCODING_FIELD = Repository.SYSTEM_PROP_PREFIX + "encoding";

    /**
     *
     */
    public static final String VERSION_HISTORY_ID_FIELD = Repository.SYSTEM_PROP_PREFIX
            + "versionHistoryId";

    /**
     *
     */
    public static final String VERSION_NUMBER_FIELD = Repository.SYSTEM_PROP_PREFIX
            + "versionNumber";

    /**
     * The who this version was saved by
     */
    public static final String VERSION_SAVEDBY_FIELD = Repository.SYSTEM_PROP_PREFIX
            + "versionSavedBy";

    /**
     * Map of the content object itself.
     */
    private ImmutableMap<String, Object> content;
    /**
     * Path locating this content object within the overall content structure.
     */
    private String path;
    /**
     * The ContentManager that manages this content object.
     */
    private ContentManagerImpl contentManager;
    /**
     * Map of content object that has been updated since this content object was
     * retrieved.
     */
    private Map<String, Object> updatedContent;
    /**
     * True if updated.
     */
    private boolean updated;
    /**
     * True if the content is new.
     */
    private boolean newcontent;
    private boolean readOnly;

    /**
     * Create a new Content Object that has not been persisted
     *
     * @param path
     *            the path.
     * @param content
     *            the content map.
     */
    public InternalContent(String path, Map<String, Object> content) {
        if (content == null) {
            content = ImmutableMap.of();
        }
        this.content = ImmutableMap.copyOf(content);
        this.updatedContent = Maps.newHashMap();
        this.path = path;
        updated = true;
        newcontent = true;
        readOnly = false;
    }

    /**
     * Convert a new content object to an internal version.
     *
     * @param contentManager
     *            the content manager now managing this content object.
     *
     * @param readOnly sets this contentManager to be either read-only or not.
     */
    void internalize(ContentManagerImpl contentManager, boolean readOnly) {
        this.contentManager = contentManager;
        updated = false;
        newcontent = false;
        this.readOnly = readOnly;
    }

    /**
     * Reset the object back to its last saved state.
     */
    public void reset(Map<String, Object> updatedMap) {
        if (!readOnly) {
            this.content = ImmutableMap.copyOf(updatedMap);
            updatedContent.clear();
            updated = false;
            LOGGER.debug("Reset to {} ", updatedMap);
        }
    }

    /**
     * @return true if the Content object is new and not connected to a content
     *         manager or structure.
     */
    public boolean isNew() {
        return newcontent;
    }

    /**
     * @return true if the content object has been updated since it was
     *         retrieved.
     */
    public boolean isUpdated() {
        if (readOnly) {
            return false;
        }
        return updated;
    }

    /**
     * @return get a read only copy of the properties. Property values are in
     *         store format and must be converted before use with
     *         StorageContentUtils.toString, toInteger, toLong etc
     */
    public Map<String, Object> getProperties() {
        LOGGER.debug("getting properties map {}", content);
        return StorageClientUtils.getFilterMap(content, updatedContent, null, null, false);
    }

    public Map<String, Object> getPropertiesForUpdate() {
        return StorageClientUtils.getFilterMap(content, updatedContent, null, null, true);
    }

    public Map<String, Object> getOriginalProperties() {
        return StorageClientUtils.getFilterMap(content, null, null, null, false);
    }

    /**
     * set a property, creating if it does not exist, overwriting if it does.
     *
     * @param key
     *            the key for the property
     * @param value
     *            the value for the property in storage format created with
     *            StorageContentUtils.toStore(). Must not be null.
     */
    public void setProperty(String key, Object value) {
        if (readOnly) {
            return;
        }
        if (value == null) {
            throw new IllegalArgumentException("value must not be null");
        }
        Object o = content.get(key);
        if (!value.equals(o)) {
            updatedContent.put(key, value);
            updated = true;
        } else if (updatedContent.containsKey(key) && !value.equals(updatedContent.get(key))) {
            updatedContent.put(key, value);
            updated = true;
        }

    }

    public void removeProperty(String name) {
        if (readOnly) {
            return;
        }
        setProperty(name, new RemoveProperty());
    }

    /**
     * @param key
     * @return the value of the property in storage format (use
     *         StorageContentUtils.toString etc to convert). null if the
     *         property does not exist. null can be ambiguous and
     *         hasProperty(String key) should be checked for an authoratative
     *         answer.
     */
    public Object getProperty(String key) {
        if (updatedContent.containsKey(key)) {
            Object o = updatedContent.get(key);
            if (o instanceof RemoveProperty) {
                return null;
            }
            return o;
        }
        Object o = content.get(key);
        if (o instanceof RemoveProperty) {
            return null;
        }
        return o;
    }

    public String getId() {
        return (String) content.get(UUID_FIELD);
    }

    /**
     * @param key
     * @return true if the property exists.
     */
    public boolean hasProperty(String key) {
        if (updatedContent.containsKey(key)) {
            return !(updatedContent.get(key) instanceof RemoveProperty);
        }
        return content.containsKey(key) && !(content.get(key) instanceof RemoveProperty);
    }

    /**
     * @return get the path of the content object.
     */
    public String getPath() {
        return path;
    }

    /**
     * @return an iterable for all children of this content item.
     */
    public Iterable<Content> listChildren() {
        if (newcontent) {
            return Collections.<Content>emptyList();
        }
        return new Iterable<Content>() {

            public Iterator<Content> iterator() {
                try {
                    return contentManager.listChildren(path);
                } catch (StorageClientException e) {
                    LOGGER.error(e.getMessage(), e);
                }
                return Iterators.emptyIterator();
            }
        };
    }

    /**
     * @return an iterable of all relative child paths of this object.
     */
    public Iterable<String> listChildPaths() {
        if (newcontent) {
            return Collections.<String>emptyList();
        }
        return new Iterable<String>() {

            public Iterator<String> iterator() {
                try {
                    return contentManager.listChildPaths(path);
                } catch (StorageClientException e) {
                    LOGGER.error(e.getMessage(), e);
                }
                return Iterators.emptyIterator();
            }
        };
    }

    public Iterable<String> listStreams() {
        final Set<String> streams = Sets.newHashSet();
        for (Entry<String, Object> e : content.entrySet()) {
            String k = e.getKey();
            String[] streamIds = StringUtils.split(k, "/", 2);
            if (streamIds.length == 2) {
                streams.add(streamIds[1]);
            }
        }
        return new Iterable<String>() {
            public Iterator<String> iterator() {
                return streams.iterator();
            }
        };
    }

    @Override
    public String toString() {
        return "Path: " + getPath() + "; Properties: " + getProperties();
    }

    /**
     *
     * @return true if the content item is deleted, the system does not delete
     *         content items, it marks items as deleted. This will allow the
     *         storage layers to maintain an update pattern that is close to
     *         append only allowing compression of the storage to be
     *         achieved by background task which may then also remove holes in the storage.
     *         This is not dissimilar from the way most file systems work.
     */
    public boolean isDeleted() {
        return TRUE.equals(content.get(DELETED_FIELD));
    }

}


/**
 *
 * Copyright 2003-2007 Jive Software.
 *
 * All rights reserved. Licensed 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.jivesoftware.smack.filter;

import org.xmpp.packet.Packet;

/**
 * Implements the logical OR operation over two or more packet filters. In other
 * words, packets pass this filter if they pass <b>any</b> of the filters.
 *
 * @author Matt Tucker
 */
public class OrFilter implements PacketFilter {

    /**
     * The current number of elements in the filter.
     */
    private int size;

    /**
     * The list of filters.
     */
    private PacketFilter[] filters;

    /**
     * Creates an empty OR filter. Filters should be added using the
     * {@link #addFilter(PacketFilter)} method.
     */
    public OrFilter() {
        size = 0;
        filters = new PacketFilter[3];
    }

    /**
     * Creates an OR filter using the two specified filters.
     *
     * @param filter1
     *            the first packet filter.
     * @param filter2
     *            the second packet filter.
     */
    public OrFilter(PacketFilter filter1, PacketFilter filter2) {
        if (filter1 == null || filter2 == null) {
            throw new IllegalArgumentException("Parameters cannot be null.");
        }
        size = 2;
        filters = new PacketFilter[2];
        filters[0] = filter1;
        filters[1] = filter2;
    }

    /**
     * Adds a filter to the filter list for the OR operation. A packet will pass
     * the filter if any filter in the list accepts it.
     *
     * @param filter
     *            a filter to add to the filter list.
     */
    public void addFilter(PacketFilter filter) {
        if (filter == null) {
            throw new IllegalArgumentException("Parameter cannot be null.");
        }
        // If there is no more room left in the filters array, expand it.
        if (size == filters.length) {
            PacketFilter[] newFilters = new PacketFilter[filters.length + 2];
            for (int i = 0; i < filters.length; i++) {
                newFilters[i] = filters[i];
            }
            filters = newFilters;
        }
        // Add the new filter to the array.
        filters[size] = filter;
        size++;
    }

    public boolean accept(Packet packet) {
        for (int i = 0; i < size; i++) {
            if (filters[i].accept(packet)) {
                return true;
            }
        }
        return false;
    }

    public String toString() {
        return filters.toString();
    }
}


package uk.co.tfd.sm.util.http;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;
import org.sakaiproject.nakamura.api.lite.StorageClientException;
import org.sakaiproject.nakamura.api.lite.StorageClientUtils;
import org.sakaiproject.nakamura.api.lite.accesscontrol.AccessDeniedException;
import org.sakaiproject.nakamura.api.lite.authorizable.Authorizable;
import org.sakaiproject.nakamura.api.lite.content.Content;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

/**
 * This class deals with processing a POST request. It needs a stream processor
 * to apply any streamed bodies within the request to something in the model. If
 * there is no stream processor given, it will ignore non form parts of a
 * multipart post.
 *
 * Loading a standard post with no streaming.
 *

 *      {@link Authorizable} authorizable = xxx;
 * 		{@link AuthorizableHelper} authorizableHelper = new {@link AuthorizableHelper}(authorizableManager);
 *		{@link ModificationRequest} modificationRequest = new {@link ModificationRequest}(null);
 *
 *      // process the request
 *		modificationRequest.processRequest(request);
 *
 *		// apply the properties to the authorizable
 *		authorizableHelper.applyProperties(authorizable, modificationRequest);
 *
 *		// save everything that was modified
 *		authorizableHelper.save();
 *
 *		// get the feedback
 *		List<String> feedback = modificationRequest.getFeedback();
 *

 * Loading a multipart post with streaming.
 *

 *      {@link Content} authorizable = xxx;
 * 		{@link ContentHelper} contentHelper = new {@link ContentHelper}(contentManager);
 *      {@link ContentRequestStreamProcessor} contentRquestStreamProcessor = new {@link ContentRequestStreamProcessor}(content, contentManager, contentHelper)
 *		ModificationRequest modificationRequest = new {@link ModificationRequest}(contentRquestStreamProcessor);
 *
 *      // process the request
 *		modificationRequest.processRequest(request);
 *
 *		// apply the properties to the authorizable
 *		contentHelper.applyProperties(content, modificationRequest);
 *
 *		// save everything that was modified
 *		contentHelper.save();
 *
 *		// get the feedback
 *		List<String> feedback = modificationRequest.getFeedback();
 *

 *
 *
 *
 * @author ieb
 *
 */
public class ModificationRequest {
	private static final Logger LOGGER = LoggerFactory
			.getLogger(ModificationRequest.class);
	private List<String> feedback = Lists.newArrayList();
	private RequestStreamProcessor<?> streamProcessor;
	private Map<ParameterType, Map<String, Object>> stores;

	/**
	 * Create a ModificationRequest that will handle streaming of bodies
	 * @param <T> the type of the StreamProcessor.
	 * @param streamProcessor
	 */
	public <T> ModificationRequest(RequestStreamProcessor<T> streamProcessor) {
		this.streamProcessor = streamProcessor;
		Builder<ParameterType, Map<String,Object>> b = ImmutableMap.builder();
		for ( ParameterType pt : ParameterType.values() ) {
			Map<String, Object> m = Maps.newHashMap();
			b.put(pt, m);
		}
		stores = b.build();
	}

	/**
	 * Create a ModificationRequest that will ignore streamed bodies.
	 */
	public ModificationRequest() {
		this(null);
	}

	/**
	 * Process the request in a stream.
	 *
	 * @param request
	 * @throws IOException
	 * @throws FileUploadException
	 * @throws StorageClientException
	 * @throws AccessDeniedException
	 */
	public void processRequest(HttpServletRequest request) throws IOException,
			FileUploadException, StorageClientException, AccessDeniedException {
		boolean debug = LOGGER.isDebugEnabled();
		if (ServletFileUpload.isMultipartContent(request)) {
			if (debug) {
				LOGGER.debug("Multipart POST ");
			}
			feedback.add("Multipart Upload");
			ServletFileUpload upload = new ServletFileUpload();
			FileItemIterator iterator = upload.getItemIterator(request);
			while (iterator.hasNext()) {
				FileItemStream item = iterator.next();
				if (debug) {
					LOGGER.debug("Got Item {}",item);
				}
				String name = item.getFieldName();
				InputStream stream = item.openStream();
				if (item.isFormField()) {
					ParameterType pt = ParameterType.typeOfRequestParameter(name);
					String propertyName = RequestUtils.propertyName(pt.getPropertyName(name));
					RequestUtils.accumulate(stores.get(pt), propertyName, RequestUtils.toValue(name, Streams.asString(stream)));
					feedback.add(pt.feedback(propertyName));
				} else {

					if (streamProcessor != null) {
						feedback.addAll(streamProcessor.processStream(name, StorageClientUtils.getObjectName(item.getName()), item.getContentType(), stream, this));
					}

				}
			}
			if (debug) {
				LOGGER.debug("No More items ");
			}

		} else {
			if (debug) {
				LOGGER.debug("Trad Post ");
			}
			// use traditional unstreamed operations.
			@SuppressWarnings("unchecked")
			Map<String, String[]> parameters = request.getParameterMap();
			if (debug) {
				LOGGER.debug("Traditional POST {} ", parameters);
			}
			Set<Entry<String, String[]>> entries = parameters.entrySet();

			for (Entry<String, String[]> param : entries) {
				String name = (String) param.getKey();
				ParameterType pt = ParameterType.typeOfRequestParameter(name);
				String propertyName = RequestUtils.propertyName(pt.getPropertyName(name));
				RequestUtils.accumulate(stores.get(pt), propertyName, RequestUtils.toValue(name, param.getValue()));
				feedback.add(pt.feedback(propertyName));
			}
		}
	}

	/**
	 * @return A map of the properties processed so far based on the stream
	 *         recieved.
	 */
	public Map<String, Object> getParameterSet(ParameterType pt) {
		return stores.get(pt);
	}

	/**
	 * Clear the current set of properties to add and remove.
	 */
	public void resetProperties() {
		for ( Entry<ParameterType, Map<String, Object>> e : stores.entrySet()) {
			e.getValue().clear();
		}
	}

	/**
	 * @return feedback from the resquest processing.
	 */
	public List<String> getFeedback() {
		return feedback;
	}
}


package uk.co.tfd.sm.proxy;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

public class Signature {

	  private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";

	  /**
	   * Calculate an RFC2104 compliant HMAC (Hash-based Message Authentication Code)
	   *
	   * @param data
	   *          The data to be signed. This data is pushed through a hex converter in this
	   *          method, so there is no need to do this before generating the HMAC.
	   * @param key
	   *          The signing key.
	   * @return The Base64-encoded RFC 2104-compliant HMAC signature.
	   * @throws java.security.SignatureException
	   *           when signature generation fails
	   */

	  public static String calculateRFC2104HMAC(String data, String key)
	  	throws java.security.SignatureException {
	    if (data == null) {
	      throw new IllegalArgumentException("String data == null");
	    }
	    if (key == null) {
	      throw new IllegalArgumentException("String key == null");
	    }
		  try {
			  // Get an hmac_sha1 key from the raw key bytes
			  byte[] keyBytes = key.getBytes("UTF-8");
			  SecretKeySpec signingKey = new SecretKeySpec(keyBytes, HMAC_SHA1_ALGORITHM);

			  // Get an hmac_sha1 Mac instance and initialize with the signing key
			  Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
			  mac.init(signingKey);

			  // Compute the hmac on input data bytes
			  byte[] rawHmac = mac.doFinal(data.getBytes("UTF-8"));

			  // Convert raw bytes to encoding
			  return Base64.encodeBase64URLSafeString(rawHmac);

		  } catch (NoSuchAlgorithmException e) {
			  throw new SignatureException("Failed to generate HMAC : " + e.getMessage(), e);
		  } catch (InvalidKeyException e) {
			  throw new SignatureException("Failed to generate HMAC : " + e.getMessage(), e);
		  } catch (UnsupportedEncodingException e) {
			  throw new SignatureException("Failed to generate HMAC : " + e.getMessage(), e);
		  }
	  }

}


/**
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF 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 uk.co.tfd.sm.http.batch;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Map;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.sakaiproject.nakamura.api.memory.Cache;
import org.sakaiproject.nakamura.api.memory.CacheManagerService;
import org.sakaiproject.nakamura.api.memory.CacheScope;

import uk.co.tfd.sm.api.template.TemplateService;

@Component(immediate = true, metatype = true)
@Service(value = Servlet.class)
@Property(name = "alias", value = "/system/batch")
public class BatchServlet extends HttpServlet {

	private static final String REQUEST_TEMPLATE = "t";

	private static final long serialVersionUID = 419598445499567027L;

	protected static final String REQUESTS_PARAMETER = "requests";

	private BatchProcessor batchProcessor;

	@Reference
	protected TemplateService templateService;

	@Reference
	protected CacheManagerService cacheManagerService;

	@Activate
	public void activate(Map<String, Object> properties) throws FileNotFoundException, IOException {
		Cache<String> cache = cacheManagerService.getCache(BatchProcessor.class.getName(), CacheScope.INSTANCE);
		batchProcessor = new BatchProcessor(cache);

	}

	@Override
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		batchRequest(request, response, false);
	}

	@Override
	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		batchRequest(request, response, true);
	}

	@Override
	protected void doDelete(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see org.apache.sling.api.servlets.SlingAllMethodsServlet#doPut(org.apache.sling.api.SlingHttpServletRequest,
	 *      org.apache.sling.api.SlingHttpServletResponse)
	 */
	@Override
	protected void doPut(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
	}

	/**
	 * Takes the original request and starts the batching.
	 *
	 * @param request
	 * @param response
	 * @throws IOException
	 * @throws ServletException
	 */
	@SuppressWarnings("unchecked")
	protected void batchRequest(HttpServletRequest request,
      HttpServletResponse response, boolean allowModify) throws IOException, ServletException {
    String json = request.getParameter(REQUESTS_PARAMETER);
    String template = request.getParameter(REQUEST_TEMPLATE);
    if ( template != null && template.length() > 0 ) {
    	if ( templateService.checkTemplateExists(template)) {
    		StringWriter processedTemplate = new StringWriter();
    		templateService.process(request.getParameterMap(), "UTF-8", processedTemplate, template);
    		json = processedTemplate.toString();
    	} else {
    		response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Template specified in request parameter t does not exist");
    	}
    }

    batchProcessor.batchRequest(request, response, json, allowModify);
  }

}


package org.sakaiproject.nakamura.api.lite.accesscontrol;

import com.google.common.collect.Lists;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Specification of a modification to be applied to an ACL.
 */
public class AclModification {
    public enum Operation {
        /**
         * Replace whatever is in already defined as an ACE with this bitmap
         */
        OP_REPLACE(),
        /**
         * Or the existing bitmap with this bitmap to geenrate the new bitmap
         */
        OP_OR(),
        /**
         * And this bitmap with the exisitng bitmap to generate the new bitmap
         */
        OP_AND(),
        /**
         * XOR this bitmapp with the existing bitmap to generte the new bitmap
         */
        OP_XOR(),
        /**
         * Invert this bitmap and replace the the existing bitmap with the
         * result.
         */
        OP_NOT(),
        /**
         * Delete the existing bitmap.
         */
        OP_DEL()
    }

    /**
     * marker to define granted ACEs (appended to the principal name)
     */
    public static final String GRANTED_MARKER = "@g";
    /**
     * marker to define denied ACEs
     */
    public static final String DENIED_MARKER = "@d";

    /**
     * The ACE key
     */
    private String key;
    /**
     * Bit map to be applied in the modification.
     */
    private int bitmap;
    /**
     * Operation to be used on the bitmap.
     */
    private Operation op;

    /**
     * @param principal
     *            the ACE key, normally the principal
     * @param bitmap
     *            the bitmap to be applied
     * @param op
     *            the operation to be applied to the existing bitmap @see
     *            {@link Operation}
     */
    public AclModification(String key, int bitmap, Operation op) {
        this.key = key;
        this.bitmap = bitmap;
        this.op = op;
    }

    public String getAceKey() {
        return this.key;
    }

    @Override
    public String toString() {
        return key+" "+Integer.toHexString(bitmap)+" "+op;
    }

    public int modify(int bits) {
        switch (this.op) {
        case OP_REPLACE:
            return this.bitmap;
        case OP_OR:
            return bits | this.bitmap;
        case OP_AND:
            return bits & this.bitmap;
        case OP_XOR:
            return this.bitmap ^ bits;
        case OP_NOT:
            return ~this.bitmap;
        }
        return this.bitmap;
    }

    public boolean isRemove() {
        return this.op.equals(Operation.OP_DEL);
    }

    public static boolean isDeny(String key) {
        return key != null && key.endsWith(DENIED_MARKER);
    }

    public static String grantKey(String key) {
        return key + GRANTED_MARKER;
    }

    public static String denyKey(String key) {
        return key + DENIED_MARKER;
    }

    public static boolean isGrant(String key) {
        return key != null && key.endsWith(GRANTED_MARKER);
    }

    /**
     * Sets the bits requsted
     *
     * @param grant
     * @param permssion
     * @param key
     * @param modifications
     */
    // TODO: Unit test
    public static void addAcl(boolean grant, Permission permssion, String key,
            List<AclModification> modifications) {
        if (grant) {
            key = AclModification.grantKey(key);
        } else {
            key = AclModification.denyKey(key);
        }
        modifications.add(new AclModification(key, permssion.getPermission(),
                AclModification.Operation.OP_OR));
    }

    /**
     * Unsets the bits requested
     *
     * @param grant
     * @param permssion
     * @param key
     * @param modifications
     */
    // TODO: Unit test
    public static void removeAcl(boolean grant, Permission permssion, String key,
            List<AclModification> modifications) {
        if (grant) {
            key = AclModification.grantKey(key);
        } else {
            key = AclModification.denyKey(key);
        }
        modifications.add(new AclModification(key, ~permssion.getPermission(),
                AclModification.Operation.OP_AND));
    }

    // TODO: Unit test
    public static void filterAcl(Map<String, Object> acl, boolean grant, Permission permission,
            boolean set, List<AclModification> modifications) {
        int perm = permission.getPermission();
        Operation op = Operation.OP_OR;
        if (!set) {
            perm = 0xffff ^ perm;
            op = Operation.OP_AND;
        }
        for (Entry<String, Object> ace : acl.entrySet()) {
            String key = ace.getKey();
            if (AclModification.isGrant(key) == grant) {
                // clear the bit if set.
                modifications.add(new AclModification(key, perm, op));
            }
        }
    }

    // TODO: Unit test
    public static String getPrincipal(String principalKey) {
        if ( principalKey == null) {
            return principalKey;
        }
        if (principalKey.length() <= GRANTED_MARKER.length()) {
            return null;
        }
        if ( principalKey.endsWith(GRANTED_MARKER) ) {
          return principalKey.substring(0, principalKey.length()-GRANTED_MARKER.length());
        } else if ( principalKey.endsWith(DENIED_MARKER) ) {
          return principalKey.substring(0, principalKey.length()-DENIED_MARKER.length());
        } else {
          return null;
        }
    }

    // TODO: Unit test
    public static Permission[] listPermissions(int perms) {
        List<Permission> permissions = Lists.newArrayList();
        for (Permission p : Permissions.PRIMARY_PERMISSIONS) {
            if ((perms & p.getPermission()) == p.getPermission()) {
                permissions.add(p);
            }
        }
        return permissions.toArray(new Permission[permissions.size()]);
    }

    public static String getPropertyKey(String id, String propertyName) {
        return AccessControlManager.PROPERTY_PRINCIPAL_STEM+id+"@"+propertyName;
    }

}


/*
 * Copyright 2010 Workplace Systems PLC (http://www.workplacesystems.com/).
 *
 * Licensed 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 com.workplacesystems.utilsj.collections.decorators;

import com.workplacesystems.utilsj.Callback;
import com.workplacesystems.utilsj.collections.SyncUtils;
import com.workplacesystems.utilsj.collections.TransactionalSortedBidiMap;

/**
 *
 * @author  Dave
 */
public class SynchronizedTransactionalSortedBidiMap<K,V> extends SynchronizedSortedBidiMap<K,V> implements TransactionalSortedBidiMap<K,V> {

    /**
     * Factory method to create a synchronized map.
     *
     * @param map  the map to decorate, must not be null
     * @throws IllegalArgumentException if map is null
     */
    public static <K,V> TransactionalSortedBidiMap<K,V> decorate(TransactionalSortedBidiMap<K,V> map) {
        return new SynchronizedTransactionalSortedBidiMap<K,V>(map);
    }

    /**
     * Constructor that wraps (not copies).
     *
     * @param map  the map to decorate, must not be null
     * @throws IllegalArgumentException if map is null
     */
    protected SynchronizedTransactionalSortedBidiMap(TransactionalSortedBidiMap<K,V> map) {
        super(map);
    }

    /**
     * Constructor that wraps (not copies).
     *
     * @param map  the map to decorate, must not be null
     * @param lock  the lock object to use, must not be null
     * @throws IllegalArgumentException if map is null
     */
    protected SynchronizedTransactionalSortedBidiMap(TransactionalSortedBidiMap<K,V> map, Object lock) {
        super(map, lock);
    }

    protected TransactionalSortedBidiMap<K,V> getTransactionalSortedBidiMap()
    {
        return (TransactionalSortedBidiMap<K,V>)map;
    }

    //-----------------------------------------------------------------------
    public void commit() {
        SyncUtils.synchronizeWrite(lock, new Callback<Object>() {
            @Override
            protected void doAction() {
                getTransactionalSortedBidiMap().commit();
            }
        });
    }

    public void rollback() {
        SyncUtils.synchronizeWrite(lock, new Callback<Object>() {
            @Override
            protected void doAction() {
                getTransactionalSortedBidiMap().rollback();
            }
        });
    }

    public void setAutoCommit(final boolean auto_commit) {
        SyncUtils.synchronizeWrite(lock, new Callback<Object>() {
            @Override
            protected void doAction() {
                getTransactionalSortedBidiMap().setAutoCommit(auto_commit);
            }
        });
    }

    public boolean isAutoCommit() {
        Boolean ret = SyncUtils.synchronizeRead(lock, new Callback<Boolean>() {
            @Override
            protected void doAction() {
                _return(getTransactionalSortedBidiMap().isAutoCommit() ? Boolean.TRUE : Boolean.FALSE);
            }
        });
        return ret.booleanValue();
    }

    public void attach(final String attach_id) {
        getTransactionalSortedBidiMap().attach(attach_id);
    }

    public void detach() {
        getTransactionalSortedBidiMap().detach();
    }
}


/**
 *
 * Copyright 2003-2007 Jive Software.
 *
 * All rights reserved. Licensed 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.jivesoftware.smack;

import java.io.IOException;
import java.io.Writer;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.xmpp.packet.Packet;

/**
 * Writes packets to a XMPP server. Packets are sent using a dedicated thread.
 * Packet interceptors can be registered to dynamically modify packets before
 * they're actually sent. Packet listeners can be registered to listen for all
 * outgoing packets.
 *
 * @see Connection#addPacketInterceptor
 * @see Connection#addPacketSendingListener
 *
 * @author Matt Tucker
 */
class PacketWriter {

    private static Logger LOGGER = Logger.getLogger(PacketWriter.class
            .getName());

    private Thread writerThread;
    private Writer writer;
    private XMPPConnection connection;
    private final BlockingQueue<Packet> queue;
    volatile boolean done;

    /**
     * Creates a new packet writer with the specified connection.
     *
     * @param connection
     *            the connection.
     */
    protected PacketWriter(XMPPConnection connection) {
        this.queue = new ArrayBlockingQueue<Packet>(500, true);
        this.connection = connection;
        init();
    }

    /**
     * Initializes the writer in order to be used. It is called at the first
     * connection and also is invoked if the connection is disconnected by an
     * error.
     */
    protected void init() {
        this.writer = connection.writer;
        done = false;

        writerThread = new Thread() {
            public void run() {
                writePackets(this);
            }
        };
        writerThread.setName("Smack Packet Writer ("
                + connection.connectionCounterValue + ")");
        writerThread.setDaemon(true);
    }

    /**
     * Sends the specified packet to the server.
     *
     * @param packet
     *            the packet to send.
     */
    public void sendPacket(Packet packet) {
        if (!done) {
            // Invoke interceptors for the new packet that is about to be sent.
            // Interceptors
            // may modify the content of the packet.
            connection.firePacketInterceptors(packet);

            try {
                queue.put(packet);
            } catch (InterruptedException ie) {
                LOGGER.log(
                        Level.ERROR,
                        "Failed to queue packet to send to server: "
                                + packet.toString(), ie);
                return;
            }
            synchronized (queue) {
                queue.notifyAll();
            }

            // Process packet writer listeners. Note that we're using the
            // sending
            // thread so it's expected that listeners are fast.
            connection.firePacketSendingListeners(packet);
        }
    }

    /**
     * Starts the packet writer thread and opens a connection to the server. The
     * packet writer will continue writing packets until {@link #shutdown} or an
     * error occurs.
     */
    public void startup() {
        writerThread.start();
    }

    void setWriter(Writer writer) {
        this.writer = writer;
    }

    /**
     * Shuts down the packet writer. Once this method has been called, no
     * further packets will be written to the server.
     */
    public void shutdown() {
        done = true;
        synchronized (queue) {
            queue.notifyAll();
        }
    }

    /**
     * Returns the next available packet from the queue for writing.
     *
     * @return the next packet for writing.
     */
    private Packet nextPacket() {
        Packet packet = null;
        // Wait until there's a packet or we're done.
        while (!done && (packet = queue.poll()) == null) {
            try {
                synchronized (queue) {
                    queue.wait();
                }
            } catch (InterruptedException ie) {
                // Do nothing
            }
        }
        return packet;
    }

    private void writePackets(Thread thisThread) {
        try {
            // Open the stream.
            openStream();
            // Write out packets from the queue.
            while (!done && (writerThread == thisThread)) {
                Packet packet = nextPacket();
                if (packet != null) {
                    LOGGER.debug("Sending packet " + packet.toXML());
                    writer.write(packet.toXML());

                    if (queue.isEmpty()) {
                        writer.flush();
                    }
                }
            }
            // Flush out the rest of the queue. If the queue is extremely large,
            // it's possible
            // we won't have time to entirely flush it before the socket is
            // forced closed
            // by the shutdown process.
            try {
                while (!queue.isEmpty()) {
                    Packet packet = queue.remove();
                    writer.write(packet.toXML());
                }
                writer.flush();
            } catch (Exception e) {
                LOGGER.warn("Error flushing queue during shutdown, ignore and continue");
            }

            // Delete the queue contents (hopefully nothing is left).
            queue.clear();

            // Close the stream.
            try {
                writer.write("</stream:stream>");
                writer.flush();
            } catch (Exception e) {
                // Do nothing
            } finally {
                try {
                    writer.close();
                } catch (Exception e) {
                    // Do nothing
                }
            }
        } catch (IOException ioe) {
            // The exception can be ignored if the the connection is 'done'
            // or if the it was caused because the socket got closed
            if (!(done || connection.isSocketClosed())) {
                done = true;
                // packetReader could be set to null by an concurrent
                // disconnect() call.
                // Therefore Prevent NPE exceptions by checking packetReader.
                if (connection.packetReader != null) {
                    connection.notifyConnectionError(ioe);
                }
            }
        }
    }

    /**
     * Sends to the server a new stream element. This operation may be requested
     * several times so we need to encapsulate the logic in one place. This
     * message will be sent while doing TLS, SASL and resource binding.
     *
     * @throws IOException
     *             If an error occurs while sending the stanza to the server.
     */
    void openStream() throws IOException {
        StringBuilder stream = new StringBuilder();
        stream.append("<stream:stream");
        stream.append(" to=\"").append(connection.getServiceName())
                .append("\"");
        stream.append(" xmlns=\"jabber:client\"");
        stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
        stream.append(" version=\"1.0\">");
        writer.write(stream.toString());
        writer.flush();
    }
}


/*
 * Copyright 2010-2011 Ning, Inc.
 *
 * Ning 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 com.ning.metrics.goodwill.access;

import com.ning.http.client.AsyncCompletionHandler;
import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.AsyncHttpClientConfig;
import com.ning.http.client.Response;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Future;

public class GoodwillAccessor extends Accessor
{
    protected static final ObjectMapper mapper = new ObjectMapper();
    protected AsyncHttpClient client;

    public GoodwillAccessor(final String host, final int port)
    {
        super(host, port);
        client = createHttpClient();
    }

    /**
     * Get the Goodwill schema associated to a schema name.
     *

     * Typical invocation:
     *

     * try {
     *   GoodwillSchema type = accessor.getSchema("test").get();
     * ...
     * }
     * catch (Exception e) {
     *   // Connection exception? Goodwill server down?
     * }
     *

     *
     * @param schemaName schema name to lookup
     * @return a future on the Schema
     */
    public Future<GoodwillSchema> getSchema(final String schemaName)
    {
        try {
            return client.prepareGet(String.format("%s/%s", url, schemaName)).addHeader("Accept", "application/json").execute(new AsyncCompletionHandler<GoodwillSchema>()
            {
                @Override
                public GoodwillSchema onCompleted(final Response response) throws Exception
                {
                    if (response.getStatusCode() != 200) {
                        return null;
                    }

                    final InputStream in = response.getResponseBodyAsStream();
                    try {
                        return mapper.readValue(in, GoodwillSchema.class);
                    }
                    finally {
                        closeStream(in);
                    }
                }

                @Override
                public void onThrowable(final Throwable t)
                {
                    log.warn("Got exception looking up the schema", t);
                }
            });
        }
        catch (IOException e) {
            log.warn("Got exception looking up the schema", e);
            return null;
        }
    }

    /**
     * Get all schemata.
     *

     * Use schemata, instead of schemas, which is closer to the original σχήματα.
     *
     * @return a future on a list of Schema
     * @see #getSchema(String) for an invocation example
     */
    public Future<List<GoodwillSchema>> getSchemata()
    {
        try {
            return client.prepareGet(url).addHeader("Accept", "application/json").execute(new AsyncCompletionHandler<List<GoodwillSchema>>()
            {
                @Override
                public List<GoodwillSchema> onCompleted(final Response response) throws Exception
                {
                    if (response.getStatusCode() != 200) {
                        return null;
                    }

                    InputStream in = response.getResponseBodyAsStream();
                    try {
                        final HashMap<String, List<GoodwillSchema>> map = mapper.readValue(in,
                            new TypeReference<HashMap<String, List<GoodwillSchema>>>()
                            {
                            });
                        return map.get("types");
                    }
                    finally {
                        closeStream(in);
                    }
                }

                @Override
                public void onThrowable(final Throwable t)
                {
                    log.warn("Got exception looking up the schema list", t);
                }
            });
        }
        catch (IOException e) {
            log.warn("Got exception looking up the schema list", e);
            return null;
        }
    }

    // note: if called from base-class constructor, couldn't sub-class; hence just make static
    private static AsyncHttpClient createHttpClient()
    {
        // Don't limit the number of connections per host
        // See https://github.com/ning/async-http-client/issues/issue/28
        final AsyncHttpClientConfig.Builder builder = new AsyncHttpClientConfig.Builder();
        builder.setMaximumConnectionsPerHost(-1);
        return new AsyncHttpClient(builder.build());
    }

    /**
     * Close the underlying http client
     */
    public synchronized void close()
    {
        client.close();
    }

    protected final void closeStream(final InputStream in)
    {
        if (in != null) {
            try {
                in.close();
            }
            catch (IOException e) {
                log.warn("Failed to close http-client - provided InputStream: {}", e.getLocalizedMessage());
            }
        }
    }
}


package org.intellimate.izou.sdk.events;

import org.intellimate.izou.events.EventBehaviourControllerModel;
import org.intellimate.izou.events.EventLifeCycle;
import org.intellimate.izou.events.EventModel;
import org.intellimate.izou.identification.Identification;
import org.intellimate.izou.resource.ListResourceProvider;
import org.intellimate.izou.resource.ResourceModel;
import org.intellimate.izou.sdk.resource.*;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

/**
 * This class represents an Event.
 * This class is immutable! for every change it will return an new instance!
 */
public class Event implements EventModel<Event> {
    private final String type;
    private final Identification source;
    private final List<String> descriptors;
    private final ListResourceProvider listResourceContainer;
    private final EventBehaviourController eventBehaviourController;
    private final ConcurrentHashMap<EventLifeCycle, List<Consumer<EventLifeCycle>>> lifeCycleListeners = new ConcurrentHashMap<>();

    /**
     * Creates a new Event Object
     * @param type the Type of the Event, try to use the predefined Event types
     * @param source the source of the Event, most likely a this reference.
     * @param descriptors the descriptors to initialize the Event with
     * @throws IllegalArgumentException if one of the Arguments is null or empty
     */
    protected Event(String type, Identification source, List<String> descriptors) throws IllegalArgumentException {
        if(type == null || type.isEmpty()) throw new IllegalArgumentException("illegal type");
        if(source == null) throw new IllegalArgumentException("source is null");
        this.type = type;
        this.source = source;
        this.descriptors = Collections.unmodifiableList(descriptors);
        listResourceContainer = new ListResourceProviderImpl();
        eventBehaviourController = new EventBehaviourController();
    }

    /**
     * Creates a new Event Object
     * @param type the Type of the Event, try to use the predefined Event types
     * @param source the source of the Event, most likely a this reference.
     * @param listResourceContainer the ResourceContainer
     * @param descriptors the descriptors to initialize the Event with
     * @param eventBehaviourController the Controller of the Event
     * @throws IllegalArgumentException if one of the Arguments is null or empty
     */
    protected Event(String type, Identification source, ListResourceProvider listResourceContainer, List<String> descriptors,
                    EventBehaviourController eventBehaviourController)throws IllegalArgumentException {
        if(type == null || type.isEmpty()) throw new IllegalArgumentException("illegal type");
        if(source == null) throw new IllegalArgumentException("source is null");
        this.type = type;
        this.source = source;
        this.descriptors = Collections.unmodifiableList(new ArrayList<>(descriptors));
        this.listResourceContainer = listResourceContainer;
        this.eventBehaviourController = eventBehaviourController;
    }

    /**
     * Creates a new Event Object
     * @param type the Type of the Event, try to use the predefined Event types
     * @param source the source of the Event, most likely a this reference.
     * @return an Optional, that may be empty if type is null or empty or source is null
     */
    public static Optional<Event> createEvent(String type, Identification source) {
        try {
            return Optional.of(new Event(type, source, new ArrayList<>()));
        } catch (IllegalArgumentException e) {
            return Optional.empty();
        }
    }

    /**
     * Creates a new Event Object
     * @param type the Type of the Event, try to use the predefined Event types
     * @param source the source of the Event, most likely a this reference.
     * @param descriptors the descriptors
     * @return an Optional, that may be empty if type is null or empty or source is null
     */
    public static Optional<Event> createEvent(String type, Identification source, List<String> descriptors) {
        try {
            return Optional.of(new Event(type, source, descriptors));
        } catch (IllegalArgumentException e) {
            return Optional.empty();
        }
    }

    /**
     * The ID of the Event.
     * It describes the Type of the Event.
     * @return A String containing an ID
     */
    @Override
    public String getID() {
        return type;
    }

    /**
     * The type of the Event.
     * It describes the Type of the Event.
     * @return A String containing an ID
     */
    @Override
    public String getType() {
        return type;
    }

    /**
     * returns the Source of the Event, e.g. the object who fired it.
     * @return an identifiable
     */
    @Override
    public Identification getSource() {
        return source;
    }

    /**
     * returns all the Resources the Event currently has
     * @return an instance of ListResourceContainer
     */
    @Override
    public ListResourceProvider getListResourceContainer() {
        return listResourceContainer;
    }

    /**
     * adds a Resource to the Container
     * @param resource an instance of the resource to add
     * @return the resulting Event (which is the same instance)
     */
    @Override
    public Event addResource(ResourceModel resource) {
        listResourceContainer.addResource(resource);
        return this;
    }

    /**
     * adds a List of Resources to the Container
     * @param resources a list containing all the resources
     */
    @Override
    public Event addResources(List<ResourceModel> resources) {
        listResourceContainer.addResource(resources);
        return this;
    }

    /**
     * returns a List containing all the Descriptors.
     * @return a List containing the Descriptors
     */
    @Override
    public List<String> getDescriptors() {
        return descriptors;
    }

    /**
     * returns a List containing all the Descriptors and the type.
     * @return a List containing the Descriptors
     */
    @Override
    public List<String> getAllInformations() {
        ArrayList<String> strings = new ArrayList<>(descriptors);
        strings.add(type);
        return strings;
    }

    /**
     * sets the Descriptors (but not the Event-Type).
     * <p>
     * Replaces all existing descriptors.
     * Since Event is immutable, it will create a new Instance.
     * </p>
     * @param descriptors a List containing all the Descriptors
     * @return the resulting Event (which is the same instance)
     */
    public Event setDescriptors(List<String> descriptors) {
        return new Event(getType(), getSource(), descriptors);
    }

    /**
     * sets the Descriptors (but not the Event-Type).
     * @param descriptor a String describing the Event.
     * @return the resulting Event (which is the same instance)
     */
    public Event addDescriptor(String descriptor) {
        List<String> newDescriptors = new ArrayList<>();
        newDescriptors.addAll(descriptors);
        newDescriptors.add(descriptor);
        return new Event(getType(), getSource(), newDescriptors);
    }

    /**
     * replaces the Descriptors
     * @param descriptors a list containing the Descriptors.
     * @return the resulting Event (which is the same instance)
     */
    public Event replaceDescriptors(List<String> descriptors) {
        return new Event(getType(), getSource(), descriptors);
    }

    /**
     * returns whether the event contains the specific descriptor.
     * this method also checks whether it matches the type.
     * @param descriptor a String with the ID of the Descriptor
     * @return boolean when the Event contains the descriptor, false when not.
     */
    @Override
    public boolean containsDescriptor(String descriptor) {
        return descriptors.contains(descriptor) || type.equals(descriptor);
    }

    /**
     * returns the associated EventBehaviourController
     * @return an instance of EventBehaviourController
     */
    @Override
    public EventBehaviourControllerModel getEventBehaviourController() {
        return eventBehaviourController;
    }

    @Override
    public void lifecycleCallback(EventLifeCycle eventLifeCycle) {
        lifeCycleListeners.getOrDefault(eventLifeCycle, new LinkedList<>()).stream()
                .forEach(eventLifeCycleConsumer -> eventLifeCycleConsumer.accept(eventLifeCycle));
    }

    /**
     * adds the Consumer to the specified EventLifeCycle.
     * In its current implementation the invocation of the Callback method is parallel, but the notificaton of the listners not.
     * @param eventLifeCycle the EventLifeCycle to target
     * @param cycleCallback the callback
     */
    @SuppressWarnings("unused")
    public Event addEventLifeCycleListener(EventLifeCycle eventLifeCycle, Consumer<EventLifeCycle> cycleCallback) {
        lifeCycleListeners.compute(eventLifeCycle, (unused, list) -> {
            if (list == null)
                list = new ArrayList<>();
            list.add(cycleCallback);
            return list;
        });
        return this;
    }

    @Override
    public String toString() {
        return "Event{" +
                "type='" + type + '\'' +
                ", source=" + source +
                ", descriptors=" + descriptors +
                ", listResourceContainer=" + listResourceContainer +
                '}';
    }
}


package org.intellimate.izou.sdk.frameworks.permanentSoundOutput.output;

import org.intellimate.izou.events.EventModel;
import org.intellimate.izou.identification.Identification;
import org.intellimate.izou.resource.ResourceModel;
import org.intellimate.izou.sdk.Context;
import org.intellimate.izou.sdk.frameworks.common.resources.SelectorResource;
import org.intellimate.izou.sdk.frameworks.permanentSoundOutput.events.MuteEvent;
import org.intellimate.izou.sdk.frameworks.permanentSoundOutput.events.StopEvent;
import org.intellimate.izou.sdk.frameworks.permanentSoundOutput.events.UnMuteEvent;
import org.intellimate.izou.sdk.output.OutputPlugin;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

/**
 * extend this class to use the framework
 * @author LeanderK
 * @version 1.0
 */
public abstract class SimplePermanentSoundOutputPlugin<T> extends OutputPlugin<T> implements PermanentSoundUsed,
        PermanentSoundHelper, PermanentSoundResources {
    private boolean isCurrentlyPlayingSound = false;
    public final boolean isUsingJava;

    /**
     * creates a new output-plugin with a new id
     *
     * @param context context
     * @param id the id of the new output-plugin
     */
    public SimplePermanentSoundOutputPlugin(Context context, String id, boolean isUsingJava) {
        super(context, id);
        resourcesInit(context);
        this.isUsingJava = isUsingJava;
    }

    /**
     * Default implementation waits until a new Event has been received and then processes it.
     * <p>
     * This method is made to be overwritten as seen fit by the developer
     *
     * @return the recently added Event-instance to be processed by the outputPlugin
     * @throws InterruptedException if interrupted while waiting
     */
    @Override
    public EventModel blockingQueueHandling() throws InterruptedException {
        EventModel eventModel = super.blockingQueueHandling();
        if (eventModel.containsDescriptor(MuteEvent.ID)) {
            List<ResourceModel> resourceModels =
                    eventModel.getListResourceContainer().provideResource(SelectorResource.RESOURCE_ID);
            if (resourceModels.isEmpty()) {
                mute();
            } else {
                resourceModels.stream()
                        .map(resourceModel -> resourceModel.getResource() instanceof Identification ?
                                ((Identification) resourceModel.getResource()) : null)
                        .filter(Objects::nonNull)
                        .filter(this::isOwner)
                        .findAny()
                        .ifPresent(id -> mute());
            }
        }
        if (eventModel.containsDescriptor(UnMuteEvent.ID)) {
            List<ResourceModel> resourceModels =
                    eventModel.getListResourceContainer().provideResource(SelectorResource.RESOURCE_ID);
            if (resourceModels.isEmpty()) {
                unMute();
            } else {
                resourceModels.stream()
                        .map(resourceModel -> resourceModel.getResource() instanceof Identification ?
                                ((Identification) resourceModel.getResource()) : null)
                        .filter(Objects::nonNull)
                        .filter(this::isOwner)
                        .findAny()
                        .ifPresent(id -> unMute());
            }
        }
        if (eventModel.containsDescriptor(StopEvent.ID)) {
            List<ResourceModel> resourceModels =
                    eventModel.getListResourceContainer().provideResource(SelectorResource.RESOURCE_ID);
            if (resourceModels.isEmpty()) {
                stopSound();
            } else {
                resourceModels.stream()
                        .map(resourceModel -> resourceModel.getResource() instanceof Identification ?
                                ((Identification) resourceModel.getResource()) : null)
                        .filter(Objects::nonNull)
                        .filter(this::isOwner)
                        .findAny()
                        .ifPresent(id -> stopSound());
            }
        }
        return eventModel;
    }

    /**
     * retruns true if playing and false if not
     *
     * @return tre if playing
     */
    @Override
    public boolean isOutputRunning() {
        return isCurrentlyPlayingSound;
    }

    /**
     * true if using java, false if not (and for example a C-library)
     *
     * @return true if using java
     */
    @Override
    public boolean isUsingJava() {
        return isUsingJava;
    }

    /**
     * use this method to start playing sound (only plays sound if it is not already playing)
     * @param function the function which plays the sound
     * @return the Future
     */
    public Optional<CompletableFuture<Void>> startPlaying(Runnable function) {
        if (isCurrentlyPlayingSound)
            return Optional.empty();
        CompletableFuture<Void> voidCompletableFuture = submit((Runnable) () -> startedSound(isUsingJava))
                .thenRun(() -> {
                    try {
                        isCurrentlyPlayingSound = true;
                        function.run();
                    } finally {
                        submit((Runnable) this::endedSound);
                        isCurrentlyPlayingSound = false;
                    }
                });
        return Optional.of(voidCompletableFuture);
    }

    /**
     * this method call must mute the plugin
     */
    public abstract void mute();

    /**
     * this method call must un-mute the plugin
     */
    public abstract void unMute();

    /**
     * this method call must stop the sound
     */
    public abstract void stopSound();
}