📜 ⬆️ ⬇️

Jython-console of your application

Let me tell you how I use the interactive Jython console to speed up Bean development in the application that I support.

Essence of the question


Everyone who has ever encountered a long-developed Java-based application knows that many of them are very slowly assembled and start. We will not discuss why this happens. This is a topic for a separate article.

On duty, I had to support a very old application with a huge code base. Worst of all, it’s going from minute to seven, and another three minutes starts. Again, it is not difficult for each programmer to imagine what kind of hell to write an unlimited amount of code, and then catch NullPointerException from external services with such a long cycle Implement-> Compile-> Start Deploy-> Wait-> Smoke-> Wait-> Test.
')
Another option is also possible. There is a certain amount of code in the class that needs to be adapted to fulfill a task that is close to the one it already has. Now imagine that this class is implemented in Java 1.4. It does not work with Generics, because they were added only in Java 1.5. In addition, programmers who previously worked on supporting the system are actively using and returning to the collection methods returned by other classes, which is not reaching the anonymous implementations of java.lang.Object.

After eating a cactus for a couple of days, I felt that I was starting to go crazy about writing five to twenty lines of working code per day.

I saw the way to accelerate development in such barbaric conditions only by screwing into the application an interpreter of a dynamic language for quick prototyping the code that is vulnerable to such conditions, first on it and then implementing the algorithm in Java. Of course, within the framework of the solution, an interactive console of this language was also needed. What is the dynamics without this? The console did not want to be in the applet due to the fact that the applets work very poorly (let's say, they simply do not work) in FreeBSD, to which I have been very used to lately.

Search for existing solutions


As a supporter of the opinion that inventing “vilo” sipedy (because often they only forks) didn’t rub off, I decided to dig at the weekend in the direction of already existing solutions to this issue. I really wanted Python. But before returning to the first choice, I managed to look away:

Jython

Python programmers are well aware of one of his concepts "batteries included", which means many different tasty libraries, even in the standard package. Only Java has its own batteries. The implementation of Python for JVM is just a reference, but the libraries are not fully ported yet (for example, setuptools began to be installed in the Jython environment quite recently), so I didn’t manage to create native Python solutions.

I tried RPyC with different versions of Jython (the night passed unnoticed). I read about Twisted Manhole . I decided not to merge the sources of the test branch of integration with Jython, because there was a danger of starting Twisted for Jython and becoming a bachelor after that.

I collected github.com/EnigmaCurry/jython-shell-server and understood: no readline, no happiness. Telnet does not support readline if the developer has not implemented this functionality on the server side. Imagine that you wrote a string 80 characters long and remembered that the first object is called differently. Of course, you can stick with the mouse in the console, but I wanted a native one similar to the bash environment.

My implementation


Server

In five minutes, I chose XMLRPC as the server interface. Over the next five minutes did not change my mind. Over the next 20 minutes realized.

Jython server code:
from SimpleXMLRPCServer import * <br/>
from os import path<br/>
from code import InteractiveConsole as BaseInteractiveConsole<br/>
<br/>
class Stdout ( object ) :<br/>
""" stdout """ <br/>
def __init__ ( self ) :<br/>
self . buffer = '' <br/>
<br/>
def get_buffer ( self ) :<br/>
""" """ <br/>
bc = self . buffer <br/>
self . buffer = '' <br/>
return bc<br/>
<br/>
def write ( self , bs ) :<br/>
""" """ <br/>
self . buffer += bs<br/>
return len ( bs ) <br/>
<br/>
class InteractiveConsole ( BaseInteractiveConsole ) :<br/>
""" , """ <br/>
<br/>
def __init__ ( self , locals ) :<br/>
""" """ <br/>
BaseInteractiveConsole. __init__ ( self , locals ) <br/>
# <br/>
self . stdout = sys . stdout = sys . stderr = Stdout ( ) <br/>
<br/>
def push ( self , line ) :<br/>
result = BaseInteractiveConsole. push ( self , line ) <br/>
return ( result, self . stdout . get_buffer ( ) ) # <br/>
<br/>
<br/>
<br/>
class Server ( SimpleXMLRPCServer ) :<br/>
"""XMLRPC-, """ <br/>
<br/>
def __init__ ( self , ls, * args, ** kwargs ) :<br/>
SimpleXMLRPCServer . __init__ ( self , * args, ** kwargs ) <br/>
self . register_introspection_functions ( ) <br/>
# <br/>
self . register_instance ( InteractiveConsole ( ls ) )

Customer

Cmd was chosen as the base interface, which supports readline out of the box, which is what we need.

The server code is this time in Python (it looks like Cmd in Jython does not support readline):
from cmd import Cmd as BaseCmd<br/>
from code import InteractiveConsole as BaseInteractiveConsole<br/>
import re , sys <br/>
from xmlrpclib import ServerProxy<br/>
<br/>
class Cmd ( BaseCmd ) :<br/>
""" -""" <br/>
reg = re . compile ( '^ \s *' ) <br/>
def __init__ ( self , host, port ) : <br/>
BaseCmd. __init__ ( self ) <br/>
self . s = ServerProxy ( 'http://%s:%d' % ( host, int ( port ) ) ) # <br/>
self . prompt = '>>> ' # <br/>
self . leading_ws = '' # <br/>
self . is_empty = False # <br/>
<br/>
def precmd ( self , line ) :<br/>
""" ,<br/>
"""
<br/>
# , .. default <br/>
self . leading_ws = self . reg . match ( line ) . group ( 0 ) <br/>
# , .. <br/>
self . is_empty = ( line == '' ) <br/>
return line # , <br/>
<br/>
def default ( self , line ) : <br/>
if ( self . is_empty ) : # y <br/>
line = '' <br/>
line = self . leading_ws + line # <br/>
( result, output ) = self . s . push ( line ) # <br/>
# <br/>
self . prompt = ( '... ' if result else '>>> ' ) <br/>
sys . stdout . write ( output ) # :) <br/>
<br/>
if __name__ == '__main__' :<br/>
HOST, PORT = sys . argv [ 1 : ] <br/>
Cmd ( HOST, PORT ) . cmdloop ( )

Java server wrapper

A simple bean to run our server. Easier just nowhere.
package net.rjyc ; <br/>
<br/>
import org.python.util.PythonInterpreter ; <br/>
import java.util.* ; <br/>
<br/>
public class Server { <br/>
private PythonInterpreter i ; <br/>
public PythonInterpreter getInterpreter ( ) { <br/>
return i ; <br/>
} <br/>
public Server ( String host, int port ) { <br/>
this ( host, port, new HashMap < String, Object > ( ) ) ; <br/>
} <br/>
public Server ( String host, int port, Map < String, Object > locals ) { <br/>
i = new PythonInterpreter ( ) ; <br/>
// <br/>
i. set ( "host" , host ) ; <br/>
i. set ( "port" , port ) ; <br/>
i. set ( "ls" , locals ) ; <br/>
} <br/>
<br/>
public void start ( ) { <br/>
// <br/>
i. exec ( "from rjyc import Server; Server(dict(ls), (host, port), logRequests = False).serve_forever()" ) ; <br/>
} <br/>
}

Using


Introduce yourself to a hypothetical servlet that lists the references from its field.
import javax.servlet.http.* ; <br/>
import java.util.* ; <br/>
import java.io.* ; <br/>
<br/>
public class Hello extends HttpServlet { <br/>
public final Map < String, String > links = new HashMap < String, String > ( ) ; <br/>
{ <br/>
links. put ( "Python" , "http://python.org" ) ; <br/>
links. put ( "Java" , "http://java.net" ) ; <br/>
} <br/>
@ Override protected void doGet ( HttpServletRequest request, HttpServletResponse response ) <br/>
throws IOException { <br/>
PrintWriter writer = response. getWriter ( ) ; <br/>
for ( Map . Entry < String, String > e: links. entrySet ( ) ) <br/>
writer. println ( "<a href= \" " +e. getValue ( ) + " \" >" +e. getKey ( ) + "</a>" ) ; <br/>
writer. close ( ) ; <br/>
} <br/>
}

This is what the web server responds to us:
 siasia@siasia ~ % wget http://localhost:8080 -O - 2>/dev/null <a href="http://python.org">Python</a> <a href="http://java.net">Java</a> 

Now we will introduce our console into it.
import javax.servlet.http.* ; <br/>
import java.util.* ; <br/>
import java.io.* ; <br/>
import net.rjyc.Server ; <br/>
<br/>
public class Hello extends HttpServlet { <br/>
public final Map < String, String > links = new HashMap < String, String > ( ) ; <br/>
{ <br/>
links. put ( "Python" , "http://python.org" ) ; <br/>
links. put ( "Java" , "http://java.net" ) ; <br/>
Thread t = new Thread ( ) { <br/>
@ Override public void run ( ) { <br/>
Map < String, Object > locals = new HashMap < String, Object > ( ) ; <br/>
locals. put ( "this" , Hello. this ) ; <br/>
new Server ( "localhost" , 8081 , locals ) . start ( ) ; <br/>
} <br/>
} ; <br/>
t. start ( ) ; <br/>
} <br/>
@ Override protected void doGet ( HttpServletRequest request, HttpServletResponse response ) <br/>
throws IOException { <br/>
PrintWriter writer = response. getWriter ( ) ; <br/>
for ( Map . Entry < String, String > e: links. entrySet ( ) ) <br/>
writer. println ( "<a href= \" " +e. getValue ( ) + " \" >" +e. getKey ( ) + "</a>" ) ; <br/>
writer. close ( ) ; <br/>
} <br/>
}

And connect to it:
 siasia@siasia ~ % python client.py localhost 8081 >>> this examples.Hello@13ebc5c >>> this.links {Python=http://python.org, Java=http://java.net} >>> this.links['Scala'] = 'http://scala-lang.org' >>> this.links {Scala=http://scala-lang.org, Python=http://python.org, Java=http://java.net} 

Check the result:
 siasia@siasia ~ % wget http://localhost:8080 -O - 2>/dev/null <a href="http://scala-lang.org">Scala</a> <a href="http://python.org">Python</a> <a href="http://java.net">Java</a> 

Maven


Prepared Maven-artifact, in case you use Maven.
  1. Add the repository to your pom.xml:
      <repository>
       <id> Rjyc Repository </ id>
       <url> http://siasia.github.com/maven2 </ url>
     </ repository> 
  2. Add a dependency on rjyc:
      <dependency>
       <groupId> org.python </ groupId>
       <artifactId> rjyc </ artifactId>
       <version> 1.0-SNAPSHOT </ version>
     </ dependency> 
  3. Import the server in your code:
    import net.rjyc.Server;
  4. Run it as described above.
  5. Download the client http://github.com/siasia/rjyc/raw/master/client.py
  6. python client.py [host] [port]
  7. PROFIT !!!

I hope this article will make someone else a little happier.
Fork me on github http://github.com/siasia .

Source: https://habr.com/ru/post/129064/


All Articles