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

import com.workplacesystems.utilsj.UtilsjException;
import com.workplacesystems.utilsj.collections.FilterableCollection;
import com.workplacesystems.utilsj.collections.IterativeCallback;

/**
 * Counts the items by calling size() on the filtered set.
 * Caveat: saves time by not actually iterating the items.
 *
 * @author  Dave
 */
public final class CounterIterativeCallback<T> extends IterativeCallback<T,Integer>
{
    /** Holds the number of counted items. */
    private int count = 0;

    // don't need to actually iterate the objects, we just get the count of the filterable collection
    @Override
    public void nextObject(T obj) { }

    /**
     * Overridden to just get the count, and nothing else.
     */
    @Override
    public Integer iterate(final FilterableCollection<? extends T> c)
    {
        checkUsed();
        // No point doing the iteration. Just return size of collection.
        count = c.size();
        return count;
    }

    /**
     * Return the number of items counted.
     * @throws QueujException if the iterative callback has not been used yet
     */
    public int getCount() throws UtilsjException
    {
        if (!hasBeenUsed())
            throw new UtilsjException("CounterIterativeCallback.getCount() called without having been used");
        return count;
    }
}


package org.intellimate.izou.sdk.frameworks.music.player.template;

import org.intellimate.izou.events.EventModel;
import org.intellimate.izou.resource.ResourceModel;
import org.intellimate.izou.sdk.frameworks.music.Capabilities;
import org.intellimate.izou.sdk.frameworks.music.events.PlayerError;
import org.intellimate.izou.sdk.frameworks.music.player.*;
import org.intellimate.izou.sdk.frameworks.music.resources.CommandResource;
import org.intellimate.izou.sdk.frameworks.music.resources.ProgressResource;
import org.intellimate.izou.sdk.frameworks.music.resources.TrackInfoResource;
import org.intellimate.izou.sdk.frameworks.music.resources.VolumeResource;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * this class is used to handle the Commands and directs them to the registered methods.
 * @author LeanderK
 * @version 1.0
 */
public class CommandHandler {
    private final MusicHelper musicHelper;
    private final MusicProvider musicProvider;
    private final Capabilities capabilities;
    private final Runnable stopCallback;
    private Consumer<String> playPause = null;
    private Consumer<TrackInfo> selectTrack = null;
    private Consumer<String> nextPrevious = null;
    private Consumer<Progress> jumpProgress = null;
    private Consumer<String> changePlayback = null;
    private Consumer<Volume> changeVolume;
    private Function<String, Playlist> playlistForNameFunction = null;
    private Supplier<List<String>> availablePlaylist = null;

    /**
     * creates a new CommandHandler
     * @param musicHelper the musicHelper
     * @param musicProvider the musicProvider
     * @param stopCallback the callback for the stop-command
     * @param capabilities the capabilities
     */
    public CommandHandler(MusicHelper musicHelper, MusicProvider musicProvider,
                          Runnable stopCallback, Capabilities capabilities) {
        this.musicHelper = musicHelper;
        this.capabilities = capabilities;
        this.musicProvider = musicProvider;
        this.stopCallback = stopCallback;
    }

    /**
     * adds the ability for the Play/Pause requests
     * @param controller  the controller for callback
     */
    public void setPlayPauseController(Consumer<String> controller) {
        if (controller == null)
            return;
        this.playPause = controller;
        capabilities.setPlayPauseControl(true);
    }

    /**
     * adds the ability to select tracks
     * @param controller  the controller for callback
     */
    public void setTrackSelectorController(Consumer<TrackInfo> controller) {
        if (controller == null)
            return;
        selectTrack = controller;
        capabilities.setAbleToSelectTrack(true);
    }

    /**
     * adds the ability to select the next/previous track
     * @param controller  the controller for callback
     */
    public void setNextPreviousController(Consumer<String> controller) {
        if (controller == null)
            return;
        nextPrevious = controller;
        capabilities.setNextPrevious(true);
    }

    /**
     * adds the ability to jump to a specified position of the current track
     * @param controller  the controller for callback
     */
    public void setJumpProgressController(Consumer<Progress> controller) {
        if (controller == null)
            return;
        jumpProgress = controller;
        capabilities.setAbleToJump(true);
    }

    /**
     * adds the ability to change the playback
     * @param controller  the controller for callback
     */
    public void setPlaybackChangeableController(Consumer<String> controller) {
        if (controller == null)
            return;
        changePlayback = controller;
        capabilities.setPlaybackChangeable(true);
    }

    /**
     * adds the ability to change the volume from outside the player
     * @param controller  the controller for callback
     */
    public void setVolumeChangeableController(Consumer<Volume> controller) {
        if (controller == null)
            return;
        changeVolume = controller;
        capabilities.setChangeVolume(true);
    }

    /**
     * adds the ability to return the available playlists on request.
     * @param availablePlaylist retruns a List of Strings which represent the Playlists
     * @param playlistForNameFunction takes a String from the List and returns the Playlist
     */
    public void broadcastAvailablePlaylists(Supplier<List<String>> availablePlaylist, Function<String, Playlist> playlistForNameFunction) {
        if (availablePlaylist == null || playlistForNameFunction == null)
            return;
        this.availablePlaylist = availablePlaylist;
        this.playlistForNameFunction = playlistForNameFunction;
        capabilities.setBroadcasting(true);
    }

    /**
     * this method gets called when a new Command was found. It automatically fires the update Event or an error
     * @param eventModel the event with the Commands
     */
    public void handleCommandResources(EventModel eventModel) {
        List<ResourceModel<String>> resourceModels = eventModel.getListResourceContainer()
                .provideResource(CommandResource.ResourceID)
                .stream()
                .filter(resourceModel -> resourceModel.getResource() instanceof String)
                .map(resourceModel -> {
                    try {
                        //noinspection unchecked
                        return (ResourceModel<String>) resourceModel;
                    } catch (ClassCastException e) {
                        return null;
                    }
                })
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        for (ResourceModel<String> resourceModel : resourceModels) {
            if (!CommandResource.verifyCommand(resourceModel.getResource()))
                continue;
            if (!CommandResource.verifyCapabilities(resourceModel.getResource(), capabilities)) {
                musicHelper.playerError(PlayerError.ERROR_NOT_ABLE + "command: " + resourceModel.getResource(),
                        resourceModel.getProvider());
                continue;
            }
            switch (resourceModel.getResource()) {
                case CommandResource.PLAY: if (!musicProvider.isPlaying())
                    playPause.accept(resourceModel.getResource());
                    break;
                case CommandResource.PAUSE: if (musicProvider.isPlaying())
                    playPause.accept(resourceModel.getResource());
                    break;
                case CommandResource.SELECT_TRACK: handleSelectTrack(eventModel, resourceModel);
                    break;
                case CommandResource.NEXT: nextPrevious.accept(resourceModel.getResource());
                    break;
                case CommandResource.PREVIOUS: nextPrevious.accept(resourceModel.getResource());
                    break;
                case CommandResource.JUMP: handleJump(eventModel, resourceModel);
                    break;
                case CommandResource.CHANGE_PLAYBACK: changePlayback.accept(resourceModel.getResource());
                    break;
                case CommandResource.CHANGE_VOLUME: handleVolume(eventModel, resourceModel);
                    break;
                case CommandResource.STOP: stopCallback.run();
                    break;
            }
        }
    }

