Wednesday, December 31, 2014

Logback - SLF4J

Pom.xml file:

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${org.slf4j-version}</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>${logback-version}</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback-version}</version>
        </dependency>



In src/main/resources folder, create logback.xml file. Put these configuration:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xml>
<configuration>
    <!-- File appender -->
     <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>D:\mmt-ws-logs\mmt-ws-log.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
               <FileNamePattern>D:\mmt-ws-logs\mmt-ws-log.%d{yyyy-MM-dd}.log</FileNamePattern>
               <MaxHistory>30</MaxHistory>
          </rollingPolicy>
   
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%d{ISO8601} [%-5p] [%t] %c %X - %m%n</pattern>
        </encoder>
    </appender>
   
    <!-- Console appender -->
      <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%d{HH:mm:ss} %level [%thread] %logger [%file:%line] %msg%n</pattern>
        </encoder>
    </appender>

  <root level="INFO,ERROR">
    <appender-ref ref="FILE" />
    <appender-ref ref="STDOUT" />
  </root>
</configuration>


In this case, we use RollingFileAppender to write log to file. It also recreate new file for new date.

For showing in Console, use ConsoleAppender.

Tuesday, December 30, 2014

Sequence in Oracle

Create Sequence

CREATE SEQUENCE customers_seq
 START WITH     1000
 INCREMENT BY   1
 NOCACHE
 NOCYCLE;
 
In case that we already have data in DB, use this snippet:

declare
    new_id number;
begin
   select NVL(max(id),0) + 1
   into   new_id
   from   my_table;

    execute immediate 'Create sequence my_seq
                       start with ' || new_id ||
                       ' increment by 1';
end;

/

Use Sequence

In Entity object:

    /** The auto id. */
    private Long autoId;

    /**
     * Gets the auto id.
     *
     * @return the auto id
     */
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq")
    @SequenceGenerator(name = "sq", sequenceName = "my_seq", allocationSize=1)
    @Column(name = "AUTOID", unique = true, nullable = false, precision = 30, scale = 0)
    public Long getAutoId() {
        return this.autoId;
    }

    /**
     * Sets the auto id.
     *
     * @param autoId the new auto id
     */
    public void setAutoId(Long autoId) {
        this.autoId = autoId;
    }



GenerationType.SEQUENCE is specific for Oracle. Because Oracle uses Sequence strategy for create AutoId.

In @SequenceGenerator, the “allocationSize” is by default set to 50
And, by default, the “increment by” is set to 1.

Note:

It’s important to note that, the sequence generator work correctly, only when the “allocationSize” and the “increment by” are same, otherwise there is a possibility of getting the “unique constraint violation” exception. therefore, it is better to explicitly define “allocationSize” and “increment by”.

Monday, December 29, 2014

Get package version from MANIFEST file

In pom.xml, specify default ImplementationEntries and SpecificationEntries as below:
 

          <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.1</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                            <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
                        </manifest>
                        <manifestEntries>
                            <Implementation-Version>${project.version}</Implementation-Version>
                        </manifestEntries>
                    </archive>
                    <archiveClasses>true</archiveClasses>
                </configuration>
            </plugin>


These fields will be automatically added to MANIFEST file.

In Java class, use this snippet to read value:
   @Override
    public BuildVersion getVersion() throws IOException {
        Package p = this.getClass().getPackage();
        BuildVersion buildVersion = new BuildVersion();
        buildVersion.setBuildName(p.getImplementationTitle());
        buildVersion.setVersion(p.getImplementationVersion());
        return buildVersion;       
    }


(BuildVersion is a custom class that contains result.)

Note: war file has to be put in server. At that time manifest file will be available to read properly.

Thursday, December 25, 2014

Useful SQL Hints to improve query performance

References:
http://ashokkumartj.blogspot.com/2011/01/useful-sql-hints-to-improve-query.html

Create Index and using Index hints

CREATE INDEX:

There are many types of index introduced by Oracle.
http://docs.oracle.com/cd/B19306_01/server.102/b14231/indexes.htm

Each type have the different way to create. Ex:

CREATE INDEX programId_index ON PROGRAM(programId) COMPUTE STATISTICS;

Put Index on columns that appear in WHERE statement.
Good for single column - one index.
Can put multi-columns in index but make sure that the all these columns appear in WHERE statement. If not, index does not help.

Ex: CREATE INDEX User_index ON PROGRAM(firstName, lastName, title) COMPUTE STATISTICS;

Great if: SELECT * from User WHERE firstName = ? AND lastName = ?

Bad if: SELECT * from User WHERE lastName = ?
       or SELECT * from User WHERE lastName = ? AND title = ?

Index will make DML (insert, update, delete) slow down (3-time slower than the table with no indexes). Should not index on column that value is too much duplicated.

INDEX HINTS:

Use Hints indicate Oracle using index:

SELECT /*+ index(table_name(column_has_indexed)) */  * FROM customer;

Oracle Semi-Joins and Anti-Joins

Using EXISTS and IN.
 
Example 1:
 
SELECT   D.deptno, D.dname
        FROM     dept D, emp E
        WHERE    E.deptno = D.deptno
        ORDER BY D.deptno;
 
Change to:
 
SELECT   D.deptno, D.dname
        FROM     dept D
        WHERE    EXISTS
                 (
                 SELECT 1
                 FROM   emp E
                 WHERE  E.deptno = D.deptno
                 )
        ORDER BY D.deptno; 

Example 2:

SELECT   D1.deptno, D1.dname
        FROM     dept D1
        MINUS
        SELECT   D2.deptno, D2.dname
        FROM     dept D2, emp E2
        WHERE    D2.deptno = E2.deptno
        ORDER BY 1;
 
Change to: 

SELECT   D.deptno, D.dname
        FROM     dept D
        WHERE    NOT EXISTS
                 (
                 SELECT 1
                 FROM   emp E
                 WHERE  E.deptno = D.deptno
                 )
        ORDER BY D.deptno;
 
References:
 
http://www.dbspecialists.com/files/presentations/semijoins.html 

Sunday, December 14, 2014

RestTemplate cannot map a single object to a list



ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
resObject = mapper.readValue(response.getBody(), ResponseObject.class);

The problem is that if ResponseObject in server side has only one item set to a list, by default, JAXB will convert this list to a single json object (not an array contains one object as we expected) so the mapper in client side cannot map correctly.

Solution 1: by code


List converters = restTemplate.getMessageConverters();
              for (Object converter : converters) {
                     if (converter instanceof MappingJacksonHttpMessageConverter) {
                           MappingJacksonHttpMessageConverter jconverter = (MappingJacksonHttpMessageConverter) converter;
                           
                           final ObjectMapper mapper = new ObjectMapper();
                             mapper.configure(
                                     DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY,
                                     true);
                           jconverter.setObjectMapper(mapper);
                          
                     }
              }

Solution 2: by XML configuration:


<!-- Jackson Mapper -->
<bean id="jacksonObjectMapper" class="org.codehaus.jackson.map.ObjectMapper" />
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject" ref="jacksonObjectMapper" />
    <property name="targetMethod" value="configure" />
    <property name="arguments">
        <list>
            <value type="org.codehaus.jackson.map.DeserializationConfig.Feature">FAIL_ON_UNKNOWN_PROPERTIES</value>
            <value>false</value>
        </list>
    </property>
</bean>
 
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
                <property name="objectMapper" ref="jacksonObjectMapper" />
            </bean>
        </list>
    </property>
</bean>

Hope that helps.

References:
http://stackoverflow.com/questions/14343477/how-do-you-globally-set-jackson-to-ignore-unknown-properties-within-spring
  

 

Friday, December 12, 2014

Jersey Test

pom.xml

