553 lines
20 KiB
Java
553 lines
20 KiB
Java
/*
|
|
* 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.catalina.tribes.demos;
|
|
|
|
import java.awt.Color;
|
|
import java.awt.Component;
|
|
import java.awt.ComponentOrientation;
|
|
import java.awt.Dimension;
|
|
import java.awt.event.ActionEvent;
|
|
import java.awt.event.ActionListener;
|
|
import java.awt.event.MouseAdapter;
|
|
import java.awt.event.MouseEvent;
|
|
import java.io.Serializable;
|
|
import java.util.Random;
|
|
|
|
import javax.swing.BoxLayout;
|
|
import javax.swing.JButton;
|
|
import javax.swing.JFrame;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JScrollPane;
|
|
import javax.swing.JTable;
|
|
import javax.swing.JTextField;
|
|
import javax.swing.table.AbstractTableModel;
|
|
import javax.swing.table.DefaultTableCellRenderer;
|
|
import javax.swing.table.TableColumn;
|
|
|
|
import org.apache.catalina.tribes.Channel;
|
|
import org.apache.catalina.tribes.ChannelListener;
|
|
import org.apache.catalina.tribes.ManagedChannel;
|
|
import org.apache.catalina.tribes.Member;
|
|
import org.apache.catalina.tribes.MembershipListener;
|
|
import org.apache.catalina.tribes.tipis.LazyReplicatedMap;
|
|
|
|
/**
|
|
* Example of how the lazy replicated map works, also shows how the BackupManager
|
|
* works in a Tomcat cluster
|
|
* @version 1.1
|
|
*/
|
|
public class MapDemo implements ChannelListener, MembershipListener{
|
|
|
|
/**
|
|
* The Map containing the replicated data
|
|
*/
|
|
protected LazyReplicatedMap<String,StringBuilder> map;
|
|
|
|
/**
|
|
* Table to be displayed in Swing
|
|
*/
|
|
protected SimpleTableDemo table;
|
|
|
|
/**
|
|
* Constructs a map demo object.
|
|
* @param channel - the Tribes channel object to be used for communication
|
|
* @param mapName - the name of this map
|
|
*/
|
|
public MapDemo(Channel channel, String mapName ) {
|
|
//instantiate the replicated map
|
|
map = new LazyReplicatedMap<>(null, channel, 5000, mapName, null);
|
|
//create a gui, name it with the member name of this JVM
|
|
table = SimpleTableDemo.createAndShowGUI(map,channel.getLocalMember(false).getName());
|
|
//add ourself as a listener for messages
|
|
channel.addChannelListener(this);
|
|
//add ourself as a listener for memberships
|
|
channel.addMembershipListener(this);
|
|
//initialize the map by receiving a fake message
|
|
this.messageReceived(null,null);
|
|
}
|
|
|
|
/**
|
|
* Decides if the messageReceived should be invoked
|
|
* will always return false since we rely on the
|
|
* lazy map to do all the messaging for us
|
|
*/
|
|
@Override
|
|
public boolean accept(Serializable msg, Member source) {
|
|
//simple refresh the table model
|
|
table.dataModel.getValueAt(-1,-1);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Invoked if accept returns true.
|
|
* No op for now
|
|
* @param msg - the message received
|
|
* @param source - the sending member
|
|
*/
|
|
@Override
|
|
public void messageReceived(Serializable msg, Member source) {
|
|
// NOOP
|
|
}
|
|
|
|
/**
|
|
* Invoked when a member is added to the group
|
|
*/
|
|
@Override
|
|
public void memberAdded(Member member) {
|
|
// NOOP
|
|
}
|
|
|
|
/**
|
|
* Invoked when a member leaves the group
|
|
*/
|
|
@Override
|
|
public void memberDisappeared(Member member) {
|
|
//just refresh the table model
|
|
table.dataModel.getValueAt(-1,-1);
|
|
}
|
|
|
|
/**
|
|
* Prints usage
|
|
*/
|
|
public static void usage() {
|
|
System.out.println("Tribes MapDemo.");
|
|
System.out.println("Usage:\n\t" +
|
|
"java MapDemo [channel options] mapName\n\t" +
|
|
"\tChannel options:" +
|
|
ChannelCreator.usage());
|
|
}
|
|
|
|
@SuppressWarnings("unused")
|
|
public static void main(String[] args) throws Exception {
|
|
long start = System.currentTimeMillis();
|
|
//create a channel object
|
|
ManagedChannel channel = (ManagedChannel) ChannelCreator.createChannel(args);
|
|
//define a map name, unless one is defined as a parameters
|
|
String mapName = "MapDemo";
|
|
if ( args.length > 0 && (!args[args.length-1].startsWith("-"))) {
|
|
mapName = args[args.length-1];
|
|
}
|
|
//start the channel
|
|
channel.start(Channel.DEFAULT);
|
|
//listen for shutdown
|
|
Runtime.getRuntime().addShutdownHook(new Shutdown(channel));
|
|
//create a map demo object
|
|
new MapDemo(channel,mapName);
|
|
|
|
//put the main thread to sleep until we are done
|
|
System.out.println("System test complete, time to start="+(System.currentTimeMillis()-start)+" ms. Sleeping to let threads finish.");
|
|
Thread.sleep(60 * 1000 * 60);
|
|
}
|
|
|
|
/**
|
|
* Listens for shutdown events, and stops this instance
|
|
*/
|
|
public static class Shutdown extends Thread {
|
|
//the channel running in this demo
|
|
ManagedChannel channel = null;
|
|
|
|
public Shutdown(ManagedChannel channel) {
|
|
this.channel = channel;
|
|
}
|
|
|
|
|
|
@Override
|
|
public void run() {
|
|
System.out.println("Shutting down...");
|
|
//create an exit thread that forces a shutdown if the JVM wont exit cleanly
|
|
SystemExit exit = new SystemExit(5000);
|
|
exit.setDaemon(true);
|
|
exit.start();
|
|
try {
|
|
//stop the channel
|
|
channel.stop(Channel.DEFAULT);
|
|
} catch (Exception x) {
|
|
x.printStackTrace();
|
|
}
|
|
System.out.println("Channel stopped.");
|
|
}
|
|
}
|
|
|
|
public static class SystemExit extends Thread {
|
|
private long delay;
|
|
public SystemExit(long delay) {
|
|
this.delay = delay;
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
Thread.sleep(delay);
|
|
} catch (Exception x) {
|
|
x.printStackTrace();
|
|
}
|
|
System.exit(0);
|
|
}
|
|
}
|
|
|
|
public static class SimpleTableDemo extends JPanel
|
|
implements ActionListener {
|
|
|
|
private static final long serialVersionUID = 1L;
|
|
|
|
private static int WIDTH = 550;
|
|
|
|
private LazyReplicatedMap<String,StringBuilder> map;
|
|
private boolean DEBUG = false;
|
|
AbstractTableModel dataModel = new AbstractTableModel() {
|
|
|
|
|
|
private static final long serialVersionUID = 1L;
|
|
String[] columnNames = {
|
|
"Rownum",
|
|
"Key",
|
|
"Value",
|
|
"Primary Node",
|
|
"Backup Node",
|
|
"isPrimary",
|
|
"isProxy",
|
|
"isBackup"};
|
|
|
|
@Override
|
|
public int getColumnCount() { return columnNames.length; }
|
|
|
|
@Override
|
|
public int getRowCount() {return map.sizeFull() +1; }
|
|
|
|
public StringBuilder getMemberNames(Member[] members){
|
|
StringBuilder buf = new StringBuilder();
|
|
if ( members!=null ) {
|
|
for (int i=0;i<members.length; i++ ) {
|
|
buf.append(members[i].getName());
|
|
buf.append("; ");
|
|
}
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
@Override
|
|
public Object getValueAt(int row, int col) {
|
|
if ( row==-1 ) {
|
|
update();
|
|
return "";
|
|
}
|
|
if ( row == 0 ) return columnNames[col];
|
|
Object[] keys = map.keySetFull().toArray();
|
|
String key = (String)keys [row-1];
|
|
LazyReplicatedMap.MapEntry<String,StringBuilder> entry =
|
|
map.getInternal(key);
|
|
switch (col) {
|
|
case 0: return String.valueOf(row);
|
|
case 1: return entry.getKey();
|
|
case 2: return entry.getValue();
|
|
case 3: return entry.getPrimary()!=null?entry.getPrimary().getName():"null";
|
|
case 4: return getMemberNames(entry.getBackupNodes());
|
|
case 5: return Boolean.valueOf(entry.isPrimary());
|
|
case 6: return Boolean.valueOf(entry.isProxy());
|
|
case 7: return Boolean.valueOf(entry.isBackup());
|
|
default: return "";
|
|
}
|
|
|
|
}
|
|
|
|
public void update() {
|
|
fireTableDataChanged();
|
|
}
|
|
};
|
|
|
|
JTextField txtAddKey = new JTextField(20);
|
|
JTextField txtAddValue = new JTextField(20);
|
|
JTextField txtRemoveKey = new JTextField(20);
|
|
JTextField txtChangeKey = new JTextField(20);
|
|
JTextField txtChangeValue = new JTextField(20);
|
|
|
|
JTable table = null;
|
|
public SimpleTableDemo(LazyReplicatedMap<String,StringBuilder> map) {
|
|
super();
|
|
this.map = map;
|
|
|
|
this.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
|
|
|
|
//final JTable table = new JTable(data, columnNames);
|
|
table = new JTable(dataModel);
|
|
|
|
table.setPreferredScrollableViewportSize(new Dimension(WIDTH, 150));
|
|
for ( int i=0; i<table.getColumnCount(); i++ ) {
|
|
TableColumn tm = table.getColumnModel().getColumn(i);
|
|
tm.setCellRenderer(new ColorRenderer());
|
|
}
|
|
|
|
|
|
if (DEBUG) {
|
|
table.addMouseListener(new MouseAdapter() {
|
|
@Override
|
|
public void mouseClicked(MouseEvent e) {
|
|
printDebugData(table);
|
|
}
|
|
});
|
|
}
|
|
|
|
//setLayout(new GridLayout(5, 0));
|
|
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
|
|
|
|
//Create the scroll pane and add the table to it.
|
|
JScrollPane scrollPane = new JScrollPane(table);
|
|
|
|
//Add the scroll pane to this panel.
|
|
add(scrollPane);
|
|
|
|
//create a add value button
|
|
JPanel addpanel = new JPanel();
|
|
addpanel.setPreferredSize(new Dimension(WIDTH,30));
|
|
addpanel.add(createButton("Add","add"));
|
|
addpanel.add(txtAddKey);
|
|
addpanel.add(txtAddValue);
|
|
addpanel.setMaximumSize(new Dimension(WIDTH,30));
|
|
add(addpanel);
|
|
|
|
//create a remove value button
|
|
JPanel removepanel = new JPanel( );
|
|
removepanel.setPreferredSize(new Dimension(WIDTH,30));
|
|
removepanel.add(createButton("Remove","remove"));
|
|
removepanel.add(txtRemoveKey);
|
|
removepanel.setMaximumSize(new Dimension(WIDTH,30));
|
|
add(removepanel);
|
|
|
|
//create a change value button
|
|
JPanel changepanel = new JPanel( );
|
|
changepanel.add(createButton("Change","change"));
|
|
changepanel.add(txtChangeKey);
|
|
changepanel.add(txtChangeValue);
|
|
changepanel.setPreferredSize(new Dimension(WIDTH,30));
|
|
changepanel.setMaximumSize(new Dimension(WIDTH,30));
|
|
add(changepanel);
|
|
|
|
|
|
//create sync button
|
|
JPanel syncpanel = new JPanel( );
|
|
syncpanel.add(createButton("Synchronize","sync"));
|
|
syncpanel.add(createButton("Replicate","replicate"));
|
|
syncpanel.add(createButton("Random","random"));
|
|
syncpanel.setPreferredSize(new Dimension(WIDTH,30));
|
|
syncpanel.setMaximumSize(new Dimension(WIDTH,30));
|
|
add(syncpanel);
|
|
|
|
|
|
}
|
|
|
|
public JButton createButton(String text, String command) {
|
|
JButton button = new JButton(text);
|
|
button.setActionCommand(command);
|
|
button.addActionListener(this);
|
|
return button;
|
|
}
|
|
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
System.out.println(e.getActionCommand());
|
|
if ( "add".equals(e.getActionCommand()) ) {
|
|
System.out.println("Add key:"+txtAddKey.getText()+" value:"+txtAddValue.getText());
|
|
map.put(txtAddKey.getText(),new StringBuilder(txtAddValue.getText()));
|
|
}
|
|
if ( "change".equals(e.getActionCommand()) ) {
|
|
System.out.println("Change key:"+txtChangeKey.getText()+" value:"+txtChangeValue.getText());
|
|
StringBuilder buf = map.get(txtChangeKey.getText());
|
|
if ( buf!=null ) {
|
|
buf.delete(0,buf.length());
|
|
buf.append(txtChangeValue.getText());
|
|
map.replicate(txtChangeKey.getText(),true);
|
|
} else {
|
|
buf = new StringBuilder();
|
|
buf.append(txtChangeValue.getText());
|
|
map.put(txtChangeKey.getText(),buf);
|
|
}
|
|
}
|
|
if ( "remove".equals(e.getActionCommand()) ) {
|
|
System.out.println("Remove key:"+txtRemoveKey.getText());
|
|
map.remove(txtRemoveKey.getText());
|
|
}
|
|
if ( "sync".equals(e.getActionCommand()) ) {
|
|
System.out.println("Syncing from another node.");
|
|
map.transferState();
|
|
}
|
|
if ( "random".equals(e.getActionCommand()) ) {
|
|
Thread t = new Thread() {
|
|
@Override
|
|
public void run() {
|
|
for (int i = 0; i < 5; i++) {
|
|
String key = random(5,0,0,true,true,null);
|
|
map.put(key, new StringBuilder(key));
|
|
dataModel.fireTableDataChanged();
|
|
table.paint(table.getGraphics());
|
|
try {
|
|
Thread.sleep(500);
|
|
} catch (InterruptedException x) {
|
|
Thread.interrupted();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
t.start();
|
|
}
|
|
|
|
if ( "replicate".equals(e.getActionCommand()) ) {
|
|
System.out.println("Replicating out to the other nodes.");
|
|
map.replicate(true);
|
|
}
|
|
dataModel.getValueAt(-1,-1);
|
|
}
|
|
|
|
public static final Random random = new Random();
|
|
public static String random(int count, int start, int end, boolean letters, boolean numbers,
|
|
char[] chars ) {
|
|
if (count == 0) {
|
|
return "";
|
|
} else if (count < 0) {
|
|
throw new IllegalArgumentException("Requested random string length " + count + " is less than 0.");
|
|
}
|
|
if ((start == 0) && (end == 0)) {
|
|
end = 'z' + 1;
|
|
start = ' ';
|
|
if (!letters && !numbers) {
|
|
start = 0;
|
|
end = Integer.MAX_VALUE;
|
|
}
|
|
}
|
|
|
|
char[] buffer = new char[count];
|
|
int gap = end - start;
|
|
|
|
while (count-- != 0) {
|
|
char ch;
|
|
if (chars == null) {
|
|
ch = (char) (random.nextInt(gap) + start);
|
|
} else {
|
|
ch = chars[random.nextInt(gap) + start];
|
|
}
|
|
if ((letters && Character.isLetter(ch))
|
|
|| (numbers && Character.isDigit(ch))
|
|
|| (!letters && !numbers))
|
|
{
|
|
if(ch >= 56320 && ch <= 57343) {
|
|
if(count == 0) {
|
|
count++;
|
|
} else {
|
|
// low surrogate, insert high surrogate after putting it in
|
|
buffer[count] = ch;
|
|
count--;
|
|
buffer[count] = (char) (55296 + random.nextInt(128));
|
|
}
|
|
} else if(ch >= 55296 && ch <= 56191) {
|
|
if(count == 0) {
|
|
count++;
|
|
} else {
|
|
// high surrogate, insert low surrogate before putting it in
|
|
buffer[count] = (char) (56320 + random.nextInt(128));
|
|
count--;
|
|
buffer[count] = ch;
|
|
}
|
|
} else if(ch >= 56192 && ch <= 56319) {
|
|
// private high surrogate, no effing clue, so skip it
|
|
count++;
|
|
} else {
|
|
buffer[count] = ch;
|
|
}
|
|
} else {
|
|
count++;
|
|
}
|
|
}
|
|
return new String(buffer);
|
|
}
|
|
|
|
private void printDebugData(JTable table) {
|
|
int numRows = table.getRowCount();
|
|
int numCols = table.getColumnCount();
|
|
javax.swing.table.TableModel model = table.getModel();
|
|
|
|
System.out.println("Value of data: ");
|
|
for (int i = 0; i < numRows; i++) {
|
|
System.out.print(" row " + i + ":");
|
|
for (int j = 0; j < numCols; j++) {
|
|
System.out.print(" " + model.getValueAt(i, j));
|
|
}
|
|
System.out.println();
|
|
}
|
|
System.out.println("--------------------------");
|
|
}
|
|
|
|
/*
|
|
* Create the GUI and show it. For thread safety,
|
|
* this method should be invoked from the
|
|
* event-dispatching thread.
|
|
*/
|
|
public static SimpleTableDemo createAndShowGUI(
|
|
LazyReplicatedMap<String,StringBuilder> map, String title) {
|
|
//Make sure we have nice window decorations.
|
|
JFrame.setDefaultLookAndFeelDecorated(true);
|
|
|
|
//Create and set up the window.
|
|
JFrame frame = new JFrame("SimpleTableDemo - "+title);
|
|
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
|
|
|
//Create and set up the content pane.
|
|
SimpleTableDemo newContentPane = new SimpleTableDemo(map);
|
|
newContentPane.setOpaque(true); //content panes must be opaque
|
|
frame.setContentPane(newContentPane);
|
|
|
|
//Display the window.
|
|
frame.setSize(450,250);
|
|
newContentPane.setSize(450,300);
|
|
frame.pack();
|
|
frame.setVisible(true);
|
|
return newContentPane;
|
|
}
|
|
}
|
|
|
|
static class ColorRenderer extends DefaultTableCellRenderer {
|
|
|
|
private static final long serialVersionUID = 1L;
|
|
|
|
public ColorRenderer() {
|
|
super();
|
|
}
|
|
|
|
@Override
|
|
public Component getTableCellRendererComponent
|
|
(JTable table, Object value, boolean isSelected,
|
|
boolean hasFocus, int row, int column) {
|
|
Component cell = super.getTableCellRendererComponent
|
|
(table, value, isSelected, hasFocus, row, column);
|
|
cell.setBackground(Color.WHITE);
|
|
if ( row > 0 ) {
|
|
Color color = null;
|
|
boolean primary = ( (Boolean) table.getValueAt(row, 5)).booleanValue();
|
|
boolean proxy = ( (Boolean) table.getValueAt(row, 6)).booleanValue();
|
|
boolean backup = ( (Boolean) table.getValueAt(row, 7)).booleanValue();
|
|
if (primary) color = Color.GREEN;
|
|
else if (proxy) color = Color.RED;
|
|
else if (backup) color = Color.BLUE;
|
|
if ( color != null ) cell.setBackground(color);
|
|
}
|
|
return cell;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|