    /**
     * handles the volume-command
     * @param eventModel the event
     * @param resourceModel the resourcemodel
     */
    private void handleVolume(EventModel eventModel, ResourceModel<String> resourceModel) {
        Optional<Volume> volumeResource = VolumeResource.getVolume(eventModel);
        if (!volumeResource.isPresent()) {
            musicHelper.playerError(PlayerError.ERROR_ILLEGAL + "command: " + resourceModel.getResource() + "missing resource",
                    resourceModel.getProvider());
        }
        changeVolume.accept(volumeResource.get());
    }

    /**
     * handles the jump-command
     * @param eventModel the event
     * @param resourceModel the resourcemodel
     */
    private void handleJump(EventModel eventModel, ResourceModel<String> resourceModel) {
        Optional<Progress> progress = ProgressResource.getProgress(eventModel);
        if (!progress.isPresent()) {
            musicHelper.playerError(PlayerError.ERROR_ILLEGAL + "command: " + resourceModel.getResource() + "missing resource",
                    resourceModel.getProvider());
        }
        jumpProgress.accept(progress.get());
    }

    /**
     * handles the select Track command
     * @param eventModel the event
     * @param resourceModel the resource
     */
    private void handleSelectTrack(EventModel eventModel, ResourceModel<String> resourceModel) {
        Optional<TrackInfo> trackInfo = TrackInfoResource.getTrackInfo(eventModel);
        if (!trackInfo.isPresent()) {
            musicHelper.playerError(PlayerError.ERROR_ILLEGAL + "command: " + resourceModel.getResource() + "missing resource",
                    resourceModel.getProvider());
        }
        selectTrack.accept(trackInfo.get());
    }

    /**
     * retruns the playlist for the specified name
     * @param name the name of the playlist
     * @return the playlist
     */
    Playlist getPlaylistFromName(String name) {
        return playlistForNameFunction.apply(name);
    }

    /**
     * generates all available playlists.
     * @return a list containing the names of all available Playlists.
     */
    List<String> getAvailablePlaylists() {
        return availablePlaylist.get();
    }
}


/*
 * 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.memory.ehcache;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;

import javax.management.MBeanServer;

import net.sf.ehcache.CacheManager;
import net.sf.ehcache.management.ManagementService;

import org.apache.commons.io.IOUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Property;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The CacheManagerServiceImpl
 */
@Component(immediate = true, metatype=true)
@Service(value=CacheManagerService.class)
public class CacheManagerServiceImpl implements CacheManagerService {

  public static final String DEFAULT_CACHE_CONFIG = "sling/ehcacheConfig.xml";

  @Property( value = DEFAULT_CACHE_CONFIG)
  public static final String CACHE_CONFIG = "cache-config";

  @Property(value = "The Sakai Foundation")
  static final String SERVICE_VENDOR = "service.vendor";

  @Property(value = "Cache Manager Service Implementation")
  static final String SERVICE_DESCRIPTION = "service.description";

  @Property()
  public static final String BIND_ADDRESS = "bind-address";

  @Property(value="sling/ehcache/data")
  public static final String CACHE_STORE = "cache-store";

  private static final String CONFIG_PATH = "uk/co/tfd/sm/memory/ehcache/ehcacheConfig.xml";

  private static final Logger LOGGER = LoggerFactory.getLogger(CacheManagerServiceImpl.class);
  private CacheManager cacheManager;
  private Map<String, Cache<?>> caches = new HashMap<String, Cache<?>>();
  private ThreadLocalCacheMap requestCacheMapHolder = new ThreadLocalCacheMap();
  private ThreadLocalCacheMap threadCacheMapHolder = new ThreadLocalCacheMap();

  public CacheManagerServiceImpl() throws IOException {
  }

  @Activate
  public void activate(Map<String, Object> properties) throws FileNotFoundException, IOException {
	  String config = toString(properties.get(CACHE_CONFIG), DEFAULT_CACHE_CONFIG);
	  File configFile = new File(config);
	  if ( configFile.exists() ) {
		  LOGGER.info("Configuring Cache from {} ",configFile.getAbsolutePath());
		  InputStream in = null;
		  try {
			  in = processConfig(new FileInputStream(configFile), properties);
			  cacheManager = new CacheManager(in);
		  } finally {
			  if ( in != null ) {
				  in.close();
			  }
		  }
	  } else {
		    LOGGER.info("Configuring Cache from Classpath Default {} ", CONFIG_PATH);
		    InputStream in = processConfig(this.getClass().getClassLoader().getResourceAsStream(CONFIG_PATH), properties);
		    if ( in == null ) {
		    	throw new IOException("Unable to open config at classpath location "+CONFIG_PATH);
		    }
		    cacheManager = new CacheManager(in);
		    in.close();
	  }

	  final WeakReference<CacheManagerServiceImpl> ref = new WeakReference<CacheManagerServiceImpl>(this);
    /*
     * Add in a shutdown hook, for safety
     */
    Runtime.getRuntime().addShutdownHook(new Thread() {
      /*
       * (non-Javadoc)
       *
       * @see java.lang.Thread#run()
       */
      @Override
      public void run() {
        try {
        	CacheManagerServiceImpl cm = ref.get();
        	if ( cm != null ) {
        		cm.deactivate();
        	}
        } catch (Throwable t) {
        	LOGGER.debug(t.getMessage(),t);
        }
      }
    });

    // register the cache manager with JMX
    MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
    ManagementService.registerMBeans(cacheManager, mBeanServer, true, true,
        true, true);

  }

  private String toString(Object object, String defaultValue) {
	  if ( object == null ) {
		  return defaultValue;
	  }
	return String.valueOf(object);
  }

