понедельник, 25 февраля 2013 г.

Использование шрифтов google

Чтобы web-странички смотрелись лучше, полезно некоторые надписи делать нестандартным шрифтом. На момент написания статьи Google Web Fonts. располагает 617 латинскими и 46 кириллическими шрифтами.

В тэг head нужно добавить ссылку на шрифт и определить какой-нибудь стиль:
  <head>
    <link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Tangerine">
    <style>
 h1 {
   font-family: 'Tangerine', serif;
   font-size: 48px;
   text-shadow: 4px 4px 4px #aaa;
 }
    </style>
  </head>

в данном примере контент всех тегов <h1> страницы будет написан шрифтом Tangerine размером в 48px и с тенью указанного цвета и отступа.

В отсутствие интернта, либо если ссылка будет указывать на несуществующий шрифт, для тега <h1> будет браться следующий вид шрифта serif. Впрочем и с ним могут возникнуть те же проблемы, так что последним в списке желательно указывать какой-нибудь банальный шрифт, который всегда присутствует в системе.

воскресенье, 3 февраля 2013 г.

GWT, использование UIBinder и истории просмотра страниц

В самом простом варианте создания пользовательского интерфейса google предлагает создавать панели и кнопки программно. А человеческая мысль вообще-то уже давненько пришла к тому, что интерфейс надо отделять от кода. Но гугл подготовился и упрекнуть его не в чем. Он подготовился настолько хорошо, что не нужно даже подключать никаких дополнительных библиотек. Технология UIBinder уже встроена в базовую библиотеку gwt-servlet.jar. И одним из бонусов её использования является готовая русификация. Весь текст, введённый на русском языке в ресурсных файлах, такими же буквами будет показан пользователю.

Англоязычный источник от самого гугла находится здесь.

Для примера создадим пару страниц:
  1. страница авторизации с полями ввода логина и пароля и кнопкой входа.
  2. главная страница, откуда будет простор для перехода во все закаулки сайта.
В папке client нужно создать пару шаблонов и соответствующих им java-файлов (соответствие заключается в одинаковости имён без расширений)
LoginPage.ui.xml

<?xml version="1.0" encoding="UTF-8"?>
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g="urn:import:com.google.gwt.user.client.ui">

<g:HorizontalPanel width="100%" height="100%">
<g:cell horizontalAlignment="ALIGN_CENTER" verticalAlignment="ALIGN_MIDDLE">

<g:VerticalPanel spacing="10">
<g:TextBox ui:field="login" styleName="input"/>
<g:PasswordTextBox ui:field="password" styleName="input"/>
<g:Button ui:field="signin" text="войти" styleName="button"/>
</g:VerticalPanel>

</g:cell>
</g:HorizontalPanel>

</ui:UiBinder>
Первым вложенным в главный тег <ui:UiBinder> является тег <g:HorizontalPanel>, поэтому соответствующий java-класс должен содержать интерфейс, расширяющий UiBinder с параметрами HorizontalPanel, LoginPage.
Также в шаблоне упомянуты:
  • TextBox с именем login
  • PasswordTextBox с именем password
  • Button с именем signin
поэтому все они и только они должны быть использованы в соответствующем java-классе. У всех этих элементов есть параметр styleName. Это чтобы всё было красиво. Соответствующие стили надо описывать в файле стилей css, лежащем в папке /war/WEB-INF и имеющем имя созданного проекта.

LoginPage.java

package ru.andrew.smsbinder.client;

import com.google.gwt.core.shared.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;

public class LoginPage extends Composite {
interface LoginUiBinder extends UiBinder<HorizontalPanel, LoginPage> {
};

private static LoginUiBinder uiBinder = GWT.create(LoginUiBinder.class);

@UiField
TextBox login;
@UiField
PasswordTextBox password;
@UiField
Button signin;

public LoginPage() {
super();
initWidget(uiBinder.createAndBindUi(this));
login.getElement().setAttribute("placeHolder", "логин");
password.getElement().setAttribute("placeHolder", "пароль");

signin.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
History.newItem("mainPage");
RootPanel.get().clear();
RootPanel.get().add(new MainPage());
}
});
}
}
Этот класс содержит, повторюсь, описание интерфейса LoginUiBinder, который:
  • расширяет стандартный интерфейс UiBinder
  • в качестве параметров принимает
    • HorizontalPanel - имя первого тега, вложенного в главный тег соответствующего шаблона
    • LoginPage - имя нашего класса
