How can I refer my Neo4j embedded Db to getGraphDb() method? - java

I want to develop a piece of software that helps the user to open and close a Neo4j embedded server within my java application. Practically, I click the button and the web admin tool should be opened in the default web browser. However, I am stuck because I have a problem with the line code
InternalAbstractGraphDatabase graphdb = getGraphDb();
I don't understand how to open my implemented db which is in the variable:
private static GraphDatabaseService BORO_DB;
and has path:
public static String DB_PATH;
Below the code:
final Variable var = new Variable(true);
InternalAbstractGraphDatabase graphdb = getGraphDb();
final WrappingNeoServerBootstrapper srv;
srv = new WrappingNeoServerBootstrapper( graphdb );
final JButton btnNewButton = new JButton("Show Graph - Start Server");
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
if (var.getVar()){
var.setVar(false);
btnNewButton.setText("Graph - Stop Server");
srv.start();
try {
String url = "http://localhost:7474";
java.awt.Desktop.getDesktop().browse(java.net.URI.create(url));
}
catch (java.io.IOException e) {
System.out.println(e.getMessage());
}
}else{
var.setVar(true);
btnNewButton.setText("Show Graph - Start Server");
srv.stop();
}
}
});
May you teach me how to link my DB (BORO_DB) to getGraphDb()?
Thank you

Using DB_PATH, you can instantiate BORO_DB like this:
BORO_DB = new GraphDatabaseFactory().newEmbeddedDatabase(DB_PATH);
srv=new WrappingNeoServerBootstrapper((GraphDatabaseAPI) BORO_DB);

Related

How can Java Web Start Swing App show servlet info?

