content: /***************************************************************
  Copyright (C) 2009-2013
  by 52 North Initiative for Geospatial Open Source Software GmbH
  
  Contact: Andreas Wytzisk
  52 North Initiative for Geospatial Open Source Software GmbH
  Martin-Luther-King-Weg 24
  48155 Muenster, Germany
  info@52north.org
  
  This program is free software; you can redistribute and/or modify it under 
  the terms of the GNU General Public License version 2 as published by the 
  Free Software Foundation.
  
  This program is distributed WITHOUT ANY WARRANTY; even without the implied
  WARRANTY OF MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  General Public License for more details.
  
  You should have received a copy of the GNU General Public License along with
  this program (see gnu-gpl v2.txt). If not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA or
  visit the Free Software Foundation web page, http://www.fsf.org.
  
   ***************************************************************/
  package org.n52.wps.ags.workspace;
  
  import java.io.IOException;
  import java.net.UnknownHostException;
  import java.util.ArrayList;
  import java.util.List;
  
  import org.slf4j.Logger;
  
  import com.esri.arcgis.interop.AutomationException;
  import com.esri.arcgis.server.IServerContext;
  import com.esri.arcgis.server.IServerObjectManager;
  import com.esri.arcgis.server.ServerConnection;
  import com.esri.arcgis.system.ServerInitializer;
  import com.esri.arcgis.system.VarArray;
  
  /**
   * Factory class for server contexts. This class caches available contexts
   * and initializes new contexts if needed.
   * 
   * @author matthes rieke
   *
   */
  public class ServerContextFactory {
  	
  	private static final Object mutex = new Object();
  	private static Logger LOGGER = LoggerFactory.getLogger(ServerContextFactory.class);
  	private static List<LockedServerContext> contexts = new ArrayList<LockedServerContext>();
  
  	
  	private static IServerContext initializeContext() throws IOException {
  		if (LOGGER.isInfoEnabled()) {
  			if (System.getProperty("JINTEGRA_NATIVE_MODE") == null) {
  				LOGGER.info("Running geoprocessor in DCOM Mode");
  			} else {
  				LOGGER.info("Running geoprocessor in Native Mode");
  			} 
  		}
  
  		try {
  			if (LOGGER.isInfoEnabled())
  				LOGGER.info("Getting AGS connection object ...");
  			ServerConnection connection = getAGSConnection();
  			IServerObjectManager som = connection.getServerObjectManager();
  			IServerContext context = som.createServerContext("", "");
  			return context;
  		}
  		catch (AutomationException ae){
  			LOGGER.error("Caught J-Integra AutomationException: " + ae.getMessage() + "\n");
  			throw new IOException(ae);
  		}
  
  		catch (IOException e){
  			LOGGER.error("Caught IOException: " + e.getMessage() + "\n");
  			throw e;
  		}
  	}
  	
  	private static final ServerConnection getAGSConnection() throws IOException {
  		if (LOGGER.isInfoEnabled())
  			LOGGER.info("initializing server ...");
  		AGSPropertiesWrapper agsProps = AGSPropertiesWrapper.getInstance();
  		ServerInitializer serverInitializer = new ServerInitializer();
  		serverInitializer.initializeServer(agsProps.getDomain(), agsProps.getUser(), agsProps.getPass());
  		
  		ServerConnection connection = null;
  		try {
  			connection = new ServerConnection();
  			connection.connect(agsProps.getIP());
  			if (LOGGER.isInfoEnabled())
  				LOGGER.info("server initialized!");
  
  		} catch (UnknownHostException e) {
  			LOGGER.error("UnknownHostException - Could not connect to AGS host " + agsProps.getDomain() + " with user " + agsProps.getUser());
  			throw new IOException("Error connecting to ArcGIS Server.");
  		} catch (IOException e) {
  			LOGGER.error("IOException - Could not connect to AGS host " + agsProps.getDomain() + " with user " + agsProps.getUser());
  			LOGGER.info("Please check firewall setup! - and maybe the folder permissions, too");
  			throw new IOException("Error connecting to ArcGIS Server.");
  		}
  
  		return connection;
  	}
  
  	/**
  	 * Returns a {@link LockedServerContext} object, either
  	 * cached or newly created.
  	 */
  	public static LockedServerContext retrieveContext() throws IOException {
  		LockedServerContext result = null;
  		List<LockedServerContext> toBeRemoved = new ArrayList<LockedServerContext>();
  		
  		synchronized (mutex) {
  			for (LockedServerContext lsc : contexts) {
  				if (lsc.lockAndGet()) {
  					if (!ensureLiveness(lsc)) {
  						toBeRemoved.add(lsc);
  						continue;
  					}
  					result = lsc;
  					break;
  				}
  			}
  			
  			if (result == null) {
  				result = new LockedServerContext(initializeContext());
  				result.lockAndGet();
  				contexts.add(result);
  			}
  			
  			if (!toBeRemoved.isEmpty()) {
  				contexts.removeAll(toBeRemoved);
  			}
  		}
  		
  		return result;
  	}
  
  	/**
  	 * Checks if a context is still alive by
  	 * trying to create a Arcobject instance.
  	 */
  	private static boolean ensureLiveness(LockedServerContext lsc) {
  		try {
  			Object object = lsc.getContext().createObject(VarArray.getClsid());
  			return object != null && object instanceof VarArray;
  		}
  		catch (Exception e) {
  			return false;
  		}
  	}
  
  	/**
  	 * Return the context and make it usable for other
  	 * processes.
  	 */
  	public static void returnContext(LockedServerContext context) {
  		context.releaseLock();
  	}
  
  	/**
  	 * Release all cached contexts.
  	 */
  	public static void releaseAllCachedContexts() throws IOException {
  		synchronized (mutex) {
  			for (LockedServerContext lsc : contexts) {
  				lsc.releaseContext();
  			}
  		}
  	}
  	
  	/**
  	 * Class for providing mutual exclusion access to an
  	 * {@link IServerContext} object.
  	 * 
  	 * @author matthes rieke
  	 *
  	 */
  	public static class LockedServerContext {
  		
  		IServerContext context;
  		private boolean locked;
  		
  		public LockedServerContext(IServerContext c) {
  			context = c;
  		}
  		
  		public synchronized boolean lockAndGet() {
  			if (this.locked) return false;
  			
  			this.locked = true;
  			return true;
  		}
  		
  		public synchronized void releaseLock() {
  			this.locked = false;
  		}
  
  		public IServerContext getContext() {
  			return context;
  		}
  		
  		private void releaseContext() throws IOException {
  			context.releaseContext();
  		}
  		
  	}
  }
  