/*
DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
Copyright (c) 2016 Payara Foundation. All rights reserved.
The contents of this file are subject to the terms of the Common Development
and Distribution License("CDDL") (collectively, the "License"). You
may not use this file except in compliance with the License. You can
obtain a copy of the License at
https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
or packager/legal/LICENSE.txt. See the License for the specific
language governing permissions and limitations under the License.
When distributing the software, include this License Header Notice in each
file and include the License file at packager/legal/LICENSE.txt.
*/
package fish.payara.appserver.micro.services;
import com.sun.enterprise.deployment.Application;
import fish.payara.appserver.micro.services.command.AsAdminCallable;
import fish.payara.appserver.micro.services.command.ClusterCommandResult;
import fish.payara.appserver.micro.services.data.ApplicationDescriptor;
import fish.payara.appserver.micro.services.data.InstanceDescriptor;
import fish.payara.nucleus.cluster.PayaraCluster;
import fish.payara.nucleus.eventbus.ClusterMessage;
import fish.payara.nucleus.eventbus.MessageReceiver;
import fish.payara.nucleus.events.HazelcastEvents;
import fish.payara.nucleus.hazelcast.HazelcastCore;
import java.io.Serializable;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.logging.Level;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import org.glassfish.api.StartupRunLevel;
import org.glassfish.api.admin.ServerEnvironment;
import org.glassfish.api.deployment.DeploymentContext;
import org.glassfish.api.event.EventListener;
import org.glassfish.api.event.EventTypes;
import org.glassfish.api.event.Events;
import org.glassfish.embeddable.CommandRunner;
import org.glassfish.grizzly.config.dom.NetworkListener;
import org.glassfish.hk2.runlevel.RunLevel;
import org.glassfish.internal.api.ServerContext;
import org.glassfish.internal.data.ApplicationInfo;
import org.glassfish.internal.deployment.Deployment;
import org.jboss.logging.Logger;
import org.jvnet.hk2.annotations.Service;
/**
* Internal Payara Service for describing instances
*
* @author steve
*/
@Service(name = "payara-instance")
@RunLevel(StartupRunLevel.VAL)
public class PayaraInstance implements EventListener, MessageReceiver {
public static final String INSTANCE_STORE_NAME = "payara.instance.store";
public static final String INTERNAL_EVENTS_NAME = "payara.micro.cluster.event";
public static final String CDI_EVENTS_NAME = "payara.micro.cdi.event";
public static final String APPLICATIONS_STORE_NAME = "payara.micro.applications.store";
private static final Logger logger = Logger.getLogger(PayaraInstance.class);
@Inject
private PayaraCluster cluster;
@Inject
private ServerContext context;
@Inject
private Events events;
@Inject
private CommandRunner commandRunner;
private HashSet<PayaraClusterListener> myListeners;
private HashSet<CDIEventListener> myCDIListeners;
private String myCurrentID;
private String instanceName;
private InstanceDescriptor me;
@Inject
private ServerEnvironment environment;
@Inject
private HazelcastCore hazelcast;
public String getInstanceName() {
return instanceName;
}
public void setInstanceName(String instanceName) {
this.instanceName = instanceName;
me.setInstanceName(instanceName);
}
public <T extends Serializable> Map<String, Future<T>> runCallable(Collection<String> memberUUIDS, Callable<T> callable) {
return cluster.getExecService().runCallable(memberUUIDS, callable);
}
public <T extends Serializable> Map<String, Future<T>> runCallable(Callable<T> callable) {
return cluster.getExecService().runCallable(callable);
}
public ClusterCommandResult executeLocalAsAdmin(String command, String... parameters) {
return new ClusterCommandResult(commandRunner.run(command, parameters));
}
public Map<String, Future<ClusterCommandResult>> executeClusteredASAdmin(String command, String... parameters) {
AsAdminCallable callable = new AsAdminCallable(command, parameters);
Map<String, Future<ClusterCommandResult>> result = cluster.getExecService().runCallable(callable);
return result;
}
public Map<String, Future<ClusterCommandResult>> executeClusteredASAdmin(Collection<String> memberGUIDs, String command, String... parameters) {
AsAdminCallable callable = new AsAdminCallable(command, parameters);
Map<String, Future<ClusterCommandResult>> result = cluster.getExecService().runCallable(memberGUIDs, callable);
return result;
}
@Override
public void receiveMessage(ClusterMessage msg) {
if (msg.getPayload() instanceof PayaraInternalEvent) {
PayaraInternalEvent pie = PayaraInternalEvent.class.cast(msg.getPayload());
switch (pie.getMessageType()) {
case ADDED:
for (PayaraClusterListener myListener : myListeners) {
myListener.memberAdded(pie.getId());
}
break;
case REMOVED:
for (PayaraClusterListener myListener : myListeners) {
myListener.memberRemoved(pie.getId());
}
break;
}
} else if (msg.getPayload() instanceof PayaraClusteredCDIEvent) {
PayaraClusteredCDIEvent cast = PayaraClusteredCDIEvent.class.cast(msg.getPayload());
for (CDIEventListener myListener : myCDIListeners) {
if (!cast.isLoopBack() && cast.getInstanceDescriptor().getMemberUUID().equals(myCurrentID)) {
// ignore this message as it is a loopback
} else {
myListener.eventReceived(cast);
}
}
}
}
@PostConstruct
@SuppressWarnings("unchecked")
void postConstruct() {
events.register(this);
myListeners = new HashSet<>(1);
myCDIListeners = new HashSet<>(1);
initialiseInstanceDescriptor();
}
/**
*
* @param event
*/
@Override
@SuppressWarnings({"unchecked"})
public void event(Event event) {
if (event.is(EventTypes.SERVER_READY)) {
PayaraInternalEvent pie = new PayaraInternalEvent(PayaraInternalEvent.MESSAGE.ADDED, me);
ClusterMessage<PayaraInternalEvent> message = new ClusterMessage<>(pie);
this.cluster.getEventBus().publish(INTERNAL_EVENTS_NAME, message);
}
// Adds the application to the clustered register of deployed applications
else if (event.is(Deployment.APPLICATION_LOADED)) {
if (event.hook() != null && event.hook() instanceof ApplicationInfo) {
ApplicationInfo applicationInfo = (ApplicationInfo) event.hook();
me.addApplication(applicationInfo);
cluster.getClusteredStore().set(INSTANCE_STORE_NAME, myCurrentID, me);
}
}
// ensures the same application id is used when there is a deployment
// if the application id is in the cluster keyed by application name
else if (event.is(Deployment.APPLICATION_PREPARED)) {
if (event.hook() != null && event.hook() instanceof DeploymentContext) {
DeploymentContext deploymentContext = (DeploymentContext) event.hook();
Application app = deploymentContext.getModuleMetaData(Application.class);
if(app != null) {
Long appID = (Long) cluster.getClusteredStore().get(APPLICATIONS_STORE_NAME, app.getName());
if (appID != null) {
app.setUniqueId(appID);
} else {
cluster.getClusteredStore().set(APPLICATIONS_STORE_NAME, app.getName(), new Long(app.getUniqueId()));
}
}
}
}
// removes the application from the clustered registry of applications
else if (event.is(Deployment.APPLICATION_UNLOADED)) {
if (event.hook() != null && event.hook() instanceof ApplicationInfo) {
ApplicationInfo applicationInfo = (ApplicationInfo) event.hook();
me.removeApplication(applicationInfo);
cluster.getClusteredStore().set(INSTANCE_STORE_NAME, myCurrentID, me);
}
} else if (event.is(EventTypes.PREPARE_SHUTDOWN)) {
PayaraInternalEvent pie = new PayaraInternalEvent(PayaraInternalEvent.MESSAGE.REMOVED, me);
ClusterMessage<PayaraInternalEvent> message = new ClusterMessage<>(pie);
this.cluster.getClusteredStore().remove(INSTANCE_STORE_NAME, myCurrentID);
this.cluster.getEventBus().publish(INTERNAL_EVENTS_NAME, message);
}
// When Hazelcast is bootstrapped, update the instance descriptor with any new information
if (event.is(HazelcastEvents.HAZELCAST_BOOTSTRAP_COMPLETE)) {
initialiseInstanceDescriptor();
}
}
public Set<InstanceDescriptor> getClusteredPayaras() {
Set<String> members = cluster.getClusterMembers();
HashSet<InstanceDescriptor> result = new HashSet<>(members.size());
for (String member : members) {
InstanceDescriptor id = (InstanceDescriptor) cluster.getClusteredStore().get(INSTANCE_STORE_NAME, member);
if (id != null) {
result.add(id);
}
}
return result;
}
public void publishCDIEvent(PayaraClusteredCDIEvent event) {
if (event.getInstanceDescriptor() == null) {
event.setInstanceDescriptor(me);
}
ClusterMessage<PayaraClusteredCDIEvent> message = new ClusterMessage<>(event);
cluster.getEventBus().publish(CDI_EVENTS_NAME, message);
}
public void removeBootstrapListener(PayaraClusterListener listener) {
myListeners.remove(listener);
}
public void addBootstrapListener(PayaraClusterListener listener) {
myListeners.add(listener);
}
public void removeCDIListener(CDIEventListener listener) {
myCDIListeners.remove(listener);
}
public void addCDIListener(CDIEventListener listener) {
myCDIListeners.add(listener);
}
public InstanceDescriptor getLocalDescriptor() {
return me;
}
public InstanceDescriptor getDescriptor(String member) {
InstanceDescriptor result = null;
if (cluster.isEnabled()) {
result = (InstanceDescriptor) cluster.getClusteredStore().get(INSTANCE_STORE_NAME, member);
}
return result;
}
private void initialiseInstanceDescriptor() {
boolean liteMember = false;
int hazelcastPort = 5900;
// Get the Hazelcast specific information
if (hazelcast.isEnabled()) {
instanceName = hazelcast.getInstance().getCluster().getLocalMember().getStringAttribute(
HazelcastCore.INSTANCE_ATTRIBUTE);
myCurrentID = hazelcast.getInstance().getCluster().getLocalMember().getUuid();
liteMember = hazelcast.getInstance().getCluster().getLocalMember().isLiteMember();
hazelcastPort = hazelcast.getInstance().getCluster().getLocalMember().getSocketAddress().getPort();
}
// Get this instance's runtime type
String instanceType = environment.getRuntimeType().toString();
// Get the ports in use by this instance from its network listener configs
List<Integer> ports = new ArrayList<>();
List<Integer> sslPorts = new ArrayList<>();
int adminPort = 0;
for (NetworkListener networkListener :
context.getConfigBean().getConfig().getNetworkConfig().getNetworkListeners().getNetworkListener()) {
// Skip the network listener if it isn't enabled
if (Boolean.parseBoolean(networkListener.getEnabled())) {
// Check if this listener is using HTTP or HTTPS
if (networkListener.findProtocol().getSecurityEnabled().equals("false")) {
// Check if this listener is the admin listener
if (networkListener.getName().equals(
context.getConfigBean().getConfig().getAdminListener().getName())) {
// Micro instances can use the admin listener as both an admin and HTTP port
if (instanceType.equals("MICRO")) {
ports.add(Integer.parseInt(networkListener.getPort()));
}
adminPort = Integer.parseInt(networkListener.getPort());
} else {
// If this listener isn't the admin listener, it must be an HTTP listener
ports.add(Integer.parseInt(networkListener.getPort()));
}
} else if (networkListener.findProtocol().getSecurityEnabled().equals("true")) {
if (networkListener.getName().equals(
context.getConfigBean().getConfig().getAdminListener().getName())) {
// Micro instances can use the admin listener as both an admin and HTTPS port
if (instanceType.equals("MICRO")) {
ports.add(Integer.parseInt(networkListener.getPort()));
}
adminPort = Integer.parseInt(networkListener.getPort());
} else {
// If this listener isn't the admin listener, it must be an HTTPS listener
sslPorts.add(Integer.parseInt(networkListener.getPort()));
}
}
}
}
// Initialise the instance descriptor and set all of its attributes
try {
// If Hazelcast is being rebooted dynamically, we don't want to lose the already registered applications
Collection<ApplicationDescriptor> deployedApplications = new ArrayList<>();
if (me != null) {
deployedApplications = me.getDeployedApplications();
}
me = new InstanceDescriptor(myCurrentID);
me.setInstanceName(instanceName);
for (int port : ports) {
me.addHttpPort(port);
}
for (int sslPort : sslPorts) {
me.addHttpsPort(sslPort);
}
me.setAdminPort(adminPort);
me.setHazelcastPort(hazelcastPort);
me.setLiteMember(liteMember);
me.setInstanceType(instanceType);
// If there were some deployed applications from the previous instance descriptor, register them with the new
// one
if (!deployedApplications.isEmpty()) {
for (ApplicationDescriptor application : deployedApplications) {
me.addApplication(application);
}
}
// Register the instance descriptor to the cluster if it's enabled
if (cluster.isEnabled()) {
cluster.getEventBus().addMessageReceiver(INTERNAL_EVENTS_NAME, this);
cluster.getEventBus().addMessageReceiver(CDI_EVENTS_NAME, this);
cluster.getClusteredStore().set(INSTANCE_STORE_NAME, myCurrentID, me);
}
} catch (UnknownHostException ex) {
java.util.logging.Logger.getLogger(PayaraInstance.class.getName()).log(Level.SEVERE,
"Could not find local hostname", ex);
}
}
/**
* Checks whether or not this instance is in a Hazelcast cluster
* @return true if this instance is in a Hazelcast cluster
*/
public boolean isClustered() {
return cluster.isEnabled();
}
}