В следующей строке создаётся экземпляр этого интерфейса uiBinder, который инстанциируется с помощью команды GWT.create().
Также в этом классе есть три вышеобозначеных поля, помеченных аннотациями @UiField.
И последнее, что есть в этом классе - его конструктор. Его существенными строками являются:
  • вызов super-конструктора
  • магическая строка initWidget(uiBinder.createAndBindUi(this));, без которой ничего не будет работать
  • работа с элементами интерфейса. Здесь, например, мы присвоили полям логина и пароля так называемые плейсхолдеры, которые будут появляться в этих полях, когда они пусты. Также мы прикрепили к кнопке обработчик её нажатия. При нажатии на кнопку будет происходить переход на вторую страницу нашего приложения, и в стек истории будет занесена запись о посещении этой второй страницы.

MainPage.ui.xml
<?xml version="1.0" encoding="UTF-8"?>
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g="urn:import:com.google.gwt.user.client.ui">

<g:HorizontalPanel width="100%" height="100%">
<g:cell horizontalAlignment="ALIGN_CENTER" verticalAlignment="ALIGN_MIDDLE">

<g:VerticalPanel spacing="10">
<g:TextBox ui:field="phone" styleName="input" />
<g:TextBox ui:field="name" styleName="input" />
<g:Button ui:field="saveContact" text="сохранить" styleName="button" />
<g:TextArea ui:field="message" styleName="input" visibleLines="5" characterWidth="50" />
<g:Button ui:field="sendMessage" text="отправить" styleName="button" />
</g:VerticalPanel>

</g:cell>
</g:HorizontalPanel>

</ui:UiBinder>
Ничего нового здесь, кажется, нет. Просто новый шаблон с пятью новыми полями.

MainPage.java

package ru.andrew.smsbinder.client;

import com.google.gwt.core.shared.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;

public class MainPage extends Composite {

interface MainPageUiBinder extends UiBinder<HorizontalPanel, MainPage>{};
private static MainPageUiBinder uiBinder = GWT.create(MainPageUiBinder.class);

@UiField
TextBox phone;
@UiField
TextBox name;
@UiField
Button saveContact;
@UiField
TextArea message;
@UiField
Button sendMessage;

public MainPage(){
super();
initWidget(uiBinder.createAndBindUi(this));
phone.getElement().setAttribute("placeHolder", "телефон");
name.getElement().setAttribute("placeHolder", "имя контакта");
message.getElement().setAttribute("placeHolder", "сообщение");

sendMessage.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
History.newItem("loginPage");
RootPanel.get().clear();
RootPanel.get().add(new LoginPage());
}
});

}
}
Вообще-то, опять вся та же структура, переменная uiBinder, являющаяся экземпляром класса MainPageUiBinder, 5 полей, описанных в шаблоне, и конструктор, в котором к полям прикреплены плейсхолдеры, а по кнопке происходит переход на страницу логина и в стек истории будет занесена запись о посещении страницы логина.

Последним действием надо отредактировать файл - точку входа в наше приложение. По умолчанию он лежит в пакете client, называется также как ваш проект и имеет расширение java. Вычищаем всё в методе и заменяем его кодом:

