midp programming networking khanh le. / 2 of chapter objectives the cldc streams model generic...
Post on 01-Jan-2016
215 Views
Preview:
TRANSCRIPT
<Module>/<Chapter> 3 of <total>
Connect your MIDlet to the Connect your MIDlet to the Real World!Real World!
In the previous sections, we have learnt all the
basic components to build MIDlet applications into
your MIDs.
Without the connection to the real world (our
Internet world), your powerful MIDlets can be no
more than a piece of powerful personal assistant or
entertainment center (if you implement some MIDlet
games) in you own device. We cannot talk with the
real world!
<Module>/<Chapter> 4 of <total>
Connect your MIDlet to the Connect your MIDlet to the Real World!Real World!
In this section, we will learn how to extend your MIDlet to the
real world, the Internet - to achieve the following goals: To retrieve information (including text information, data,
image, ...) from the Internet
To implement interactive Web applications, just like what we
have done using our desktop computers.
In the technical point of view, we will disuss: The network framework to support Internet connectivity
All the necessary APIs to support these functions
Integration with Java Servlet to implement interactive Web
applications
<Module>/<Chapter> 6 of <total>
MIDlet Network Connectivity MIDlet Network Connectivity FrameworkFramework
Before start working on the program implementation.
Let's have an overview of the Network Connectivity
framework implemented in MIDP/CLDC.
Actually, MIDP/CLDC implemented a well-defined
framework to support network connection, which is
implemented in the javax.microedition.io package.
Which provides a family tree of network connectivity
modules to support various kinds of network connection:
including HTTP (our major focus) and generic Datagram
connection.
<Module>/<Chapter> 7 of <total>
MIDlet Network Connectivity MIDlet Network Connectivity FrameworkFramework
Although MIDP/CLDC support various kind of network connections.
In this section, we will focus on the implementation of HTTP
connection mode, which is main focus of contemporary Web
system.
NOTE: Before we start the MIDlet HTTP programming, one very important point
is: Similar to Java Servlet HTTP programming, MIDlet network
programming will NOT ONLY support HTTP connection, actually it also
provides rooms for other network connection modes (and protocols) as
well, for the future extension.
In other words, one can define your way of network connectivity (with
your specific protocols) and develop wireless applications using
MIDlets.
<Module>/<Chapter> 8 of <total>
Basic Idea of HTTP Basic Idea of HTTP Connection in MIDletsConnection in MIDlets
In MIDlet network programming, the link between the Connection
interfaces and actual implementation is a class called
javax.microedition.io.Connector. In fact, it is also the only class in
the javax.microedition.io package.
The basic idea of MIDlet HTTP programming is very straightforward:
1. First of all you pass a connection string (can be a simple HTTP
URL to retrieve document or the locator of a sever-side application
such as a servlet) to one of the Connector's static methods.
2. It will get back an implementation of the HttpConnection
<Module>/<Chapter> 9 of <total>
Basic Idea of HTTP Basic Idea of HTTP Connection in MIDletsConnection in MIDlets
3. In HTTP request, you can either using a GET, POST or
HEAD command to make a request to the remote Web
server.
4. If the connection is okay, the remote Web server will
make a response upon your request, such as:
- send the requested document back to your MID
- send the requested data (e.g. videos, images, ..) back
to your MID
- invoke the server-side program (e.g. servlet) and
provide interactions with you
5. After that, the HTTP connection will be close.
<Module>/<Chapter> 10 of <total>
Making a ConnectionMaking a Connection
Connector is a Connection factory Pass a URL-style string to open(), get
back some Connection implementation MIDP says that HTTP must be
supported Other connection types are optional
Note that HTTP does not have to run over TCP/IP
<Module>/<Chapter> 12 of <total>
Connection and its FamilyConnection and its Family
Connection represents some kind of I/O connection
It has subinterfaces that define more specific connection types
<Module>/<Chapter> 13 of <total>
Review of HTTPReview of HTTP
General data transfer protocol Client sends request Server sends response Requests and responses have two parts:
Headers (metadata) Content
Requests often have no content Requests can contain parameters
Think of the values from an HTML form
<Module>/<Chapter> 14 of <total>
ParametersParameters
For GET, parameters are encoded and tacked on to the end of the URL http://www23.brinkster.com.com/
lenqkhanh/simple?user=lenqkhanh&zip=084
With POST, parameters are sent as the content part of the request The same encoding is used
<Module>/<Chapter> 15 of <total>
Performing a GETPerforming a GET
It’s amazingly simple:String url = "
http://www23.brinkster.com.com/lenqkhanh/simple";
InputConnection ic = (InputConnection)Connector.open(url);
InputStream in = ic.openInputStream();
// Now read stuff from the InputStream.
// Remember to clean up.
ic.close();
Remember to catch IOExceptions
<Module>/<Chapter> 16 of <total>
POSTing a FormPOSTing a Form
It’s a little more complicated than GET You need HttpConnection methods Change the request method with
setRequestMethod() Tell the server the length of your parameters
(“Content-Length”) with setRequestProperty()
Send the parameters in the output stream of the connection of the connection
<Module>/<Chapter> 17 of <total>
Invoking a CGI ScriptInvoking a CGI Script
Both the GET and POST methods can be used to invoke a CGI (Common Gateway Interface) scripts and supply input data
<Module>/<Chapter> 18 of <total>
HTTP Connections HTTP Connections in MIDPin MIDP
The MIDP profile supports HTTP version 1.1 connections via the HttpConnection interface. The GET, POST, and HEAD schemes of HTTP are supported.
<Module>/<Chapter> 19 of <total>
HTTP ConnectionsHTTP Connections in MIDP in MIDP (cont)(cont)
Nothing better than try the programs. So in the following sections, we will
discuss MIDlet HTTP programming techniques by using the following
programming examples.
1. HTTPMIDlet.java - make a direct connection Web server and
download document onto your MID
2. ImageLoader.java - an interesting HTTP MIDlet to download an
image from a Web site
3. CookieMIDlet.java - to demonstrate how to manipulate cookies for the
implementation of session tracking in HTTP MIDlets (and integrating with
servlet session tracking APIs.
4. BookSearchMIDlet.java - an integration of HTTP MIDlet to link up with a
search engine in our Virtual Bookstore system.
<Module>/<Chapter> 20 of <total>
Directly download document onto Directly download document onto your MID: HTTPMIDlet.javayour MID: HTTPMIDlet.java
Loading document from a server (e.g. Web server) is very simple. Especially when you are performing the HTTP GET command (which is the default case).
All you need to do is to pass a URL to the Connector's static open() method. The returned Connection will then be an implementation of HttpConnection.
Using this HttpConnection object, you can access the InputStream (and redirect to the DataInputStream) and get the corresponding and read the data, and (or) display onto your MID display.
In the following example "HTTPMIDlet", we demonstrate how to implement a simple document accessing application using GET (default) command.
<Module>/<Chapter> 21 of <total>
Directly download document onto Directly download document onto your MID: HTTPMIDlet.javayour MID: HTTPMIDlet.java
The key connection statements are as follows: // openning up http connection with the web server when
the connection is opened, the default // request method is GET hc = (HttpConnection) Connector.open(urlstring);
// establishing input stream from the connection dis = new DataInputStream(hc.openInputStream());
// reading the response from web server character by character int ch; while ((ch = dis.read()) != -1) { message = message + (char) ch; }
Note: Of course, the part Highlighted in RED is subject to your implementation!
<Module>/<Chapter> 22 of <total>
HTTPMIDlet.java HTTPMIDlet.java (cont.)(cont.)
NOTE:
1. In fact, HTTPMIDlet is a "generic" HTTP MIDlet in the
sense that it allow user to input ANY URL (by using a
simple MIDlet Form), the default one is a simple
HelloWorld text document located in my home directory
http://www23.brinkster.com/lenqkhanh/kita/hello.txt.
2. Noted that the default page is a simple TEXT document
(not a HTML file). Remember your MIDlet display is just a
screen display, not a Web browser (by default). So don't
try to do silly thing to download a complex HTML page
onto your MID, it will come to a MESS!
<Module>/<Chapter> 23 of <total>
HTTPMIDlet.java HTTPMIDlet.java (cont.)(cont.)3. Anyway, in the following sample MIDlet, we have make THREE trails:
a. Connect and GET hello.txt document at
http://www23.brinkster.com/lenqkhanh/kita/hello.txt.
b. Connect and GET the hello.htm document
http://www23.brinkster.com/lenqkhanh/kita/hello.htm
c. Invoke the Java servlet HelloWorld at
http://wwwteach.comp.polyu.edu.hk/projects/servlet/csstlee.servlet1.ServletHelloWorld
(For this one, please try this on a standard Web browser and check the
difference!
For details, please refer to our EC Lab at http://
wwwteach.comp.polyu.edu.hk/raymond/htmlpage/mainframe.htm)
4. Try the MIDlet and access any information you prefer.
<Module>/<Chapter> 24 of <total>
OUTPUT:
Case 1. Using DefaultURL Case 2. Retrieve Hello.htm Case 3. Invoke ServletHelloWorld
Result Result Result
<Module>/<Chapter> 25 of <total>
Download image from Internet: Download image from Internet: LogoLoader.javaLogoLoader.java
In fact, HTTP is more than the transfer of HTML pages. Actually, it
provides a generic file-exchange protocol.
In this section, we will implement an image loading HTTP MiDlet -
LogoLoader.java - a MIDlet that retrieves an image logo from the
Internet and displays it onto your MID display.
Main Features:
1. This HTTP MIDlet will randomly download THREE Logos from my
home directory. To do this, we implement a Random number
generator (remember Random is the one the few Math element which
exist in MIDP spec.) and a Vector object.
2. First, in the constructor LogoLoader(), we create the Vector LogoUrls
and ADD ALL the three logo urls into it.
<Module>/<Chapter> 26 of <total>
Download image from Internet: Download image from Internet: LogoLoader.javaLogoLoader.java
3. The loadImage() method contains all the main stuffs for HTTP
networking coding. The mechanism is not diffcult: Each time when we launch the LogoLoader MIDlet, we will generate a
random index - the LogoIndex - for the Vector pool.
Based on this vector index, we can the Logo URL.
Then we pass the URL of this logo to the Connector's open() method and
cast the result to HttpConnection (Similar technique as before).
Then we retrieve the length of the logo image (by using the getlength()
method).
Given the length, we can create a byte array and read data into it.
After reading the entire logo image into the array, we can create the Logo
image from the raw data and display onto your MID display.
<Module>/<Chapter> 27 of <total>
LogoLoader.java – Sample LogoLoader.java – Sample ScreensScreens
Entry Screen Case 1: PolyU Logo Case 2: CU Logo Case 3: HKU Logo
<Module>/<Chapter> 28 of <total>
How to work on POST How to work on POST command?command?
The mechanism of POST command is similar to GET, except that all the parameters are
attached at the end of the request header (on a separate line) instead of appended right
after the URL request line.
In HTTP MIDlet programming, the mechanism is a bit complicated as compared with
HTTP GET.
The implementation procedures are shown as follows:
1. First, obtain an HttpConnection object (by using the Connector's open() method)
2. Obtain the output stream for the HttpConnection (by calling openDataOutputStream() method).
This will send the request headers to the remote server.
3. Then send the request parameters (if exist) on the output stream.
4. Finally you can read the response from the server input stream as usual (by using the
outInputStream() method).
The following is a sample program fragment using the HTTP POST scheme:
As an exercise, based on these program fragment to modify the HTTPMIDlet.java
program to use POST method to get the remote document instead.
<Module>/<Chapter> 29 of <total>
Sample HTTP POST program Sample HTTP POST program statementsstatements
HttpConnection hc = null;
DataInputStream dis = null;
DataOutputStream dos = null;
String message = "";
// specifying the query string
String requeststring = "request=gettimestamp";
try {
// openning up http connection with the web server
// for both read and write access
hc = (HttpConnection)
Connector.open(urlstring, Connector.READ_WRITE);
// setting the request method to POST
hc.setRequestMethod(HttpConnection.POST);
<Module>/<Chapter> 30 of <total>
Sample HTTP POST program Sample HTTP POST program statements statements (cont.)(cont.)
// obtaining output stream for sending query string
dos = hc.openDataOutputStream();
byte[] request_body = requeststring.getBytes();
// sending query string to web server
for (int i = 0; i < request_body.length; i++)
{
dos.writeByte(request_body[i]);
}
// flush out
dos.flush();
// obtaining input stream for receiving HTTP response
dis = new DataInputStream(hc.openInputStream());
// reading the response from web server character by character
int ch;
while ((ch = dis.read()) != -1) {
message = message + (char) ch;
}
<Module>/<Chapter> 31 of <total>
Session Tracking on HTTP Session Tracking on HTTP MIDletsMIDlets
Similar to Java servlet programming. In most e-commerce applications (e.g. online
shopping on Amazon.com), we need to maintain user session in the sense that when
user leave the shopping mall for a while (e.g. visit other Web sites) and come back, we
expected that he/she can automatically retrieve his/her shopping cart and continue
shopping, without need to restart the whole shopping from the beginning.
Remember in Java Servlet - Session Tracking, there are different kinds of
implementation skill for session tracking, including:
URL rewriting
User Authorization
Cookies
Servlet session tracking API
So, in order that your MID (either your Java phone or PDA) can maintain HTTP session
during the whole Web interactions. Which one is the best? Or what we have to do?
The answer maybe various, depend on situation.
<Module>/<Chapter> 32 of <total>
Session Tracking on HTTP Session Tracking on HTTP MIDlets MIDlets (cont.)(cont.)
However, if one want to implement a generic session
tracking application (e.g. online shopping cart or even more
complex session object), Servlet session tracking API must
be your solution.
As Session Tracking APIs are all the servlets stuff
implemented at the Web server, so does it meant we
DON'T need to do anything in our MID, JUST to launch the
Java servlet? Just like the one we have learnt in e-
Commerce???
SORRY, the answer is NO !!!
<Module>/<Chapter> 33 of <total>
Session Tracking on HTTP Session Tracking on HTTP MIDlets MIDlets (cont.)(cont.)
The reason is very simple, as I have discussed with you before ....
YOUR MID IS NOT A BROWSER.
In other words, you cannot expect your MID (e.g your Java Phone) as
a Web browser that can AUTOMATICALLY send (receive) COOKIES
to (or from) your MID with the Web server. (Remember that one
important basic factor for Servlet session tracking is base on the your
Session ID - which is a cookie for your particular session assigned by
the Web server, and the Web server is based on it to maintain session
with you!)
So, what you have to learnt in this session is to know how to:
manipulate cookies in your MIDs.
Don't worry, it is not too difficult, you can handle it!
<Module>/<Chapter> 34 of <total>
How to manipulate cookies in How to manipulate cookies in MIDlets?MIDlets?
The whole mechanism of manipulating cookies in MIDlets
are:
1. Every time when receiving a HTTP response from a Web
server, check for a cookie.
2. If there is a cookie present, save it away for later user. In
fact, a cookie is just a particular HTTP response header
line found in the response header. You can check for it by
calling the getHeaderField() on the HttpConnection object
after the request has been sent.
<Module>/<Chapter> 35 of <total>
How to manipulate cookies in How to manipulate cookies in MIDlets?MIDlets?
3. When sending a request to the server, send the session ID cookie if
it has been previously received. Again, sending a cookie to the
server is just a matter of putting it in the request header line, by using
the HttpConnection's setRequestProperty() method.
Throughout the Web interaction, each time you send a request to the
Web server, you will be sending a session ID as a request line. The
server uses this session ID to look up a session object (maybe more,
it depends on your implementation) for you, to do all Web operations
such as online shopping using shopping cart.
As discuss earlier, what you have learnt is just how the Web browser
works. In fact it is not difficult, is't it?
<Module>/<Chapter> 36 of <total>
How to manipulate cookies in How to manipulate cookies in MIDlets? MIDlets? (cont.)(cont.)
So, if you have a session ID cookie, you should send it when you open up an HTTP connection to the same Web server again, by doing this: HttpConnection hc = (HttpConnection)Connector.open(url);
if (mSession != null) hc.setRequestProperty("cookie", mSession);
Note: This code assumes that you have Session ID cookie saved away in the mSession
variable. Of course, when you are the first time to visit the Web server, you won't have it. It's okay.
Later,. when you receive a response from a HTTP request, just look for the cookie. If you can find one, paste it to the Session ID and save it away, like this:
InputStream in = hc.openInputStream(); String cookie = hc.getHeaderField("Set-cookie"); if (cookie != null){ int semicolon = cookie.indexOf(';'); mSession = cookie.substring(0, semicolon); }
The cookie string needs to be parsed because it comes in two pieces. The first one is a path that can be used to determine when the cookie should be sent back to the server. The second part contains the session ID, the core part.
Remember, the syntax of the SetCookie request header line: Set-Cookie: cookieName=cookieValue
<Module>/<Chapter> 37 of <total>
CookieMIDlet – HTTP MIDlet CookieMIDlet – HTTP MIDlet for Session Trackingfor Session Tracking
The following example CookieMIDlet.java will demonstrate
how to maintain session with the back-end Web server
using cookie concept.
It consists of two part:
1. CookieMIDlet.java - is the HTTP MIDlet to "talk" to the
"back-end" Web server and to manipulate with cookies to
maintain session.
2. Getsession.java - is a very simple servlet that implement
servlet session object and to maintain session with the
client (ie. your MID)
<Module>/<Chapter> 38 of <total>
CookieMIDlet CookieMIDlet (cont.)(cont.)
Main Features in the programs The Getsession.java is a very simple Java servlet that demonstrate
how to maintain session.
- It implements the HttpServlet interface (i.e. using the Servlet Session
Tracking APIs)
- First of all, it tries to get the session using getSession() method
- If the session ID is exist and known in the Web server (ie. have visit
beforehand), it will obtains the existing session object.
- If not, it will create a new session.
- After that, the servlet will get the session ID number by using getId()
method and put it into the PrintWriter object to send it back to the client
(ie. your MID)
<Module>/<Chapter> 39 of <total>
CookieMIDlet CookieMIDlet (cont.)(cont.) At the first time when your MID invoke this servlet, it will
generate a session object (and generate the session ID as
well) and put the session ID as a cookie to the response
header line.
So, the CookieMIDlet basically does the following (as
implemented in the send() method): In the TRY block, first of all it create the HttpConnector object and
make connection with the Web server (and invoke the servlet as
well)
Then it tries to check whether mSession is exist or not.
<Module>/<Chapter> 40 of <total>
CookieMIDlet CookieMIDlet (cont.)(cont.)Main Features in the programs
If so, that's it has visit the server before and get back the mSession, it will set the cookie
property and prepare for the Set-cookie header line.
After that, it will read all the data from the server outputstream and display onto the MID display
One interesting fact is, you can see that on the servlet part, we don't have to deal with
any cookie. The reason is, servlet (via the usage of HttpSession object), it will do the
cookie manipulation automatically, as what the Web browser done.
Note:
You might be wondering whether the cookie concept is works or not (or whether we
really have to doing so many thing to maintain session in the MIDlet).
So, in order to make it clear. I have commented the RED Highlighted portion (i.e. the
main manipulation scheme of cookie) in CookieMIDlet program and give it another name
call CookieMIDlet1.java and do the same program testing.
The following is some sample screens from implementing CookieMIDlet and
CookieMIDlet1. Let's find out their DIFFERENCE!
<Module>/<Chapter> 41 of <total>
CookieMIDlet Entry Screen 1st Trial 2nd Trial 3rd Trial
CookieMIDlet Entry Screen 1st Trial 2nd Trial 3rd Trial
<Module>/<Chapter> 42 of <total>
Integrated Example: Integrated Example: BookSearchMIDletBookSearchMIDlet
The following is the FINAL example, and also an integrated example to illustrate how to create simple interactive MIDlet-based Web book search engine by using the following techniques:a. MIDlet Commandsb. MIDlet Formsc. HTTP MIDletsd. Java Servletse. Servlet JDBC
The main concept of this program is very simple:
1. On the Web server side, we have a online bookstore (using our existing VBS Referece Boookstore) which is collected by JDBC with an Oracle database (the so called book category).
<Module>/<Chapter> 43 of <total>
Integrated Example: Integrated Example: BookSearchMIDletBookSearchMIDlet
2. First, we develop a servlet called ServletBooksearch.java which will do a simple "category" search based on a book "category" as search criteria. In our VBS, we have the following book category:
Cryptography E-commerce Internet Java J2ME Perl Security Servlet WAP XML
<Module>/<Chapter> 44 of <total>
Integrated Example: Integrated Example: BookSearchMIDlet BookSearchMIDlet (cont.)(cont.)
3. On the MID side, we develop a simple HTTP MIDlet called SearchForm.java which consist of a form with an embedded ChoiceGroup item (cgroup in our program) that contains the list of choices of book category. private String[] cat_choices={“cryptography", “e-commerce", “internet", “java", "J2ME", “perl", “security", “servlet", “wap", “xml"};
// the ChoiceGroup ChoiceGroup cgroup = new ChoiceGroup("category",Choice.EXCLUSIVE, cat_choices,null);
4. After the use selects the suitable book category and press the "Send" button. The MIDlet will:- Collect the ChoiceGroup selected item (by using getSelectedIndex() method). // Get the chosen category String s_cat = cgroup.getString(cgroup.getSelectedIndex());
5. Then it will parse the category item to form the complete URL for HTTP request: // Compose the Query url and send it out qString = url + "?category=" + s_cat; String resultstring = sendGetRequest(qString);
6. Call the sendGetRequest() method to invoke the ServletBooksearch servlet, which will display all the result onto the MID display.
<Module>/<Chapter> 45 of <total>
Integrated Example: Integrated Example: BookSearchMIDlet BookSearchMIDlet (cont.)(cont.)
7. In the "back-end" ServletBooksearch.java program, it will base on the category string to invoke the query onto the bookstore database:
// Create query string String qString = "SELECT Name, Author FROM bookstore where category like '%" + cat + "%' ";
// Call the bookquery procedure bookquery("oracle.jdbc.driver.OracleDriver", "jdbc:oracle:thin:@csha01b.comp.polyu.edu.hk:1521:ora8", qString, out);
Note: For the details of the Servlet JDBC, please refer to Chapter 5 of the E-commerce textbook or visit our
EC Lab on Servlet JDBC over here. Click the following to view:
SearchForm.java – HTTP MIDlet for the book search. ServletBooksearch.java – the servlet to perform the actual book searching in the “backend”
server
Sample screens using the RIM Java Handheld device (with bigger screen) are used for illustration.
<Module>/<Chapter> 46 of <total>
BookSearchMIDlet: Sample BookSearchMIDlet: Sample ScreensScreens
Entry Screen Main Book Category
<Module>/<Chapter> 47 of <total>
BookSearchMIDlet: Sample BookSearchMIDlet: Sample Screens Screens (cont.)(cont.)
Query results Query results (cont.)
<Module>/<Chapter> 48 of <total>
The WTK Network MonitorThe WTK Network Monitor
Network monitor enables monitoring of transmissions between a MIDlet and the network.
This is especially useful when a MIDlet is interfacing to a back-endprocess, such as a servlet, via HTTP.
The monitor allows examination of the HTTPrequests/responsesthe MIDlet has handled.The monitor also supportsmonitoring of the secureHTTPS protocol.
<Module>/<Chapter> 49 of <total>
Design TipsDesign Tips
Use GET instead of POST Don’t hard-code URLs Network access should go in its own
thread Handle exceptions gracefully Clean up
<Module>/<Chapter> 50 of <total>
Differences between J2ME and J2SE Networking
There's no MIDP java.net package as there is in J2SE.
MIDP java.io package only supports a subset of the familiar J2SE byte- and character-oriented input and output stream classes.
The following application-level facilities are missing from the MIDP: RMI requires too much processing for mobile devices to
support at this time. Jini requires RMI; therefore, it's not present. JavaSpaces doesn't exist in J2ME. CORBA middleware doesn't exist in J2ME.
<Module>/<Chapter> 51 of <total>
SummarySummary In this section, we have discussed all the main skills to integrate your
MIDlet into the outside world - the Internet world - by using HTTP MIDlet concept.
In particular, we have discussed the how to: Make connection to the Web server to retrieve document To download images (in fact you can extend this concept to download
other binary data, and even programs, video, etc) To invoke a server-side program such as servlets To maintain session
By integrating all these MIDlet concept with the Java servlet programming knowledge. I hope you can develop innovative and exciting mobile Web interactive system.
But the most important fact is:
YOU OWN EFFORT AND CREATIVE THINKING!
<Module>/<Chapter> 52 of <total>
LabLab
Write, build and run a MIDlet reading the content of a text file (http://www23.brinkster.com/lenqkhanh/kita/Lab.txt). Display on a screen (e.g. use TextField) second half of the content of the read file (starting from “APIs” string). Also display the size (=num of chars) of the displayed text.
To debug your code include System.out.println(…) clauses in relevant parts of your source code to trace the progress on monitor screen.
top related