The Java SDK docs don't cover launching a spot instance into a VPC with a Public IP: https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/tutorial-spot-adv-java.html.
How to do that?
Here's a SSSCE using aws-java-sdk-ec2-1.11.487:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import com.amazonaws.services.ec2.AmazonEC2;
import com.amazonaws.services.ec2.AmazonEC2ClientBuilder;
import com.amazonaws.services.ec2.model.CreateTagsRequest;
import com.amazonaws.services.ec2.model.InstanceNetworkInterfaceSpecification;
import com.amazonaws.services.ec2.model.InstanceType;
import com.amazonaws.services.ec2.model.LaunchSpecification;
import com.amazonaws.services.ec2.model.RequestSpotInstancesRequest;
import com.amazonaws.services.ec2.model.SpotInstanceRequest;
import com.amazonaws.services.ec2.model.Tag;
public class SpotLauncher
{
private static final int kInstances = 25;
private static final String kMaxPrice = "0.007";
private static final InstanceType kInstanceType = InstanceType.M3Medium;
private static final String kSubnet = "subnet-xxxx";
private static final String kAmi = "ami-xxxx";
private static final String kName = "spot";
private static final String kSecurityGroup2 = "sg-xxxx";
private static final String kSecurityGroup1 = "sg-yyyy";
public static void main(String[] args)
{
AmazonEC2 ec2 = AmazonEC2ClientBuilder.defaultClient();
RequestSpotInstancesRequest request = new RequestSpotInstancesRequest();
request.setSpotPrice(kMaxPrice); // max price we're willing to pay
request.setInstanceCount(kInstances);
LaunchSpecification launchSpecification = new LaunchSpecification();
launchSpecification.setImageId(kAmi);
launchSpecification.setInstanceType(kInstanceType);
launchSpecification.setKeyName("aws");
// security group IDs - don't add them, they're already added to the network spec
// launchSpecification.withAllSecurityGroups(new GroupIdentifier().withGroupId("sg-xxxx"), new GroupIdentifier().withGroupId("sg-yyyy"));
List<String> securityGroups = new ArrayList<String>();
securityGroups.add(kSecurityGroup1);
securityGroups.add(kSecurityGroup2);
InstanceNetworkInterfaceSpecification networkSpec = new InstanceNetworkInterfaceSpecification();
networkSpec.setDeviceIndex(0);
networkSpec.setSubnetId(kSubnet);
networkSpec.setGroups(securityGroups);
networkSpec.setAssociatePublicIpAddress(true);
List<InstanceNetworkInterfaceSpecification> nicWrapper = new ArrayList<InstanceNetworkInterfaceSpecification>();
nicWrapper.add(networkSpec);
// launchSpecification.setSubnetId("subnet-ccde4ce1"); // don't add this, it's already added to the network interface spec
launchSpecification.setNetworkInterfaces(nicWrapper);
// add the launch specifications to the request
request.setLaunchSpecification(launchSpecification);
// call the RequestSpotInstance API
ec2.requestSpotInstances(request);
while (!SetEc2Names(ec2))
{
Sleep(2000);
}
System.out.println("\nDONE.");
}
private static void Sleep(long aMillis)
{
try
{
Thread.sleep(aMillis);
}
catch (InterruptedException aEx)
{
aEx.printStackTrace();
}
}
private static boolean SetEc2Names(AmazonEC2 aEc2Client)
{
List<SpotInstanceRequest> requests = aEc2Client.describeSpotInstanceRequests().getSpotInstanceRequests();
Collections.sort(requests, GetCreatedDescComparator());
for (int i = 0; i < kInstances; i++)
{
SpotInstanceRequest request = requests.get(i);
if (request.getLaunchSpecification().getImageId().equals(kAmi))
{
System.out.println("request: " + request);
String instanceId = request.getInstanceId();
if (instanceId == null)
{
System.out.println("instance not launched yet, we don't have an id");
return false;
}
System.out.println("setting name for newly launched spot instance, id: " + instanceId);
AssignName(aEc2Client, request);
}
}
return true;
}
private static void AssignName(AmazonEC2 aEc2Client, SpotInstanceRequest aRequest)
{
String instanceId = aRequest.getInstanceId();
Tag tag = new Tag("Name", kName);
CreateTagsRequest tagRequest = new CreateTagsRequest();
List<String> instanceIds = new ArrayList<String>();
instanceIds.add(instanceId);
tagRequest.withResources(instanceIds);
List<Tag> tags = new ArrayList<Tag>();
tags.add(tag);
tagRequest.setTags(tags);
aEc2Client.createTags(tagRequest);
}
private static Comparator<SpotInstanceRequest> GetCreatedDescComparator()
{
return new Comparator<SpotInstanceRequest>()
{
#Override
public int compare(SpotInstanceRequest o1, SpotInstanceRequest o2)
{
return -1 * o1.getCreateTime().compareTo(o2.getCreateTime());
}
};
}
}
Related
I am trying to learn how to use Apache Zookeeper. A part of this is I need to check if the parent node has been created and if it hasn't, then I need to create the node, then populate the registry with information on the services currently running in the cluster. This is happening in the method initialise(). I cannot figure out the checking if the parent node exists. The code I have is what I have so far.
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class ServiceRegistry implements Watcher {
private String zookeeperAddress = "localhost:2181";
private int sessionTimeout = 3000;
private ZooKeeper zooKeeper;
private String currentZNodeName;
private String serviceName = "";
private String serviceAddress = "";
private static final String SERVICES_NAMESPACE = "/services";
public ServiceRegistry(String zookeeperAddress, int sessionTimeout) {
this.sessionTimeout = sessionTimeout;
this.zookeeperAddress = zookeeperAddress;
}
//Works
public void connectToZookeeper() throws IOException {
this.zooKeeper = new ZooKeeper(zookeeperAddress, sessionTimeout, this);
}
public void process(WatchedEvent event) {
switch (event.getType()){
case None:
if(event.getState() == Watcher.Event.KeeperState.SyncConnected){
System.out.println("Successfully connected to Zookeeper");
}else{
synchronized (zooKeeper){
System.out.println("Disconnected from Zookeeper");
zooKeeper.notifyAll();
}
}
break;
case NodeCreated:
System.out.println("Node created");
try {
initialise();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (KeeperException e) {
throw new RuntimeException(e);
}
break;
case NodeDataChanged:
System.out.println("Node data changed");
getServices();
break;
}
}
public void initialise() throws InterruptedException, KeeperException {
Stat servicesStat = null;
if(servicesStat == null){
String znodePrefix = SERVICES_NAMESPACE + "/c_";
String znodeFullPath = zooKeeper.create(znodePrefix, new byte[]{}, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println("znode name " + znodeFullPath);
servicesStat = zooKeeper.exists(SERVICES_NAMESPACE + "/", this);
}
}
public void run() throws InterruptedException {
synchronized (zooKeeper){
zooKeeper.wait();
}
}
I've successfully implemented the simple Java Amazon SWF example called hello_sample. I created the ActivityWorker executable that polls SWF for activity tasks to process. I created the WorkflowWorker executable that polls SWF for decision tasks and I have a WorkflowStarter executable that kicks off the workflow execution. It works as advertised. What I don't understand is how do I configure and add a second activity to run after the first activity?
WorkflowWorker:
public class WorkflowWorker {
private static final AmazonSimpleWorkflow swf = AmazonSimpleWorkflowClientBuilder.defaultClient();
public static void main(String[] args) {
PollForDecisionTaskRequest task_request =
new PollForDecisionTaskRequest()
.withDomain(Constants.DOMAIN)
.withTaskList(new TaskList().withName(Constants.TASKLIST));
while (true) {
System.out.println(
"WorkflowWorker is polling for a decision task from the tasklist '" +
Constants.TASKLIST + "' in the domain '" +
Constants.DOMAIN + "'.");
DecisionTask task = swf.pollForDecisionTask(task_request);
String taskToken = task.getTaskToken();
if (taskToken != null) {
try {
executeDecisionTask(taskToken, task.getEvents());
}
catch (Throwable th) {
th.printStackTrace();
}
}
}
}
private static void executeDecisionTask(String taskToken, List<HistoryEvent> events) throws Throwable {
List<Decision> decisions = new ArrayList<Decision>();
String workflow_input = null;
int scheduled_activities = 0;
int open_activities = 0;
boolean activity_completed = false;
String result = null;
System.out.println("WorkflowWorker is executing the decision task for the history events: [");
for (HistoryEvent event : events) {
System.out.println(" " + event);
switch(event.getEventType()) {
case "WorkflowExecutionStarted":
workflow_input = event.getWorkflowExecutionStartedEventAttributes().getInput();
break;
case "ActivityTaskScheduled":
scheduled_activities++;
break;
case "ScheduleActivityTaskFailed":
scheduled_activities--;
break;
case "ActivityTaskStarted":
scheduled_activities--;
open_activities++;
break;
case "ActivityTaskCompleted":
open_activities--;
activity_completed = true;
result = event.getActivityTaskCompletedEventAttributes().getResult();
break;
case "ActivityTaskFailed":
open_activities--;
break;
case "ActivityTaskTimedOut":
open_activities--;
break;
}
}
System.out.println("]");
if (activity_completed) {
decisions.add(
new Decision()
.withDecisionType(DecisionType.CompleteWorkflowExecution)
.withCompleteWorkflowExecutionDecisionAttributes(
new CompleteWorkflowExecutionDecisionAttributes()
.withResult(result)));
}
else {
if (open_activities == 0 && scheduled_activities == 0) {
ScheduleActivityTaskDecisionAttributes attrs =
new ScheduleActivityTaskDecisionAttributes()
.withActivityType(new ActivityType()
.withName(Constants.ACTIVITY)
.withVersion(Constants.ACTIVITY_VERSION))
.withActivityId(UUID.randomUUID().toString())
.withInput(workflow_input);
decisions.add(
new Decision()
.withDecisionType(DecisionType.ScheduleActivityTask)
.withScheduleActivityTaskDecisionAttributes(attrs));
}
else {
// an instance of HelloActivity is already scheduled or running. Do nothing, another
// task will be scheduled once the activity completes, fails or times out
}
}
System.out.println("WorkflowWorker is exiting the decision task with the decisions " + decisions);
swf.respondDecisionTaskCompleted(
new RespondDecisionTaskCompletedRequest()
.withTaskToken(taskToken)
.withDecisions(decisions));
}
}
ActivityWorker:
public class ActivityWorker {
private static final AmazonSimpleWorkflow swf = AmazonSimpleWorkflowClientBuilder.defaultClient();
private static CountDownLatch waitForTermination = new CountDownLatch(1);
private static volatile boolean terminate = false;
private static String executeActivityTask(String g_species) throws Throwable {
String s = " ******** Hello, " + g_species + "!";
System.out.println(s);
String cwd = Paths.get(".").toAbsolutePath().normalize().toString();
String filename = "g_species.txt";
Path filePath = Paths.get(cwd, filename);
String filePathName = filePath.toString();
BufferedWriter output = null;
try {
File file = new File (filePathName);
output = new BufferedWriter(new FileWriter(file));
output.write(g_species);
}
catch (IOException e) {
e.printStackTrace();
}
finally {
if (output != null) {
output.close();
}
}
return g_species;
}
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread() {
#Override
public void run() {
try {
terminate = true;
System.out.println("ActivityWorker is waiting for the current poll request to return before shutting down.");
waitForTermination.await(60, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
// ignore
System.out.println(e.getMessage());
}
}
});
try {
pollAndExecute();
}
finally {
waitForTermination.countDown();
}
}
public static void pollAndExecute() {
while (!terminate) {
System.out.println("ActivityWorker is polling for an activity task from the tasklist '"
+ Constants.TASKLIST + "' in the domain '" + Constants.DOMAIN + "'.");
ActivityTask task = swf.pollForActivityTask(new PollForActivityTaskRequest()
.withDomain(Constants.DOMAIN)
.withTaskList(new TaskList().withName(Constants.TASKLIST)));
String taskToken = task.getTaskToken();
if (taskToken != null) {
String result = null;
Throwable error = null;
try {
System.out.println("ActivityWorker is executing the activity task with input '" + task.getInput() + "'.");
result = executeActivityTask(task.getInput());
}
catch (Throwable th) {
error = th;
}
if (error == null) {
System.out.println("The activity task succeeded with result '" + result + "'.");
swf.respondActivityTaskCompleted(
new RespondActivityTaskCompletedRequest()
.withTaskToken(taskToken)
.withResult(result));
}
else {
System.out.println("The activity task failed with the error '"
+ error.getClass().getSimpleName() + "'.");
swf.respondActivityTaskFailed(
new RespondActivityTaskFailedRequest()
.withTaskToken(taskToken)
.withReason(error.getClass().getSimpleName())
.withDetails(error.getMessage()));
}
}
}
}
}
WorkflowStarter that kicks it all off:
public class WorkflowStarter {
private static final AmazonSimpleWorkflow swf = AmazonSimpleWorkflowClientBuilder.defaultClient();
public static final String WORKFLOW_EXECUTION = "HelloWorldWorkflowExecution";
public static void main(String[] args) {
String workflow_input = "Amazon SWF";
if (args.length > 0) {
workflow_input = args[0];
}
System.out.println("Starting the workflow execution '" + WORKFLOW_EXECUTION +
"' with input '" + workflow_input + "'.");
WorkflowType wf_type = new WorkflowType()
.withName(Constants.WORKFLOW)
.withVersion(Constants.WORKFLOW_VERSION);
Run run = swf.startWorkflowExecution(new StartWorkflowExecutionRequest()
.withDomain(Constants.DOMAIN)
.withWorkflowType(wf_type)
.withWorkflowId(WORKFLOW_EXECUTION)
.withInput(workflow_input)
.withExecutionStartToCloseTimeout("90"));
System.out.println("Workflow execution started with the run id '" +
run.getRunId() + "'.");
}
}
I would recommend to not reinvent the wheel and use the AWS Flow Framework for Java that is officially supported by Amazon. It already implements all the low level details and allows you to focus on a business logic of your workflow directly.
Here is an example worklow that uses three activities (taken from the developer guide).
Activities interface:
import com.amazonaws.services.simpleworkflow.flow.annotations.Activities;
import com.amazonaws.services.simpleworkflow.flow.annotations.ActivityRegistrationOptions;
#ActivityRegistrationOptions(defaultTaskScheduleToStartTimeoutSeconds = 300,
defaultTaskStartToCloseTimeoutSeconds = 10)
#Activities(version="1.0")
public interface GreeterActivities {
public String getName();
public String getGreeting(String name);
public void say(String what);
}
Activities implementation:
public class GreeterActivitiesImpl implements GreeterActivities {
#Override
public String getName() {
return "World";
}
#Override
public String getGreeting(String name) {
return "Hello " + name;
}
#Override
public void say(String what) {
System.out.println(what);
}
}
Workflow interface:
import com.amazonaws.services.simpleworkflow.flow.annotations.Execute;
import com.amazonaws.services.simpleworkflow.flow.annotations.Workflow;
import com.amazonaws.services.simpleworkflow.flow.annotations.WorkflowRegistrationOptions;
#Workflow
#WorkflowRegistrationOptions(defaultExecutionStartToCloseTimeoutSeconds = 3600)
public interface GreeterWorkflow {
#Execute(version = "1.0")
public void greet();
}
Workflow implementation:
import com.amazonaws.services.simpleworkflow.flow.core.Promise;
public class GreeterWorkflowImpl implements GreeterWorkflow {
private GreeterActivitiesClient operations = new GreeterActivitiesClientImpl();
public void greet() {
Promise<String> name = operations.getName();
Promise<String> greeting = operations.getGreeting(name);
operations.say(greeting);
}
}
The worker that hosts both of them. Obviously it can be broken into separate activity and workflow workers:
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflow;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClient;
import com.amazonaws.services.simpleworkflow.flow.ActivityWorker;
import com.amazonaws.services.simpleworkflow.flow.WorkflowWorker;
public class GreeterWorker {
public static void main(String[] args) throws Exception {
ClientConfiguration config = new ClientConfiguration().withSocketTimeout(70*1000);
String swfAccessId = System.getenv("AWS_ACCESS_KEY_ID");
String swfSecretKey = System.getenv("AWS_SECRET_KEY");
AWSCredentials awsCredentials = new BasicAWSCredentials(swfAccessId, swfSecretKey);
AmazonSimpleWorkflow service = new AmazonSimpleWorkflowClient(awsCredentials, config);
service.setEndpoint("https://swf.us-east-1.amazonaws.com");
String domain = "helloWorldWalkthrough";
String taskListToPoll = "HelloWorldList";
ActivityWorker aw = new ActivityWorker(service, domain, taskListToPoll);
aw.addActivitiesImplementation(new GreeterActivitiesImpl());
aw.start();
WorkflowWorker wfw = new WorkflowWorker(service, domain, taskListToPoll);
wfw.addWorkflowImplementationType(GreeterWorkflowImpl.class);
wfw.start();
}
}
The workflow starter:
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflow;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClient;
public class GreeterMain {
public static void main(String[] args) throws Exception {
ClientConfiguration config = new ClientConfiguration().withSocketTimeout(70*1000);
String swfAccessId = System.getenv("AWS_ACCESS_KEY_ID");
String swfSecretKey = System.getenv("AWS_SECRET_KEY");
AWSCredentials awsCredentials = new BasicAWSCredentials(swfAccessId, swfSecretKey);
AmazonSimpleWorkflow service = new AmazonSimpleWorkflowClient(awsCredentials, config);
service.setEndpoint("https://swf.us-east-1.amazonaws.com");
String domain = "helloWorldWalkthrough";
GreeterWorkflowClientExternalFactory factory = new GreeterWorkflowClientExternalFactoryImpl(service, domain);
GreeterWorkflowClientExternal greeter = factory.getClient("someID");
greeter.greet();
}
}
I'm trying to fetch CPU stats from an EC2 instance using the CloudWatch API:
http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/cloudwatch/AmazonCloudWatchClient.html
I have the following code but its returning an empty result, even though the instance Id, and AWS access and secret keys are correct.
I can see the CPU util for the instance on the CloudWatch UI, but can't seem to get it below?
I'm using version 1.9.0 of the AWS SDK.
Any help much appreciated.
import java.util.Date;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.cloudwatch.AmazonCloudWatchClient;
import com.amazonaws.services.cloudwatch.model.Datapoint;
import com.amazonaws.services.cloudwatch.model.Dimension;
import com.amazonaws.services.cloudwatch.model.GetMetricStatisticsRequest;
import com.amazonaws.services.cloudwatch.model.GetMetricStatisticsResult;
public class AmazonCloudWatchFetchCpuUtilTest {
public static void main(String[] args) {
final String awsAccessKey = ...;
final String awsSecretKey = ...;
final String instanceId = ...;
final AmazonCloudWatchClient client = client(awsAccessKey, awsSecretKey);
final GetMetricStatisticsRequest request = request(instanceId);
final GetMetricStatisticsResult result = result(client, request);
toStdOut(result, instanceId);
}
private static AmazonCloudWatchClient client(final String awsAccessKey, final String awsSecretKey) {
return new AmazonCloudWatchClient(new BasicAWSCredentials(awsAccessKey, awsSecretKey));
}
private static GetMetricStatisticsRequest request(final String instanceId) {
final long twentyFourHrs = 1000 * 60 * 60 * 24;
final int oneHour = 60 * 60;
return new GetMetricStatisticsRequest()
.withStartTime(new Date(new Date().getTime()- twentyFourHrs))
.withNamespace("AWS/EC2")
.withPeriod(oneHour)
.withDimensions(new Dimension().withName("InstanceId").withValue(instanceId))
.withMetricName("CPUUtilization")
.withStatistics("Average", "Maximum")
.withEndTime(new Date());
}
private static GetMetricStatisticsResult result(
final AmazonCloudWatchClient client, final GetMetricStatisticsRequest request) {
return client.getMetricStatistics(request);
}
private static void toStdOut(final GetMetricStatisticsResult result, final String instanceId) {
System.out.println(result); // outputs empty result: {Label: CPUUtilization,Datapoints: []}
for (final Datapoint dataPoint : result.getDatapoints()) {
System.out.printf("%s instance's average CPU utilization : %s%n", instanceId, dataPoint.getAverage());
System.out.printf("%s instance's max CPU utilization : %s%n", instanceId, dataPoint.getMaximum());
}
}
}
I was missing the endpoint setting on the client. Working now.
Changed this:
private static AmazonCloudWatchClient client(final String awsAccessKey, final String awsSecretKey) {
return new AmazonCloudWatchClient(new BasicAWSCredentials(awsAccessKey, awsSecretKey));
}
to this:
private static AmazonCloudWatchClient client(final String awsAccessKey, final String awsSecretKey) {
final AmazonCloudWatchClient client = new AmazonCloudWatchClient(new BasicAWSCredentials(awsAccessKey, awsSecretKey));
client.setEndpoint("http://monitoring.eu-west-1.amazonaws.com");
return client;
}
I've dug up the following code for serializing an ItemStack in bukkit (Minecraft). I've been able to serialize an item in hand with the following:
itemString = ItemStackUtils.deserialize(player.getInventory().getItemInHand());
I can't figure out how to utilize the deserial call however. What I am trying to do is to pull an item from the players hand, serialize it, stick it into a config file, then when the player runs another command... deserialize it and slap it into their inventory. I am fairly certain this class will meet my needs if I just can get the last part working.
public final class ItemStackUtils {
public static String getEnchants(ItemStack i){
List<String> e = new ArrayList<String>();
Map<Enchantment, Integer> en = i.getEnchantments();
for(Enchantment t : en.keySet()) {
e.add(t.getName() + ":" + en.get(t));
}
return StringUtils.join(e, ",");
}
public static String deserialize(ItemStack i){
String[] parts = new String[6];
parts[0] = i.getType().name();
parts[1] = Integer.toString(i.getAmount());
parts[2] = String.valueOf(i.getDurability());
parts[3] = i.getItemMeta().getDisplayName();
parts[4] = String.valueOf(i.getData().getData());
parts[5] = getEnchants(i);
return StringUtils.join(parts, ";");
}
public ItemStack deserial(String p){
String[] a = p.split(";");
ItemStack i = new ItemStack(Material.getMaterial(a[0]), Integer.parseInt(a[1]));
i.setDurability((short) Integer.parseInt(a[2]));
ItemMeta meta = i.getItemMeta();
meta.setDisplayName(a[3]);
i.setItemMeta(meta);
MaterialData data = i.getData();
data.setData((byte) Integer.parseInt(a[4]));
i.setData(data);
if (a.length > 5) {
String[] parts = a[5].split(",");
for (String s : parts) {
String label = s.split(":")[0];
String amplifier = s.split(":")[1];
Enchantment type = Enchantment.getByName(label);
if (type == null)
continue;
int f;
try {
f = Integer.parseInt(amplifier);
} catch(Exception ex) {
continue;
}
i.addEnchantment(type, f);
}
}
return i;
}
}
here it saves & loads the whole NBT
package XXXXXXXXX
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.bukkit.Material;
import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftItemStack;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.material.MaterialData;
import net.minecraft.server.v1_8_R3.MojangsonParseException;
import net.minecraft.server.v1_8_R3.MojangsonParser;
import net.minecraft.server.v1_8_R3.NBTTagCompound;
public class ItemSerialize {
public ItemSerialize() {
}
public static String serialize(ItemStack i) {
String[] parts = new String[7];
parts[0] = i.getType().name();
parts[1] = Integer.toString(i.getAmount());
parts[2] = String.valueOf(i.getDurability());
parts[3] = i.getItemMeta().getDisplayName();
parts[4] = String.valueOf(i.getData().getData());
parts[5] = getEnchants(i);
parts[6] = getNBT(i);
return StringUtils.join(parts, ";");
}
public static String getEnchants(ItemStack i) {
List<String> e = new ArrayList<String>();
Map<Enchantment, Integer> en = i.getEnchantments();
for (Enchantment t : en.keySet()) {
e.add(t.getName() + ":" + en.get(t));
}
return StringUtils.join(e, ",");
}
public static String getLore(ItemStack i) {
List<String> e = i.getItemMeta().getLore();
return StringUtils.join(e, ",");
}
public static String getNBT(ItemStack i) {
net.minecraft.server.v1_8_R3.ItemStack nmsStack = CraftItemStack.asNMSCopy(i);
NBTTagCompound compound = nmsStack.hasTag() ? nmsStack.getTag() : new NBTTagCompound();
return compound.toString();
}
public static ItemStack setNBT(ItemStack i, String NBT) {
net.minecraft.server.v1_8_R3.ItemStack nmsStack = CraftItemStack.asNMSCopy(i);
try {
NBTTagCompound compound = MojangsonParser.parse(NBT);
nmsStack.setTag(compound);
} catch (MojangsonParseException e1) {
e1.printStackTrace();
}
return CraftItemStack.asBukkitCopy(nmsStack);
}
#SuppressWarnings("deprecation")
public static ItemStack deserial(String p) {
String[] a = p.split(";");
ItemStack i = new ItemStack(Material.getMaterial(a[0]), Integer.parseInt(a[1]));
i.setDurability((short) Integer.parseInt(a[2]));
ItemMeta meta = i.getItemMeta();
meta.setDisplayName(a[3]);
i.setItemMeta(meta);
MaterialData data = i.getData();
data.setData((byte) Integer.parseInt(a[4]));
i.setData(data);
if (!a[6].isEmpty()) {
i = setNBT(i, a[6]);
}
if (!a[5].isEmpty()) {
String[] parts = a[5].split(",");
for (String s : parts) {
String label = s.split(":")[0];
String amplifier = s.split(":")[1];
Enchantment type = Enchantment.getByName(label);
if (type == null)
continue;
int f;
try {
f = Integer.parseInt(amplifier);
} catch (Exception ex) {
continue;
}
i.addUnsafeEnchantment(type, f);
}
}
return i;
}
}
ItemStack rItem = ItemStackUtils.deserial(itemString);
I have a method:
private List<String> userCns = Collections.synchronizedList(new ArrayList<String>());
private List<String> recipients = Collections.synchronizedList(new ArrayList<String>());
public void sendEmailToLegalUsers() {
try {
synchronized (lock) {
searchGroup();
if(userCns.size() > 0) {
for(String userCn : userCns) {
String mail = getUserMail(userCn);
if(mail != null) {
recipients.add(mail);
}
}
}
String docName = m_binder.getLocal("docname");
String docId = m_binder.getLocal("docid");
String url = m_binder.getLocal("serverURL");
if(recipients.size() > 0) {
m_binder.addResultSet("LOI_EVIN_MAIL", getLoiEvinMailResultSet(docName, docId, url));
for(String recipient : recipients) {
Log.info("Sending mail to: " + recipient);
InternetFunctions.sendMailToEx(recipient, "MH_LOI_EVIN_SEND_EMAIL", "Update Evin Law Compliance for the item: " + docName, m_service, true);
}
}
}
} catch (Exception e) {
Log.info("Error occurred in LDAPSendMail: "+ e.getMessage());
}
}
Now this sendEmailToLegalUsers method can be called from different threads. I am wondering is it the right way to lock the code block so that there is no chances of data mixup in the list?
Edit: whole class:
package com.edifixio.ldapsendmail.handlers;
import intradoc.common.Log;
import intradoc.data.DataResultSet;
import intradoc.server.InternetFunctions;
import intradoc.server.ServiceHandler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
public class LDAPSendMail extends ServiceHandler {
private final Object lock = new Object();
private String ldapURL;
private String baseDN;
private String groupDN;
private String username;
private String password;
private DirContext context;
private List<String> userCns = Collections.synchronizedList(new ArrayList<String>());
private List<String> recipients = Collections.synchronizedList(new ArrayList<String>());
public void sendEmailToLegalUsers() {
try {
synchronized (lock) {
searchGroup();
if(userCns.size() > 0) {
for(String userCn : userCns) {
String mail = getUserMail(userCn);
if(mail != null) {
recipients.add(mail);
}
}
}
String docName = m_binder.getLocal("docname");
String docId = m_binder.getLocal("docid");
String url = m_binder.getLocal("serverURL");
if(recipients.size() > 0) {
m_binder.addResultSet("LOI_EVIN_MAIL", getLoiEvinMailResultSet(docName, docId, url));
for(String recipient : recipients) {
Log.info("Sending mail to: " + recipient);
InternetFunctions.sendMailToEx(recipient, "MH_LOI_EVIN_SEND_EMAIL", "Update Evin Law Compliance for the item: " + docName, m_service, true);
}
}
userCns.clear();
recipients.clear();
}
} catch (Exception e) {
Log.info("Error occurred in LDAPSendMail: "+ e.getMessage());
}
}
private String getUserMail(String userCn) throws NamingException {
NamingEnumeration<SearchResult> searchResults = getLdapDirContext().search(userCn, "(objectclass=person)", getSearchControls());
while (searchResults.hasMore()){
SearchResult searchResult = searchResults.next();
Attributes attributes = searchResult.getAttributes();
Attribute mail = null;
try {
mail = attributes.get("mail");
} catch (Exception e) {
mail = null;
}
if(mail != null) {
return (String)mail.get();
}
}
return null;
}
private void searchGroup() throws NamingException {
NamingEnumeration<SearchResult> searchResults = getLdapDirContext().search(groupDN, "(objectclass=groupOfUniqueNames)", getSearchControls());
String searchGroupCn = getCNForBrand(m_binder.getLocal("brandId"), m_binder.getLocal("brandName"));
while (searchResults.hasMore()) {
SearchResult searchResult = searchResults.next();
Attributes attributes = searchResult.getAttributes();
Attribute groupCn = null;
try {
groupCn = attributes.get("cn");
} catch (Exception e) {
groupCn = null;
}
if(groupCn != null) {
if(searchGroupCn.equals((String)groupCn.get())) {
Attribute uniqueMembers = attributes.get("uniqueMember");
for(int i = 0; i < uniqueMembers.size(); i++){
String uniqueMemberCN = (String) uniqueMembers.get(i);
userCns.add(uniqueMemberCN);
}
break;
}
}
}
}
private DirContext getLdapDirContext() throws NamingException {
if(context != null) {
return context;
}
ldapURL = m_binder.getLocal("ldapUrl");
baseDN = m_binder.getLocal("baseDN");
groupDN = new StringBuilder().append("ou=").append(getAccountGroup(m_binder.getLocal("account"))).append(",").append("ou=groups,").append(baseDN).toString();
username = m_binder.getLocal("username");
password = m_binder.getLocal("password");
Hashtable<String, String> environment = new Hashtable<String, String>();
environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
environment.put(Context.PROVIDER_URL, ldapURL);
environment.put(Context.SECURITY_PRINCIPAL, username);
environment.put(Context.SECURITY_CREDENTIALS, password);
context = new InitialDirContext(environment);
return context;
}
private SearchControls getSearchControls() {
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
return searchControls;
}
private String getCNForBrand(String brandId, String brandName) {
String[] brandIdSplittedArray = brandId.split("/");
return new StringBuilder().append(brandIdSplittedArray[0]).append("-").append(brandIdSplittedArray[1]).append("-").
append(brandIdSplittedArray[2]).append("-").append(brandName.replaceAll("\\s","")).append("-LU").toString();
}
private String getAccountGroup(String account) {
return account.split("/")[1];
}
private DataResultSet getLoiEvinMailResultSet(String docName, String docId, String url) {
DataResultSet resultSet = new DataResultSet(new String[]{"DOCNAME", "DOCID", "URL"});
Vector<String> vector = new Vector<String>();
vector.add(docName);
vector.add(docId);
vector.add(url);
resultSet.addRow(vector);
return resultSet;
}
}
What is lock? Are you using it elsewhere? Typically you want the synchronized blocks to be pretty small. If you're using lock everywhere as a general purpose lock then you might be stopping a thread from doing some useful work in a totally unrelated area (i.e., one where there is no contention for shared resources).
Second, does recipients really need to be an instance variable? It seems strange that you would keep adding emails to recipients without checking to see if that email already exists in the list. I can't see any code where you're clearing our recipients either. So that is a potential issue. If you are going to be building recipients from scratch every time, then just make it a local variable in the method. If you really need access to that data, you can always pull it out of userCns.
Once you make recipients a local variable, then you only need to synchronize by using userCns as a lock:
synchronized(userCns) {
...
}
edit: Your code shows that you only use recipients once, and that's inside the sendEmailToLegalUsers method. Another thing, as I pointed out, is that you never clear recipients so that's a bug in your code. Since you don't use recipients anywhere, make it a local variable to sendEmailToLEgalUsers. Also, just synchronize over userCns. You won't need to synchronize over recipients; you can create it inside the synchronized block.
I would do
private final List<String> userCns = new ArrayList<String>();
private final List<String> recipients = new ArrayList<String>();
with
synchronized(userCns) {
// as Vivin suggests.
}
you don't need an additional lock.