When I typed in my question, similar questions came up, like this one [ How can I communicate Servlet from Swing ], but I looked at it, and didn't find the answer I'm looking for, so here is my question.
I'm developing a web app for internal call center, it uses a Java application [ CCT : call center technology ] to call asterisk server to make calls, this CCT has lots of servlets to generate html web pages so caller agents can access web pages and make calls. In order to better develop and debug, I added a Swing GUI panel to it, so when it runs on my development PC, it will open a window with lots of buttons, each represents a caller agent with their setting info, so I can click and look at the details. It runs fine and I've copies the code to a test server, when it runs there I can remote into it and see the Swing panel with JButtons representing each caller agent.
But the problem is, when this Java app runs on production server, it will be run as a Windows service [ so I was told by my boss ], and there will be no display, so the Swing panel won't be seen, therefore I thought about Java web Start, with it I can have a web page with a "WebStartLaunchButton" so when I click that button it will let me download a Jar file which will start a Swing App. But then I realize the Jar file will be downloaded onto my local PC, and run from the PC, it will have no knowledge of the servlet setting/info, it is independent, it has nothing to do with the call center web app sevlet, and won't be able to show caller agents info. Am I right, is there a way to have a Swing app run on the server and see [ have access to ] it from my PC ?
Here are more details, from the servlet there is a class like this :
public class DialerService extends Service implements Runnable
{
private boolean running = false;
private Thread thread;
private CallManager cm = null;
private AgentDialListManager adlm = null; // <-- My Swing program
private DataAccessManager dam = null;
...
public void run()
{
adlm = new AgentDialListManager(); // <-- My Swing program
...
}
...
private CallData getNextDial(SessionManager sm, SessionData session)
{
CallData nextDial = null;
AgentData agent = session.getAgentData();
if (agent != null)
{
SystemSettingsData settings = dam.getSystemSettings();
if (settings.isNextDialBufferOn())
{
nextDial = adlm.getNextNumber(dam,session); // <-- My Swing program : Get a number from self-managed agent dial list
}
...
}
}
...
}
Here is my main Swing program [ other related classes not shown ] :
package com.amerisave.cct.call;
import com.amerisave.cct.data.DataAccessManager;
import com.amerisave.cct.session.AgentData;
import com.amerisave.cct.session.SessionData;
import com.orderlysoftware.orderlycalls.OrderlyCalls;
...
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.SwingConstants;
...
public class AgentDialListManager extends JPanel implements Runnable
{
public static final long serialVersionUID = 26362862L;
static Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
static int W = 1136, H = 800, updateInterval = 1000;
HashMap<Integer,AgentDialListRunner> agentDialListMap = null;
HashMap<Integer,JFrame> Agent_Panel_Frame_Map = null;
AgentData agent;
Insets anInset = new Insets(0,0,0,0);
AgentDialListRunner adlr;
JPanel agentPanel = new JPanel(),Log_Panel=new JPanel(new FlowLayout(1,0,0));
...
static JTabbedPane Tabbed_Pane;
static JTextArea Log_TextArea = new JTextArea(Get_System_Properties());
String hostAddress,hostName,dialerServerInfo="";
List dialerServerList = null;
Thread agentDialListManagerThread;
AgentDialListManager()
{
if (!agentDialListManager_Exists_B)
{
agentDialListMap = new HashMap();
Agent_Panel_Frame_Map = new HashMap();
shutdown = false;
if (showGUI_B) Init();
start();
agentDialListManager_Exists_B = true;
}
}
void Init()
{
setLayout(new FlowLayout(1,0,1));
Tabbed_Pane=new JTabbedPane();
Tabbed_Pane.setDoubleBuffered(true);
Tabbed_Pane.setPreferredSize(new Dimension(W - 20,H - 42));
Tabbed_Pane.setAutoscrolls(true);
Tabbed_Pane.setFont(new Font("Times New Roman",0,16));
Tabbed_Pane.setForeground(new Color(0,0,230));
add(Tabbed_Pane);
agentPanel.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.RAISED,Color.white,new Color(134,134,134))," Agents",TitledBorder.CENTER,TitledBorder.TOP,new Font("Times New Roman",0,15),new Color(0,60,198)));
agentPanel.setPreferredSize(new Dimension(W - 20,H - 42));
Tabbed_Pane.addTab("Agents",null,agentPanel,"");
JPanel bottomPanel = new JPanel(new FlowLayout(1,0,0));
JPanel skipRatioPanel = new JPanel(new FlowLayout(1,0,0));
skipRatioPanel.setBorder(new EtchedBorder(EtchedBorder.RAISED,new Color(0,188,250),new Color(134,134,134)));
skipRatioPanel.setPreferredSize(new Dimension(330,30));
bottomPanel.add(skipRatioPanel);
...
Tabbed_Pane.addTab("Log",null,Log_Panel,"");
setPreferredSize(new Dimension(W,H));
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
CallData getNextNumber(DataAccessManager dam,SessionData session)
{
CallData nextDial = null;
if (agentDialListMap != null)
{
agent = session.getAgentData();
int nEmployeeId = agent.nEmployeeId;
if (!agentDialListMap.containsKey(nEmployeeId))
{
adlr = new AgentDialListRunner(nEmployeeId,dam);
agentDialListMap.put(nEmployeeId,adlr);
agentCount = agentDialListMap.size();
agentPanel.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.RAISED,Color.white,new Color(134,134,134)),agentCount + " Agent" + (agentCount > 1 ? "s" : ""),TitledBorder.CENTER,TitledBorder.TOP,new Font("Times New Roman",0,15),new Color(0,60,198)));
addAgent(adlr.id,adlr,agent);
}
adlr = agentDialListMap.get(nEmployeeId);
nextDial = adlr.getNextNumber();
}
return nextDial;
}
void createAndShowGUI()
{
JFrame frame = new JFrame("AgentDialListManager [ Java = " + System.getProperty("java.version") + " | jetty.home = "+System.getProperty("jetty.home")+" ] dialerServerInfo = " + dialerServerInfo+" [ hostAddress = " + hostAddress+" hostName = " + hostName+" ]");
frame.add(this);
frame.addWindowListener(new WindowAdapter()
{
public void windowActivated(WindowEvent e) { }
public void windowClosed(WindowEvent e) { }
public void windowClosing(WindowEvent e) { if (runFromMain_B) System.exit(0); }
public void windowDeactivated(WindowEvent e) { }
public void windowDeiconified(WindowEvent e) { repaint(); }
public void windowGainedFocus(WindowEvent e) { repaint(); }
public void windowIconified(WindowEvent e) { }
public void windowLostFocus(WindowEvent e) { }
public void windowOpening(WindowEvent e) { repaint(); }
public void windowOpened(WindowEvent e) { }
public void windowResized(WindowEvent e) { repaint(); }
public void windowStateChanged(WindowEvent e) { repaint(); }
});
frame.pack();
if (runFromMain_B) frame.setLocationRelativeTo(null);
else frame.setBounds(displayCount==3?-1728:0,displayCount==3?0:26,this.getWidth(),this.getHeight() + 43);
frame.setVisible(true);
}
public void run()
{
try
{
while (!shutdown)
{
Thread.sleep(sleepTime);
AgentDialListRunner.averageTimeToMoreNumbers = 0;
for (Map.Entry<Integer,AgentDialListRunner> mapElement : agentDialListMap.entrySet())
{
int nEmployeeId = (int)mapElement.getKey();
AgentDialListRunner adlr = (AgentDialListRunner)mapElement.getValue();
AgentDialListRunner.averageTimeToMoreNumbers += adlr.averageTimeInSecondsBeforeGettingMoreNumbers;
}
agentCount = agentDialListMap.size();
if (agentCount > 0)
{
AgentDialListRunner.averageTimeToMoreNumbers /= agentCount;
averageTimeToMoreNumbersValueLabel.setText(AgentDialListRunner.averageTimeToMoreNumbers + " Sec.");
}
}
}
catch (Exception e)
{
log.error("Exception in runner thread:", e);
}
}
public void start()
{
stop();
if (agentDialListManagerThread == null)
{
agentDialListManagerThread = new Thread(this);
agentDialListManagerThread.setPriority(Thread.NORM_PRIORITY);
agentDialListManagerThread.start();
}
}
public void stop()
{
if (agentDialListManagerThread != null) agentDialListManagerThread = null;
}
...
public static void main(String[] args)
{
runFromMain_B = true;
new AgentDialListManager();
}
}
The GUI when the server starts looks like the following, on the left side there is a window showing all active caller agents [ only showing one on my development PC ], when the agent button is clicked, another panel shows up on the right side displaying the numbers in the buffer for the agent to call, mouse over each number, customer info for that number will be displayed in tooltip of the button :
Hope these details will make the question more clear.
I was trying to understand for quite a while how your application works. I think what you are doing is that you have a servlet which is handling an http request AND as a part of the request handling you start a thread which opens up a Swing application. Is this correct?
If so, this is a bad design. Servlet is for serving web content. If you start a Swing app, it runs in the same JVM and you can actually access same backend resources as in the servlet but the problem is that the content that servlet serves is transported over the network to the client. While this can work on your local machine (where the server machine is also the client machine) it will not work on a normal client-server architecture, where server is a different machine than the client. Here the client receives the response (html) but has no access to the Swing app running on server machine.
Another problem with this design is how would you bind the swing application to the correct client? Image 100 users access the servlet, does it open 100 instances of the Swing application? As Kayaman said, you would need some kind of another connection to client's machine to access the Swing application. This is not trivial and not worth doing.
I am actually from Webswing team, but this is not the right use case for Webswing. Webswing is for standalone Swing/JavaFX applications that user would normally start up from desktop, not from a servlet.
I think the best choice for you is to implement the logic from you Swing app into an HTML page served by the servlet, probably use some AJAX calls on the buttons, etc. There is really a lot of possibilities how to access backend from webpage in a very responsive and native-like way.

