• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Ron McLeod
  • Paul Clapham
  • Tim Cooke
  • Devaka Cooray
Sheriffs:
  • Liutauras Vilda
  • paul wheaton
  • Rob Spoor
Saloon Keepers:
  • Tim Moores
  • Stephan van Hulst
  • Tim Holloway
  • Piet Souris
  • Mikalai Zaikin
Bartenders:
  • Carey Brown
  • Roland Mueller

Struts2-json plugin not serializing action class to json

 
Greenhorn
Posts: 24
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hello, Im having problems getting struts2-json-plugin 2.1.8.1 to
work with struts-jquery-plugin.

Im trying to get struts-jquery grid to load json data that struts2-json plugin forms from a List/object property in that action class.
my action class has to load a "success" result. The success result loads a jsp page that has the struts-jquery grid. that grid should get
its json data from the action class that called the result. Instead, my grid is showing up with no data in it (no json data was loaded).
Im not sure if im just configuring this wrong, or what.


I have the struts2-json-plugin installed and added to my classpath. Same for the struts2-jquery-plugin.

Heres my struts.xml

**********************************************************************************
<struts>

<constant name="struts.devMode" value="true"/>
<constant name="struts.objectFactory" value="guice"/>

<package name="org.webhop.ywdc" namespace="/" extends="struts-default,json-default">

<result-types>
<result-type name="json" class="com.googlecode.jsonplugin.JSONResult">

</result-type>
</result-types>
<action name="login" class="org.webhop.ywdc.LoginAction">

<result type="json">
<param name="root">gridModel</param>
</result>
<result name="success" type="dispatcher">/pages/uiTags/Success.jsp</result>
<result name="error" type="redirect">/pages/uiTags/Login.jsp</result>
<interceptor-ref name="cookie">
<param name="cookiesName">JSESSIONID</param>
</interceptor-ref>
</action>
<action name="logout" class="org.webhop.ywdc.LogoutAction" >

<result name="success" type="redirect">/pages/uiTags/Login.jsp</result>
</action>
</package>



</struts>
***************************************************************************

Under package I have it extend struts-default and json-default. I do this because I need both i believe.
under result types i put <result-type name="json" class="com.googlecode.jsonplugin.JSONResult">, which lets struts know what to do with a json result in your action config.

Under action I have 3 different result types, one for success, one for error, and one for json.
The examples I saw online were simplified ones where they didnt have the result mapped to a certain page.
I find this very confusing, because if your json data has to be read from a certain jsp page (like my "success" result type), then how will struts know which page to load after
the action complets/json serialized?

<result type="json">
<param name="root">gridModel</param>
</result>

As far as I can tell, this result type="json" should cause the json plugin to, for this action class, find any paramter with the name gridModel, and json-ize it.

Heres my action class

*****************************************************************************
public class LoginAction extends ActionSupport {

public String JSESSIONID;
public int id;
private String userId;
private String password;
public Members member;
public List<Customer> gridModel;

public String execute()
{
Cookie cookie = new Cookie("ywdcsid", password);
cookie.setMaxAge(3600);
HttpServletResponse response = ServletActionContext.getResponse();
response.addCookie(cookie);

HttpServletRequest request = ServletActionContext.getRequest();
Cookie[] ckey = request.getCookies();
for(Cookie c: ckey)
{
System.out.println(c.getName() + "/cookie_name + " + c.getValue() + "/cookie_value");
}
Map requestParameters = ActionContext.getContext().getParameters();//getParameters();
String[] testString = (String[])requestParameters.get("password");
String passwordString = testString[0];
String[] usernameArray = (String[])requestParameters.get("userId");
String usernameString = usernameArray[0];

Injector injector = Guice.createInjector(new GuiceModule());
HibernateConnection connection = injector.getInstance(HibernateConnection.class);
AuthenticationServices currentService = injector.getInstance(AuthenticationServices.class);
currentService.setConnection(connection);
currentService.setInjector(injector);
member = currentService.getMemberByUsernamePassword(usernameString, passwordString);
userId = member.getUsername();
password = member.getPassword();

CustomerFactory customerFactory = new CustomerFactory();
gridModel = customerFactory.getCustomers();

if(member == null)
{
return ERROR;
}
else
{
id = member.getId();
Map session = ActionContext.getContext().getSession();
session.put(usernameString, member);
return SUCCESS;
}
}

public String logout() throws Exception
{
Map session = ActionContext.getContext().getSession();
session.remove("logged-in");
return SUCCESS;
}
public List<Customer> getGridModel()
{
return gridModel;
}
public void setGridModel(List<Customer> gridModel)
{
this.gridModel = gridModel;
}
public String getPassword()
{
return password;
}

public void setPassword(String password)
{
this.password = password;
}

public String getUserId()
{
return userId;
}

public void setUserId(String userId)
{
this.userId = userId;
}
public String getJSESSIONID() {
return JSESSIONID;
}

public void setJSESSIONID(String jsessionid) {
JSESSIONID = jsessionid;
}

}

