/*
 * Copyright (c) OSGi Alliance (2004, 2017). 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.osgi.service.application;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;

import org.osgi.framework.Constants;

/**
 * ApplicationHandle is an OSGi service interface which represents an instance
 * of an application. It provides the functionality to query and manipulate the
 * lifecycle state of the represented application instance. It defines constants
 * for the lifecycle states.
 * 
 * @author $Id: 18dc483a492dca5c28af6137ce93606c32d0ab7b $
 */
public abstract class ApplicationHandle {
	/*
	 * NOTE: An implementor may also choose to replace this class in their
	 * distribution with a class that directly interfaces with the
	 * org.osgi.service.application implementation. This replacement class MUST
	 * NOT alter the public/protected signature of this class.
	 */

	/**
	 * The property key for the unique identifier (PID) of the application
	 * instance.
	 */
	public static final String			APPLICATION_PID					= Constants.SERVICE_PID;

	/**
	 * The property key for the pid of the corresponding application descriptor.
	 */
	public final static String			APPLICATION_DESCRIPTOR			= "application.descriptor";

	/**
	 * The property key for the state of this application instance.
	 */
	public final static String			APPLICATION_STATE				= "application.state";

	/**
	 * The property key for the supports exit value property of this application
	 * instance.
	 * 
	 * @since 1.1
	 */
	public final static String			APPLICATION_SUPPORTS_EXITVALUE	= "application.supports.exitvalue";

	/**
	 * The application instance is running. This is the initial state of a newly
	 * created application instance.
	 */
	public final static String			RUNNING							= "RUNNING";

	/**
	 * The application instance is being stopped. This is the state of the
	 * application instance during the execution of the {@code destroy()}
	 * method.
	 */
	public final static String			STOPPING						= "STOPPING";

	private final String				instanceId;

	private final ApplicationDescriptor	descriptor;

	/**
	 * Application instance identifier is specified by the container when the
	 * instance is created. The instance identifier must remain static for the
	 * lifetime of the instance, it must remain the same even across framework
	 * restarts for the same application instance. This value must be the same
	 * as the {@code service.pid} service property of this application handle.
	 * <p>
	 * The instance identifier should follow the following scheme:
	 * &lt;<i>application descriptor PID</i>&gt;.&lt;<i>index</i>&gt; where
	 * &lt;<i>application descriptor PID</i>&gt; is the PID of the corresponding
	 * {@code ApplicationDescriptor} and &lt;<i>index</i>&gt; is a unique
	 * integer index assigned by the application container. Even after
	 * destroying the application index the same index value should not be
	 * reused in a reasonably long timeframe.
	 * 
	 * @param instanceId the instance identifier of the represented application
	 *        instance. It must not be null.
	 * 
	 * @param descriptor the {@code ApplicationDescriptor} of the represented
	 *        application instance. It must not be null.
	 * 
	 * @throws NullPointerException if any of the arguments is null.
	 */
	protected ApplicationHandle(String instanceId, ApplicationDescriptor descriptor) {
		if ((null == instanceId) || (null == descriptor)) {
			throw new NullPointerException("Parameters must not be null!");
		}

		this.instanceId = instanceId;
		this.descriptor = descriptor;

		try {
			delegate = new Delegate();
			delegate.setApplicationHandle(this, descriptor.delegate);
		} catch (Exception e) {
			// Too bad ...
			e.printStackTrace();
			System.err.println("No implementation available for ApplicationDescriptor, property is: " + Delegate.cName);
		}
	}

	/**
	 * Retrieves the {@code ApplicationDescriptor} to which this
	 * {@code ApplicationHandle} belongs.
	 * 
	 * @return The corresponding {@code ApplicationDescriptor}
	 */
	public final ApplicationDescriptor getApplicationDescriptor() {
		return descriptor;
	}

	/**
	 * Get the state of the application instance.
	 * 
	 * @return the state of the application.
	 * 
	 * @throws IllegalStateException if the application handle is unregistered
	 */
	public abstract String getState();

	/**
	 * Returns the exit value for the application instance. The timeout
	 * specifies how the method behaves when the application has not yet
	 * terminated. A negative, zero or positive value may be used.
	 * <ul>
	 * <li>negative - The method does not wait for termination. If the
	 * application has not terminated then an {@code ApplicationException} is
	 * thrown.</li>
	 * 
	 * <li>zero - The method waits until the application terminates.</li>
	 * 
	 * <li>positive - The method waits until the application terminates or the
	 * timeout expires. If the timeout expires and the application has not
	 * terminated then an {@code ApplicationException} is thrown.</li>
	 * </ul>
	 * <p>
	 * The default implementation throws an
	 * {@code UnsupportedOperationException}. The application model should
	 * override this method if exit values are supported.
	 * </p>
	 * 
	 * @param timeout The maximum time in milliseconds to wait for the
	 *        application to timeout.
	 * @return The exit value for the application instance. The value is
	 *         application specific.
	 * @throws UnsupportedOperationException If the application model does not
	 *         support exit values.
	 * @throws InterruptedException If the thread is interrupted while waiting
	 *         for the timeout.
	 * @throws ApplicationException If the application has not terminated. The
	 *         error code will be
	 *         {@link ApplicationException#APPLICATION_EXITVALUE_NOT_AVAILABLE}.
	 * 
	 * @since 1.1
	 */
	public Object getExitValue(long timeout) throws ApplicationException, InterruptedException {
		throw new UnsupportedOperationException();
	}

