/*
* JBoss, Home of Professional Open Source.
* Copyright 2010, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.controller.client.helpers.standalone.impl;
import java.io.DataOutput;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.jboss.as.controller.client.logging.ControllerClientLogger;
import org.jboss.as.controller.client.helpers.standalone.AddDeploymentPlanBuilder;
import org.jboss.as.controller.client.helpers.standalone.DeploymentAction;
import org.jboss.as.controller.client.helpers.standalone.DeploymentPlan;
import org.jboss.as.controller.client.helpers.standalone.DeploymentPlanBuilder;
import org.jboss.as.controller.client.helpers.standalone.InitialDeploymentPlanBuilder;
import org.jboss.as.controller.client.helpers.standalone.ReplaceDeploymentPlanBuilder;
import org.jboss.as.controller.client.helpers.standalone.UndeployDeploymentPlanBuilder;
import org.jboss.as.controller.client.helpers.standalone.DeploymentAction.Type;
import org.jboss.as.controller.client.impl.InputStreamEntry;
import org.jboss.as.protocol.StreamUtils;
/**
* {@link DeploymentPlanBuilder} implementation meant to handle in-VM calls.
*
* @author Brian Stansberry
*/
class DeploymentPlanBuilderImpl
implements AddDeploymentPlanBuilder, InitialDeploymentPlanBuilder, UndeployDeploymentPlanBuilder {
private final boolean shutdown;
private final long gracefulShutdownPeriod;
private final boolean globalRollback;
private volatile boolean cleanupInFinalize = true;
private final List<DeploymentActionImpl> deploymentActions = new ArrayList<DeploymentActionImpl>();
DeploymentPlanBuilderImpl() {
this.shutdown = false;
this.globalRollback = true;
this.gracefulShutdownPeriod = -1;
}
DeploymentPlanBuilderImpl(DeploymentPlanBuilderImpl existing) {
this.deploymentActions.addAll(existing.deploymentActions);
this.shutdown = existing.shutdown;
this.globalRollback = existing.globalRollback;
this.gracefulShutdownPeriod = existing.gracefulShutdownPeriod;
existing.cleanupInFinalize = false;
}
DeploymentPlanBuilderImpl(DeploymentPlanBuilderImpl existing, boolean globalRollback) {
this.deploymentActions.addAll(existing.deploymentActions);
this.shutdown = false;
this.globalRollback = globalRollback;
this.gracefulShutdownPeriod = -1;
existing.cleanupInFinalize = false;
}
DeploymentPlanBuilderImpl(DeploymentPlanBuilderImpl existing, long gracefulShutdownPeriod) {
this.deploymentActions.addAll(existing.deploymentActions);
this.shutdown = true;
this.globalRollback = false;
this.gracefulShutdownPeriod = gracefulShutdownPeriod;
existing.cleanupInFinalize = false;
}
DeploymentPlanBuilderImpl(DeploymentPlanBuilderImpl existing, DeploymentActionImpl modification) {
this(existing);
this.deploymentActions.add(modification);
}
@Override
public DeploymentAction getLastAction() {
return deploymentActions.size() == 0 ? null : deploymentActions.get(deploymentActions.size() - 1);
}
@Override
public List<DeploymentAction> getDeploymentActions() {
return new ArrayList<DeploymentAction>(deploymentActions);
}
@Override
public long getGracefulShutdownTimeout() {
return gracefulShutdownPeriod;
}
@Override
public boolean isGlobalRollback() {
return globalRollback;
}
@Override
public boolean isGracefulShutdown() {
return shutdown && gracefulShutdownPeriod > -1;
}
@Override
public boolean isShutdown() {
return shutdown;
}
@Override
public DeploymentPlan build() {
DeploymentPlan dp = new DeploymentPlanImpl(Collections.unmodifiableList(deploymentActions), globalRollback, shutdown, gracefulShutdownPeriod);
cleanupInFinalize = false;
return dp;
}
@Override
public AddDeploymentPlanBuilder add(File file) throws IOException {
String name = file.getName();
return add(name, file);
}
@Override
public AddDeploymentPlanBuilder add(URL url) throws IOException {
String name = getName(url);
return add(name, name, url);
}
@Override
public AddDeploymentPlanBuilder add(String name, File file) throws IOException {
final InputStream is = new FileStreamEntry(file);
return add(name, name, is, true);
}
@Override
public AddDeploymentPlanBuilder add(String name, URL url) throws IOException {
return add(name, name, url);
}
private AddDeploymentPlanBuilder add(String name, String commonName, URL url) throws IOException {
try {
URLConnection conn = url.openConnection();
conn.connect();
InputStream stream = conn.getInputStream();
return add(name, commonName, stream, true);
} catch (IOException e) {
cleanup();
throw e;
}
}
private DeploymentPlanBuilder replace(String name, String commonName, URL url) throws IOException {
try {
URLConnection conn = url.openConnection();
conn.connect();
InputStream stream = conn.getInputStream();
return replace(name, commonName, stream, true);
} catch (IOException e) {
cleanup();
throw e;
}
}
@Override
public AddDeploymentPlanBuilder add(String name, InputStream stream) {
return add(name, name, stream);
}
@Override
public AddDeploymentPlanBuilder add(String name, String commonName, InputStream stream) {
return add(name, commonName, stream, false);
}
private AddDeploymentPlanBuilder add(String name, String commonName, InputStream stream, boolean internalStream) {
DeploymentActionImpl mod = DeploymentActionImpl.getAddAction(name, commonName, stream, internalStream);
return new DeploymentPlanBuilderImpl(this, mod);
}
/* (non-Javadoc)
* @see org.jboss.as.deployment.client.api.server.AddDeploymentPlanBuilder#andDeploy()
*/
@Override
public DeploymentPlanBuilder andDeploy() {
String addedKey = getAddedContentKey();
DeploymentActionImpl deployMod = DeploymentActionImpl.getDeployAction(addedKey);
return new DeploymentPlanBuilderImpl(this, deployMod);
}
/* (non-Javadoc)
* @see org.jboss.as.deployment.client.api.server.AddDeploymentPlanBuilder#andReplace(java.lang.String)
*/
@Override
public ReplaceDeploymentPlanBuilder andReplace(String toReplace) {
String newContentKey = getAddedContentKey();
return replace(newContentKey, toReplace);
}
@Override
public DeploymentPlanBuilder deploy(String key) {
DeploymentActionImpl mod = DeploymentActionImpl.getDeployAction(key);
return new DeploymentPlanBuilderImpl(this, mod);
}
@Override
public UndeployDeploymentPlanBuilder undeploy(String key) {
DeploymentActionImpl mod = DeploymentActionImpl.getUndeployAction(key);
return new DeploymentPlanBuilderImpl(this, mod);
}
@Override
public DeploymentPlanBuilder redeploy(String deploymentName) {
DeploymentActionImpl mod = DeploymentActionImpl.getRedeployAction(deploymentName);
return new DeploymentPlanBuilderImpl(this, mod);
}
@Override
public ReplaceDeploymentPlanBuilder replace(String replacement, String toReplace) {
DeploymentActionImpl mod = DeploymentActionImpl.getReplaceAction(replacement, toReplace);
return new ReplaceDeploymentPlanBuilderImpl(this, mod);
}
@Override
public DeploymentPlanBuilder replace(File file) throws IOException {
String name = file.getName();
return replace(name, file);
}
@Override
public DeploymentPlanBuilder replace(URL url) throws IOException {
String name = getName(url);
return replace(name, name, url);
}
@Override
public DeploymentPlanBuilder replace(String name, File file) throws IOException {
final InputStream is = new FileStreamEntry(file);
return replace(name, name, is, true);
}
@Override
public DeploymentPlanBuilder replace(String name, URL url) throws IOException {
return replace(name, name, url);
}
@Override
public DeploymentPlanBuilder replace(String name, InputStream stream) {
return replace(name, name, stream);
}
@Override
public DeploymentPlanBuilder replace(String name, String commonName, InputStream stream) {
return replace(name, commonName, stream, false);
}
private DeploymentPlanBuilder replace(String name, String commonName, InputStream stream, boolean internalStream) {
DeploymentActionImpl mod = DeploymentActionImpl.getFullReplaceAction(name, commonName, stream, internalStream);
return new DeploymentPlanBuilderImpl(this, mod);
}
@Override
public DeploymentPlanBuilder andRemoveUndeployed() {
DeploymentAction last = getLastAction();
if (last.getType() != Type.UNDEPLOY) {
// Someone cast to the impl class instead of using the interface
cleanup();
throw ControllerClientLogger.ROOT_LOGGER.invalidPrecedingAction(Type.UNDEPLOY);
}
DeploymentActionImpl removeMod = DeploymentActionImpl.getRemoveAction(last.getDeploymentUnitUniqueName());
return new DeploymentPlanBuilderImpl(this, removeMod);
}
@Override
public DeploymentPlanBuilder remove(String key) {
DeploymentActionImpl removeMod = DeploymentActionImpl.getRemoveAction(key);
return new DeploymentPlanBuilderImpl(this, removeMod);
}
@Override
@Deprecated
public DeploymentPlanBuilder withRollback() {
if (deploymentActions.size() > 0) {
// Someone has cast to this impl class
cleanup();
throw ControllerClientLogger.ROOT_LOGGER.operationsNotAllowed(InitialDeploymentPlanBuilder.class.getSimpleName());
}
if (shutdown) {
cleanup();
throw ControllerClientLogger.ROOT_LOGGER.globalRollbackNotCompatible();
}
return new DeploymentPlanBuilderImpl(this, true);
}
@Override
public DeploymentPlanBuilder withoutRollback() {
if (deploymentActions.size() > 0) {
// Someone has cast to this impl class
cleanup();
throw ControllerClientLogger.ROOT_LOGGER.operationsNotAllowed(InitialDeploymentPlanBuilder.class.getSimpleName());
}
return new DeploymentPlanBuilderImpl(this, false);
}
@Override
public DeploymentPlanBuilder withGracefulShutdown(long timeout, TimeUnit timeUnit) {
// TODO determine how to remove content. Perhaps with a signal to the
// deployment repository service such that as part of shutdown after
// undeploys are done it then removes the content?
if (deploymentActions.size() > 0) {
// Someone has to cast this impl class
cleanup();
throw ControllerClientLogger.ROOT_LOGGER.operationsNotAllowed(InitialDeploymentPlanBuilder.class.getSimpleName());
}
if (globalRollback) {
cleanup();
throw ControllerClientLogger.ROOT_LOGGER.globalRollbackNotCompatible();
}
long period = timeUnit.toMillis(timeout);
if (shutdown && period != gracefulShutdownPeriod) {
cleanup();
throw ControllerClientLogger.ROOT_LOGGER.gracefulShutdownAlreadyConfigured(gracefulShutdownPeriod);
}
return new DeploymentPlanBuilderImpl(this, period);
}
@Override
public DeploymentPlanBuilder withShutdown() {
// TODO determine how to remove content. Perhaps with a signal to the
// deployment repository service such that as part of shutdown after
// undeploys are done it then removes the content?
if (deploymentActions.size() > 0) {
// Someone has to cast this impl class
cleanup();
throw ControllerClientLogger.ROOT_LOGGER.operationsNotAllowed(InitialDeploymentPlanBuilder.class.getSimpleName());
}
if (globalRollback) {
cleanup();
throw ControllerClientLogger.ROOT_LOGGER.globalRollbackNotCompatible();
}
if (shutdown && gracefulShutdownPeriod != -1) {
cleanup();
throw ControllerClientLogger.ROOT_LOGGER.gracefulShutdownAlreadyConfigured(gracefulShutdownPeriod);
}
return new DeploymentPlanBuilderImpl(this, -1);
}
private String getAddedContentKey() {
DeploymentAction last = getLastAction();
if (last.getType() != Type.ADD) {
// Someone cast to the impl class instead of using the interface
cleanup();
throw ControllerClientLogger.ROOT_LOGGER.invalidPrecedingAction(Type.ADD);
}
return last.getDeploymentUnitUniqueName();
}
private String getName(URL url) {
if ("file".equals(url.getProtocol())) {
try {
File f = new File(url.toURI());
return f.getName();
} catch (URISyntaxException e) {
cleanup();
throw ControllerClientLogger.ROOT_LOGGER.invalidUri(e, url);
}
}
String path = url.getPath();
int idx = path.lastIndexOf('/');
while (idx == path.length() - 1) {
path = path.substring(0, idx);
idx = path.lastIndexOf('/');
}
if (idx == -1) {
cleanup();
throw ControllerClientLogger.ROOT_LOGGER.cannotDeriveDeploymentName(url);
}
return path.substring(idx + 1);
}
protected void cleanup() {
for (DeploymentActionImpl action : deploymentActions) {
if (action.isInternalStream() && action.getContentStream() != null) {
StreamUtils.safeClose(action.getContentStream());
}
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
if (cleanupInFinalize) {
cleanup();
}
}
@Override
public DeploymentPlanBuilder explodeDeployment(String deploymentName) throws IOException {
DeploymentActionImpl mod = DeploymentActionImpl.getExplodeAction(deploymentName);
return new DeploymentPlanBuilderImpl(this, mod);
}
@Override
public DeploymentPlanBuilder addContentToDeployment(String deploymentName, Map<String, InputStream> contents) throws IOException {
DeploymentActionImpl mod = DeploymentActionImpl.getAddContentAction(deploymentName, contents);
return new DeploymentPlanBuilderImpl(this, mod);
}
@Override
public DeploymentPlanBuilder removeContenFromDeployment(String deploymentName, List<String> paths) throws IOException {
DeploymentActionImpl mod = DeploymentActionImpl.getRemoveContentAction(deploymentName, paths);
return new DeploymentPlanBuilderImpl(this, mod);
}
// Wrap the FIS in a streamEntry so that the controller-client has access to the underlying File
private static class FileStreamEntry extends FilterInputStream implements InputStreamEntry {
private final File file;
private FileStreamEntry(final File file) throws IOException {
super(new FileInputStream(file)); // This stream will get closed regardless of autoClose
this.file = file;
}
@Override
public int initialize() throws IOException {
return (int) file.length();
}
@Override
public void copyStream(final DataOutput output) throws IOException {
final FileInputStream is = new FileInputStream(file);
try {
StreamUtils.copyStream(is, output);
is.close();
} finally {
StreamUtils.safeClose(is);
}
}
}
}