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.
Wednesday, December 31, 2014
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.
<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
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
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
* Using NamedQuery
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:
<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:
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:
Look at portlet-model-hints.xml file,
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:
/htm/menu/buttons.jsp file:
But wait a minute, how can we save data with localization? Here it is:
Add Menu portlet to Control Panel:
Add 2 control panel entry element to portlet definition in liferay-portlet.xml, like below:
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:
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">In this case, pay your attention on:
<!-- 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>
<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>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:
<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>
<%@ include file="/html/menu/init.jsp"%>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.
<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>
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"%>MenuItem model has function help us to fetch Menu label corresponding to current locale.
<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>
/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");See my highlighted code. We save a map with locale value, not just a single value as usual.
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);
}
Add Menu portlet to Control Panel:
Add 2 control panel entry element to portlet definition in liferay-portlet.xml, like below:
<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:
<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>
<category name="category.hidden">Embed Display Menu portlet to theme:
<portlet id="menu"></portlet>
</category>
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">That's all. Tell me if you have any problem. All comments are welcome. See you next time.
#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>
Subscribe to:
Posts (Atom)