public void onModuleLoad(){
RootPanel.get().add(new LoginPage());
History.newItem("loginPage");

History.addValueChangeHandler(new ValueChangeHandler<String>() {
public void onValueChange(ValueChangeEvent<String> event) {
String historyToken = event.getValue();
System.out.println("call for history" + historyToken);
try {
if("mainPage".equals(historyToken)){
RootPanel.get().clear();
RootPanel.get().add(new MainPage());
}else if("loginPage".equals(historyToken)){
RootPanel.get().clear();
RootPanel.get().add(new LoginPage());
}else{
throw new IndexOutOfBoundsException("no! " + historyToken);
}
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
}
});
}

В этом коде первая строка говорит, какую страницу загружать, вторая добавляет в стек истории запись об этой странице, а весь последующий код ответственен за работу историю в браузере - какую страницу и с какими данными следует отображать, когда пользователь жмёт в браузере кнопки Back / Forward.
Ну всё, можно запускать, кликать на кнопки и любоваться.
Run As > Web Application



суббота, 2 февраля 2013 г.

Spring Security - кастомизация (т.е. настройка)

Вот в этой статье написано, как использовать Spring security в GWT в самом простом варианте, нужно разобраться как использовать это технологию по-взрослому.

Решаемые вопросы:
  1. при отсутствии авторизации пользователю должен выдаваться алерт с предложением ввести логин/пароль
  2. подключение логаута
  3. получение авторизации должно проходить на основе базы данных

Авторизация в виде алерта
Редактируем тэг <http> настроечного файла applicationContext.xml

<http auto-config="true">
<intercept-url pattern="/favicon.ico" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<intercept-url pattern="/images/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<intercept-url pattern="/js/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<intercept-url pattern="/resources/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<intercept-url pattern="/securegwt/**" access="ROLE_USER" />
<intercept-url pattern="/gwt/**" access="ROLE_USER" />
<intercept-url pattern="/**/*.html" access="ROLE_USER" />
<intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />

<http-basic />
</http>

В первых четырёх строках мы предоставляем анонимный доступ на ресурсы (веб-иконку favicon.ico, рисунки, которые хранятся в папке /images проекта, ресурсы /resources и джаваскрипты /js)

В последней строке мы добавляем тег <http-basic />, который говорит Spring Security, что нужно использовать так называемую бэйсик-авторизацию, то есть авторизацию в виде алерта.

Подключение логаута
англоязыйчный источник находится здесь.
Чтобы сделать логаут, нужно добавить ссылку на УРЛ: j_spring_security_logout, например:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<body>
 <h3>messages, whatever</h3> 
 <a href="<c:url value="j_spring_security_logout" />" > Logout</a>
</body>
</html>

в терминах GWT это будет выглядеть так:

Label lbl = new Label("Logout");
lbl.addClickHandler(...);
lbl.setStyleName("hyperlink_style_label");

При использовании UIBinder в файл.ui.xml нужно добавить тэг <g:Hyperlink>

Подключение авторизации на основе базы данных
Если мы хотим поменьше конфигурировать настроечные файлы spring и вообще не писать java-файлов, мы должны согласиться на создание таблиц пользователей и ролей по схеме spring security. Они должны быть созданы и населены по следующей схеме (источник):


CREATE TABLE `users` (
  `USER_ID` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `USERNAME` VARCHAR(45) NOT NULL,
  `PASSWORD` VARCHAR(45) NOT NULL,
  `ENABLED` tinyint(1) NOT NULL,
  PRIMARY KEY (`USER_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `user_roles` (
  `USER_ROLE_ID` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `USER_ID` INT(10) UNSIGNED NOT NULL,
  `AUTHORITY` VARCHAR(45) NOT NULL,
  PRIMARY KEY (`USER_ROLE_ID`),
  KEY `FK_user_roles` (`USER_ID`),
  CONSTRAINT `FK_user_roles` FOREIGN KEY (`USER_ID`) REFERENCES `users` (`USER_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO users (USERNAME, PASSWORD, ENABLED)
VALUES ('andrew', md5('password'), TRUE);

INSERT INTO user_roles (USER_ID, AUTHORITY)
VALUES ((select user_id from users where username='andrew'), 'ROLE_USER');

Добавим в проект библиотеки:

  • mysql-connector-java-5.1.23-bin.jar
  • spring-jdbc-3.2.1.RELEASE.jar
  • spring-tx-3.2.1.RELEASE.jar


В конфигурационном файле applicationContext.xml нужно определить бин подключения к базе данных dataSource:

   <bean id="dataSource"
 class="org.springframework.jdbc.datasource.DriverManagerDataSource">
 
 <property name="driverClassName" value="com.mysql.jdbc.Driver" />
 <property name="url" value="jdbc:mysql://localhost:3306/mydatabase" />
 <property name="username" value="root" />
 <property name="password" value="password" />
   </bean>

И заменить authentication-manager:

 <authentication-manager>
  <authentication-provider>
      <password-encoder hash="md5"/>
   <jdbc-user-service data-source-ref="dataSource"

    users-by-username-query="
        select username,password, enabled 
        from users where username=?"

    authorities-by-username-query="
        select u.username, ur.authority from users u, user_roles ur 
        where u.user_id = ur.user_id and u.username =?  " />
  </authentication-provider>
 </authentication-manager>

Вставка в таблицу БД пароля пользователя с помощью функции md5() согласована с тегом <password-encoder hash="md5"/> в настроечном файле. Пароль будет храниться в базе в зашифрованном md5-алгоритмом виде, никто никогда не сможет расшифровать его, а можно будет только сравнивать хранимую строку с аналогичным образом зашиброванным вариантом пользователя.

После этого в вашем приложении смогут авторизовываться только пользователи, зарегистрированные в вашей базе данных.