blob: f945aeba46115b11dd6b1ede67c5b1fd4485790f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.apache.brooklyn.entity.software.base;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.file.FileVisitOption.FOLLOW_LINKS;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import org.apache.brooklyn.api.entity.EntityLocal;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.api.mgmt.TaskAdaptable;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
import org.apache.brooklyn.core.feed.ConfigToAttributes;
import org.apache.brooklyn.entity.software.base.lifecycle.MachineLifecycleEffectorTasks;
import org.apache.brooklyn.entity.software.base.lifecycle.MachineLifecycleEffectorTasks.CloseableLatch;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.ResourceUtils;
import org.apache.brooklyn.util.core.json.ShellEnvironmentSerializer;
import org.apache.brooklyn.util.core.task.DynamicTasks;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.core.text.TemplateProcessor;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.os.Os;
import org.apache.brooklyn.util.stream.ReaderInputStream;
import org.apache.brooklyn.util.text.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.Beta;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
/**
* An abstract implementation of the {@link SoftwareProcessDriver}.
*/
public abstract class AbstractSoftwareProcessDriver implements SoftwareProcessDriver {
private static final Logger log = LoggerFactory.getLogger(AbstractSoftwareProcessDriver.class);
protected final EntityLocal entity;
protected final ResourceUtils resource;
protected final Location location;
// we cache these for efficiency and in case the entity becomes unmanaged
private volatile String installDir;
private volatile String runDir;
private volatile String expandedInstallDir;
private final Object installDirSetupMutex = new Object();
public AbstractSoftwareProcessDriver(EntityLocal entity, Location location) {
this.entity = checkNotNull(entity, "entity");
this.location = checkNotNull(location, "location");
this.resource = ResourceUtils.create(entity);
}
/*
* (non-Javadoc)
* @see org.apache.brooklyn.entity.software.base.SoftwareProcessDriver#rebind()
*/
@Override
public void rebind() {
// no-op
}
/**
* Start the entity.
* <p>
* This installs, configures and launches the application process. However,
* users can also call the {@link #install()}, {@link #customize()} and
* {@link #launch()} steps independently. The {@link #postLaunch()} will
* be called after the {@link #launch()} metheod is executed, but the
* process may not be completely initialised at this stage, so care is
* required when implementing these stages.
* <p>
* The {@link BrooklynConfigKeys#SKIP_ENTITY_START_IF_RUNNING} key can be set on the location
* or the entity to skip the startup process if the entity is already running,
* according to the {@link #isRunning()} method. To force the startup to be
* skipped, {@link BrooklynConfigKeys#SKIP_ENTITY_START} can be set on the entity.
* The {@link BrooklynConfigKeys#SKIP_ENTITY_INSTALLATION} key can also be used to
* skip the {@link #setup()}, {@link #copyInstallResources()} and
* {@link #install()} methods if set on the entity or location.
*
* @see #stop()
*/
@Override
public void start() {
boolean skipStart = false;
Optional<Boolean> locationRunning = Optional.fromNullable(getLocation().getConfig(BrooklynConfigKeys.SKIP_ENTITY_START_IF_RUNNING));
Optional<Boolean> entityRunning = Optional.fromNullable(entity.getConfig(BrooklynConfigKeys.SKIP_ENTITY_START_IF_RUNNING));
Optional<Boolean> entityStarted = Optional.fromNullable(entity.getConfig(BrooklynConfigKeys.SKIP_ENTITY_START));
if (locationRunning.or(entityRunning).or(false)) {
skipStart = isRunning();
} else {
skipStart = entityStarted.or(false);
}
DynamicTasks.queue("prepare", new Runnable() { @Override public void run() {
prepare();
}});
if (!skipStart) {
DynamicTasks.queue("install", new Runnable() { @Override public void run() {
Optional<Boolean> locationInstalled = Optional.fromNullable(getLocation().getConfig(BrooklynConfigKeys.SKIP_ENTITY_INSTALLATION));
Optional<Boolean> entityInstalled = Optional.fromNullable(entity.getConfig(BrooklynConfigKeys.SKIP_ENTITY_INSTALLATION));
boolean skipInstall = locationInstalled.or(entityInstalled).or(false);
if (!skipInstall) {
DynamicTasks.queue("copy-pre-install-resources", new Runnable() { @Override public void run() {
try (CloseableLatch value = waitForLatch(BrooklynConfigKeys.PRE_INSTALL_RESOURCES_LATCH)) {
copyPreInstallResources();
}
}});
DynamicTasks.queue("pre-install", new Runnable() { @Override public void run() {
preInstall();
}});
DynamicTasks.queue("pre-install-command", new Runnable() { @Override public void run() {
runPreInstallCommand();
}});
DynamicTasks.queue("setup", new Runnable() { @Override public void run() {
try (CloseableLatch value = waitForLatch(BrooklynConfigKeys.SETUP_LATCH)) {
setup();
}
}});
DynamicTasks.queue("copy-install-resources", new Runnable() { @Override public void run() {
try (CloseableLatch value = waitForLatch(BrooklynConfigKeys.INSTALL_RESOURCES_LATCH)) {
copyInstallResources();
}
}});
DynamicTasks.queue("install (main)", new Runnable() { @Override public void run() {
try (CloseableLatch value = waitForLatch(BrooklynConfigKeys.INSTALL_LATCH)) {
install();
}
}});
DynamicTasks.queue("post-install-command", new Runnable() { @Override public void run() {
runPostInstallCommand();
}});
}
}});
DynamicTasks.queue("customize", new Runnable() { @Override public void run() {
DynamicTasks.queue("copy-pre-customize-resources", new Runnable() { @Override public void run() {
try (CloseableLatch value = waitForLatch(BrooklynConfigKeys.CUSTOMIZE_RESOURCES_LATCH)) {
copyCustomizeResources();
}
}});
DynamicTasks.queue("pre-customize-command", new Runnable() { @Override public void run() {
runPreCustomizeCommand();
}});
DynamicTasks.queue("customize (main)", new Runnable() { @Override public void run() {
try (CloseableLatch value = waitForLatch(BrooklynConfigKeys.CUSTOMIZE_LATCH)) {
customize();
}
}});
DynamicTasks.queue("post-customize-command", new Runnable() { @Override public void run() {
runPostCustomizeCommand();
}});
}});
DynamicTasks.queue("launch", new Runnable() { @Override public void run() {
DynamicTasks.queue("copy-runtime-resources", new Runnable() { public void run() {
try (CloseableLatch value = waitForLatch(BrooklynConfigKeys.RUNTIME_RESOURCES_LATCH)) {
copyRuntimeResources();
}
}});
DynamicTasks.queue("pre-launch-command", new Runnable() { @Override public void run() {
runPreLaunchCommand();
}});
DynamicTasks.queue("launch (main)", new Runnable() { @Override public void run() {
try (CloseableLatch value = waitForLatch(BrooklynConfigKeys.LAUNCH_LATCH)) {
launch();
}
}});
DynamicTasks.queue("post-launch-command", new Runnable() { @Override public void run() {
runPostLaunchCommand();
}});
}});
}
DynamicTasks.queue("post-launch", new Runnable() { @Override public void run() {
postLaunch();
}});
}
private CloseableLatch waitForLatch(ConfigKey<Boolean> configKey) {
return MachineLifecycleEffectorTasks.waitForCloseableLatch((EntityInternal)entity, configKey);
}
@Override
public abstract void stop();
/**
* Prepare the entity instance before running any commands. Always executed during {@link #start()}.
*/
public void prepare() {}
/**
* Implement this method in child classes to add some pre-install behavior
*/
public void preInstall() {}
/**
* Implementations should fail if the return code is non-zero, by throwing some appropriate exception.
*/
public abstract void runPreInstallCommand();
/**
* Implementations should fail if the return code is non-zero, by throwing some appropriate exception.
*/
public abstract void setup();
/**
* Implementations should fail if the return code is non-zero, by throwing some appropriate exception.
*/
public abstract void install();
/**
* Implementations should fail if the return code is non-zero, by throwing some appropriate exception.
*/
public abstract void runPostInstallCommand();
/**
* Implementations should fail if the return code is non-zero, by throwing some appropriate exception.
*/
public abstract void runPreCustomizeCommand();
/**
* Implementations should fail if the return code is non-zero, by throwing some appropriate exception.
*/
public abstract void customize();
/**
* Implementations should fail if the return code is non-zero, by throwing some appropriate exception.
*/
public abstract void runPostCustomizeCommand();
/**
* Implementations should fail if the return code is non-zero, by throwing some appropriate exception.
*/
public abstract void runPreLaunchCommand();
/**
* Implementations should fail if the return code is non-zero, by throwing some appropriate exception.
*/
public abstract void launch();
/**
* Only run if launch is run (if start is not skipped).
* Implementations should fail if the return code is non-zero, by throwing some appropriate exception.
*/
public abstract void runPostLaunchCommand();
@Override
public void kill() {
stop();
}
/**
* Implement this method in child classes to add some post-launch behavior.
* This is run even if start is skipped and launch is not run.
*/
public void postLaunch() {}
@Override
public void restart() {
DynamicTasks.queue("stop (best effort)", new Runnable() {
@Override
public void run() {
DynamicTasks.markInessential();
boolean previouslyRunning = isRunning();
try {
ServiceStateLogic.setExpectedState(getEntity(), Lifecycle.STOPPING);
stop();
} catch (Exception e) {
// queue a failed task so that there is visual indication that this task had a failure,
// without interrupting the parent
if (previouslyRunning) {
log.warn(getEntity() + " restart: stop failed, when was previously running (ignoring)", e);
DynamicTasks.queue(Tasks.fail("Primary job failure (when previously running)", e));
} else {
log.debug(getEntity() + " restart: stop failed (but was not previously running, so not a surprise)", e);
DynamicTasks.queue(Tasks.fail("Primary job failure (when not previously running)", e));
}
// the above queued tasks will cause this task to be indicated as failed, with an indication of severity
}
}
});
DynamicTasks.queue("restart", new Runnable() {
@Override
public void run() {
try {
if (doFullStartOnRestart()) {
DynamicTasks.waitForLast();
ServiceStateLogic.setExpectedState(getEntity(), Lifecycle.STARTING);
start();
} else {
DynamicTasks.queue("pre-launch-command", new Runnable() { @Override public void run() {
ServiceStateLogic.setExpectedState(getEntity(), Lifecycle.STARTING);
runPreLaunchCommand();
}});
DynamicTasks.queue("launch (main)", new Runnable() { @Override public void run() {
launch();
}});
DynamicTasks.queue("post-launch-command", new Runnable() { @Override public void run() {
runPostLaunchCommand();
}});
DynamicTasks.queue("post-launch", new Runnable() { @Override public void run() {
postLaunch();
}});
}
DynamicTasks.waitForLast();
} catch (Exception e) {
ServiceStateLogic.setExpectedState(entity, Lifecycle.ON_FIRE);
throw Exceptions.propagate(e);
}
}
});
}
@Beta
/** ideally restart() would take options, e.g. whether to do full start, skip installs, etc;
* however in the absence here is a toggle - not sure how well it works;
* default is false which is similar to previous behaviour (with some seemingly-obvious tidies),
* meaning install and configure will NOT be done on restart. */
protected boolean doFullStartOnRestart() {
return false;
}
@Override
public EntityLocal getEntity() { return entity; }
@Override
public Location getLocation() { return location; }
public InputStream getResource(String url) {
return resource.getResourceFromUrl(url);
}
/**
* Files and templates to be copied to the server <em>before</em> pre-install. This allows the {@link #preInstall()}
* process to have access to all required resources.
* <p>
* Will be prefixed with the entity's {@link #getInstallDir() install directory} if relative.
*
* @see SoftwareProcess#PRE_INSTALL_FILES
* @see SoftwareProcess#PRE_INSTALL_TEMPLATES
* @see #copyRuntimeResources()
*/
public void copyPreInstallResources() {
copyResources(getInstallDir(), entity.getConfig(SoftwareProcess.PRE_INSTALL_FILES), entity.getConfig(SoftwareProcess.PRE_INSTALL_TEMPLATES));
}
/**
* Files and templates to be copied to the server <em>before</em> installation. This allows the {@link #install()}
* process to have access to all required resources.
* <p>
* Will be prefixed with the entity's {@link #getInstallDir() install directory} if relative.
*
* @see SoftwareProcess#INSTALL_FILES
* @see SoftwareProcess#INSTALL_TEMPLATES
* @see #copyRuntimeResources()
*/
public void copyInstallResources() {
// Ensure environment variables are not looked up here, otherwise sub-classes might
// lookup port numbers and fail with ugly error if port is not set; better to wait
// until in Entity's code (e.g. customize) where such checks are done explicitly.
copyResources(getInstallDir(), entity.getConfig(SoftwareProcess.INSTALL_FILES), entity.getConfig(SoftwareProcess.INSTALL_TEMPLATES));
}
/**
* Files and templates to be copied to the server <em>before</em> customize. This allows the {@link #customize()}
* process to have access to all required resources.
* <p>
* Will be prefixed with the entity's {@link #getInstallDir() install directory} if relative.
*
* @see SoftwareProcess#INSTALL_FILES
* @see SoftwareProcess#INSTALL_TEMPLATES
* @see #copyRuntimeResources()
*/
public void copyCustomizeResources() {
copyResources(getInstallDir(), entity.getConfig(SoftwareProcess.CUSTOMIZE_FILES), entity.getConfig(SoftwareProcess.CUSTOMIZE_TEMPLATES));
}
protected void copyResources(String destinationParentDir, Map<String, String> files, Map<String, String> templates) {
if (files == null) files = Collections.emptyMap();
if (templates == null) templates = Collections.emptyMap();
final List<TaskAdaptable<?>> tasks = new ArrayList<>(files.size() + templates.size());
applyFnToResourcesAppendToList(files, newCopyResourceFunction(), destinationParentDir, tasks);
applyFnToResourcesAppendToList(templates, newCopyTemplateFunction(), destinationParentDir, tasks);
if (!tasks.isEmpty()) {
String oldBlockingDetails = Tasks.setBlockingDetails("Copying resources");
try {
DynamicTasks.queue(Tasks.sequential(tasks)).getUnchecked();
} finally {
Tasks.setBlockingDetails(oldBlockingDetails);
}
}
}
protected String mergePaths(String ...s) {
return Os.mergePathsUnix(s);
}
private void applyFnToResourcesAppendToList(
Map<String, String> resources, final Function<SourceAndDestination, Task<?>> function,
String destinationParentDir, final List<TaskAdaptable<?>> tasks) {
for (Map.Entry<String, String> entry : resources.entrySet()) {
final String source = checkNotNull(entry.getKey(), "Missing source for resource");
String target = checkNotNull(entry.getValue(), "Missing destination for resource");
final String destination = Os.isAbsolutish(target) ? target : mergePaths(destinationParentDir, target);
// if source is a directory then copy all files underneath.
// e.g. /tmp/a/{b,c/d}, source = /tmp/a, destination = dir/a/b and dir/a/c/d.
final File srcFile = new File(source);
if (srcFile.isDirectory() && srcFile.exists()) {
try {
final Path start = srcFile.toPath();
final int startElements = start.getNameCount();
// Actually walking to a depth of Integer.MAX_VALUE would be interesting.
Files.walkFileTree(start, EnumSet.of(FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (attrs.isRegularFile()) {
Path relativePath = file.subpath(startElements, file.getNameCount());
tasks.add(function.apply(new SourceAndDestination(file.toString(), mergePaths(destination, relativePath.toString()))));
}
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
throw Exceptions.propagate(e);
}
} else {
tasks.add(function.apply(new SourceAndDestination(source, destination)));
}
}
}
private static class SourceAndDestination {
final String source;
final String destination;
private SourceAndDestination(String source, String destination) {
this.source = source;
this.destination = destination;
}
}
private Function<SourceAndDestination, Task<?>> newCopyResourceFunction() {
return new Function<SourceAndDestination, Task<?>>() {
@Override
public Task<?> apply(final SourceAndDestination input) {
return Tasks.builder()
.displayName("Copying file: source=" + input.source + ", destination=" + input.destination)
.body(new Callable<Object>() {
@Override
public Integer call() {
return copyResource(input.source, input.destination, true);
}
})
.build();
}
};
}
private Function<SourceAndDestination, Task<?>> newCopyTemplateFunction() {
return new Function<SourceAndDestination, Task<?>>() {
@Override
public Task<?> apply(final SourceAndDestination input) {
return Tasks.builder()
.displayName("Copying template: source=" + input.source + ", destination=" + input.destination)
.body(new Callable<Object>() {
@Override
public Integer call() {
Map<String, Object> substitutions = MutableMap.copyOf(entity.config().get(SoftwareProcess.TEMPLATE_SUBSTITUTIONS)).asUnmodifiable();
return copyTemplate(input.source, input.destination, true, substitutions);
}
})
.build();
}
};
}
protected abstract void createDirectory(String directoryName, String summaryForLogging);
/**
* Files and templates to be copied to the server <em>after</em> customisation. This allows overwriting of
* existing files such as entity configuration which may be copied from the installation directory
* during the {@link #customize()} process.
* <p>
* Will be prefixed with the entity's {@link #getRunDir() run directory} if relative.
*
* @see SoftwareProcess#RUNTIME_FILES
* @see SoftwareProcess#RUNTIME_TEMPLATES
* @see #copyInstallResources()
*/
public void copyRuntimeResources() {
try {
copyResources(getRunDir(), entity.getConfig(SoftwareProcess.RUNTIME_FILES), entity.getConfig(SoftwareProcess.RUNTIME_TEMPLATES));
} catch (Exception e) {
log.warn("Error copying runtime resources", e);
throw Exceptions.propagate(e);
}
}
/**
* @param template File to template and copy.
* @param target Destination on server.
* @return The exit code the SSH command run.
*/
public int copyTemplate(File template, String target) {
return copyTemplate(template.toURI().toASCIIString(), target);
}
/**
* @param template URI of file to template and copy, e.g. file://.., http://.., classpath://..
* @param target Destination on server.
* @return The exit code of the SSH command run.
*/
public int copyTemplate(String template, String target) {
return copyTemplate(template, target, false, ImmutableMap.<String, String>of());
}
/**
* @param templateUrl URI of file to template and copy, e.g. file://.., http://.., classpath://..
* @param target Destination on server.
* @param extraSubstitutions Extra substitutions for the templater to use, for example
* "foo" -> "bar", and in a template ${foo}.
* @return The exit code of the SSH command run.
*/
public int copyTemplate(String templateUrl, String target, boolean createParent, Map<String, ?> extraSubstitutions) {
log.debug("Processing template "+templateUrl+" and copying to "+target+" on "+getLocation()+" for "+getEntity());
String data = processTemplate(templateUrl, extraSubstitutions);
return copyResource(MutableMap.<Object,Object>of(), new StringReader(data), target, createParent);
}
public abstract int copyResource(Map<Object,Object> sshFlags, String sourceUrl, String target, boolean createParentDir);
public abstract int copyResource(Map<Object,Object> sshFlags, InputStream source, String target, boolean createParentDir);
/**
* @param file File to copy.
* @param target Destination on server.
* @return The exit code the SSH command run.
*/
public int copyResource(File file, String target) {
return copyResource(file.toURI().toASCIIString(), target);
}
/**
* @param resource URI of file to copy, e.g. file://.., http://.., classpath://..
* @param target Destination on server.
* @return The exit code of the SSH command run
*/
public int copyResource(String resource, String target) {
return copyResource(MutableMap.of(), resource, target);
}
public int copyResource(String resourceUrl, String target, boolean createParentDir) {
return copyResource(MutableMap.of(), resourceUrl, target, createParentDir);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public int copyResource(Map sshFlags, String source, String target) {
return copyResource(sshFlags, source, target, false);
}
/**
* @see #copyResource(Map, InputStream, String, boolean)
*/
public int copyResource(Reader source, String target) {
return copyResource(MutableMap.of(), source, target, false);
}
/**
* @see #copyResource(Map, InputStream, String, boolean)
*/
public int copyResource(Map<Object,Object> sshFlags, Reader source, String target, boolean createParent) {
return copyResource(sshFlags, new ReaderInputStream(source), target, createParent);
}
/**
* @see #copyResource(Map, InputStream, String, boolean)
*/
public int copyResource(InputStream source, String target) {
return copyResource(MutableMap.of(), source, target, false);
}
public String getResourceAsString(String url) {
return resource.getResourceAsString(url);
}
public String processTemplate(File templateConfigFile, Map<String,Object> extraSubstitutions) {
return processTemplate(templateConfigFile.toURI().toASCIIString(), extraSubstitutions);
}
public String processTemplate(File templateConfigFile) {
return processTemplate(templateConfigFile.toURI().toASCIIString());
}
/** Takes the contents of a template file from the given URL (often a classpath://com/myco/myprod/myfile.conf or .sh)
* and replaces "${entity.xxx}" with the result of entity.getXxx() and similar for other driver, location;
* as well as replacing config keys on the management context
* <p>
* uses Freemarker templates under the covers
**/
public String processTemplate(String templateConfigUrl) {
return processTemplate(templateConfigUrl, ImmutableMap.<String,String>of());
}
public String processTemplate(String templateConfigUrl, Map<String,? extends Object> extraSubstitutions) {
return processTemplateContents(getResourceAsString(templateConfigUrl), extraSubstitutions);
}
public String processTemplateContents(String templateContents) {
return processTemplateContents(templateContents, ImmutableMap.<String,String>of());
}
public String processTemplateContents(String templateContents, Map<String,? extends Object> extraSubstitutions) {
return TemplateProcessor.processTemplateContents(templateContents, this, extraSubstitutions);
}
public String getArchiveNameFormat() {
return getEntity().config().get(SoftwareProcess.ARCHIVE_DIRECTORY_NAME_FORMAT);
}
public String getVersion() {
return getEntity().config().get(SoftwareProcess.SUGGESTED_VERSION);
}
/**
* The environment variables to be set when executing the commands (for install, run, check running, etc).
* @see SoftwareProcess#SHELL_ENVIRONMENT
*/
public Map<String, String> getShellEnvironment() {
Map<String, Object> env = entity.getConfig(SoftwareProcess.SHELL_ENVIRONMENT);
ShellEnvironmentSerializer envSerializer = new ShellEnvironmentSerializer(((EntityInternal)entity).getManagementContext());
return envSerializer.serialize(env);
}
protected void setInstallDir(String installDir) {
this.installDir = installDir;
entity.sensors().set(SoftwareProcess.INSTALL_DIR, installDir);
}
public String getInstallDir() {
if (installDir != null) return installDir;
String existingVal = getEntity().getAttribute(SoftwareProcess.INSTALL_DIR);
if (Strings.isNonBlank(existingVal)) { // e.g. on rebind
installDir = existingVal;
return installDir;
}
synchronized (installDirSetupMutex) {
// previously we looked at sensor value, but we shouldn't as it might have been converted from the config key value
// *before* we computed the install label, or that label may have changed since previous install; now force a recompute
setInstallLabel();
// set it null first so that we force a recompute
setInstallDir(null);
setInstallDir(Os.tidyPath(ConfigToAttributes.apply(getEntity(), SoftwareProcess.INSTALL_DIR)));
return installDir;
}
}
protected void setInstallLabel() {
if (((EntityInternal)getEntity()).config().getLocalRaw(SoftwareProcess.INSTALL_UNIQUE_LABEL).isPresentAndNonNull()) return;
getEntity().config().set(SoftwareProcess.INSTALL_UNIQUE_LABEL,
getEntity().getEntityType().getSimpleName()+
(Strings.isNonBlank(getVersion()) ? "_"+getVersion() : "")+
(Strings.isNonBlank(getInstallLabelExtraSalt()) ? "_"+getInstallLabelExtraSalt() : "") );
}
/** allows subclasses to return extra salt (ie unique hash)
* for cases where install dirs need to be distinct e.g. based on extra plugins being placed in the install dir;
* {@link #setInstallLabel()} uses entity-type simple name and version already
* <p>
* this salt should not be too long and must not contain invalid path chars.
* a hash code of other relevant info is not a bad choice.
**/
protected String getInstallLabelExtraSalt() {
return null;
}
protected void setRunDir(String runDir) {
this.runDir = runDir;
entity.sensors().set(SoftwareProcess.RUN_DIR, runDir);
}
public String getRunDir() {
if (runDir != null) return runDir;
String existingVal = getEntity().getAttribute(SoftwareProcess.RUN_DIR);
if (Strings.isNonBlank(existingVal)) { // e.g. on rebind
runDir = existingVal;
return runDir;
}
setRunDir(Os.tidyPath(ConfigToAttributes.apply(getEntity(), SoftwareProcess.RUN_DIR)));
return runDir;
}
public void setExpandedInstallDir(String val) {
String oldVal = getEntity().getAttribute(SoftwareProcess.EXPANDED_INSTALL_DIR);
if (Strings.isNonBlank(oldVal) && !oldVal.equals(val)) {
log.info("Resetting expandedInstallDir (to "+val+" from "+oldVal+") for "+getEntity());
}
expandedInstallDir = val;
getEntity().sensors().set(SoftwareProcess.EXPANDED_INSTALL_DIR, val);
}
public String getExpandedInstallDir() {
if (expandedInstallDir != null) return expandedInstallDir;
String existingVal = getEntity().getAttribute(SoftwareProcess.EXPANDED_INSTALL_DIR);
if (Strings.isNonBlank(existingVal)) { // e.g. on rebind
expandedInstallDir = existingVal;
return expandedInstallDir;
}
String untidiedVal = ConfigToAttributes.apply(getEntity(), SoftwareProcess.EXPANDED_INSTALL_DIR);
if (Strings.isNonBlank(untidiedVal)) {
setExpandedInstallDir(Os.tidyPath(untidiedVal));
return expandedInstallDir;
} else {
throw new IllegalStateException("expandedInstallDir is null; most likely install was not called for "+getEntity());
}
}
}