Creating a Pretty URL With Struts Framework and URLRewrite
On this tutorial im trying to create a pretty URL using one of java’s most famous framework, Struts Framework. I want to change a usual struts URL from news.do?id=newsID
into a pretty URL such as news/newsID/newsTitle
. The main benefits are your links get easily indexed by search engines and make them easy-to-read and easy-to-remember.
Im using urlrewrite library for url rewriting. As for this example, im using old version of struts 1.3.8 and iBatis ORM version 2.3.4.
First as always, a simple MySQL table
CREATE TABLE news ( id bigint NOT NULL AUTO_INCREMENT, title VARCHAR(100) NOT NULL, content text NOT NULL, createddate DATETIME NOT NULL, PRIMARY KEY (id) ) insert into news (id, title, content, createddate) values (1, 'A Tale of Two More Earths?', 'Example of Content', '2011-12-26 19:00:00'); insert into news (id, title, content, createddate) values (2, 'India: Boat Capsizes; 11 Missing', 'Example of Content number 2', '2011-12-27 19:00:00');
and a javabean and xml for database mapping. And please be aware, im injecting Slugify class to getUrl method so i could get a clean url property inside this bean.
package com.edw.bean; import com.edw.util.Slugify; import java.util.Date; public class News { private Long id; private String title; private Date createddate; private String content; // added for pretty URL private String url; // other getter and setter // do title formatting for a clean url public String getUrl() { return Slugify.slugify(getTitle()); } public void setUrl(String url) { this.url = url; } }
And this is my xml queries
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd" > <sqlMap namespace="news" > <resultMap id="newsBean" class="com.edw.bean.News" > <result column="id" property="id" jdbcType="BIGINT" /> <result column="title" property="title" jdbcType="VARCHAR" /> <result column="content" property="content" jdbcType="LONGVARCHAR" /> <result column="createddate" property="createddate" jdbcType="TIMESTAMP" /> </resultMap> <select id="select" resultMap="newsBean" > select id, title, createddate, content from news </select> <select id="selectWithId" resultMap="newsBean" parameterClass="java.lang.Integer" > select id, title, createddate, content from news where id = #id# </select> </sqlMap>
This is my utility java class, its main purpose is to encode and clean news titles so they can fit into URLs.
package com.edw.util; import java.net.URLEncoder; import java.text.Normalizer; import org.apache.log4j.Logger; public class Slugify { private static final Logger logger = Logger.getLogger(Slugify.class); /** * * modified version of Jozef Ševcík's slugify * * @link http://maddemcode.com/java/seo-friendly-urls-using-slugify-in-java/ * @param input * @return formatted URL */ public static String slugify(String input) { if (input == null || input.length() == 0) { return ""; } try { String toReturn = normalize(input); toReturn = toReturn.replaceAll("[^\\w\\s\\-]", ""); toReturn = toReturn.replace(" ", "-"); toReturn = toReturn.toLowerCase(); toReturn = URLEncoder.encode(toReturn, "UTF-8"); return toReturn; } catch (Exception e) { logger.error(e, e); } return ""; } private static String normalize(String input) { if (input == null || input.length() == 0) { return ""; } return Normalizer.normalize(input, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", ""); } }
And an xml file to load all my xml queries
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sqlMapConfig PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-config-2.dtd"> <sqlMapConfig> <settings useStatementNamespaces="true" lazyLoadingEnabled="true" enhancementEnabled="true" maxSessions="20" /> <transactionManager type="JDBC" commitRequired="false"> <dataSource type="SIMPLE"> <property name="SetAutoCommitAllowed" value="false"/> <property name="DefaultAutoCommit" value="false"/> <property name="JDBC.Driver" value="com.mysql.jdbc.Driver"/> <!-- my database name = pepe --> <property name="JDBC.ConnectionURL" value="jdbc:mysql://localhost/pepe"/> <property name="JDBC.Username" value="root"/> <property name="JDBC.Password" value="xxx"/> </dataSource> </transactionManager> <sqlMap resource="com/edw/sqlmap/news.xml"/> </sqlMapConfig>
A java class to load my xml configuration
package com.edw.sqlmap.config; import com.ibatis.common.resources.Resources; import com.ibatis.sqlmap.client.SqlMapClient; import com.ibatis.sqlmap.client.SqlMapClientBuilder; import java.io.Reader; public class SqlMapConfig { protected static final SqlMapClient sqlMap; static { try { Reader reader = Resources.getResourceAsReader("com/edw/sqlmap/sqlmapconfig.xml"); sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader); } catch (Exception e){ throw new RuntimeException("Fatal Error. Cause: " + e, e); } } public static SqlMapClient getSqlMap() { return sqlMap; } }
Next is im creating a Struts Action class
package com.edw.action; import com.edw.bean.News; import com.edw.sqlmap.config.SqlMapConfig; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; public class NewsAction extends org.apache.struts.action.Action { private static final String SUCCESS = "success"; @Override public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { List<News> newses = null; if(request.getParameter("id") != null) newses = SqlMapConfig.getSqlMap().queryForList("news.selectWithId", Integer.parseInt(request.getParameter("id"))); else newses = SqlMapConfig.getSqlMap().queryForList("news.select"); request.setAttribute("newses", newses); return mapping.findForward(SUCCESS); } }
dont forget to register NewsAction to strutsconfig.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_3.dtd"> <struts-config> <form-beans></form-beans> <global-exceptions></global-exceptions> <global-forwards> <forward name="welcome" path="/Welcome.do"/> </global-forwards> <action-mappings> <action path="/news" type="com.edw.action.NewsAction"> <forward name="success" path="/WEB-INF/pages/news.jsp" /> </action> <action path="/Welcome" forward="/welcomeStruts.jsp"/> </action-mappings> <message-resources parameter="com/edw/res/ApplicationResource"/> </struts-config>
Next is my presentation layer. Im using a plain JSP and Struts tags, and put my JSP file under WEB-INF folder, so it cant be accessed directly.
<%@page contentType="text/html" pageEncoding="UTF-8"%> <!DOCTYPE html> <%@taglib uri="http://struts.apache.org/tags-bean" prefix="bean"%> <%@taglib uri="http://struts.apache.org/tags-logic" prefix="logic"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>News Page</title> </head> <body> <h1>News</h1> <logic:iterate name="newses" id="news"> <bean:write name="news" property="id"/> , <bean:write name="news" property="title"/>, <bean:write name="news" property="createddate"/>, <bean:write name="news" property="content"/> <br /> <a href="${pageContext.request.contextPath}/news/<bean:write name="news" property="id"/>/<bean:write name="news" property="url"/>">link</a> <br /> <br /> <br /> </logic:iterate> </body> </html>
Next is where the magic of urlrewrite starts, first i register urlrewrite filter to my web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <filter> <filter-name>UrlRewriteFilter</filter-name> <filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class> </filter> <filter-mapping> <filter-name>UrlRewriteFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>action</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> <init-param> <param-name>debug</param-name> <param-value>2</param-value> </init-param> <init-param> <param-name>detail</param-name> <param-value>2</param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>action</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> <session-config> <session-timeout> 30 </session-timeout> </session-config> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
and create a urlrewrite xml under WEB-INF
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE urlrewrite PUBLIC "-//tuckey.org//DTD UrlRewrite 3.2//EN" "http://tuckey.org/res/dtds/urlrewrite3.2.dtd"> <urlrewrite> <rule> <note>for news with parameters</note> <from>^/news/([0-9]+)/(.*)</from> <to>/news.do?id=$1</to> </rule> <rule> <note>display all news</note> <from>^/news$</from> <to>/news.do</to> </rule> </urlrewrite>
The final result is my url path will be changed from
http://localhost:8084/StrutsPrettyURL/news.do and http://localhost:8084/StrutsPrettyURL/news.do?id=1
to
http://localhost:8084/StrutsPrettyURL/news and http://localhost:8084/StrutsPrettyURL/news/1/a-tale-of-two-more-earths
This are my netbeans project structure and libraries.
Have Fun
No Comments