You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2015/08/12 17:55:40 UTC
[22/35] incubator-brooklyn git commit: [BROOKLYN-162] package rename
to org.apache.brooklyn: software/webapp
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java
deleted file mode 100644
index 3beee70..0000000
--- a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * 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 brooklyn.entity.webapp.nodejs;
-
-import java.net.URI;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver;
-import brooklyn.entity.basic.Attributes;
-import brooklyn.entity.basic.SoftwareProcess;
-import brooklyn.entity.webapp.WebAppService;
-import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.util.collections.MutableList;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.file.ArchiveUtils;
-import brooklyn.util.net.Networking;
-import brooklyn.util.os.Os;
-import brooklyn.util.ssh.BashCommands;
-import brooklyn.util.text.Strings;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-
-public class NodeJsWebAppSshDriver extends AbstractSoftwareProcessSshDriver implements NodeJsWebAppDriver {
-
- private static final Logger LOG = LoggerFactory.getLogger(NodeJsWebAppService.class);
-
- public NodeJsWebAppSshDriver(NodeJsWebAppServiceImpl entity, SshMachineLocation machine) {
- super(entity, machine);
- }
-
- public NodeJsWebAppServiceImpl getEntity() {
- return (NodeJsWebAppServiceImpl) super.getEntity();
- }
-
- @Override
- public Integer getHttpPort() {
- return getEntity().getAttribute(Attributes.HTTP_PORT);
- }
-
- @Override
- public String getAppDir() {
- return Os.mergePaths(getRunDir(), getEntity().getConfig(NodeJsWebAppService.APP_NAME));
- }
-
- @Override
- public void postLaunch() {
- String rootUrl = String.format("http://%s:%d/", getHostname(), getHttpPort());
- entity.setAttribute(Attributes.MAIN_URI, URI.create(rootUrl));
- entity.setAttribute(WebAppService.ROOT_URL, rootUrl);
- }
-
- protected Map<String, Integer> getPortMap() {
- return MutableMap.of("http", getHttpPort());
- }
-
- @Override
- public Set<Integer> getPortsUsed() {
- return ImmutableSet.<Integer>builder()
- .addAll(super.getPortsUsed())
- .addAll(getPortMap().values())
- .build();
- }
-
- // TODO Suggest that other entities follow this pattern as well: check for port availability early
- // to report failures early, and in case getShellEnvironment() tries to convert any null port numbers
- // to int.
- @Override
- public void preInstall() {
- super.preInstall();
- Networking.checkPortsValid(getPortMap());
- }
-
- @Override
- public void install() {
- LOG.info("Installing Node.JS {}", getEntity().getConfig(SoftwareProcess.SUGGESTED_VERSION));
-
- List<String> commands = MutableList.<String>builder()
- .add(BashCommands.INSTALL_CURL)
- .add(BashCommands.ifExecutableElse0("apt-get", BashCommands.chain(
- BashCommands.installPackage("software-properties-common python-software-properties python g++ make"),
- BashCommands.sudo("add-apt-repository ppa:chris-lea/node.js"))))
- .add(BashCommands.installPackage(MutableMap.of("yum", "git nodejs npm", "apt", "git-core nodejs"), null))
- .add("mkdir \"$HOME/.npm\"")
- .add(BashCommands.sudo("npm install -g n"))
- .add(BashCommands.sudo("n " + getEntity().getConfig(SoftwareProcess.SUGGESTED_VERSION)))
- .build();
-
- newScript(INSTALLING)
- .body.append(commands)
- .execute();
- }
-
- @Override
- public void customize() {
- List<String> commands = Lists.newLinkedList();
-
- String gitRepoUrl = getEntity().getConfig(NodeJsWebAppService.APP_GIT_REPOSITORY_URL);
- String archiveUrl = getEntity().getConfig(NodeJsWebAppService.APP_ARCHIVE_URL);
- String appName = getEntity().getConfig(NodeJsWebAppService.APP_NAME);
- if (Strings.isNonBlank(gitRepoUrl) && Strings.isNonBlank(archiveUrl)) {
- throw new IllegalStateException("Only one of Git or archive URL must be set for " + getEntity());
- } else if (Strings.isNonBlank(gitRepoUrl)) {
- commands.add(String.format("git clone %s %s", gitRepoUrl, appName));
- commands.add(String.format("cd %s", appName));
- } else if (Strings.isNonBlank(archiveUrl)) {
- ArchiveUtils.deploy(archiveUrl, getMachine(), getRunDir());
- } else {
- throw new IllegalStateException("At least one of Git or archive URL must be set for " + getEntity());
- }
-
- commands.add(BashCommands.ifFileExistsElse1("package.json", "npm install"));
- List<String> packages = getEntity().getConfig(NodeJsWebAppService.NODE_PACKAGE_LIST);
- if (packages != null && packages.size() > 0) {
- commands.add(BashCommands.sudo("npm install -g " + Joiner.on(' ').join(packages)));
- }
-
- newScript(CUSTOMIZING)
- .body.append(commands)
- .execute();
- }
-
- @Override
- public void launch() {
- List<String> commands = Lists.newLinkedList();
-
- String appName = getEntity().getConfig(NodeJsWebAppService.APP_NAME);
- String appFile = getEntity().getConfig(NodeJsWebAppService.APP_FILE);
- String appCommand = getEntity().getConfig(NodeJsWebAppService.APP_COMMAND);
- String appCommandLine = getEntity().getConfig(NodeJsWebAppService.APP_COMMAND_LINE);
-
- if (Strings.isBlank(appCommandLine)) {
- appCommandLine = appCommand + " " + appFile;
- }
-
- // Ensure global NPM modules are on Node's path.
- commands.add("export NODE_PATH=\"$NODE_PATH:$(npm root -g)\"");
- commands.add(String.format("cd %s", Os.mergePathsUnix(getRunDir(), appName)));
- commands.add("nohup " + appCommandLine + " > console.out 2>&1 &");
-
- newScript(MutableMap.of(USE_PID_FILE, true), LAUNCHING)
- .body.append(commands)
- .execute();
- }
-
- @Override
- public boolean isRunning() {
- return newScript(MutableMap.of(USE_PID_FILE, true), CHECK_RUNNING).execute() == 0;
- }
-
- @Override
- public void stop() {
- newScript(MutableMap.of(USE_PID_FILE, true), STOPPING).execute();
- }
-
- @Override
- public Map<String, String> getShellEnvironment() {
- return MutableMap.<String, String>builder().putAll(super.getShellEnvironment())
- .put("PORT", Integer.toString(getHttpPort()))
- .build();
- }
-
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7Driver.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7Driver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7Driver.java
deleted file mode 100644
index d5a98ac..0000000
--- a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7Driver.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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 brooklyn.entity.webapp.tomcat;
-
-@Deprecated
-public interface Tomcat7Driver extends TomcatDriver {
-}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7SshDriver.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7SshDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7SshDriver.java
deleted file mode 100644
index 7fc6150..0000000
--- a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7SshDriver.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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 brooklyn.entity.webapp.tomcat;
-
-import brooklyn.location.basic.SshMachineLocation;
-
-@Deprecated
-public class Tomcat7SshDriver extends TomcatSshDriver implements Tomcat7Driver {
-
- public Tomcat7SshDriver(TomcatServerImpl entity, SshMachineLocation machine) {
- super(entity, machine);
- }
-}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat8Server.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat8Server.java b/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat8Server.java
deleted file mode 100644
index 6262ac7..0000000
--- a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat8Server.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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 brooklyn.entity.webapp.tomcat;
-
-import org.apache.brooklyn.catalog.Catalog;
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.SoftwareProcess;
-import brooklyn.entity.proxying.ImplementedBy;
-import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
-import brooklyn.util.flags.SetFromFlag;
-import brooklyn.util.javalang.JavaClassNames;
-
-/**
- * An {@link brooklyn.entity.Entity} that represents a single Tomcat instance.
- */
-@Catalog(name="Tomcat Server",
- description="Apache Tomcat is an open source software implementation of the Java Servlet and JavaServer Pages technologies",
- iconUrl="classpath:///tomcat-logo.png")
-@ImplementedBy(Tomcat8ServerImpl.class)
-public interface Tomcat8Server extends TomcatServer {
-
- @SetFromFlag("version")
- ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "8.0.22");
-
- @SetFromFlag("downloadUrl")
- BasicAttributeSensorAndConfigKey<String> DOWNLOAD_URL = new BasicAttributeSensorAndConfigKey<String>(
- SoftwareProcess.DOWNLOAD_URL, "http://download.nextag.com/apache/tomcat/tomcat-8/v${version}/bin/apache-tomcat-${version}.tar.gz");
-
- @SetFromFlag("server.xml")
- ConfigKey<String> SERVER_XML_RESOURCE = ConfigKeys.newStringConfigKey(
- "tomcat.serverxml", "The file to template and use as the Tomcat process' server.xml",
- JavaClassNames.resolveClasspathUrl(Tomcat8Server.class, "tomcat8-server.xml"));
-
- @SetFromFlag("web.xml")
- ConfigKey<String> WEB_XML_RESOURCE = ConfigKeys.newStringConfigKey(
- "tomcat.webxml", "The file to template and use as the Tomcat process' web.xml",
- JavaClassNames.resolveClasspathUrl(Tomcat8Server.class, "tomcat8-web.xml"));
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat8ServerImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat8ServerImpl.java b/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat8ServerImpl.java
deleted file mode 100644
index 6858f51..0000000
--- a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat8ServerImpl.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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 brooklyn.entity.webapp.tomcat;
-
-/**
- * An {@link brooklyn.entity.Entity} that represents a single Tomcat instance.
- */
-public class Tomcat8ServerImpl extends TomcatServerImpl implements Tomcat8Server {
-}
-
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatDriver.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatDriver.java
deleted file mode 100644
index 00b4628..0000000
--- a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatDriver.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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 brooklyn.entity.webapp.tomcat;
-
-import brooklyn.entity.webapp.JavaWebAppDriver;
-
-public interface TomcatDriver extends JavaWebAppDriver {
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatServer.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatServer.java b/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatServer.java
deleted file mode 100644
index 24cd729..0000000
--- a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatServer.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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 brooklyn.entity.webapp.tomcat;
-
-import org.apache.brooklyn.catalog.Catalog;
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.SoftwareProcess;
-import brooklyn.entity.java.UsesJmx;
-import brooklyn.entity.proxying.ImplementedBy;
-import brooklyn.entity.trait.HasShortName;
-import brooklyn.entity.webapp.JavaWebAppSoftwareProcess;
-import brooklyn.event.AttributeSensor;
-import brooklyn.event.basic.BasicAttributeSensor;
-import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
-import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
-import brooklyn.location.basic.PortRanges;
-import brooklyn.util.flags.SetFromFlag;
-import brooklyn.util.javalang.JavaClassNames;
-import brooklyn.util.time.Duration;
-
-/**
- * An {@link brooklyn.entity.Entity} that represents a single Tomcat instance.
- */
-@Catalog(name="Tomcat Server",
- description="Apache Tomcat is an open source software implementation of the Java Servlet and JavaServer Pages technologies",
- iconUrl="classpath:///tomcat-logo.png")
-@ImplementedBy(TomcatServerImpl.class)
-public interface TomcatServer extends JavaWebAppSoftwareProcess, UsesJmx, HasShortName {
-
- @SetFromFlag("version")
- ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "7.0.56");
-
- @SetFromFlag("downloadUrl")
- BasicAttributeSensorAndConfigKey<String> DOWNLOAD_URL = new BasicAttributeSensorAndConfigKey<String>(
- SoftwareProcess.DOWNLOAD_URL, "http://download.nextag.com/apache/tomcat/tomcat-7/v${version}/bin/apache-tomcat-${version}.tar.gz");
-
- /**
- * Tomcat insists on having a port you can connect to for the sole purpose of shutting it down.
- * Don't see an easy way to disable it; causes collisions in its out-of-the-box location of 8005,
- * so override default here to a high-numbered port.
- */
- @SetFromFlag("shutdownPort")
- PortAttributeSensorAndConfigKey SHUTDOWN_PORT =
- ConfigKeys.newPortSensorAndConfigKey("tomcat.shutdownport", "Suggested shutdown port", PortRanges.fromString("31880+"));
-
- @SetFromFlag("server.xml")
- ConfigKey<String> SERVER_XML_RESOURCE = ConfigKeys.newStringConfigKey(
- "tomcat.serverxml", "The file to template and use as the Tomcat process' server.xml",
- JavaClassNames.resolveClasspathUrl(TomcatServer.class, "server.xml"));
-
- @SetFromFlag("web.xml")
- ConfigKey<String> WEB_XML_RESOURCE = ConfigKeys.newStringConfigKey(
- "tomcat.webxml", "The file to template and use as the Tomcat process' web.xml",
- JavaClassNames.resolveClasspathUrl(TomcatServer.class, "web.xml"));
-
- ConfigKey<Duration> START_TIMEOUT = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.START_TIMEOUT, Duration.FIVE_MINUTES);
-
- AttributeSensor<String> CONNECTOR_STATUS =
- new BasicAttributeSensor<String>(String.class, "webapp.tomcat.connectorStatus", "Catalina connector state name");
-
- AttributeSensor<String> JMX_SERVICE_URL = UsesJmx.JMX_URL;
-
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatServerImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatServerImpl.java b/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatServerImpl.java
deleted file mode 100644
index e5b5ac0..0000000
--- a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatServerImpl.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * 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 brooklyn.entity.webapp.tomcat;
-
-import static java.lang.String.format;
-
-import java.util.concurrent.TimeUnit;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.entity.java.JavaAppUtils;
-import brooklyn.entity.webapp.JavaWebAppSoftwareProcessImpl;
-import brooklyn.event.feed.jmx.JmxAttributePollConfig;
-import brooklyn.event.feed.jmx.JmxFeed;
-
-import com.google.common.base.Functions;
-import com.google.common.base.Predicates;
-
-/**
- * An {@link brooklyn.entity.Entity} that represents a single Tomcat instance.
- */
-public class TomcatServerImpl extends JavaWebAppSoftwareProcessImpl implements TomcatServer {
-
- private static final Logger LOG = LoggerFactory.getLogger(TomcatServerImpl.class);
-
- public TomcatServerImpl() {
- super();
- }
-
- private volatile JmxFeed jmxWebFeed;
- private volatile JmxFeed jmxAppFeed;
-
- @Override
- public void connectSensors() {
- super.connectSensors();
-
- if (getDriver().isJmxEnabled()) {
- String requestProcessorMbeanName = "Catalina:type=GlobalRequestProcessor,name=\"http-*\"";
-
- Integer port = isHttpsEnabled() ? getAttribute(HTTPS_PORT) : getAttribute(HTTP_PORT);
- String connectorMbeanName = format("Catalina:type=Connector,port=%s", port);
- boolean retrieveUsageMetrics = getConfig(RETRIEVE_USAGE_METRICS);
-
- jmxWebFeed = JmxFeed.builder()
- .entity(this)
- .period(3000, TimeUnit.MILLISECONDS)
- .pollAttribute(new JmxAttributePollConfig<Boolean>(SERVICE_PROCESS_IS_RUNNING)
- // TODO Want to use something different from SERVICE_PROCESS_IS_RUNNING,
- // to indicate this is jmx MBean's reported state (or failure to connect)
- .objectName(connectorMbeanName)
- .attributeName("stateName")
- .onSuccess(Functions.forPredicate(Predicates.<Object>equalTo("STARTED")))
- .setOnFailureOrException(false)
- .suppressDuplicates(true))
- .pollAttribute(new JmxAttributePollConfig<String>(CONNECTOR_STATUS)
- .objectName(connectorMbeanName)
- .attributeName("stateName")
- .suppressDuplicates(true))
- .pollAttribute(new JmxAttributePollConfig<Integer>(ERROR_COUNT)
- .objectName(requestProcessorMbeanName)
- .attributeName("errorCount")
- .enabled(retrieveUsageMetrics))
- .pollAttribute(new JmxAttributePollConfig<Integer>(REQUEST_COUNT)
- .objectName(requestProcessorMbeanName)
- .attributeName("requestCount")
- .enabled(retrieveUsageMetrics))
- .pollAttribute(new JmxAttributePollConfig<Integer>(TOTAL_PROCESSING_TIME)
- .objectName(requestProcessorMbeanName)
- .attributeName("processingTime")
- .enabled(retrieveUsageMetrics))
- .build();
-
- jmxAppFeed = JavaAppUtils.connectMXBeanSensors(this);
- } else {
- // if not using JMX
- LOG.warn("Tomcat running without JMX monitoring; limited visibility of service available");
- connectServiceUpIsRunning();
- }
- }
-
- @Override
- public void disconnectSensors() {
- super.disconnectSensors();
- if (getDriver() != null && getDriver().isJmxEnabled()) {
- if (jmxWebFeed != null) jmxWebFeed.stop();
- if (jmxAppFeed != null) jmxAppFeed.stop();
- } else {
- disconnectServiceUpIsRunning();
- }
- }
-
- @SuppressWarnings("rawtypes")
- @Override
- public Class getDriverInterface() {
- return TomcatDriver.class;
- }
-
- @Override
- public String getShortName() {
- return "Tomcat";
- }
-}
-
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatSshDriver.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatSshDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatSshDriver.java
deleted file mode 100644
index 76c820d..0000000
--- a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/TomcatSshDriver.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * 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 brooklyn.entity.webapp.tomcat;
-
-import static java.lang.String.format;
-
-import java.io.InputStream;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.webapp.JavaWebAppSshDriver;
-import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.util.collections.MutableList;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.net.Networking;
-import brooklyn.util.os.Os;
-import brooklyn.util.ssh.BashCommands;
-import brooklyn.util.text.StringEscapes.BashStringEscapes;
-
-import com.google.common.base.Preconditions;
-
-public class TomcatSshDriver extends JavaWebAppSshDriver implements TomcatDriver {
-
- private static final String KEYSTORE_FILE = "keystore";
-
- public TomcatSshDriver(TomcatServerImpl entity, SshMachineLocation machine) {
- super(entity, machine);
- }
-
- @Override
- public void preInstall() {
- resolver = Entities.newDownloader(this);
- setExpandedInstallDir(Os.mergePaths(getInstallDir(), resolver.getUnpackedDirectoryName("apache-tomcat-"+getVersion())));
- }
-
- @Override
- public void install() {
- List<String> urls = resolver.getTargets();
- String saveAs = resolver.getFilename();
-
- List<String> commands = new LinkedList<String>();
- commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs));
- commands.add(BashCommands.INSTALL_TAR);
- commands.add(format("tar xvzf %s", saveAs));
-
- newScript(INSTALLING)
- .environmentVariablesReset()
- .body.append(commands)
- .execute();
- }
-
- @Override
- public void customize() {
- newScript(CUSTOMIZING)
- .body.append("mkdir -p conf logs webapps temp")
- .failOnNonZeroResultCode()
- .execute();
-
- copyTemplate(entity.getConfig(TomcatServer.SERVER_XML_RESOURCE), Os.mergePaths(getRunDir(), "conf", "server.xml"));
- copyTemplate(entity.getConfig(TomcatServer.WEB_XML_RESOURCE), Os.mergePaths(getRunDir(), "conf", "web.xml"));
-
- // Deduplicate same code in JBoss
- if (isProtocolEnabled("HTTPS")) {
- String keystoreUrl = Preconditions.checkNotNull(getSslKeystoreUrl(), "keystore URL must be specified if using HTTPS for " + entity);
- String destinationSslKeystoreFile = getHttpsSslKeystoreFile();
- InputStream keystoreStream = resource.getResourceFromUrl(keystoreUrl);
- getMachine().copyTo(keystoreStream, destinationSslKeystoreFile);
- }
-
- getEntity().deployInitialWars();
- }
-
- @Override
- public void launch() {
- Map<String, Integer> ports = MutableMap.of("httpPort", getHttpPort(), "shutdownPort", getShutdownPort());
- Networking.checkPortsValid(ports);
-
- // We wait for evidence of tomcat running because, using
- // brooklyn.ssh.config.tool.class=brooklyn.util.internal.ssh.cli.SshCliTool,
- // we saw the ssh session return before the tomcat process was fully running
- // so the process failed to start.
- newScript(MutableMap.of(USE_PID_FILE, false), LAUNCHING)
- .body.append(
- format("%s/bin/startup.sh >>$RUN/console 2>&1 </dev/null",getExpandedInstallDir()),
- "for i in {1..10}\n" +
- "do\n" +
- " if [ -s "+getLogFileLocation()+" ]; then exit; fi\n" +
- " sleep 1\n" +
- "done\n" +
- "echo \"Couldn't determine if tomcat-server is running (logs/catalina.out is still empty); continuing but may subsequently fail\""
- )
- .execute();
- }
-
- @Override
- public boolean isRunning() {
- return newScript(MutableMap.of(USE_PID_FILE, "pid.txt"), CHECK_RUNNING).execute() == 0;
- }
-
- @Override
- public void stop() {
- newScript(MutableMap.of(USE_PID_FILE, "pid.txt"), STOPPING).execute();
- }
-
- @Override
- public void kill() {
- newScript(MutableMap.of(USE_PID_FILE, "pid.txt"), KILLING).execute();
- }
-
- @Override
- protected List<String> getCustomJavaConfigOptions() {
- return MutableList.<String>builder()
- .addAll(super.getCustomJavaConfigOptions())
- .add("-Xms200m")
- .add("-Xmx800m")
- .add("-XX:MaxPermSize=400m")
- .build();
- }
-
- @Override
- public Map<String, String> getShellEnvironment() {
- Map<String, String> shellEnv = MutableMap.<String, String>builder()
- .putAll(super.getShellEnvironment())
- .remove("JAVA_OPTS")
- .put("CATALINA_PID", "pid.txt")
- .put("CATALINA_BASE", getRunDir())
- .put("RUN", getRunDir())
- .build();
-
- // Double quoting of individual JAVA_OPTS entries required due to eval in catalina.sh
- List<String> javaOpts = getJavaOpts();
- String sJavaOpts = BashStringEscapes.doubleQuoteLiteralsForBash(javaOpts.toArray(new String[0]));
- shellEnv.put("CATALINA_OPTS", sJavaOpts);
-
- return shellEnv;
- }
-
- @Override
- protected String getLogFileLocation() {
- return Os.mergePathsUnix(getRunDir(), "logs/catalina.out");
- }
-
- @Override
- protected String getDeploySubdir() {
- return "webapps";
- }
-
- public Integer getShutdownPort() {
- return entity.getAttribute(TomcatServerImpl.SHUTDOWN_PORT);
- }
-
- public String getHttpsSslKeystoreFile() {
- return Os.mergePathsUnix(getRunDir(), "conf", KEYSTORE_FILE);
- }
-
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsService.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsService.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsService.java
new file mode 100644
index 0000000..670a6aa
--- /dev/null
+++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsService.java
@@ -0,0 +1,59 @@
+/*
+ * 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.dns;
+
+import java.util.Map;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Entity;
+import brooklyn.entity.Group;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.trait.Startable;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.basic.BasicAttributeSensor;
+import brooklyn.location.geo.HostGeoInfo;
+
+import com.google.common.reflect.TypeToken;
+
+public interface AbstractGeoDnsService extends Entity {
+
+ public static final ConfigKey<Boolean> INCLUDE_HOMELESS_ENTITIES = ConfigKeys.newBooleanConfigKey("geodns.includeHomeless", "Whether to include entities whose geo-coordinates cannot be inferred", false);
+ public static final ConfigKey<Boolean> USE_HOSTNAMES = ConfigKeys.newBooleanConfigKey("geodns.useHostnames", "Whether to use the hostname for the returned value for routing, rather than IP address (defaults to true)", true);
+
+ public static final AttributeSensor<Lifecycle> SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL;
+ public static final AttributeSensor<Boolean> SERVICE_UP = Startable.SERVICE_UP;
+ public static final AttributeSensor<String> HOSTNAME = Attributes.HOSTNAME;
+ public static final AttributeSensor<String> ADDRESS = Attributes.ADDRESS;
+ @SuppressWarnings("serial")
+ public static final AttributeSensor<Map<String,String>> TARGETS = new BasicAttributeSensor<Map<String,String>>(
+ new TypeToken<Map<String,String>>() {}, "geodns.targets", "Map of targets currently being managed (entity ID to URL)");
+
+ public void setServiceState(Lifecycle state);
+
+ /** sets target to be a group whose *members* will be searched (non-Group items not supported) */
+ // prior to 0.7.0 the API accepted non-group items, but did not handle them correctly
+ public void setTargetEntityProvider(final Group entityProvider);
+
+ /** should return the hostname which this DNS service is configuring */
+ public String getHostname();
+
+ public Map<Entity, HostGeoInfo> getTargetHosts();
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java
new file mode 100644
index 0000000..a6b2b5a
--- /dev/null
+++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/AbstractGeoDnsServiceImpl.java
@@ -0,0 +1,373 @@
+/*
+ * 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.dns;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.entity.webapp.WebAppService;
+import org.apache.brooklyn.policy.PolicySpec;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.Group;
+import brooklyn.entity.basic.AbstractEntity;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.DynamicGroup;
+import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
+import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
+import brooklyn.entity.group.AbstractMembershipTrackingPolicy;
+import brooklyn.location.geo.HostGeoInfo;
+import brooklyn.util.collections.MutableSet;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.flags.SetFromFlag;
+import brooklyn.util.net.Networking;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+
+public abstract class AbstractGeoDnsServiceImpl extends AbstractEntity implements AbstractGeoDnsService {
+ private static final Logger log = LoggerFactory.getLogger(AbstractGeoDnsService.class);
+
+ @SetFromFlag
+ protected Group targetEntityProvider;
+ protected AbstractMembershipTrackingPolicy tracker;
+
+ protected Map<Entity, HostGeoInfo> targetHosts = Collections.synchronizedMap(new LinkedHashMap<Entity, HostGeoInfo>());
+
+ // We complain (at debug) when we encounter a target entity for whom we can't derive hostname/ip information;
+ // this is the commonest case for the transient condition between the time the entity is created and the time
+ // it is started (at which point the location is specified). This set contains those entities we've complained
+ // about already, to avoid repetitive logging.
+ transient protected Set<Entity> entitiesWithoutHostname = new HashSet<Entity>();
+
+ // We complain (at info/warn) when we encounter a target entity for whom we can't derive geo information, even
+ // when hostname/ip is known. This set contains those entities we've complained about already, to avoid repetitive
+ // logging.
+ transient protected Set<Entity> entitiesWithoutGeoInfo = new HashSet<Entity>();
+
+ public AbstractGeoDnsServiceImpl() {
+ super();
+ }
+
+ @Override
+ public Map<Entity, HostGeoInfo> getTargetHosts() {
+ return targetHosts;
+ }
+
+ @Override
+ public void onManagementBecomingMaster() {
+ super.onManagementBecomingMaster();
+ startTracker();
+ }
+ @Override
+ public void onManagementNoLongerMaster() {
+ endTracker();
+ super.onManagementNoLongerMaster();
+ }
+
+ @Override
+ public void destroy() {
+ setServiceState(Lifecycle.DESTROYED);
+ super.destroy();
+ }
+
+ @Override
+ public void setServiceState(Lifecycle state) {
+ setAttribute(HOSTNAME, getHostname());
+ ServiceStateLogic.setExpectedState(this, state);
+ if (state==Lifecycle.RUNNING)
+ ServiceNotUpLogic.clearNotUpIndicator(this, SERVICE_STATE_ACTUAL);
+ else
+ ServiceNotUpLogic.updateNotUpIndicator(this, SERVICE_STATE_ACTUAL, "Not in RUNNING state");
+ }
+
+ @Override
+ public void setTargetEntityProvider(final Group entityProvider) {
+ this.targetEntityProvider = checkNotNull(entityProvider, "targetEntityProvider");
+ startTracker();
+ }
+
+ /** should set up so these hosts are targeted, and setServiceState appropriately */
+ protected abstract void reconfigureService(Collection<HostGeoInfo> targetHosts);
+
+ protected synchronized void startTracker() {
+ if (targetEntityProvider==null || !getManagementSupport().isDeployed()) {
+ log.debug("Tracker for "+this+" not yet active: "+targetEntityProvider+" / "+getManagementContext());
+ return;
+ }
+ endTracker();
+ log.debug("Initializing tracker for "+this+", following "+targetEntityProvider);
+ tracker = addPolicy(PolicySpec.create(MemberTrackingPolicy.class)
+ .displayName("GeoDNS targets tracker")
+ .configure("sensorsToTrack", ImmutableSet.of(HOSTNAME, ADDRESS, Attributes.MAIN_URI, WebAppService.ROOT_URL))
+ .configure("group", targetEntityProvider));
+ refreshGroupMembership();
+ }
+
+ protected synchronized void endTracker() {
+ if (tracker == null || targetEntityProvider==null) return;
+ removePolicy(tracker);
+ tracker = null;
+ }
+
+ public static class MemberTrackingPolicy extends AbstractMembershipTrackingPolicy {
+ @Override
+ protected void onEntityEvent(EventType type, Entity entity) {
+ ((AbstractGeoDnsServiceImpl)super.entity).refreshGroupMembership();
+ }
+ }
+
+ @Override
+ public abstract String getHostname();
+
+ long lastUpdate = -1;
+
+ // TODO: remove group member polling once locations can be determined via subscriptions
+ protected void refreshGroupMembership() {
+ try {
+ if (log.isDebugEnabled()) log.debug("GeoDns {} refreshing targets", this);
+ if (targetEntityProvider == null)
+ return;
+ if (targetEntityProvider instanceof DynamicGroup)
+ ((DynamicGroup) targetEntityProvider).rescanEntities();
+ Set<Entity> pool = MutableSet.copyOf(targetEntityProvider instanceof Group ? ((Group)targetEntityProvider).getMembers(): targetEntityProvider.getChildren());
+ if (log.isDebugEnabled()) log.debug("GeoDns {} refreshing targets, pool now {}", this, pool);
+
+ boolean changed = false;
+ Set<Entity> previousOnes = MutableSet.copyOf(targetHosts.keySet());
+ for (Entity e: pool) {
+ previousOnes.remove(e);
+ changed |= addTargetHost(e);
+ }
+ // anything left in previousOnes is no longer applicable
+ for (Entity e: previousOnes) {
+ changed = true;
+ removeTargetHost(e, false);
+ }
+
+ // do a periodic full update hourly once we are active (the latter is probably not needed)
+ if (changed || (lastUpdate>0 && Time.hasElapsedSince(lastUpdate, Duration.ONE_HOUR)))
+ update();
+
+ } catch (Exception e) {
+ log.error("Problem refreshing group membership: "+e, e);
+ }
+ }
+
+ /**
+ * Adds this host, if it is absent or if its hostname has changed.
+ * <p>
+ * For whether to use hostname or ip, see config and attributes {@link AbstractGeoDnsService#USE_HOSTNAMES},
+ * {@link Attributes#HOSTNAME} and {@link Attributes#ADDRESS} (via {@link #inferHostname(Entity)} and {@link #inferIp(Entity)}.
+ * Note that the "hostname" could in fact be an IP address, if {@link #inferHostname(Entity)} returns an IP!
+ * <p>
+ * TODO in a future release, we may change this to explicitly set the sensor(s) to look at on the entity, and
+ * be stricter about using them in order.
+ *
+ * @return true if host is added or changed
+ */
+ protected boolean addTargetHost(Entity entity) {
+ try {
+ HostGeoInfo oldGeo = targetHosts.get(entity);
+ String hostname = inferHostname(entity);
+ String ip = inferIp(entity);
+ String addr = (getConfig(USE_HOSTNAMES) || ip == null) ? hostname : ip;
+
+ if (addr==null) addr = ip;
+ if (addr == null) {
+ if (entitiesWithoutHostname.add(entity)) {
+ log.debug("GeoDns ignoring {} (no hostname/ip/URL info yet available)", entity);
+ }
+ return false;
+ }
+
+ // prefer the geo from the entity (or location parent), but fall back to inferring
+ // e.g. if it supplies a URL
+ HostGeoInfo geo = HostGeoInfo.fromEntity(entity);
+ if (geo==null) geo = inferHostGeoInfo(hostname, ip);
+
+ if (Networking.isPrivateSubnet(addr) && ip!=null && !Networking.isPrivateSubnet(ip)) {
+ // fix for #1216
+ log.debug("GeoDns using IP "+ip+" for "+entity+" as addr "+addr+" resolves to private subnet");
+ addr = ip;
+ }
+ if (Networking.isPrivateSubnet(addr)) {
+ if (getConfig(INCLUDE_HOMELESS_ENTITIES)) {
+ if (entitiesWithoutGeoInfo.add(entity)) {
+ log.info("GeoDns including {}, even though {} is a private subnet (homeless entities included)", entity, addr);
+ }
+ } else {
+ if (entitiesWithoutGeoInfo.add(entity)) {
+ log.warn("GeoDns ignoring {} (private subnet detected for {})", entity, addr);
+ }
+ return false;
+ }
+ }
+
+ if (geo == null) {
+ if (getConfig(INCLUDE_HOMELESS_ENTITIES)) {
+ if (entitiesWithoutGeoInfo.add(entity)) {
+ log.info("GeoDns including {}, even though no geography info available for {})", entity, addr);
+ }
+ geo = HostGeoInfo.create(addr, "unknownLocation("+addr+")", 0, 0);
+ } else {
+ if (entitiesWithoutGeoInfo.add(entity)) {
+ log.warn("GeoDns ignoring {} (no geography info available for {})", entity, addr);
+ }
+ return false;
+ }
+ }
+
+ if (!addr.equals(geo.getAddress())) {
+ // if the location provider did not have an address, create a new one with it
+ geo = HostGeoInfo.create(addr, geo.displayName, geo.latitude, geo.longitude);
+ }
+
+ // If we already knew about it, and it hasn't changed, then nothing to do
+ if (oldGeo != null && geo.getAddress().equals(oldGeo.getAddress())) {
+ return false;
+ }
+
+ entitiesWithoutHostname.remove(entity);
+ entitiesWithoutGeoInfo.remove(entity);
+ log.info("GeoDns adding "+entity+" at "+geo+(oldGeo != null ? " (previously "+oldGeo+")" : ""));
+ targetHosts.put(entity, geo);
+ return true;
+
+ } catch (Exception ee) {
+ log.warn("GeoDns ignoring "+entity+" (error analysing location): "+ee, ee);
+ return false;
+ }
+ }
+
+ /** remove if host removed */
+ protected boolean removeTargetHost(Entity e, boolean doUpdate) {
+ if (targetHosts.remove(e) != null) {
+ log.info("GeoDns removing reference to {}", e);
+ if (doUpdate) update();
+ return true;
+ }
+ return false;
+ }
+
+ protected void update() {
+ lastUpdate = System.currentTimeMillis();
+
+ Map<Entity, HostGeoInfo> m;
+ synchronized(targetHosts) { m = ImmutableMap.copyOf(targetHosts); }
+ if (log.isDebugEnabled()) log.debug("Full update of "+this+" ("+m.size()+" target hosts)");
+
+ Map<String,String> entityIdToAddress = Maps.newLinkedHashMap();
+ for (Map.Entry<Entity, HostGeoInfo> entry : m.entrySet()) {
+ entityIdToAddress.put(entry.getKey().getId(), entry.getValue().address);
+ }
+
+ reconfigureService(new LinkedHashSet<HostGeoInfo>(m.values()));
+
+ if (log.isDebugEnabled()) log.debug("Targets being set as "+entityIdToAddress);
+ setAttribute(TARGETS, entityIdToAddress);
+ }
+
+ protected String inferHostname(Entity entity) {
+ String hostname = entity.getAttribute(Attributes.HOSTNAME);
+ URI url = entity.getAttribute(Attributes.MAIN_URI);
+ if (url!=null) {
+ try {
+ URL u = url.toURL();
+
+ String hostname2 = u.getHost();
+ if (hostname==null) {
+ if (!entitiesWithoutGeoInfo.contains(entity)) //don't log repeatedly
+ log.warn("GeoDns "+this+" using URL {} to redirect to {} (HOSTNAME attribute is preferred, but not available)", url, entity);
+ hostname = hostname2;
+ } else if (!hostname.equals(hostname2)) {
+ if (!entitiesWithoutGeoInfo.contains(entity)) //don't log repeatedly
+ log.warn("GeoDns "+this+" URL {} of "+entity+" does not match advertised HOSTNAME {}; using hostname, not URL", url, hostname);
+ }
+
+ if (u.getPort() > 0 && u.getPort() != 80 && u.getPort() != 443) {
+ if (!entitiesWithoutGeoInfo.contains(entity)) //don't log repeatedly
+ log.warn("GeoDns "+this+" detected non-standard port in URL {} for {}; forwarding may not work", url, entity);
+ }
+
+ } catch (MalformedURLException e) {
+ log.warn("Invalid URL {} for entity {} in {}", new Object[] {url, entity, this});
+ }
+ }
+ return hostname;
+ }
+
+ protected String inferIp(Entity entity) {
+ return entity.getAttribute(Attributes.ADDRESS);
+ }
+
+ protected HostGeoInfo inferHostGeoInfo(String hostname, String ip) throws UnknownHostException {
+ HostGeoInfo geoH = null;
+ if (hostname != null) {
+ try {
+ // For some entities, the hostname can actually be an IP! Therefore use Networking.getInetAddressWithFixedName
+ InetAddress addr = Networking.getInetAddressWithFixedName(hostname);
+ geoH = HostGeoInfo.fromIpAddress(addr);
+ } catch (RuntimeException e) {
+ // Most likely caused by (a wrapped) UnknownHostException
+ Exceptions.propagateIfFatal(e);
+ if (ip == null) {
+ if (log.isTraceEnabled()) log.trace("inferHostGeoInfo failing ("+Exceptions.getFirstInteresting(e)+"): hostname="+hostname+"; ip="+ip);
+ throw e;
+ } else {
+ if (log.isTraceEnabled()) log.trace("GeoDns failed to infer GeoInfo from hostname {}; will try with IP {} ({})", new Object[] {hostname, ip, e});
+ }
+ }
+ }
+
+ // Try IP address (prior to Mar 2014 we did not do this if USE_HOSTNAME was set but don't think that is desirable due to #1216)
+ if (ip != null) {
+ if (geoH == null) {
+ InetAddress addr = Networking.getInetAddressWithFixedName(ip);
+ geoH = HostGeoInfo.fromIpAddress(addr);
+ if (log.isTraceEnabled()) log.trace("GeoDns inferred GeoInfo {} from ip {} (could not infer from hostname {})", new Object[] {geoH, ip, hostname});
+ } else {
+ geoH = HostGeoInfo.create(ip, geoH.displayName, geoH.latitude, geoH.longitude);
+ if (log.isTraceEnabled()) log.trace("GeoDns inferred GeoInfo {} from hostname {}; switching it to ip {}", new Object[] {geoH, hostname, ip});
+ }
+ } else {
+ if (log.isTraceEnabled()) log.trace("GeoDns inferred GeoInfo {} from hostname {}", geoH, hostname);
+ }
+
+ return geoH;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsService.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsService.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsService.java
new file mode 100644
index 0000000..31c8831
--- /dev/null
+++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsService.java
@@ -0,0 +1,71 @@
+/*
+ * 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.dns.geoscaling;
+
+import java.net.URI;
+
+import org.apache.brooklyn.entity.dns.AbstractGeoDnsService;
+import org.apache.brooklyn.entity.webapp.WebAppServiceConstants;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.proxying.ImplementedBy;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.basic.BasicAttributeSensor;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.util.flags.SetFromFlag;
+
+@ImplementedBy(GeoscalingDnsServiceImpl.class)
+public interface GeoscalingDnsService extends AbstractGeoDnsService {
+
+ @SetFromFlag("sslTrustAll")
+ public static final ConfigKey<Boolean> SSL_TRUST_ALL = ConfigKeys.newBooleanConfigKey(
+ "ssl.trustAll",
+ "Whether to trust all certificates, or to fail with 'peer not authenticated' if untrusted (default false)",
+ false);
+ @SetFromFlag("randomizeSubdomainName")
+ public static final ConfigKey<Boolean> RANDOMIZE_SUBDOMAIN_NAME = new BasicConfigKey<Boolean>(
+ Boolean.class, "randomize.subdomain.name");
+ @SetFromFlag("username")
+ public static final ConfigKey<String> GEOSCALING_USERNAME = new BasicConfigKey<String>(
+ String.class, "geoscaling.username");
+ @SetFromFlag("password")
+ public static final ConfigKey<String> GEOSCALING_PASSWORD = new BasicConfigKey<String>(
+ String.class, "geoscaling.password");
+ @SetFromFlag("primaryDomainName")
+ public static final ConfigKey<String> GEOSCALING_PRIMARY_DOMAIN_NAME = new BasicConfigKey<String>(
+ String.class, "geoscaling.primary.domain.name");
+ @SetFromFlag("smartSubdomainName")
+ public static final ConfigKey<String> GEOSCALING_SMART_SUBDOMAIN_NAME = new BasicConfigKey<String>(
+ String.class, "geoscaling.smart.subdomain.name");
+
+ public static final AttributeSensor<String> GEOSCALING_ACCOUNT = new BasicAttributeSensor<String>(
+ String.class, "geoscaling.account", "Active user account for the GeoScaling.com service");
+ public static final AttributeSensor<URI> MAIN_URI = Attributes.MAIN_URI;
+ public static final AttributeSensor<String> ROOT_URL = WebAppServiceConstants.ROOT_URL;
+ public static final AttributeSensor<String> MANAGED_DOMAIN = new BasicAttributeSensor<String>(
+ String.class, "geoscaling.managed.domain", "Fully qualified domain name that will be geo-redirected; " +
+ "this will be the same as "+ROOT_URL.getName()+" but the latter will only be set when the domain has active targets");
+
+ public void applyConfig();
+
+ /** minimum/default TTL here is 300s = 5m */
+ public long getTimeToLiveSeconds();
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java
new file mode 100644
index 0000000..5779ec2
--- /dev/null
+++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingDnsServiceImpl.java
@@ -0,0 +1,200 @@
+/*
+ * 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.dns.geoscaling;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.apache.brooklyn.entity.dns.geoscaling.GeoscalingWebClient.PROVIDE_CITY_INFO;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.Set;
+
+import org.apache.brooklyn.entity.dns.AbstractGeoDnsServiceImpl;
+import org.apache.brooklyn.entity.dns.geoscaling.GeoscalingWebClient.Domain;
+import org.apache.brooklyn.entity.dns.geoscaling.GeoscalingWebClient.SmartSubdomain;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
+import brooklyn.location.geo.HostGeoInfo;
+import brooklyn.util.collections.MutableSet;
+import brooklyn.util.http.HttpTool;
+import brooklyn.util.text.Identifiers;
+import brooklyn.util.text.Strings;
+
+public class GeoscalingDnsServiceImpl extends AbstractGeoDnsServiceImpl implements GeoscalingDnsService {
+
+ private static final Logger log = LoggerFactory.getLogger(GeoscalingDnsServiceImpl.class);
+
+ // Must remember any desired redirection targets if they're specified before configure() has been called.
+ private Set<HostGeoInfo> rememberedTargetHosts;
+ private GeoscalingWebClient webClient;
+
+ // These are available only after the configure() method has been invoked.
+ private boolean randomizeSmartSubdomainName;
+ private String username;
+ private String password;
+ private String primaryDomainName;
+ private String smartSubdomainName;
+
+ public GeoscalingDnsServiceImpl() {
+ }
+
+ @Override
+ public void init() {
+ super.init();
+
+ // defaulting to randomized subdomains makes deploying multiple applications easier
+ if (getConfig(RANDOMIZE_SUBDOMAIN_NAME)==null) setConfig(RANDOMIZE_SUBDOMAIN_NAME, true);
+
+ Boolean trustAll = getConfig(SSL_TRUST_ALL);
+ if (Boolean.TRUE.equals(trustAll)) {
+ webClient = new GeoscalingWebClient(HttpTool.httpClientBuilder().trustAll().build());
+ } else {
+ webClient = new GeoscalingWebClient();
+ }
+ }
+
+ // Ensure our configure() method gets called; may be able to remove this if/when the framework detects this
+ // and invokes the configure() method automatically?
+ @Override
+ public void onManagementBecomingMaster() {
+ try {
+ applyConfig();
+ } catch (Exception e) {
+ // don't prevent management coming up, but do mark it as on fire
+ log.error("Geoscaling did not come up correctly: "+e, e);
+ ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
+ }
+ super.onManagementBecomingMaster();
+ }
+
+ boolean isConfigured = false;
+
+ public synchronized void applyConfig() {
+ randomizeSmartSubdomainName = getConfig(RANDOMIZE_SUBDOMAIN_NAME);
+ username = getConfig(GEOSCALING_USERNAME);
+ password = getConfig(GEOSCALING_PASSWORD);
+ primaryDomainName = getConfig(GEOSCALING_PRIMARY_DOMAIN_NAME);
+ smartSubdomainName = getConfig(GEOSCALING_SMART_SUBDOMAIN_NAME);
+
+ // Ensure all mandatory configuration is provided.
+ checkNotNull(username, "The GeoScaling username is not specified");
+ checkNotNull(password, "The GeoScaling password is not specified");
+ checkNotNull(primaryDomainName, "The GeoScaling primary domain name is not specified");
+
+ if (randomizeSmartSubdomainName) {
+ // if no smart subdomain specified, but random is, use something random
+ if (smartSubdomainName != null) smartSubdomainName += "-";
+ else smartSubdomainName = "";
+ smartSubdomainName += Identifiers.makeRandomId(8);
+ }
+ checkNotNull(smartSubdomainName, "The GeoScaling smart subdomain name is not specified or randomized");
+
+ String fullDomain = smartSubdomainName+"."+primaryDomainName;
+ log.info("GeoScaling service will configure redirection for '"+fullDomain+"' domain");
+ setAttribute(GEOSCALING_ACCOUNT, username);
+ setAttribute(MANAGED_DOMAIN, fullDomain);
+ setAttribute(HOSTNAME, getHostname());
+
+ isConfigured = true;
+
+ if (rememberedTargetHosts != null) {
+ reconfigureService(rememberedTargetHosts);
+ rememberedTargetHosts = null;
+ }
+ }
+
+ @Override
+ public String getHostname() {
+ String result = getAttribute(MANAGED_DOMAIN);
+ return (Strings.isBlank(result)) ? null : result;
+ }
+
+ /** minimum/default TTL here is 300s = 5m */
+ public long getTimeToLiveSeconds() { return 5*60; }
+
+ @Override
+ public void destroy() {
+ setServiceState(Lifecycle.STOPPING);
+ if (!isConfigured) return;
+
+ // Don't leave randomized subdomains configured on our GeoScaling account.
+ if (randomizeSmartSubdomainName) {
+ webClient.login(username, password);
+ Domain primaryDomain = webClient.getPrimaryDomain(primaryDomainName);
+ SmartSubdomain smartSubdomain = (primaryDomain != null) ? primaryDomain.getSmartSubdomain(smartSubdomainName) : null;
+ if (smartSubdomain != null) {
+ log.info("Deleting randomized GeoScaling smart subdomain '"+smartSubdomainName+"."+primaryDomainName+"'");
+ smartSubdomain.delete();
+ }
+ webClient.logout();
+ }
+
+ super.destroy();
+
+ isConfigured = false;
+ }
+
+ protected void reconfigureService(Collection<HostGeoInfo> targetHosts) {
+ if (!isConfigured) {
+ this.rememberedTargetHosts = MutableSet.copyOf(targetHosts);
+ return;
+ }
+
+ webClient.login(username, password);
+ Domain primaryDomain = webClient.getPrimaryDomain(primaryDomainName);
+ if (primaryDomain==null)
+ throw new NullPointerException(this+" got null from web client for primary domain "+primaryDomainName);
+ SmartSubdomain smartSubdomain = primaryDomain.getSmartSubdomain(smartSubdomainName);
+
+ if (smartSubdomain == null) {
+ log.info("GeoScaling {} smart subdomain '{}.{}' does not exist, creating it now", new Object[] {this, smartSubdomainName, primaryDomainName});
+ // TODO use WithMutexes to ensure this is single-entrant
+ primaryDomain.createSmartSubdomain(smartSubdomainName);
+ smartSubdomain = primaryDomain.getSmartSubdomain(smartSubdomainName);
+ }
+
+ if (smartSubdomain != null) {
+ log.debug("GeoScaling {} being reconfigured to use {}", this, targetHosts);
+ String script = GeoscalingScriptGenerator.generateScriptString(targetHosts);
+ smartSubdomain.configure(PROVIDE_CITY_INFO, script);
+ if (targetHosts.isEmpty()) {
+ setServiceState(Lifecycle.CREATED);
+ setAttribute(ROOT_URL, null);
+ setAttribute(MAIN_URI, null);
+ } else {
+ setServiceState(Lifecycle.RUNNING);
+ String domain = getAttribute(MANAGED_DOMAIN);
+ if (!Strings.isEmpty(domain)) {
+ setAttribute(ROOT_URL, "http://"+domain+"/");
+ setAttribute(MAIN_URI, URI.create("http://"+domain+"/"));
+ }
+ }
+ } else {
+ log.warn("Failed to retrieve or create GeoScaling smart subdomain '"+smartSubdomainName+"."+primaryDomainName+
+ "', aborting attempt to configure service");
+ setServiceState(Lifecycle.ON_FIRE);
+ }
+
+ webClient.logout();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingScriptGenerator.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingScriptGenerator.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingScriptGenerator.java
new file mode 100644
index 0000000..ae3883f
--- /dev/null
+++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/dns/geoscaling/GeoscalingScriptGenerator.java
@@ -0,0 +1,79 @@
+/*
+ * 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.dns.geoscaling;
+
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.TimeZone;
+
+import brooklyn.location.geo.HostGeoInfo;
+import brooklyn.util.ResourceUtils;
+import brooklyn.util.javalang.JavaClassNames;
+import brooklyn.util.os.Os;
+import brooklyn.util.text.Strings;
+
+public class GeoscalingScriptGenerator {
+
+ private static final String PHP_SCRIPT_TEMPLATE_RESOURCE = JavaClassNames.resolveClasspathUrl(GeoscalingScriptGenerator.class, "template.php");
+ private static final String HOSTS_DECLARATIONS_MARKER = "/* HOST DECLARATIONS TO BE SUBSTITUTED HERE */";
+ private static final String DATESTAMP_MARKER = "DATESTAMP";
+
+
+ public static String generateScriptString(Collection<HostGeoInfo> hosts) {
+ return generateScriptString(new Date(), hosts);
+ }
+
+ public static String generateScriptString(Date generationTime, Collection<HostGeoInfo> hosts) {
+ String template = ResourceUtils.create(GeoscalingScriptGenerator.class).getResourceAsString(PHP_SCRIPT_TEMPLATE_RESOURCE);
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss 'UTC'");
+ sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+ String datestamp = sdf.format(generationTime);
+ String declarations = getHostsDeclaration(hosts);
+ return template
+ .replaceAll(DATESTAMP_MARKER, datestamp)
+ .replace(HOSTS_DECLARATIONS_MARKER, declarations);
+ }
+
+ private static String getHostsDeclaration(Collection<HostGeoInfo> hosts) {
+ StringBuffer sb = new StringBuffer();
+ sb.append("$hosts = array(").append(Os.LINE_SEPARATOR);
+ Iterator<HostGeoInfo> iServer = hosts.iterator();
+ while (iServer.hasNext()) {
+ HostGeoInfo server = iServer.next();
+ sb.append(" array('name' => '").append(escape(server.displayName)).append("',").append(Os.LINE_SEPARATOR);
+ sb.append(" 'latitude' => ").append(server.latitude).append(",").append(Os.LINE_SEPARATOR);
+ sb.append(" 'longitude' => ").append(server.longitude).append(",").append(Os.LINE_SEPARATOR);
+ sb.append(" 'ip' => '").append(escape(server.address)).append("')");
+ if (iServer.hasNext()) sb.append(",").append(Os.LINE_SEPARATOR);
+ sb.append(Os.LINE_SEPARATOR);
+ }
+ sb.append(");").append(Os.LINE_SEPARATOR);
+ return sb.toString();
+ }
+
+ private static String escape(String txt) {
+ txt = Strings.replaceAllNonRegex(txt, "\\", "\\\\");
+ txt = Strings.replaceAllNonRegex(txt, "'", "\\'");
+ txt = Strings.replaceAllNonRegex(txt, "\"", "\\\"'");
+ return txt;
+ }
+
+}