GUI (client) freezes when creating chat from a combobox (chat client)

I am creating a chat client which contains a class Server ( main class ) and a client class ( which starts a client and connects to the server ).
Then now I am trying to add a combobox with contacts before I connect a client to the server, so I did a new class for Combobox, with a actionListener and actionPerformed to open a client when pressing a contact. Problem is the Client that opens is running (seeing the printouts when messaging through Server ) but the Client GUI is gray and frozen, (like when a program does not answer). Same problem was with the combobox before I did dispose().
So I am currently quite new to thread coding, but my understanding of the problem (might be wrong) is that the Swing component thread which is used in the comboBoxClass is used on creating and clicking on contacts in the comboBox. When I then create a new Client in the comboBoxClass I already use the Swingthread there and the Client will not be able to use "enough" threads. Not sure thought but that is my conclusion.
Also here is a link to whole the code: https://pastebin.com/mNeBFn7q
(Using 4 textfiles so it wont run without them, but you will see the full structure).
};
class comboBoxClass {
public comboBoxClass() {
String[] favContacts = {
"BigKarlos",
"lilVillz",
"metaforMatte",
"BirkoStoteles",
"Övriga Kontakter!!"
JComboBox contactList = new JComboBox(favContacts);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 100);
Container cont = frame.getContentPane();
cont.setLayout(new FlowLayout());
cont.add(contactList);
frame.setVisible(true);
contactList.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
//contactList.addActionListener(this);
ArrayList<String> arrayList = readFile();
contentArr = new String[arrayList.size()];
arrayList.toArray(contentArr);
if (contactList.getSelectedIndex() == 0) {
port = contentArr[4];
IP = contentArr[5];
}
if (contactList.getSelectedIndex() == 1) {
port = contentArr[7];
IP = contentArr[8];
}
if (contactList.getSelectedIndex() == 2) {
port = contentArr[1];
IP = contentArr[2];
}
if (contactList.getSelectedIndex() == 3) {
port = contentArr[10];
IP = contentArr[11];
}
if (contactList.getSelectedIndex() == 4) {
JFileChooser contactChooser = new JFileChooser("/Users/kasperknudsen/Documents/Chatten");
contactChooser.getFileFilter();
contactChooser.showOpenDialog(frame);
}
frame.dispose();
Client kontakt = new Client(IP,Integer.parseInt(port));
}
});
}