	/**
	 * Returns the unique identifier of this instance. This value is also
	 * available as a service property of this application handle's service.pid.
	 * 
	 * @return the unique identifier of the instance
	 */
	public final String getInstanceId() {
		return instanceId;
	}

	/**
	 * The application instance's lifecycle state can be influenced by this
	 * method. It lets the application instance perform operations to stop the
	 * application safely, e.g. saving its state to a permanent storage.
	 * <p>
	 * The method must check if the lifecycle transition is valid; a STOPPING
	 * application cannot be stopped. If it is invalid then the method must
	 * exit. Otherwise the lifecycle state of the application instance must be
	 * set to STOPPING. Then the destroySpecific() method must be called to
	 * perform any application model specific steps for safe stopping of the
	 * represented application instance.
	 * <p>
	 * At the end the {@code ApplicationHandle} must be unregistered. This
	 * method should free all the resources related to this
	 * {@code ApplicationHandle}.
	 * <p>
	 * When this method is completed the application instance has already made
	 * its operations for safe stopping, the ApplicationHandle has been
	 * unregistered and its related resources has been freed. Further calls on
	 * this application should not be made because they may have unexpected
	 * results.
	 * 
	 * @throws SecurityException if the caller doesn't have "lifecycle"
	 *         {@code ApplicationAdminPermission} for the corresponding
	 *         application.
	 * 
	 * @throws IllegalStateException if the application handle is unregistered
	 */
	public final void destroy() {
		try {
			delegate.destroy();
		} catch (SecurityException se) {
			descriptor.isLaunchableSpecific(); /*
												 * check whether the bundle was
												 * uninstalled
												 */
			/* if yes, throws IllegalStateException */
			throw se; /* otherwise throw the caught SecurityException */
		}
		destroySpecific();
	}

	/**
	 * Called by the destroy() method to perform application model specific
	 * steps to stop and destroy an application instance safely.
	 * 
	 * @throws IllegalStateException if the application handle is unregistered
	 */
	protected abstract void destroySpecific();

	Delegate	delegate;

	/**
	 * This class will load the class named by the
	 * org.osgi.vendor.application.ApplicationHandle and delegate method calls
	 * to an instance of the class.
	 */
	static class Delegate {
		static String	cName;
		static Class	implementation;
		static Method	setApplicationHandle;
		static Method	destroy;

		static {
			AccessController.doPrivileged(new PrivilegedAction() {
				public Object run() {
					cName = System.getProperty("org.osgi.vendor.application.ApplicationHandle");
					if (cName == null) {
						throw new NoClassDefFoundError("org.osgi.vendor.application.ApplicationHandle property must be set");
					}

					try {
						implementation = Class.forName(cName);
					} catch (ClassNotFoundException e) {
						throw new NoClassDefFoundError(e.toString());
					}

					try {
						setApplicationHandle = implementation.getMethod("setApplicationHandle", new Class[] {ApplicationHandle.class, Object.class});
						destroy = implementation.getMethod("destroy", new Class[] {});
					} catch (NoSuchMethodException e) {
						throw new NoSuchMethodError(e.toString());
					}

					return null;
				}
			});
		}

		Object			target;

		Delegate() throws Exception {
			target = AccessController.doPrivileged(new PrivilegedExceptionAction() {
				public Object run() throws Exception {
							return implementation.getConstructor()
									.newInstance();
				}
			});
		}

		void setApplicationHandle(ApplicationHandle d, ApplicationDescriptor.Delegate descriptor) {
			try {
				try {
					setApplicationHandle.invoke(target, new Object[] {d, descriptor.target});
				} catch (InvocationTargetException e) {
					throw e.getTargetException();
				}
			} catch (Error e) {
				throw e;
			} catch (RuntimeException e) {
				throw e;
			} catch (Throwable e) {
				throw new RuntimeException(e);
			}
		}

		void destroy() {
			try {
				try {
					destroy.invoke(target, new Object[] {});
				} catch (InvocationTargetException e) {
					throw e.getTargetException();
				}
			} catch (Error e) {
				throw e;
			} catch (RuntimeException e) {
				throw e;
			} catch (Throwable e) {
				throw new RuntimeException(e);
			}
		}
	}
}