**********************************************************************************

From reading online, all i need to do is have my list property i want json-ized and have a public accessor for it (get, set), which i do.
My action class has a success return which will thru the struts.xml file, open up Success.jsp, which will then load and when the
grid for success.jsp is loaded, it should find that json data and load it to the grid.


My "Success.jsp" page contains a grid from the new struts-jquery-plugin.

***************************************************************************************

<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib prefix="sj" uri="/struts-jquery-tags"%>
<%@ taglib prefix="sjg" uri="/struts-jquery-grid-tags"%>

<%@ page language="java" contentType="text/html" import="java.util.*"%>
<jsp:include page="/pages/uiTags/CheckLogin.jsp" />

<html>
<head>
<sj:head jqueryui="true" jquerytheme="redmond" />
<title>Welcome, you have logged in!</title>
</head>
<body>

<s:url id="remoteurl" action="login"/>

<sjg:grid
id="gridtable"
caption="Customer Examples"
dataType="json"
href="%{remoteurl}"
pager="false"
gridModel="gridModel"
>
<sjg:gridColumn name="id" key="true" index="id" title="ID" formatter="integer" sortable="false"/>

</sjg:grid>

Welcome, you have logged in.

Session Time: <%=new Date(session.getLastAccessedTime())%>






<h2>Password:<s:property value="password"/></h2>

<h2>userId:<s:property value="userId"/></h2>


<a href="<%= request.getContextPath() %>/logout.action">Logout


ID: <s:property value="id"/>
session id: <s:property value="JSESSIONID"/>
</body>
</html>

******************************************************************************************

Here my struts-jquery grid is set up to read the json data from the action class (at least i think so).
My grid shows up, but with no data loaded.

Im not even sure if the struts2-json plugin is actually working. None of the examples were set up like this, where the packages extension
needed to be struts-default as well as json-default.
Im confused about the
<result type="json">
<param name="root">gridModel</param>
</result>
that i mapped in the action class. I read online someone saying that if your action returned a "SUCCESS" then you dont need it to map to json data.
Well, thats not the case. I need my action to open up "success", and also have that page read the json data from the action class that is supposed to have
had a property (gridModel) json-ized.

Can someone explain how to do both of these together? Also, how to see the json data that the plugin json-ized from the action property collection (list<customer>),
just to make sure that Im getting something.

Please help me, you will be my super hero.



Sincerely,

thebravedave

ps: im sure that my action class's List<Customer> (the data that is supposed to be json-ized in the end) is loaded with Customer objects. So, json plugin shouldnt have a problem with a null value.
 
Author
Posts: 12617
IntelliJ IDE Ruby
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Please UseCodeTags when posting code or configuration. Unformatted code and configuration is unnecessarily difficult to read. You can edit your post by using the button.
 
David Newton
Author
Posts: 12617
IntelliJ IDE Ruby
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Under package I have it extend struts-default and json-default. I do this because I need both i believe.


I thought json-default already extended struts-default, but I don't believe the double-extension should cause an issue.
 
David Newton
Author
Posts: 12617
IntelliJ IDE Ruby
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

