/*
 * 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.apache.aries.subsystem.core.internal;

import java.io.InputStream;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;

import org.apache.aries.util.filesystem.IDirectory;
import org.osgi.service.coordinator.Coordination;
import org.osgi.service.coordinator.CoordinationException;
import org.osgi.service.subsystem.Subsystem.State;
import org.osgi.service.subsystem.SubsystemException;

public class InstallAction implements PrivilegedAction<BasicSubsystem> {
	private final IDirectory content;
	private final AccessControlContext context;
	private final InputStream deploymentManifest;
	private final String location;
	private final BasicSubsystem parent;
	
	public InstallAction(String location, IDirectory content, BasicSubsystem parent, AccessControlContext context, InputStream deploymentManifest) {
		this.location = location;
		this.content = content;
		this.parent = parent;
		this.context = context;
		this.deploymentManifest = deploymentManifest;
	}
	
	@Override
	public BasicSubsystem run() {
		// Doesn't appear to be any need of protecting against re-entry in the
		// case of installation.
		BasicSubsystem result = null;
		// Acquire the global write lock to prevent all other operations until
		// the installation is complete. There is no need to hold any other locks.
		Activator.getInstance().getLockingStrategy().writeLock();
		try {
			State state = parent.getState();
			if (State.INSTALLING.equals(state)) {
				throw new SubsystemException("A child subsystem may not be installed while the parent is in the INSTALLING state");
			}
			// Initialization of a null coordination must be privileged and,
			// therefore, occur in the run() method rather than in the constructor.
			Coordination coordination = Utils.createCoordination(parent);
			try {
				TargetRegion region = new TargetRegion(parent);
				SubsystemResource ssr = new SubsystemResource(location, content, parent, coordination);
				result = Activator.getInstance().getSubsystems().getSubsystemByLocation(location);
				if (result != null) {
					if (!region.contains(result))
						throw new SubsystemException("Location already exists but existing subsystem is not part of target region: " + location);
					if (!(result.getSymbolicName().equals(ssr.getSubsystemManifest().getSubsystemSymbolicNameHeader().getSymbolicName())
							&& result.getVersion().equals(ssr.getSubsystemManifest().getSubsystemVersionHeader().getVersion())
							&& result.getType().equals(ssr.getSubsystemManifest().getSubsystemTypeHeader().getType())))
						throw new SubsystemException("Location already exists but symbolic name, version, and type are not the same: " + location);
				}
				else {
					result = (BasicSubsystem)region.find(
							ssr.getSubsystemManifest().getSubsystemSymbolicNameHeader().getSymbolicName(), 
							ssr.getSubsystemManifest().getSubsystemVersionHeader().getVersion());
					if (result != null) {
						if (!result.getType().equals(ssr.getSubsystemManifest().getSubsystemTypeHeader().getType()))
							throw new SubsystemException("Subsystem already exists in target region but has a different type: " + location);
					}
					else {
						result = new BasicSubsystem(ssr, deploymentManifest);
					}
				}
				checkLifecyclePermission(result);
				return (BasicSubsystem)ResourceInstaller.newInstance(coordination, result, parent).install();
			}
			catch (Throwable t) {
				coordination.fail(t);
			}
			finally {
				try {
					coordination.end();
				}
				catch (CoordinationException e) {
					Throwable t = e.getCause();
					if (t instanceof SubsystemException)
						throw (SubsystemException)t;
					if (t instanceof SecurityException)
						throw (SecurityException)t;
					throw new SubsystemException(t);
				}
			}
		}
		finally {
			// Release the global write lock.
			Activator.getInstance().getLockingStrategy().writeUnlock();
		}
		return result;
	}
	
	private void checkLifecyclePermission(final BasicSubsystem subsystem) {
		AccessController.doPrivileged(new PrivilegedAction<Object>() {
			@Override
			public Object run() {
				SecurityManager.checkLifecyclePermission(subsystem);
				return null;
			}
		},
		context);
	}
}