How to Download XML file from a web site with login [duplicate]

This question already has answers here:
Download a file from the internet using java : How to authenticate?
(6 answers)
Closed 7 years ago.
I have a problem, I am very new to Java and I'm currently trying to create a Java application to download files from a website. To enter the website you need a password and username. I use NativeSwing to enter and get the path of the files, I saw many examples, but I don't know how implement them.
Any advice ?
public class Test {
public JFrame frame;
private static JWebBrowser browser;
private static JPanel configurationButtonPanel;
public Test() {
frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createContent(), BorderLayout.CENTER);
frame.setSize(800, 800);
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public JComponent createContent() {
JPanel contentPane = new JPanel(new BorderLayout());
JPanel configurationPanel = new JPanel(new BorderLayout());
configurationButtonPanel = new JPanel(new FlowLayout(
FlowLayout.CENTER, 0, 0));
JButton beginButton = new JButton("Download");
beginButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ArrayList<String> ligas = new ArrayList<String>();
int a=0;
Document doc = Jsoup.parse(browser.getHTMLContent());
Element ele = doc.getElementById("ctl00_MainContent_PnlResultados");
System.setProperty("java.net.useSystemProxies", "true");
try{
Elements img_2 = ele.getElementsByClass("BtnDescarga");
for (Element el : img_2) {
for( Attribute attribute : el.attributes() )
{
if( attribute.getKey().equalsIgnoreCase("onclick"))
{
ligas.add("https://portalcfdi.facturaelectronica.sat.gob.mx/"+attribute.getValue().substring(19,535));
}
}
}
}
catch(NullPointerException nulo){
}
for( int i = 0 ; i < ligas.size() ; i++ ) {
System.out.println( ligas.get( i ) );
}
}
});
browser = new JWebBrowser();
browser.navigate("https://cfdiau.sat.gob.mx/nidp/app/login?id=SATUPCFDiCon&sid=0&option=credential&sid=0");
configurationButtonPanel.add(beginButton);
configurationButtonPanel.setVisible(true);
configurationPanel.add(configurationButtonPanel, BorderLayout.NORTH);
contentPane.add(configurationPanel, BorderLayout.SOUTH);
contentPane.add(browser, BorderLayout.CENTER);
return contentPane;
}
/**
* #param args
*/
public static void main(String[] args) {
NativeInterface.open();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Test();
}
});
NativeInterface.runEventPump();
}
}
When it run this part
for( int i = 0 ; i < ligas.size() ; i++ ) {
System.out.println( ligas.get( i ) );
}
I get the following URL like this:
https://portalcfdi.facturaelectronica.sat.gob.mx/RecuperaCfdi.aspx?Datos=huswUYX1eXMlGkDiItMUBgaWREHHqhXOWtYxqyUh0oUZnCKLYE/gx6ENJ+0TwW5auWw8d/AiCJyuFSDNVY+5l0vkiELroo/fEmF+x5w+DQDDTfMX9qIINS1NgP9C1bFhirjcVXpZI1ed4ycpLPczkYMEGEKvqWemni8LWcbqC0BuZskOJnCQCaWRh1Kt7AL5GdBVKqkm3T5mvzhtkmE5dn0vcWbCFFO3d3G8hu7rlcc0XM+7+6iR52SZYYaHa/TOhcl2DjuzztADpa9tPxZ9VO6EzMVkYKTfDOqHwZO8m2U9BZ7UhFjqsyoAwsQneqhIqGwN21yEpGEcptsTb9uZ1t0Fc/1Ggd6SuK9NeGdBpiawn6cv6QM1uc4QQHMNpAgG89Rq5tOd4YAoRQHBe/vO8ppq60JwvJgQ4BN76EtZF0UtEWK+k57P01vatuvTHIdMBncbXyU+TrtE5AlhdGKkY2a8HwSxHw3nfoQ+SLBrjyg=
They use applets for non-standard authentication, so take a look at my second post. This one is only valid for normal authentication mechanisms, i will leave it here, because it could be valid for someone else.
You need to connect to this site first, by sending the HTTPS authentication POST, with your credentials. You should do all this communication in the "https://...".
The next step is to read a cookie value from the authentication response, and then set this cookie in to the header of the next URLconnection you will do for the resource. It is standard mechanism for server to distinguish the logged user, the authentication cookie is added to every request after login automatically by the browser, but in java, you need to do it manually.
It should work, if the server has a standard authentication mechanism, if it has different mechanism, you need to reverse enginerrig what is send, by using for example FireBug plugin, and switch to the Network tab, doing some log in, log out, downloading file, inspect what the communication looks like, and repeat it in your code.
Here you can find how to send your AUTHENTICATION POST with parameters in java: http://www.mkyong.com/java/how-to-send-http-request-getpost-in-java/
You will probably need to send parameters like password and login, but it could vary, look at the firebug network tab, when you login, it will give idea what params for that looks like. For example:
password=MySecretPassword&login=MyLogin
how to get cookies:
URLConnection connection = new URL("your url").openConnection();
// insert auth parameters and set method to post
// call doInput, doOutput on connection
// read cookie
List<String> cookies = connection.getHeaderFields().get("Set-Cookie");
myCookie = you need to change cookies list to one String containing all cookies, i don't have this code right now, but is should be easy.
URLConnection resourceCon = new URL("your url").openConnection();
resourceCon.setRequestProperty("Cookie", myCookie);
I see, that they don't use standard authentication mechanism at this site:
https://cfdiau.sat.gob.mx/nidp/wsfed/ep?id=SATx509Custom&sid=0&option=credential&sid=0
They use some applets, you need to download the applets files that they have:
https://cfdiau.sat.gob.mx/nidp/applet/SgiCripto.jar
and
https://cfdiau.sat.gob.mx/nidp/applet/x509applet.jar
Then you could use some java decompiler, for instance this one: http://jd.benow.ca/
And after decompiling those project, you need to dig in to the code and do reverse-engineering to investigate how this site works.