The examples I saw online were simplified ones where they didnt have the result mapped to a certain page.
I find this very confusing, because if your json data has to be read from a certain jsp page (like my "success" result type), then how will struts know which page to load after
the action complets/json serialized?


I'm not sure what this means: a JSON result only returns JSON data--there's no page mapping; it's just returning data for use by an Ajax request.
 
David Brave
Greenhorn
Posts: 24
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Thanks for the reply,
You said:
*I'm not sure what this means: a JSON result only returns JSON data--there's no page mapping; it's just returning data for use by an Ajax request.*

So I guess that the struts-jquery-grid makes the ajax request to the action class that has my List that has been set up to be json-ized, and is supposed
to access that json data and load it into the grid.
Sorry for my ignorance, but i wasnt able to find the info i needed online

Am I setting up my json result for action correctly? I have 2 other results encoded in my struts.xml file, one is the jsp page that the grid is located in, and
the other is the error result.
how can i tell if the json plugin is creating the json data?
I used <code><param name="root"> gridModel</param></code> to specify the action attribute that the json plugin should turn into json data
in struts.xml under result-types, i specified the json result type (result-type name="json" class="com.googlecode.jsonplugin.JSONResult").
I also set my grid up to look for this action class and the gridmodel for the grid is named "gridModel".

So, its cool having the json result type in with the other result types for your action class?
Im not sure where the breakdown is, but dont know how to check to make sure the json data is formed properly,
or to findout if the grid is recieving the json data.
Please help if you have any ideas.

sincerely,

thebravedave




 
David Newton
Author
Posts: 12617
IntelliJ IDE Ruby
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

David Pew wrote:how can i tell if the json plugin is creating the json data?


Look at the response?
 
David Brave
Greenhorn
Posts: 24
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

The struts2-json plugin is automatically forming json from my gridModel list property of my action class
and sends that back to the jsp page.
I think the struts-jquery-grid makes the call for the json when the page is loaded.
How would one access this json data from the response?

thanks again.

thebravedave
 
David Newton
Author
Posts: 12617
IntelliJ IDE Ruby
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Firebug?
 
David Brave
Greenhorn
Posts: 24
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Im programming in eclipse, and debugging thru the server.
When i do this eclipse/tomcat calls up a small "browser" window inside eclipse that doesnt
allow me the options available in firebug.
Do I have to prepare and export my project every time I want to look at one of these types of responses?
Thanks again for all your help.

sincerely,

thebravedave
 
David Newton
Author
Posts: 12617
IntelliJ IDE Ruby
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
It's still running on a webserver you can access from a non-embedded browser. IIRC if the log level is DEBUG for the JSON plugin it'll dump it to the log anyway.
 
David Brave
Greenhorn
Posts: 24
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Hello. thanks again for all your help with this greenhorn.
Well, im using firefox now to test the program and firebug as you suggested.

Under the firebug Net tab option, under all, there are 2 calls to the action class.
The first was a POST which is an authentication thing that my program uses
to check that the person is in the database. This worked from before I even
started trying to use the grid.
Right ater that is a GET login.action log.
Under Response tab of the GET login.action information that firebug displays,
is a struts problem report.
Here is the report
<code>