  private InputStream processConfig(InputStream in,
		Map<String, Object> properties) throws IOException {
	  if ( in == null ) {
		  return null;
	  }
	  StringBuilder config = new StringBuilder(IOUtils.toString(in, "UTF-8"));
	  in.close();
	  int pos = 0;
	  for(;;) {
		  int start = config.indexOf("${",pos);
		  if ( start < 0 ) {
			  break;
		  }
		  int end = config.indexOf("}", start);
		  if ( end < 0 ) {
				throw new IllegalArgumentException(
						"Config file malformed, unterminated variable "
								+ config.substring(start,
										Math.min(start + 10, config.length())));
		  }
		  String key = config.substring(start+2, end);
		  if ( properties.containsKey(key)) {
			  String replacement = (String) properties.get(key);
			  config.replace(start, end+1, replacement);
			  pos = start + replacement.length();
		  } else {
			  throw new IllegalArgumentException("Missing replacement property "+key);
		  }
	  }
	  return new ByteArrayInputStream(config.toString().getBytes("UTF-8"));

  }

/**
   * perform a shutdown
   */
  @Deactivate
  public void deactivate() {
	  if ( cacheManager != null ) {
		  cacheManager.shutdown();
		  cacheManager = null;
	  }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.sakaiproject.nakamura.api.memory.CacheManagerService#getCache(java.lang.String)
   */
  public <V> Cache<V> getCache(String name, CacheScope scope) {
    switch (scope) {
    case INSTANCE:
      return getInstanceCache(name);
    case CLUSTERINVALIDATED:
      return getInstanceCache(name);
    case CLUSTERREPLICATED:
      return getInstanceCache(name);
    case REQUEST:
      return getRequestCache(name);
    case THREAD:
      return getThreadCache(name);
    default:
      return getInstanceCache(name);
    }
  }

  /**
   * Generate a cache bound to the thread.
   *
   * @param name
   * @return
   */
  @SuppressWarnings("unchecked")
  private <V> Cache<V> getThreadCache(String name) {
    Map<String, Cache<?>> threadCacheMap = threadCacheMapHolder.get();
    Cache<V> threadCache = (Cache<V>) threadCacheMap.get(name);
    if (threadCache == null) {
      threadCache = new MapCacheImpl<V>();
      threadCacheMap.put(name, threadCache);
    }
    return threadCache;
  }

  /**
   * Generate a cache bound to the request
   *
   * @param name
   * @return
   */
  @SuppressWarnings("unchecked")
  private <V> Cache<V> getRequestCache(String name) {
    Map<String, Cache<?>> requestCacheMap = requestCacheMapHolder.get();
    Cache<V> requestCache = (Cache<V>) requestCacheMap.get(name);
    if (requestCache == null) {
      requestCache = new MapCacheImpl<V>();
      requestCacheMap.put(name, requestCache);
    }
    return requestCache;
  }

  /**
   * @param name
   * @return
   */
  @SuppressWarnings("unchecked")
  private <V> Cache<V> getInstanceCache(String name) {
    if (name == null) {
      return new CacheImpl<V>(cacheManager, null);
    } else {
      Cache<V> c = (Cache<V>) caches.get(name);
      if (c == null) {
        c = new CacheImpl<V>(cacheManager, name);
        caches.put(name, c);
      }
      return c;
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.sakaiproject.nakamura.api.memory.CacheManagerService#unbind(org.sakaiproject.nakamura.api.memory.CacheScope)
   */
  public void unbind(CacheScope scope) {
    switch (scope) {
    case REQUEST:
      unbindRequest();
      break;
    case THREAD:
      unbindThread();
      break;
    }
  }

  /**
   *
   */
  private void unbindThread() {
    Map<String, Cache<?>> threadCache = threadCacheMapHolder.get();
    for (Cache<?> cache : threadCache.values()) {
      cache.clear();
    }
    threadCacheMapHolder.remove();
  }

  /**
   *
   */
  private void unbindRequest() {
    Map<String, Cache<?>> requestCache = requestCacheMapHolder.get();
    for (Cache<?> cache : requestCache.values()) {
      cache.clear();
    }
    requestCacheMapHolder.remove();
  }

}


package org.sakaiproject.nakamura.lite;

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

import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.sakaiproject.nakamura.api.lite.StoreListener;
import org.sakaiproject.nakamura.api.lite.accesscontrol.Security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Map;

/**
 * When this {@link StoreListener} is notified of a storage action
 * it posts an OSGi {@link Event} to the {@link EventAdmin}
 */
@Component(immediate = true, metatype = true)
@Service
public class OSGiStoreListener implements StoreListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(OSGiStoreListener.class);
    public static final String DEFAULT_DELETE_TOPIC = TOPIC_BASE + DELETE_TOPIC;
    public static final String DEFAULT_CREATE_TOPIC = TOPIC_BASE + ADDED_TOPIC;
    public static final String DEFAULT_UPDATE_TOPIC = TOPIC_BASE + UPDATED_TOPIC;
    @Reference
    protected EventAdmin eventAdmin;
    private static Map<String, String> deleteTopics;
    private static Map<String, String> createTopics;
    private static Map<String, String> updateTopics;

    static {
        Builder<String, String> deleteTopicsBuilder = ImmutableMap.builder();
        deleteTopicsBuilder.put(Security.ADMIN_AUTHORIZABLES, TOPIC_BASE + "authorizables/" + DELETE_TOPIC);
        deleteTopicsBuilder.put(Security.ADMIN_GROUPS, TOPIC_BASE + "groups/" + DELETE_TOPIC);
        deleteTopicsBuilder.put(Security.ADMIN_USERS, TOPIC_BASE + "users/" + DELETE_TOPIC);
        deleteTopicsBuilder.put(Security.ZONE_ADMIN, TOPIC_BASE + "admin/" + DELETE_TOPIC);
        deleteTopicsBuilder.put(Security.ZONE_AUTHORIZABLES, TOPIC_BASE + "authorizables/" + DELETE_TOPIC);
        deleteTopicsBuilder.put(Security.ZONE_CONTENT, TOPIC_BASE + "content/" + DELETE_TOPIC);
        deleteTopics = deleteTopicsBuilder.build();
        Builder<String, String> createTopicsBuilder = ImmutableMap.builder();
        createTopicsBuilder.put(Security.ADMIN_AUTHORIZABLES, TOPIC_BASE + "authorizables/"+ ADDED_TOPIC);
        createTopicsBuilder.put(Security.ADMIN_GROUPS, TOPIC_BASE + "groups/"+ ADDED_TOPIC);
        createTopicsBuilder.put(Security.ADMIN_USERS, TOPIC_BASE + "users/"+ ADDED_TOPIC);
        createTopicsBuilder.put(Security.ZONE_ADMIN, TOPIC_BASE + "admin/"+ ADDED_TOPIC);
        createTopicsBuilder.put(Security.ZONE_AUTHORIZABLES, TOPIC_BASE + "authorizables/"+ ADDED_TOPIC);
        createTopicsBuilder.put(Security.ZONE_CONTENT, TOPIC_BASE + "content/"+ ADDED_TOPIC);
        createTopics = createTopicsBuilder.build();
        Builder<String, String> updateTopicsBuilder = ImmutableMap.builder();
        updateTopicsBuilder.put(Security.ADMIN_AUTHORIZABLES, TOPIC_BASE + "authorizables/"+ UPDATED_TOPIC);
        updateTopicsBuilder.put(Security.ADMIN_GROUPS, TOPIC_BASE + "groups/"+ UPDATED_TOPIC);
        updateTopicsBuilder.put(Security.ADMIN_USERS, TOPIC_BASE + "users/"+ UPDATED_TOPIC);
        updateTopicsBuilder.put(Security.ZONE_ADMIN, TOPIC_BASE + "admin/"+ UPDATED_TOPIC);
        updateTopicsBuilder.put(Security.ZONE_AUTHORIZABLES, TOPIC_BASE + "authorizables/"+ UPDATED_TOPIC);
        updateTopicsBuilder.put(Security.ZONE_CONTENT, TOPIC_BASE + "content/"+ UPDATED_TOPIC);
        updateTopics = updateTopicsBuilder.build();

    }

    /**
     * {@inheritDoc}
     */
    public void onDelete(String zone, String path, String user, String resourceType, Map<String, Object> beforeEvent, String ... attributes) {
        String topic = DEFAULT_DELETE_TOPIC;
        if (deleteTopics.containsKey(zone)) {
            topic = deleteTopics.get(zone);
        }
        postEvent(topic, path, user, resourceType, beforeEvent, attributes);
    }

    /**
     * {@inheritDoc}
     */
    public void onUpdate(String zone, String path, String user, String resourceType, boolean isNew,  Map<String, Object> beforeEvent, String... attributes) {

        String topic = DEFAULT_UPDATE_TOPIC;
        if (isNew) {
            topic = DEFAULT_CREATE_TOPIC;
            if (deleteTopics.containsKey(zone)) {
                topic = createTopics.get(zone);
            }
        } else {
            if (deleteTopics.containsKey(zone)) {
                topic = updateTopics.get(zone);
            }

        }
        postEvent(topic, path, user, resourceType, beforeEvent, attributes);
    }

    /**
     * {@inheritDoc}
     *
     * No event is posted for these actions.
     */
    public void onLogin(String userid, String sessionID) {
        LOGGER.debug("Login {} {} ", userid, sessionID);
    }

    /**
     * {@inheritDoc}
     *
     * No event is posted for these actions.
     */
    public void onLogout(String userid, String sessionID) {
        LOGGER.debug("Logout {} {} ", userid, sessionID);
    }

    private void postEvent(String topic, String path, String user, String resourceType,  Map<String, Object> beforeEvent, String[] attributes) {
        final Dictionary<String, Object> properties = new Hashtable<String, Object>();
        if (attributes != null) {
            for (String attribute : attributes) {
                String[] parts = StringUtils.split(attribute, ":", 2);
                if (parts != null) {
                    if (parts.length == 1) {
                        properties.put(parts[0], parts[0]);
                    } else if (parts.length == 2) {
                        properties.put(parts[0], parts[1]);
                    }
                }
            }
        }
        if (path != null) {
            properties.put(PATH_PROPERTY, path);
        }
        if ( resourceType != null ) {
            properties.put(RESOURCE_TYPE_PROPERTY, resourceType);
        }
        properties.put(USERID_PROPERTY, user);
        if ( beforeEvent != null) {
            properties.put(BEFORE_EVENT_PROPERTY, beforeEvent);
        }
        eventAdmin.postEvent(new Event(topic, properties));

    }

}


/*
 * 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.api.lite;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.FastDateFormat;
import org.sakaiproject.nakamura.api.lite.accesscontrol.AccessDeniedException;
import org.sakaiproject.nakamura.api.lite.content.Content;
import org.sakaiproject.nakamura.api.lite.content.ContentManager;
import org.sakaiproject.nakamura.api.lite.util.Type1UUID;
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.Maps;

/**
 * Utilites for managing storage related to the Sparse Map Content Store.
 */
public class StorageClientUtils {

    /**
     * UTF8 charset constant
     */
    public final static String UTF8 = "UTF-8";
    /** how are numbers encoded, base ? */
    public final static int ENCODING_BASE = 10;
    /**
     * Default hashing algorithm for passwords
     */
    public final static String SECURE_HASH_DIGEST = "SHA-512";

    /**
     * Based on JackRabbit: Jackrabbit uses a subset of 8601 (8601:2000) for
     * their date times.
     */
    public static String ISO8601_JCR_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ";
    @SuppressWarnings("unused")
    private final static FastDateFormat ISO8601_JCR_FORMAT = FastDateFormat.getInstance(
            ISO8601_JCR_PATTERN, TimeZone.getTimeZone("UTC"), Locale.ENGLISH);

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

    /**
     * Concert a storage object to string. In the sparse store everything is
     * stored in as an efficient way as possible. To avoid unnecessary
     * marshalling to and from the store we leave it to the application to
     * marshal properties in and out of the store on demand.
     *
     * @param object
     *            the storage object
     * @return a string representation of the storage object.
     * @deprecated the application code should convert to a string if necessary,
     *             this is not required for storage any more.
     */
    @Deprecated
    public static String toString(Object object) {
        try {
            if (object instanceof String) {
                return (String) object;
            } else if (object == null || object instanceof RemoveProperty) {
                return null;
            } else if (object instanceof byte[]) {
                return new String((byte[]) object, UTF8);
            } else {
                LOGGER.warn("Converting " + object.getClass() + " to String via toString");
                return String.valueOf(object);
            }
        } catch (UnsupportedEncodingException e) {
            return null; // no utf8.. get real!
        }
    }

    /**
     * Get the name of an alternative field for an alternative stream.
     *
     * @param field
     *            the fieldname
     * @param streamId
     *            the alternative stream name
     * @return the alternative field name.
     */
    public static String getAltField(String field, String streamId) {
        if (streamId == null) {
            return field;
        }
        return field + "/" + streamId;
    }

    /**
     * @param objectPath
     * @return true if the objectPath represents a root path.
     */
    public static boolean isRoot(String objectPath) {
        return (objectPath == null) || "/".equals(objectPath) || "".equals(objectPath)
                || (objectPath.indexOf("/") < 0);
    }

    /**
     * @param objectPath
     * @return the parent of the supplied path or the path if already a root.
     */
    public static String getParentObjectPath(String objectPath) {
        if ("/".equals(objectPath)) {
            return "/";
        }
        int i = objectPath.lastIndexOf('/');
        if (i == objectPath.length() - 1) {
            i = objectPath.substring(0, i).lastIndexOf('/');
        }
        String res = objectPath;
        if (i > 0) {
            res = objectPath.substring(0, i);
        } else if (i == 0) {
            return "/";
        }
        return res;
    }

    /**
     * @param objectPath
     * @return the name of the supplied path, normally defiend as the last
     *         element in the path.
     */
    public static String getObjectName(String objectPath) {
        if ( objectPath == null || "".equals(objectPath)) {
            return "";
        }
        if ("/".equals(objectPath)) {
            return "/";
        }
        int i = objectPath.lastIndexOf('/');
        int j = objectPath.length();
        if (i == objectPath.length() - 1) {
            j--;
            i = objectPath.substring(0, i).lastIndexOf('/');
        }
        String res = objectPath;
        if (i >= 0) {
            res = objectPath.substring(i + 1, j);
        }
        return res;

    }

    /**
     * @param naked
     * @return a lower cost insecure hash of the naked value which can be used
     *         for keys as its not too long.
     */
    // TODO: Unit test
    public static String insecureHash(String naked) {
        try {
            return insecureHash(naked.getBytes(UTF8));
        } catch (UnsupportedEncodingException e3) {
            LOGGER.error("no UTF-8 Envoding, get a real JVM, nothing will work here. NPE to come");
            return null;
        }
    }

    public static String insecureHash(byte[] b) {
        try {
            MessageDigest md;
            try {
                md = MessageDigest.getInstance("SHA-1");
            } catch (NoSuchAlgorithmException e1) {
                try {
                    md = MessageDigest.getInstance("MD5");
                } catch (NoSuchAlgorithmException e2) {
                    LOGGER.error("You have no Message Digest Algorightms intalled in this JVM, secure Hashes are not availalbe, encoding bytes :"
                            + e2.getMessage());
                    return encode(StringUtils.leftPad((new String(b,"UTF-8")), 10, '_').getBytes(UTF8));
                }
            }
            byte[] bytes = md.digest(b);
            return encode(bytes);
        } catch (UnsupportedEncodingException e3) {
            LOGGER.error("no UTF-8 Envoding, get a real JVM, nothing will work here. NPE to come");
            return null;
        }
    }

    /**
     * @param password
     * @return as secure hash of the supplied password, unsuitable for keys as
     *         its too long.
     */
    public static String secureHash(String password) {
        try {
            MessageDigest md;
            try {
                md = MessageDigest.getInstance(SECURE_HASH_DIGEST);
            } catch (NoSuchAlgorithmException e) {
                try {
                    md = MessageDigest.getInstance("SHA-1");
                } catch (NoSuchAlgorithmException e1) {
                    try {
                        md = MessageDigest.getInstance("MD5");
                    } catch (NoSuchAlgorithmException e2) {
                        LOGGER.error("You have no Message Digest Algorightms intalled in this JVM, secure Hashes are not availalbe, encoding bytes :"
                                + e2.getMessage());
                        return encode(StringUtils.leftPad(password, 10, '_').getBytes(UTF8));
                    }
                }
            }
            byte[] bytes = md.digest(password.getBytes(UTF8));
            return encode(bytes);
        } catch (UnsupportedEncodingException e3) {
            LOGGER.error("no UTF-8 Envoding, get a real JVM, nothing will work here. NPE to come");
            return null;
        }
    }

    /**
     * Generate an encoded array of chars using as few chars as possible
     *
     * @param hash
     *            the hash to encode
     * @return
     */
    public static String encode(byte[] hash) {
        return Base64.encodeBase64URLSafeString(hash);
    }

    /**
     * Converts to an Immutable map, with keys that are in the filter not
     * transfered. Nested maps are also transfered.
     *
     * @param <K> the type of the key
     * @param <V> the type of the value
     * @param source a map of values to start with
     * @param modified a map to oveeride values in source
     * @param include if not null, only include these keys in the returned map
     * @param exclude if not null, exclude these keys from the returned map
     * @return a map with the modifications applied and filtered by the includes and excludes
     */
    @SuppressWarnings("unchecked")
    public static <K, V> Map<K, V> getFilterMap(Map<K, V> source, Map<K, V> modified, Set<K> include, Set<K> exclude, boolean includingRemoveProperties ) {
       if ((modified == null || modified.size() == 0) && (include == null) && ( exclude == null || exclude.size() == 0)) {
           if ( source instanceof ImmutableMap ) {
               return source;
           } else {
               return ImmutableMap.copyOf(source);
           }
        }

        Builder<K, V> filteredMap = new ImmutableMap.Builder<K, V>();
        for (Entry<K, V> e : source.entrySet()) {
            K k = e.getKey();
            if (include == null || include.contains(k)) {
                if (exclude == null || !exclude.contains(k)) {
                    if ( modified != null && modified.containsKey(k) ) {
                        V o = modified.get(k);
                        if (o instanceof Map) {
                            filteredMap.put(k,
                                    (V) getFilterMap((Map<K, V>) o, null, null, exclude, includingRemoveProperties));
                        } else if ( includingRemoveProperties ) {
                            filteredMap.put(k, o);
                        } else if ( !(o instanceof RemoveProperty) ) {
                            filteredMap.put(k, o);
                        }
                    } else {
                        Object o = e.getValue();
                        if (o instanceof Map) {
                            filteredMap.put(k,
                                    (V) getFilterMap((Map<K, V>) e.getValue(), null, null, exclude, includingRemoveProperties));
                        } else {
                            filteredMap.put(k, e.getValue());
                        }
                    }
                }
            }
        }
        if ( modified != null ) {
            // process additions
            for (Entry<K, V> e : modified.entrySet()) {
                K k = e.getKey();
                if ( !source.containsKey(k)) {
                    V v = e.getValue();
                    if ( !(v instanceof RemoveProperty) && v != null ) {
                        filteredMap.put(k,v);
                    }
                }
            }
        }
        return filteredMap.build();
    }

    /**
     * Converts a map into Map or byte[] values with String keys. No control
     * over depth of nesting. Keys in the filter set are not transfered
     * Resulting map is mutable.
     *
     * @param source a map of values to modify
     * @param filter a map of values to remove by key from source
     * @return the map less any keys from filter
     */
    @SuppressWarnings("unchecked")
    public static Map<String, Object> getFilteredAndEcodedMap(Map<String, Object> source,
            Set<String> filter) {
        Map<String, Object> filteredMap = Maps.newHashMap();
        for (Entry<String, Object> e : source.entrySet()) {
            if (!filter.contains(e.getKey())) {
                Object o = e.getValue();
                if (o instanceof Map) {
                    filteredMap.put(e.getKey(),
                            getFilteredAndEcodedMap((Map<String, Object>) o, filter));
                } else {
                    filteredMap.put(e.getKey(), o);
                }
            }
        }
        return filteredMap;
    }

    /**
     * @return a UUID, compact encoded, suitable for use in URLs
     */
    public static String getUuid() {
        return StorageClientUtils.encode(Type1UUID.next());
    }

    /**
     * @param object
     * @return the store object as an int.
     */
    public static int toInt(Object object) {
        if (object instanceof Integer) {
            return ((Integer) object).intValue();
        } else if (object == null || object instanceof RemoveProperty) {
            return 0;
        }
        return Integer.parseInt(toString(object), ENCODING_BASE);
    }

    /**
     * @param object
     * @return the store object as a Long
     */
    public static long toLong(Object object) {
        if (object instanceof Long) {
            return ((Long) object).longValue();
        } else if (object == null || object instanceof RemoveProperty) {
            return 0;
        }
        return Long.parseLong(toString(object), ENCODING_BASE);
    }

    /**
     * @param path
     * @param child
     * @return create a new path by appending the child to the parent path.
     */
    public static String newPath(String path, String child) {
        if (!path.endsWith("/")) {
            if (!child.startsWith("/")) {
                return path + "/" + child;
            } else {
                return path + child;

            }
        } else {
            if (!child.startsWith("/")) {
                return path + child;
            } else {
                return path + child.substring(1);
            }
        }
    }

    /**
     * @param <T>
     * @param setting
     * @param defaultValue
     * @return gets a setting of type <T> usign the default value if null.
     */
    @SuppressWarnings("unchecked")
    public static <T> T getSetting(Object setting, T defaultValue) {
        if (setting != null && defaultValue != null) {
            if (defaultValue.getClass().isAssignableFrom(setting.getClass())) {
                return (T) setting;
            }
            // handle conversions
            if ( defaultValue instanceof Long ) {
                return (T) new Long(String.valueOf(setting));
            } else if ( defaultValue instanceof Integer ) {
                return (T) new Integer(String.valueOf(setting));
            } else if (defaultValue instanceof Boolean ) {
                return (T) Boolean.valueOf(String.valueOf(setting));
            } else if ( defaultValue instanceof Double ) {
                return (T) new Double(String.valueOf(setting));
            } else if ( defaultValue instanceof String[] ) {
                return (T) StringUtils.split(String.valueOf(setting), ',');
            }
            return (T) setting;
        }
        return defaultValue;
    }

    /**
     * @param id
     * @return perform a 3 level shard of the path using base^^2 as the width of
     *         the shard where base is the cardinality of the encoding of the
     *         ID.
     */
    // TODO: There is no reason to use this method in sparse (or very little),
    // check usage.
    // For instance the SparsePrincipal uses it.
    public static String shardPath(String id) {
        String hash = insecureHash(id);
        return hash.substring(0, 2) + "/" + hash.substring(2, 4) + "/" + hash.substring(4, 6) + "/"
                + id;
    }

    /**
     * @param string
     * @return an escaped version of the supplied string suitable for using in a
     *         stored array. The implemenation is not that efficient.
     */
    // TODO: Unit test
    public static String arrayEscape(String string) {
        string = string.replaceAll("%", "%1");
        string = string.replaceAll(",", "%2");
        return string;
    }

    /**
     * @param string
     * @return reverse the arrayEscape operation.
     */
    // TODO: Unit test
    public static String arrayUnEscape(String string) {
        string = string.replaceAll("%2", ",");
        string = string.replaceAll("%1", "%");
        return string;
    }

    /**
     * @param object
     * @return null or the store object converted to a string[]
     * @deprecated no need to convert, just get the String[] object directly out of the store.
     */
    @Deprecated
    public static String[] toStringArray(Object object) {
        if ( object instanceof String[] ) {
            return (String[]) object;
        } else if (object == null) {
                return null;
        } else {
            String[] v = StringUtils.split(StorageClientUtils.toString(object), ',');
            for (int i = 0; i < v.length; i++) {
                v[i] = StorageClientUtils.arrayUnEscape(v[i]);
            }
            return v;
        }
    }

    /**
     * @param parameterValues
     * @return ensure that a string[] is not null after conversion from store.
     *         Use this to make iterators easier on stored arrays.
     */
    // TODO: Unit test
    public static String[] nonNullStringArray(String[] parameterValues) {
        if (parameterValues == null) {
            return new String[0];
        }
        return parameterValues;
    }

    /**
     * Adapt an object to a session. I haven't used typing here becuase I don't
     * want to bind to the Jars in question and create dependencies.
     *
     * @param source
     *            the input object that the method
     * @return
     */
    public static Session adaptToSession(Object source) {
        if (source instanceof SessionAdaptable) {
            return ((SessionAdaptable) source).getSession();
        } else {
            // assume this is a JCR session of someform, in which case there
            // should be a SparseUserManager
            Object userManager = safeMethod(source, "getUserManager", new Object[0], new Class[0]);
            if (userManager != null) {
                return (Session) safeMethod(userManager, "getSession", new Object[0], new Class[0]);
            }
            return null;
        }
    }

    /**
     * Make the method on the target object accessible and then invoke it.
     * @param target the object with the method to invoke
     * @param methodName the name of the method to invoke
     * @param args the arguments to pass to the invoked method
     * @param argsTypes the types of the arguments being passed to the method
     * @return
     */
    private static Object safeMethod(Object target, String methodName, Object[] args,
            @SuppressWarnings("rawtypes") Class[] argsTypes) {
        if (target != null) {
            try {
                Method m = target.getClass().getMethod(methodName, argsTypes);
                if (!m.isAccessible()) {
                    m.setAccessible(true);
                }
                return m.invoke(target, args);
            } catch (Throwable e) {
                LOGGER.info("Failed to invoke method " + methodName + " " + target, e);
            }
        }
        return null;
    }

    /**
     * Delete an entire tree starting from the deepest part of the tree and
     * working back up. Will stop the moment a permission denied is encountered
     * either for read or for delete.
     *
     * @param contentManager
     * @param path
     * @throws AccessDeniedException
     * @throws StorageClientException
     */
    public static void deleteTree(ContentManager contentManager, String path)
            throws AccessDeniedException, StorageClientException {
        Content content = contentManager.get(path);
        if (content != null) {
            for (String childPath : content.listChildPaths()) {
                deleteTree(contentManager, childPath);
            }
        }
        contentManager.delete(path);
    }

    public static String getInternalUuid() {
        return getUuid()+"+"; // URL safe base 64 does not use + chars
    }

    public static void copyTree(ContentManager contentManager, String sourcePath, String destPath,
            boolean withStreams) throws StorageClientException, AccessDeniedException, IOException {
        contentManager.copy(sourcePath, destPath, withStreams);
        LOGGER.info("Copied {} to {} ", sourcePath, destPath );
        Content content = contentManager.get(sourcePath);
        if (content != null) {
            for (String childPath : content.listChildPaths()) {
                String name = StorageClientUtils.getObjectName(childPath);
                String childSourcePath = StorageClientUtils.newPath(sourcePath, name);
                String childDestPath = StorageClientUtils.newPath(destPath, name);
                copyTree(contentManager, childSourcePath, childDestPath, withStreams);
            }
        }
    }

    public static void dumpTree(Content content) {
        if ( content != null ) {
            LOGGER.info("Path {} ",content.getPath());
            for ( Content child : content.listChildren() ) {
                dumpTree(child);
            }
        }
    }

}


/*
 * 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;

import java.util.HashMap;

import org.apache.commons.collections.keyvalue.MultiKey;

/**
 * Convenience class that can be used as a starting point for things that need to
 * accumulate values into four levels. Extends a standard map, but uses the MultiKey
 * class to create the key.
 */
public abstract class HashMap4Template<K1,K2,K3,K4,V> extends HashMap<MultiKey,V>
{
    /**
     * Method to get existing [key1,key2,key3,key4] value or create new one if it's
     * absent. Needs implementation of create(key1,key2,key3,key4) in order to work
     */
    public V getOrCreate(K1 key1, K2 key2, K3 key3, K4 key4)
    {
        // already got it?
        MultiKey multi_key = new MultiKey(key1, key2, key3, key4);
        if (containsKey(multi_key))
            return get(multi_key);

        // if not, create and add it
        V result = create(key1, key2, key3, key4);
        put(multi_key, result);
        return result;
    }

    /** Template method to enable getOrCreate() */
    protected abstract V create(K1 key1, K2 key2, K3 key3, K4 key4);

    public void put(K1 key1, K2 key2, K3 key3, K4 key4, V value)
    {
        put(new MultiKey(key1, key2, key3, key4), value);
    }

    public V get(K1 key1, K2 key2, K3 key3, K4 key4)
    {
        return get(new MultiKey(key1, key2, key3, key4));
    }
}


/**
 * Copyright 2012-2013 Florian Schmaus
 *
 * 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.keepalive;

import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

import org.jivesoftware.smack.Connection;
import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketCollector;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.ping.PingFailedListener;
import org.jivesoftware.smack.ping.packet.Ping;
import org.xmpp.packet.Packet;

/**
 * Using an implementation of XMPP Ping (XEP-0199).
 * This class provides keepalive functionality with the server that will
 * periodically "ping" the server to maintain and/or verify that the connection
 * still exists.
 *

 * The ping is done at the application level and is therefore protocol agnostic.
 * It will thus work for both standard TCP connections as well as BOSH or any
 * other transport protocol. It will also work regardless of whether the server
 * supports the Ping extension, since an error response to the ping serves the
 * same purpose as a pong.
 *
 * @author Florian Schmaus
 */
public class KeepAliveManager {
    private static Map<Connection, KeepAliveManager> instances = Collections
            .synchronizedMap(new WeakHashMap<Connection, KeepAliveManager>());
    private static volatile ScheduledExecutorService periodicPingExecutorService;

    static {
        if (SmackConfiguration.getKeepAliveInterval() > 0) {
            Connection
                    .addConnectionCreationListener(new ConnectionCreationListener() {
                        public void connectionCreated(Connection connection) {
                            getInstanceFor(connection);
                        }
                    });
        }
    }

    private WeakReference<Connection> weakRefConnection;
    private long pingInterval = SmackConfiguration.getKeepAliveInterval();
    private Set<PingFailedListener> pingFailedListeners = Collections
            .synchronizedSet(new HashSet<PingFailedListener>());
    private volatile ScheduledFuture<?> periodicPingTask;
    private volatile long lastSuccessfulContact = -1;

    /**
     * Retrieves a {@link KeepAliveManager} for the specified {@link Connection}
     * , creating one if it doesn't already exist.
     *
     * @param connection
     *            The connection the manager is attached to.
     * @return The new or existing manager.
     */
    public synchronized static KeepAliveManager getInstanceFor(
            Connection connection) {
        KeepAliveManager pingManager = instances.get(connection);

        if (pingManager == null) {
            pingManager = new KeepAliveManager(connection);
            instances.put(connection, pingManager);
        }
        return pingManager;
    }

    /*
     * Start the executor service if it hasn't been started yet.
     */
    private synchronized static void enableExecutorService() {
        if (periodicPingExecutorService == null) {
            periodicPingExecutorService = new ScheduledThreadPoolExecutor(1,
                    new ThreadFactory() {
                        @Override
                        public Thread newThread(Runnable runnable) {
                            Thread pingThread = new Thread(runnable,
                                    "Smack Keepalive");
                            pingThread.setDaemon(true);
                            return pingThread;
                        }
                    });
        }
    }

    /*
     * Stop the executor service if all monitored connections are disconnected.
     */
    private synchronized static void handleDisconnect(Connection con) {
        if (periodicPingExecutorService != null) {
            instances.remove(con);

            if (instances.isEmpty()) {
                periodicPingExecutorService.shutdownNow();
                periodicPingExecutorService = null;
            }
        }
    }

    private KeepAliveManager(Connection connection) {
        weakRefConnection = new WeakReference<Connection>(connection);

        connection.addConnectionListener(new ConnectionListener() {

            @Override
            public void connectionClosed() {
                stopPingServerTask();
                Connection connection = weakRefConnection.get();
                handleDisconnect(connection);
            }

            @Override
            public void connectionClosedOnError(Exception arg0) {
                stopPingServerTask();
                Connection connection = weakRefConnection.get();
                handleDisconnect(connection);
            }

            @Override
            public void reconnectionSuccessful() {
                handleConnect();
                schedulePingServerTask();
            }

            @Override
            public void reconnectingIn(int seconds) {
            }

            @Override
            public void reconnectionFailed(Exception e) {
            }
        });

        instances.put(connection, this);
        schedulePingServerTask();
        handleConnect();
    }

    /*
     * Call after every connection to add the packet listener.
     */
    private void handleConnect() {
        Connection connection = weakRefConnection.get();
        // Listen for all incoming packets and reset the scheduled ping whenever
        // one arrives.
        connection.addPacketListener(new PacketListener() {

            @Override
            public void processPacket(Packet packet) {
                // reschedule the ping based on this last server contact
                lastSuccessfulContact = System.currentTimeMillis();
                schedulePingServerTask();
            }
        }, null);
    }

    /**
     * Sets the ping interval.
     *
     * @param pingInterval
     *            The new ping time interval in milliseconds.
     */
    public void setPingInterval(long newPingInterval) {
        if (pingInterval == newPingInterval)
            return;

        // Enable the executor service
        if (newPingInterval > 0)
            enableExecutorService();

        pingInterval = newPingInterval;

        if (pingInterval < 0) {
            stopPinging();
        } else {
            schedulePingServerTask();
        }
    }

    /**
     * Stops pinging the server. This cannot stop a ping that has already
     * started, but will prevent another from being triggered.
     *

     * To restart, call {@link #setPingInterval(long)}.
     */
    public void stopPinging() {
        pingInterval = -1;
        stopPingServerTask();
    }

    /**
     * Gets the ping interval.
     *
     * @return The ping interval in milliseconds.
     */
    public long getPingInterval() {
        return pingInterval;
    }

    /**
     * Add listener for notification when a server ping fails.
     *
     *

     * Please note that this doesn't necessarily mean that the connection is
     * lost, a slow to respond server could also cause a failure due to taking
     * too long to respond and thus causing a reply timeout.
     *
     * @param listener
     *            The listener to be called
     */
    public void addPingFailedListener(PingFailedListener listener) {
        pingFailedListeners.add(listener);
    }

    /**
     * Remove the listener.
     *
     * @param listener
     *            The listener to be removed.
     */
    public void removePingFailedListener(PingFailedListener listener) {
        pingFailedListeners.remove(listener);
    }

    /**
     * Returns the elapsed time (in milliseconds) since the last successful
     * contact with the server (i.e. the last time any message was received).
     *

     * Note: Result is -1 if no message has been received since manager
     * was created and 0 if the elapsed time is negative due to a clock reset.
     *
     * @return Elapsed time since last message was received.
     */
    public long getTimeSinceLastContact() {
        if (lastSuccessfulContact == -1)
            return lastSuccessfulContact;
        long delta = System.currentTimeMillis() - lastSuccessfulContact;

        return (delta < 0) ? 0 : delta;
    }

    /**
     * Cancels any existing periodic ping task if there is one and schedules a
     * new ping task if pingInterval is greater then zero.
     *
     * This is designed so only one executor is used for scheduling all pings on
     * all connections. This results in only 1 thread used for pinging.
     */
    private synchronized void schedulePingServerTask() {
        enableExecutorService();
        stopPingServerTask();

        if (pingInterval > 0) {
            periodicPingTask = periodicPingExecutorService.schedule(
                    new Runnable() {
                        @Override
                        public void run() {
                            Ping ping = new Ping();
                            PacketFilter responseFilter = new PacketIDFilter(
                                    ping.getID());
                            Connection connection = weakRefConnection.get();
                            final PacketCollector response = pingFailedListeners
                                    .isEmpty() ? null : connection
                                    .createPacketCollector(responseFilter);
                            connection.sendPacket(ping);

                            if (response != null) {
                                // Schedule a collector for the ping reply,
                                // notify listeners if none is received.
                                periodicPingExecutorService.schedule(
                                        new Runnable() {
                                            @Override
                                            public void run() {
                                                Packet result = response
                                                        .nextResult(1);

                                                // Stop queuing results
                                                response.cancel();

                                                // The actual result of the
                                                // reply can be ignored since we
                                                // only care if we actually got
                                                // one.
                                                if (result == null) {
                                                    for (PingFailedListener listener : pingFailedListeners) {
                                                        listener.pingFailed();
                                                    }
                                                }
                                            }
                                        }, SmackConfiguration
                                                .getPacketReplyTimeout(),
                                        TimeUnit.MILLISECONDS);
                            }
                        }
                    }, getPingInterval(), TimeUnit.MILLISECONDS);
        }
    }

    private void stopPingServerTask() {
        if (periodicPingTask != null) {
            periodicPingTask.cancel(true);
            periodicPingTask = null;
        }
    }
}


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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.pool.PoolableObjectFactory;

/**
 * Abstract ThreadObjectFactory. Override to initialise newly created threads.
 */
public abstract class ThreadObjectFactory implements PoolableObjectFactory {
   /**
    * Logger for this class
    */
   private final static Log log = LogFactory.getLog(ThreadObjectFactory.class);

   /* (non-Javadoc)
    * @see org.apache.commons.pool.PoolableObjectFactory#makeObject()
    */
   public final Object makeObject() {
      log.debug(" makeObject...");
      WorkerThread thread = new WorkerThread();
      initialiseThread(thread);
      thread.start();
      return thread;
   }

   public abstract void initialiseThread(Thread thread);

   /* (non-Javadoc)
    * @see org.apache.commons.pool.PoolableObjectFactory#destroyObject(java.lang.Object)
    */
   public final void destroyObject(Object obj) {
      log.debug(" !!! destroyObject... !!!" + obj);
      if (obj instanceof WorkerThread) {
         WorkerThread rt = (WorkerThread) obj;
         rt.setStopped();
      }
   }

   /* (non-Javadoc)
    * @see org.apache.commons.pool.PoolableObjectFactory#validateObject(java.lang.Object)
    */
   public final boolean validateObject(Object obj) {
      log.debug(" validateObject..." + obj);
      if (obj instanceof WorkerThread) {
         WorkerThread rt = (WorkerThread) obj;
         if(!rt.isDone()) {  //if the thread is running the previous task, get another one.
            return false;
         }
         if (rt.isRunning()) {
            if (rt.getThreadGroup() == null) {
               return false;
            }
            return true;
         }
      }
      return true;
   }

   /* (non-Javadoc)
    * @see org.apache.commons.pool.PoolableObjectFactory#activateObject(java.lang.Object)
    */
   public final void activateObject(Object obj) {
      log.debug(" activateObject...");
      activateThread((Thread)obj);
   }

   public abstract void activateThread(Thread thread);

   /* (non-Javadoc)
    * @see org.apache.commons.pool.PoolableObjectFactory#passivateObject(java.lang.Object)
    */
   public final void passivateObject(Object obj) {
      log.debug(" passivateObject..." + obj);
      passivateThread((Thread)obj);
   }

   public abstract void passivateThread(Thread thread);

}


/*
 * 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.memory.ehcache;

import org.sakaiproject.nakamura.api.memory.Cache;
import org.sakaiproject.nakamura.api.memory.ThreadBound;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

/**
 *
 */
public class MapCacheImpl<V> extends HashMap<String, V>implements Cache<V>  {

  /**
   *
   */
  private static final long serialVersionUID = -5400056532743570231L;

  /**
   * {@inheritDoc}
   * @see org.sakaiproject.nakamura.api.memory.Cache#containsKey(java.lang.String)
   */
  public boolean containsKey(String key) {
    return super.containsKey(key);
 }

  /**
   * {@inheritDoc}
   * @see org.sakaiproject.nakamura.api.memory.Cache#get(java.lang.String)
   */
  public V get(String key) {
    return super.get(key);
  }

  /**
   * {@inheritDoc}
   * @see org.sakaiproject.nakamura.api.memory.Cache#remove(java.lang.String)
   */
  public void remove(String key) {
    V o = super.remove(key);
    if ( o instanceof ThreadBound ) {
      ((ThreadBound) o).unbind();
    }
  }

  /**
   * {@inheritDoc}
   * @see java.util.HashMap#clear()
   */
  @Override
  public void clear() {
    for ( String k : super.keySet() ) {
      Object o = get(k);
      if( o instanceof ThreadBound ) {
        ((ThreadBound) o).unbind();
      }
    }
    super.clear();
  }

  /**
   * {@inheritDoc}
   * @see org.sakaiproject.nakamura.api.memory.Cache#removeChildren(java.lang.String)
   */
  public void removeChildren(String key) {
    super.remove(key);
    if ( !key.endsWith("/") ) {
      key = key + "/";
    }
    Set<String> keys = super.keySet();
    for ( String k : keys) {
      if ( (k).startsWith(key) ) {
        super.remove(k);
      }
    }
  }

  /**
   * {@inheritDoc}
   * @see org.sakaiproject.nakamura.api.memory.Cache#list()
   */
  public List<V> list() {
    return new ArrayList<V>(super.values());
  }

}


/* Copyright 2014 Sven van der Meer <vdmeer.sven@mykolab.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 de.vandermeer.execs.options;

import java.util.HashSet;
import java.util.Set;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

/**
 * CLI implementation for applications.
 *
 * @author     Sven van der Meer <vdmeer.sven@mykolab.com>
 * @version    v0.4.0 build 170413 (13-Apr-17) for Java 1.8
 * @since      v0.0.1
 */
public class ExecS_CliParser {

	/** Parsing options. */
	protected Options options;

	/** Parsed command line. */
	protected CommandLine cmdLine;

	/** Set of options already added, used. */
	protected final Set<String> usedOptions;

	/**
	 * Returns a new CLI parser.
	 */
	public ExecS_CliParser(){
		this.options = new Options();
		this.usedOptions = new HashSet<>();
	}

	/**
	 * Adds a CLI option to the parser.
	 * @param option new CLI option, ignored if the option is null or getOption() on the option is null
	 * @throws IllegalArgumentException if the option uses short/long options already in use
	 * @return self to allow for chaining
	 */
	public ExecS_CliParser addOption(ApplicationOption<?> option){
		if(option!=null && option.getCliOption()!=null){
			this._addOption(option.getCliOption());
		}
		return this;
	}

	/**
	 * Adds all given option ignoring null elements.
	 * @param options iterable of options
	 * @return self to allow for chaining
	 */
	public ExecS_CliParser addAllOptions(Iterable<ApplicationOption<?>> options){
		if(options!=null){
			for(ApplicationOption<?> option : options){
				this.addOption(option);
			}
		}
		return this;
	}

	/**
	 * Adds all given option ignoring null elements.
	 * @param options array of options
	 * @return self to allow for chaining
	 */
	public ExecS_CliParser addAllOptions(ApplicationOption<?>[] options){
		if(options!=null){
			for(ApplicationOption<?> option : options){
				this.addOption(option);
			}
		}
		return this;
	}

	/**
	 * Adds a CLI option to the parser.
	 * @param option new CLI option, ignored if the option is null or getOption() on the option is null
	 * @throws IllegalArgumentException if the option uses short/long options already in use
	 * @return self to allow for chaining
	 * @throws IllegalArgumentException if short or long option was in use already
	 */
	public ExecS_CliParser addOption(Option option){
		if(option!=null){
			this._addOption(option);
		}
		return this;
	}

	/**
	 * Adds a CLI option to the parser.
	 * @param option new CLI option, ignored if the option is null or getOption() on the option is null
	 * @throws IllegalArgumentException if short or long option was in use already
	 */
	protected void _addOption(Option option){
		if(option==null){
			return;
		}

		if(this.usedOptions.contains(option.getOpt())){
			throw new IllegalArgumentException("ExecS Cli: short option <" + option.getOpt() + "> already in use");
		}
		if(this.usedOptions.contains(option.getLongOpt())){
			throw new IllegalArgumentException("ExecS Cli: long option <" + option.getLongOpt() + "> already in use");
		}
		this.options.addOption(option);
		if(option.getOpt()!=null){
			this.usedOptions.add(option.getOpt());
		}
		if(option.getLongOpt()!=null){
			this.usedOptions.add(option.getLongOpt());
		}
	}

	/**
	 * Tests if an option is already added to the parser.
	 * @param option the option to test for
	 * @return false if option was null or n short and long version of this option is used, true otherwise
	 */
	public boolean hasOption(Option option){
		if(option==null){
			return false;
		}
		if(this.usedOptions.contains(option.getOpt())){
			return true;
		}
		if(this.usedOptions.contains(option.getLongOpt())){
			return true;
		}
		return false;
	}

	/**
	 * Parses command line arguments and fills set options.
	 * @param args command line arguments
	 * @return null on success, an exception with message on parsing error
	 */
	public ParseException parse(String[] args){
		ParseException ret = null;
		CommandLineParser parser = new DefaultParser();
		try{
			this.cmdLine = parser.parse(this.options, args);
		}
		catch(ParseException pe){
			ret = pe;
		}
		return ret;
	}

	/**
	 * Prints a usage screen based on set options.
	 * @param appName name of the application to be used for the usage screen
	 */
	public void usage(String appName){
		HelpFormatter formatter=new HelpFormatter();
		formatter.printHelp(appName, null, this.options, null, false);
	}

	/**
	 * Parses command line arguments for a given CLI parser.
	 * @param args command line arguments to parse
	 * @param cli CLI object with options to parse for
	 * @param appName application name for error messages
	 * @return 0 on success, -1 on error with error being logged
	 */
	static int doParse(String[] args, ExecS_CliParser cli, String appName){
		Exception err = cli.parse(args);
		if(err!=null){
			System.err.println(appName + ": error parsing command line -> " + err.getMessage());
			return -1;
		}
		return 0;
	}

	/**
	 * Returns the command line.
	 * @return command line with arguments if parsed, null otherwise
	 */
	public CommandLine getCommandLine(){
		return this.cmdLine;
	}
}