<!-- Jersey -->
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-server</artifactId>
            <version>${jersey.version}</version>
        </dependency>

        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-servlet</artifactId>
            <version>${jersey.version}</version>
        </dependency>

        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-core</artifactId>
            <version>${jersey.version}</version>
        </dependency>

        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-json</artifactId>
            <version>${jersey.version}</version>
        </dependency>

        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-client</artifactId>
            <version>${jersey.version}</version>
        </dependency>

        <dependency>
            <groupId>com.sun.jersey.jersey-test-framework</groupId>
            <artifactId>jersey-test-framework-grizzly2</artifactId>
            <version>1.8</version>
            <scope>test</scope>
        </dependency>

Test class:

public class MyClassTest extends JerseyTest {
   
    @Override
    public AppDescriptor configure() {
        return new WebAppDescriptor.Builder("com.example.person.endpoint")
                                    .servletClass(SpringServlet.class)
                                    .contextParam("contextConfigLocation", "classpath:context/endpointIntegrationContext.xml")
                                    .contextListenerClass(ContextLoaderListener.class)
                                    .contextPath("test-project").build();
    }

    @Before
    public void setUp() {
        mapper.configure(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
    }

    @After
    public void tearDown() {
    }
   
    @Test
    public void testGetByUserId() throws Exception {
        RestTemplate restTemplate = new RestTemplate();
       
        Long userId = 1L;
       
        String endpoint = getBaseURI() + "test-project/person/getByUserId/{userId}";
        final Map<String, Object> urlParameter = new HashMap<String, Object>();
        urlParameter.put("userId", userId);

        HttpHeaders headers = new HttpHeaders();
        headers.add("Accept", MediaType.APPLICATION_JSON.toString());
        HttpEntity<String> entity = new HttpEntity<String>(headers);
       
        ResponseEntity<String> responseEntity = restTemplate.exchange(endpoint, HttpMethod.GET, entity, String.class, urlParameter);
       responseEntity.getBody()
        //...
    }

Thursday, December 11, 2014

Using Spring Data JPA

Spring Data JPA is a library / framework that adds an extra layer of abstraction on the top of our JPA provider.
DAO Structure
Using JpaRepository and Entity Manager persistence

Configuration:
 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/data/jpa
        http://www.springframework.org/schema/data/jpa/spring-jpa.xsd

        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
        ">

    <!-- Enables the Spring MVC @Controller programming model -->
    <mvc:annotation-driven />
   
    <jpa:repositories base-package="com.journaldev.spring.repository"/>

    <!-- Handles HTTP GET requests for /resources/** by efficiently serving
        up static resources in the ${webappRoot}/resources directory -->
    <mvc:resources mapping="/resources/**" location="/resources/" />

    <!-- Resolves views selected for rendering by @Controllers to .jsp resources
        in the /WEB-INF/views directory -->
    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url"
            value="jdbc:mysql://localhost:3306/spring-data-jpa" />
        <property name="username" value="root" />
        <property name="password" value="root" />
    </bean>
   
    <bean id="jpaVendorAdapter"
        class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <property name="showSql" value="true"/>
        <property name="generateDdl" value="true"/>
        <property name="database" value="MYSQL"/>
    </bean>

    <bean id="entityManagerFactory"
        class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
        <!-- spring based scanning for entity classes-->
        <property name="packagesToScan" value="com.journaldev.spring.model"/>
    </bean>

    <bean id="personDAO" class="com.journaldev.spring.dao.PersonDAOImpl">
        <property name="em" ref="entityManagerFactory" />
    </bean>
    <bean id="personService" class="com.journaldev.spring.service.PersonServiceImpl">
        <property name="personDAO" ref="personDAO"></property>
    </bean>
   
    <context:component-scan base-package="com.journaldev.spring" />
   
    <bean id="transactionManager"
        class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>



Usage:

import org.springframework.data.jpa.repository.JpaRepository;
import com.journaldev.spring.model.Person;
public interface PersonRepository extends JpaRepository<Person, Integer> {

}


Add to where to use:
@Autowired
private PersonRepository personRepository;

To add function to JpaRepository, we have 3 ways:
* Declare method name by combine keyword and fieldName
public List<Person> findByLastName(String lastName);

* Using NamedQuery

@Entity
@NamedQuery(name = "Person.findByName", query = "SELECT p FROM Person p WHERE LOWER(p.lastName) = LOWER(?1)")
@Table(name = "persons")
public class Person {
     
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
Then add method which name match namedQuery name:
public List<Person> findByName(String lastName);
* Using @Query at method definition

@Query("SELECT p FROM Person p WHERE LOWER(p.lastName) = LOWER(:lastName)")
    public List<Person> find(@Param("lastName") String lastName);

Entity Manager

 @PersistenceContext
    private EntityManager em;

 
 @Override
    public List<Person> findByCountry(String country) {
        List<Person> results = em.createNamedQuery("findByCountry").setParameter(1, country).getResultList();
        return results;
    }


EntityManager allow to run native Sql by em.createNativeQuery(String sqlString).

References:
http://www.petrikainulainen.net/spring-data-jpa-tutorial/ 

Saturday, June 28, 2014

Changes from Liferay 6.1 to 6.2

  • ThemeSetting:
In liferay-look-and-feel .xml:
<setting configurable="true" key="header-banner" type="textarea" value=""/>

In portal_normal.vm:
#set($bg_header = $theme_display.getThemeSetting("header-banner"))

In this case, we use $theme_display.getThemeSetting(key)  instead of $theme.getSetting(key)

  • Ajax using AUI:
AUI().use('node','aui-base','aui-io-request', function(A) {
        var groupId = A.one('#' + namespace + "groupId").get("value");
        A.io.request(ajaxPageLayoutUrl, {
            dataType : 'json',
            data : {
                <portlet:namespace/>groupId: groupId
            },
            on : {
                success : function() {
                    var data = this.get('responseData');
                    for(var i = 0; i < data.length; i++) {
                        var page = data[i];
                        var selected = page.plid == curPageSelected ? "selected" : "";
                        var newOption = A.Node.create('<option value="' + page.plid + '"' + selected + '>'+ page.title +'</option>');
                        A.one('#' + namespace + "plid").appendChild(newOption);
                    }
                }
            }
        });
    });

Friday, June 27, 2014

Accessing Service and Util in Application Display Template



See: (in content and comment)

1. http://liferaytrends.blogspot.com/2012/05/access-liferay-service-util-and-custom.html

2. http://stackoverflow.com/questions/12106827/liferay-obtain-filesize-in-template

Sunday, June 22, 2014

Using Application Display Template in (ADT) Liferay 6.2

If you want to get portlet title in ADT, using this code snippets:

#set ($portletTitle = $themeDisplay.getPortletDisplay().getTitle())

If you want to use localization, using code like this:

$languageUtil.get($locale, "read-more")

Wednesday, April 30, 2014

Create Liferay portlet with localized input value

Hi all,
Today, I am going to show you how to create a Liferay portlet with localized input value. You also know how to put a portlet in Control Panel and embed a portlet in theme.

When doing with localization, you can have a couple of choices. If you are in theme template files, you can use #language("your_language_key"), and if you are in portlet view file (.jsp file...), it is LanguageUtil.get(pageContext, "your_language_key"). You will have localization ability.

But it is all the things you know already. You prepare your language properties files, put words on it, along with the same key, and then you show the correct language sentence when user change their language in Portal. What about the value that user puts? Do you want to localize it? Sure. You can see what it looks like by creating a structure or a template in Web Content in Control Panel.

Now, for example, we have to create a manual Menu portlet that allows admin user create the menu on his own. We will localize the Menu label.

We have two portlets, one for managing Menu item in Control Panel, and the another for displaying Menu bar in your theme. These are working on the same Menu Item data model.

Firstly, create Menu Item entity in service.xml file, like below:
<entity name="MenuItem" local-service="true" remote-service="true" cache-enabled="false">

        <!-- PK fields -->

        <column name="menuItemId" type="long" primary="true" />

        <!-- Audit fields -->

        <column name="companyId" type="long" />
        <column name="userId" type="long" />
        <column name="userName" type="String" />
        <column name="createDate" type="Date" primary="false"/>
        <column name="modifiedDate" type="Date" primary="false"/>

        <!-- Other fields -->

        <column name="name" type="String" localized="true"/>
        <column name="description" type="String" localized="true" />
        <column name="path" type="String" />
        <column name="targetType" type="String" />
        <column name="parentId" type="long" />
        <column name="weight" type="int"></column>

        <order by="asc">
            <order-column name="menuItemId" order-by="asc"/>
        </order>

        <!-- Finder methods -->

        <finder name="Name" return-type="Collection">
            <finder-column name="name" />
        </finder>
        <finder name="ParentId" return-type="Collection">
            <finder-column name="parentId" />
        </finder>
    </entity>
In this case, pay your attention on:
<column name="name" type="String" localized="true"/>
<column name="description" type="String" localized="true" />
It makes the Portal save the user input value in xml format that supports localization. One thing else you should know is I set cache-enabled="false". I don't know how cache in portlet level works but if you set this to true, when user adds new value to database, it does not affect to view immediately until I redeploy source code again.

Look at portlet-model-hints.xml file,
<model-hints>
    <model name="vn.edu.bvu.model.MenuItem">
        <field name="menuItemId" type="long" />
        <field name="companyId" type="long" />
        <field name="userId" type="long" />
        <field name="userName" type="String" />
        <field name="createDate" type="Date" />
        <field name="modifiedDate" type="Date" />
        <field name="name" type="String" localized="true" />
        <field name="description" type="String" localized="true" />
        <field name="path" type="String" />
        <field name="targetType" type="String" />
        <field name="parentId" type="long" />
        <field name="weight" type="int" />
    </model>
</model-hints>
From there, in view files, you can use aui taglib to display form input that supports localization. This is my code on edit.jsp file:
 <%@ include file="/html/menu/init.jsp"%>

<portlet:actionURL var="addMenuItemURL" name="add"></portlet:actionURL>

<%
long menuItemId = ParamUtil.getLong(renderRequest, "menuItemId");
MenuItem mi = null;
if (menuItemId > 0) {
    mi = MenuItemLocalServiceUtil.getMenuItem(menuItemId);
}

List<MenuItem> list = MenuItemLocalServiceUtil.getMenuItems(0, MenuItemLocalServiceUtil.getMenuItemsCount());

%>

<aui:form action="<%= addMenuItemURL %>" method="post">
    <aui:fieldset>
        <aui:model-context model="<%= MenuItem.class %>" bean="<%= mi %>" />  

        <aui:input name="menuItemId" type="hidden" />
      
        <aui:input name="name" label="menu-item-name"/>
      
        <aui:input name="description" label="menu-item-description"/>
      
        <aui:input name="path" label="menu-item-path"/>
      
        <aui:select name="targetType" label="menu-item-target-type">
            <aui:option selected="<%= mi != null ? (mi.getTargetType().equals(\"_self\") ? true : false) : false %>" value="_self">_self</aui:option>
            <aui:option selected="<%= mi != null ? (mi.getTargetType().equals(\"_blank\") ? true : false) : false %>" value="_blank">_blank</aui:option>
            <aui:option selected="<%= mi != null ? (mi.getTargetType().equals(\"_parent\") ? true : false) : false %>" value="_parent">_parent</aui:option>
            <aui:option selected="<%= mi != null ? (mi.getTargetType().equals(\"_top\") ? true : false) : false %>" value="_top">_top</aui:option>
        </aui:select>

        <aui:input name="weight" label="menu-item-weight"/>
      
        <aui:select name="parentId" label="menu-item-parentId">
            <aui:option selected="<%= mi != null ? (mi.getParentId() == 0 ? true : false) : false %>" value="<%= 0 %>"><%= StringPool.BLANK %></aui:option>
            <%
                for(int i = 0; i < list.size(); i++) {
                    MenuItem item = list.get(i);
                    if(item.getMenuItemId() != menuItemId) {
                        %>
                        <aui:option selected="<%= item.getMenuItemId() == menuItemId %>" value="<%= item.getMenuItemId() %>"><%= item.getName(currentLocale) %></aui:option>
                        <%
                    }
                }
            %>
        </aui:select>

        <aui:button name="submit" type="submit" value="Add"/>
      
    </aui:fieldset>
</aui:form>
The magic thing comes from portlet-model-hints.xml file to <aui:input ...> taglib. AUI taglib automatically recognize localized=true and add more component to allow user add more language translation.
See more benefits from model-hints from this link https://www.liferay.com/documentation/liferay-portal/6.1/development/-/ai/using-model-hints-liferay-portal-6-1-dev-guide-en.

In view result file, see my code below:
 <%@ include file="/html/menu/init.jsp"%>

<portlet:renderURL var="addMenuItemUrl" portletMode="edit" />

<liferay-ui:search-container delta="5" emptyResultsMessage="no-menu-items-were-found">
    <liferay-ui:search-container-results>
        <%
            List<MenuItem> list = MenuItemLocalServiceUtil
                            .getMenuItems(0, MenuItemLocalServiceUtil
                                    .getMenuItemsCount());
                    List<MenuItem> tempResults = ListUtil.subList(list,searchContainer.getStart(), searchContainer.getEnd());
                    int tempTotal = list.size();
                    pageContext.setAttribute("results", tempResults);
                    pageContext.setAttribute("total", tempTotal);
        %>
    </liferay-ui:search-container-results>

    <liferay-ui:search-container-row className="vn.edu.bvu.model.MenuItem"
        keyProperty="menuItemId" modelVar="item">
        <liferay-ui:search-container-column-text name="menu-item-id"
            value="<%=StringUtil.valueOf(item.getMenuItemId()) %>" />
        <liferay-ui:search-container-column-text name="menu-item-name"
            value="<%=item.getName(currentLocale) %>" />
        <liferay-ui:search-container-column-text name="menu-item-description"
            value="<%=item.getDescription(currentLocale)%>" />
        <liferay-ui:search-container-column-text name="menu-item-path"
            value="<%=item.getPath()%>" />
        <liferay-ui:search-container-column-text name="menu-item-target-type"
            value="<%=item.getTargetType()%>" />
        <liferay-ui:search-container-column-text name="menu-item-weight"
            value="<%=String.valueOf(item.getWeight())%>" />
        <liferay-ui:search-container-column-text name="menu-item-parentId"
            value="<%= item.getParentId() == 0 ? StringPool.BLANK : MenuItemLocalServiceUtil.getMenuItem(item.getParentId()).getName(currentLocale) %>" />
        <liferay-ui:search-container-column-jsp path="/html/menu/buttons.jsp"
            align="right" />
    </liferay-ui:search-container-row>

    <liferay-ui:search-iterator />
</liferay-ui:search-container>

<a href="<%=addMenuItemUrl%>">Add new</a>
MenuItem model has function help us to fetch Menu label corresponding to current locale.

/htm/menu/buttons.jsp file:
<%@ include file="/html/menu/init.jsp" %>

<%
    ResultRow rslt = (ResultRow)renderRequest.getAttribute(WebKeys.SEARCH_CONTAINER_RESULT_ROW);
    MenuItem mi = (MenuItem)rslt.getObject();
%>
<liferay-ui:icon-menu>
    <portlet:actionURL name="edit" var="editUrl">
        <portlet:param name="menuItemId" value="<%= StringUtil.valueOf(mi.getMenuItemId()) %>"/>
    </portlet:actionURL>
    <portlet:actionURL name="delete" var="deleteUrl">
        <portlet:param name="menuItemId" value="<%=StringUtil.valueOf(mi.getMenuItemId()) %>"/>
    </portlet:actionURL>
   
    <liferay-ui:icon image="edit" message="Edit" url="<%= editUrl %>"/>
    <liferay-ui:icon image="delete" message="Delete" url="<%= deleteUrl %>"/>
</liferay-ui:icon-menu>

But wait a minute, how can we save data with localization? Here it is:
public void add(
            ActionRequest actionRequest, ActionResponse actionResponse)
        throws IOException, PortletException, SystemException, PortalException {
        Map<Locale, String> name = LocalizationUtil.getLocalizationMap(actionRequest, "name");
        Map<Locale, String> description = LocalizationUtil.getLocalizationMap(actionRequest, "description");
        String path = ParamUtil.getString(actionRequest, "path");
        String targetType = ParamUtil.getString(actionRequest, "targetType");
        int weight = ParamUtil.getInteger(actionRequest, "weight");
        long parentId = ParamUtil.getLong(actionRequest, "parentId");
       
        long menuItemId = ParamUtil.getLong(actionRequest, "menuItemId");
       
        MenuItem menuItem = null;
        if(menuItemId > 0) {
            menuItem = MenuItemLocalServiceUtil.getMenuItem(menuItemId);
        } else {
            menuItemId = CounterLocalServiceUtil.increment(MenuPortlet.class.getName());
            menuItem = MenuItemLocalServiceUtil.createMenuItem(menuItemId);
        }
       
        menuItem.setNameMap(name);
        menuItem.setDescriptionMap(description);
        menuItem.setPath(path);
        menuItem.setTargetType(targetType);
        menuItem.setWeight(weight);
        menuItem.setParentId(parentId);
       
        MenuItemLocalServiceUtil.updateMenuItem(menuItem);
       
        actionResponse.setPortletMode(PortletMode.VIEW);
    }
See my highlighted code. We save a map with locale value, not just a single value as usual.

Add Menu portlet to Control Panel:
Add 2 control panel entry element to portlet definition in liferay-portlet.xml, like below:
<portlet>
        <portlet-name>menu</portlet-name>
        <icon>/icon.png</icon>
        <control-panel-entry-category>
            content
        </control-panel-entry-category>
        <control-panel-entry-weight>1.5</control-panel-entry-weight>
        <instanceable>false</instanceable>
        <header-portlet-css>/css/menu/main.css</header-portlet-css>
        <footer-portlet-javascript>
            /js/menu/main.js
        </footer-portlet-javascript>
        <css-class-wrapper>menu-portlet</css-class-wrapper>
    </portlet>
Also, in liferay-display.xml, add this portlet to hidden categrory to hide it from user selection in add Portlet list on their pages:
 <category name="category.hidden">
        <portlet id="menu"></portlet>
    </category>
Embed Display Menu portlet to theme:
After you get all menu items you need to display in menu bar, embed Menu Display portlet to theme to make it visible to all pages and does not let user change, move or re-configure it. In navigation.vm file, do something like this:
<nav class="$nav_css_class menu" id="navigation">
    #set ($VOID = $velocityPortletPreference.setValue('display-style', '1'))
    #set ($VOID = $velocityPortletPreference.setValue('portlet-setup-show-borders', 'false'))
   
    #set ($portletId = "menudisplay_WAR_portalportlet")
   
    $theme.runtime($portletId, '', $velocityPortletPreferences.toString())
    #set ($VOID = $velocityPortletPreferences.reset())
</nav>
That's all. Tell me if you have any problem. All comments are welcome. See you next time.