<html>
<head>
<title>Struts Problem Report</title>
<style>
pre {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<h2>Struts Problem Report</h2>
<p>
Struts has detected an unhandled exception:
</p>


<div id="exception-info">
<table>
<tr>
<td><strong>Messages</strong>:</td>
<td>
</td>
</tr>
<tr>
<td><strong>File</strong>:</td>
<td>org/webhop/ywdc/LoginAction.java</td>
</tr>
<tr>
<td><strong>Line number</strong>:</td>
<td>46</td>
</tr>

</table>
</div>


<div id="stacktraces">
<hr />
<h3>Stacktraces</h3>
<div class="stacktrace" style="padding-left: 0em">
<strong>java.lang.NullPointerException</strong>
<div>
<pre>
org.webhop.ywdc.LoginAction.execute(LoginAction.java:46)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
java.lang.reflect.Method.invoke(Unknown Source)
com.opensymphony.xwork2.DefaultActionInvocation.invokeAction(DefaultActionInvocation.java:441)
com.opensymphony.xwork2.DefaultActionInvocation.invokeActionOnly(DefaultActionInvocation.java:280)
com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:243)
org.apache.struts2.interceptor.CookieInterceptor.intercept(CookieInterceptor.java:218)
com.google.inject.struts2.GuiceObjectFactory$ProvidedInterceptor.intercept(GuiceObjectFactory.java:224)
com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:237)
org.apache.struts2.impl.StrutsActionProxy.execute(StrutsActionProxy.java:52)
org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:488)
org.apache.struts2.dispatcher.ng.ExecuteOperations.executeAction(ExecuteOperations.java:77)
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:91)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
com.google.inject.servlet.FilterChainInvocation.doFilter(FilterChainInvocation.java:67)
com.google.inject.servlet.ManagedFilterPipeline.dispatch(ManagedFilterPipeline.java:122)
com.google.inject.servlet.GuiceFilter.doFilter(GuiceFilter.java:110)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:852)
org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
java.lang.Thread.run(Unknown Source)
</pre>
</div>
</div>
</div>

<div class="footer">
<hr />
<p>
You are seeing this page because development mode is enabled. Development mode, or devMode, enables extra
debugging behaviors and reports to assist developers. To disable this mode, set:
<pre>
struts.devMode=false
</pre>
in your <code>WEB-INF/classes/struts.properties</code> file.
</p>
</div>
</body>
</html>

</code>



Its saying that the problem is at login.action line 46. This line is right after I get the request parameters from the login (username and password).
This line is me extracting the value from the request, and declaring a string with the request password. (see earlier coderanch posting for my loginaction class)

In firebox's console tab, there a whole bunch of struts logs, but at the very end
it shows the same error that was showed in the net tab. Right below the error
posting is a red tagged json error that shows:
a red circle and JSON.parse
</head>


So Im guessing that the struts2-json plugin is going to my action class
and trying to create a json mapping from it. Somehow it gets hung
up on this local string i created and assigned the password request variable to.

In my struts.xml file, when i was configuring all of the json, I put <code> <param name="root">gridModel</param> </code>
which should tell it to serialize the gridModel list into json data, but its geting stuck on this local String which assigned the password obtained from the request.

Any ideas?

sincerely,

thebravedave
 
David Brave
Greenhorn
Posts: 24
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Also, when the json data is returned in the post, it should be in the response where this error message is, am I correct?

thebravedave
 
David Newton
Author
Posts: 12617
IntelliJ IDE Ruby
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

David Newton wrote:Please UseCodeTags when posting code or configuration. Unformatted code and configuration is unnecessarily difficult to read. You can edit your post by using the button.

I'm actually serious about that; I not going to spend much time trying to read unformatted code or config.
 
David Newton
Author
Posts: 12617
IntelliJ IDE Ruby
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

David Pew wrote:Also, when the json data is returned in the post, it should be in the response where this error message is, am I correct?


Not if there's an NPE before it ever gets to the point of rendering a result--there's been an exception: a break in the normal flow of execution.

As far as I can tell, this result type="json" should cause the json plugin to, for this action class, find any paramter with the name gridModel, and json-ize it.


Any (action) property, not parameter.

Right now it looks like you don't understand how S2 and/or HTTP works, although it's difficult to read the configuration. There are a number of issues with the following:1) You have two results named "success". "success" is the default result name. For all intents and purposes, the behavior will be undefined. The action returning the JSON data can be in the action *class*, but cannot be the same *method* in the action. Every request needs to be handled by an action (or action alias). This is configured to handle two completely different requests with the same action and action method--this is not possible.

Also, when you define *any* action-specific interceptors, you must define *all* of them: this action is configured to use *only* the "cookie" interceptor, which is almost certainly not what you want.

It also appears as though you're redirecting directly to a JSP--this would not be considered good design in an MVC app: all requests should be handled by actions, even if it's one defined via convention (and the convention plugin) or via XML an action defined without specifying a class.