Method connect() from type PircBotX not visible

I am trying to create a bot using PircBotX, however I am unable to even get started making it. Using just the basic example code, I am unable to get the connect() method to work, it always give the error at compile mentioned in the title. Here is the code I am using:
import org.pircbotx.Configuration;
import org.pircbotx.PircBotX;
public class MyBot {
public static void main(String[] args) throws Exception {
Configuration configuration = new Configuration.Builder()
.setName("PircBotX") //Set the nick of the bot. CHANGE IN YOUR CODE
.setLogin("LQ") //login part of hostmask, eg name:login#host
.setAutoNickChange(true) //Automatically change nick when the current one is in use
.setCapEnabled(true) //Enable CAP features
.setServerHostname("irc.freenode.net")
.addAutoJoinChannel("#pircbotx") //Join the official #pircbotx channel
.buildConfiguration();
PircBotX bot = new PircBotX(configuration);
//Connect to server
try {
bot.connect();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
The error occurs with the line bot.connect();
PircBotX can be found at its Google Code page: https://code.google.com/p/pircbotx/
On the docs, there is a method called startBot(), should that be used instead?
void startBot() Start the bot by connecting to the server.
import org.pircbotx.Configuration;
import org.pircbotx.PircBotX;
public class MyBot {
public static void main(String[] args) throws Exception {
Configuration configuration = new Configuration.Builder()
.setName("PircBotX") //Set the nick of the bot. CHANGE IN YOUR CODE
.setLogin("LQ") //login part of hostmask, eg name:login#host
.setAutoNickChange(true) //Automatically change nick when the current one is in use
.setCapEnabled(true) //Enable CAP features
.setServerHostname("irc.freenode.net")
.addAutoJoinChannel("#pircbotx") //Join the official #pircbotx channel
.buildConfiguration();
PircBotX bot = new PircBotX(configuration);
//Connect to server
try {
//bot.connect();
bot.startBot();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

how to connect java derby database in netbeans

i have already created the table in connection and table name is "contact db"
i have window where i add detail. Here is the code, i want to add the entered details into my database table
i dont know how to connect to my table in netbeans, i was able to create the table using an example from netbeans site but connection examples are too fuzzy on net
public void addVendor(String nam,String adres,String num)
{
l1= new JLabel ("Name");
l2= new JLabel ("Address");
l3= new JLabel ("contactNumber");
Name= new JTextField ("");
Address= new JTextField ("");
ContactNumber= new JTextField ("");
b1 = new JButton("Add");
border = new BorderLayout();
this.setLayout(border);
JPanel Panel = new JPanel(new GridLayout(0,1));
this.add(Panel);
Panel.add(l1);
Panel.add(Name);
Panel.add(l2);
Panel.add(Address);
Panel.add(l3);
Panel.add(ContactNumber);
Panel.add(b1);
b1.addActionListener(this);
this.setVisible(true);
this.setSize(425,325);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public void actionPerformed( ActionEvent a)
{
JButton button;
button = (JButton) a.getSource();
if (button==b1)
{
//here i want to add data to table
}
}
If you're trying to connect to a Derby database in Netbeans, you'll need to know the database name (which you do seem to know), as well as a respective username and password, which you should have needed to specify when you created the database.
You'll also need to know the URL, this is critical. It should be something akin to "jdbc:derby://localhost:1527/YOUR_DATABASE_NAME"
Also, a quick note, it looks like your database name contains a space, don't ever do that ever. Just call it something like CONTACTSDB or something like that.
Here's something that should hopefully work for you.
final String databaseURL = "jdbc:derby://localhost:1527/YourDatabaseName";
final String databaseName = "NameOfYourDatabase";
final String username = "YourUserName";
final String password = "YourPassword";
// Below is the method invoked to establish a connection to the database
public void accessDatabase() throws ClassNotFoundException {
try {
Class.forName(rolodexDriver).newInstance();
connection = DriverManager.getConnection(databaseURL, username, password);
statement = connection.createStatement();
}
catch (InstantiationException | IllegalAccessException | SQLException ex) {
Logger.getLogger(rolodexBean.class.getName()).log(Level.SEVERE, null, ex);
}
} // end of accessDatabase method
And then just use the accessDatabase() method and everything should work out, I believe. I hope this helps out.

Categories