If you post any more code or configuration, please UseCodeTags.
 
David Brave
Greenhorn
Posts: 24
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hello, thanks again for getting back to me.
I was pretty confused about the struts2-json-plugin.
So now Ive created a new action class that specifically is set to return only the json data I need. Ive also fixed a few of the issues you mentioned that didnt
specifically pertain to the json struts problem im having,
but on to my fix of the problem that still doesnt work.



then in my Success.jsp page



Now my success.jsp page should be accessing the json action name for the class JsonAction, which should the serialize the gridModel property.
When I run the program in firebug and look in the Net Tab and find the error related
to my grid trying to get the json data. The error is as such:



I know it looks like im just not mapping it correctly, but my struts.xml file is correctly configured, i believe



This should map to my JsonAction class, which is in the same folder as the LoginAction class.
I replaced json action name with the action name for LoginAction (which is "login"), and
it knew right away which class i was talking about, it just wasnt the correct class.

I followed the instructions in the apache struts2-json plugin page https://cwiki.apache.org/WW/json-plugin.html
I seem to be setting my application up correctly, but it cant find my action class JsonAction.
Can I get a little help here?
I really do appreciate all of your help.

Sincerely,

thebravedave

 
David Brave
Greenhorn
Posts: 24
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
ps: in the link that the grid in my success.jsp page gets its json data from, for the action class, I have the action listed as "json.action"
I tried a bunch of different permutations on this, but they all give the same error result listed above.
ty.

thebravedave
 
David Brave
Greenhorn
Posts: 24
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi, just a heads up,
I wasnt sure if mabey having my package extend struts-default and json-default, if this might give errors,
so i created a new package in struts.xml with the same namespace, and only had this one extend json-default, but
I get the same error.
 
David Newton
Author
Posts: 12617
IntelliJ IDE Ruby
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Start by using the config-browser plugin to see what S2 thinks your configuration is; I don't have access to a machine I can test anything on right now.
 
David Brave
Greenhorn
Posts: 24
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Once again, I must thank you for your patience in helping me. I REALLY appreciate it.

I ran the config-browser and it returned the config information. I
took 4 screen shots. 1st one is the intial page that the config info showed
2nd is the "/" namespace which the login, logout, and jsonaction actions share,
3rd is the jsonaction config info and i cropped the 4th image to the 3rd.
4th is the login action config info, just for a reference, because this action class works.
2.jpg
[Thumbnail for 2.jpg]
1.jpg
[Thumbnail for 1.jpg]
3.jpg
[Thumbnail for 3.jpg]
 
David Brave
Greenhorn
Posts: 24
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

hello. So I was messing around with my struts.xml file, and before when i had the json action and the login action in the same package,
i had changed my login.actions return string to be "Authenticated" instead of what i was using before SUCCESS.
I was wondering if it would still work it i put it back as SUCCESS returning in the LoginAction class.
Now my jsonaction class is returning the json data.
This is confusing to me. here is my struts.xml file



All I did was, in my LoginAction class, change my return to "SUCCESS" instead of a string i made up called AUTHENTICATED.
Then all i did was change this in struts.xml and now its working in firebug.
The grid is still not loading the data, but at least im getting my json data back.
Any idea why this would occure?

Sincerely,

thebravedave

ps: thanks again so much.
 
David Newton
Author
Posts: 12617
IntelliJ IDE Ruby
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Yeah, you'd have to return the same string as your result is named :)

I'm not familiar with the jQuery plugin--the first thing I'd check is what specific format (not just "JSON", but in an array, does it want header columns, blah blah blah) the grid control expects, then make sure whatever you're serializing to JSON is ending up being output in that format.
 
Greenhorn
Posts: 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Did you got it correct i am also facing the same isssue.
 
Yeah, but how did the squirrel get in there? Was it because of the tiny ad?
We need your help - Coderanch server fundraiser
https://coderanch.com/wiki/782867/Coderanch-server-fundraiser
reply
    Bookmark Topic Watch Topic
  • New Topic