提交 7fe22298 编写于 作者: C Calvin

#57 将quickstart重新命名mini-web

上级 6e0109be
......@@ -6,8 +6,7 @@ eclipse-local.bat
org.eclipse.jdt.core.prefs
org.eclipse.wst.common.component
org.eclipse.wst.common.project.facet.core.xml
/examples/showcase/bin/perf4j-0.9.16.jar
/examples/showcase/bin/yuicompressor-2.4.7.jar
/support/script
/support/selenium-server/selenium-server-standalone-2.21.0.jar
/TODO.txt
/examples/mini-web-old
\ No newline at end of file
#Thu Apr 08 18:37:16 CST 2010
eclipse.preferences.version=1
encoding/<project>=UTF-8
......@@ -2,34 +2,63 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springside</groupId>
<artifactId>springside-parent</artifactId>
<version>4.0.0.RC4-SNAPSHOT</version>
<relativePath>../../modules/parent/</relativePath>
</parent>
<groupId>org.springside.examples</groupId>
<artifactId>mini-web</artifactId>
<version>4.0.0.RC4-SNAPSHOT</version>
<packaging>war</packaging>
<name>Springside :: Example :: Mini-Web</name>
<!-- 项目属性 -->
<properties>
<!-- 主要依赖库的版本定义 -->
<springside.version>4.0.0.RC4-SNAPSHOT</springside.version>
<spring.version>3.1.2.RELEASE</spring.version>
<hibernate.version>4.1.4.Final</hibernate.version>
<spring-data-jpa.version>1.1.0.RELEASE</spring-data-jpa.version>
<commons-dbcp.version>1.4</commons-dbcp.version>
<sitemesh.version>2.4.2</sitemesh.version>
<guava.version>12.0</guava.version>
<commons-lang3.version>3.1</commons-lang3.version>
<jackson.version>2.0.4</jackson.version>
<slf4j.version>1.6.6</slf4j.version>
<log4j.version>1.2.17</log4j.version>
<jetty.version>7.6.4.v20120524</jetty.version>
<h2.version>1.3.167</h2.version>
<junit.version>4.10</junit.version>
<mockito.version>1.9.0</mockito.version>
<selenium.version>2.24.1</selenium.version>
<!-- Plugin的属性定义 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jdk.version>1.6</jdk.version>
<!-- 项目属性 -->
<jdbc.driver.groupId>com.h2database</jdbc.driver.groupId>
<jdbc.driver.artifactId>h2</jdbc.driver.artifactId>
<jdbc.driver.version>${h2.version}</jdbc.driver.version>
<!--
<jdbc.driver.groupId>com.oracle</jdbc.driver.groupId>
<jdbc.driver.artifactId>ojdbc14</jdbc.driver.artifactId>
<jdbc.driver.version>10.2.0.3</jdbc.driver.version>
-->
<!--
<jdbc.driver.groupId>mysql</jdbc.driver.groupId>
<jdbc.driver.artifactId>mysql-connector-java</jdbc.driver.artifactId>
<jdbc.driver.version>5.1.13</jdbc.driver.version>
-->
</properties>
<!-- 设定仓库 如有Nexus私服, 取消注释并指向正确的服务器地址.
<repositories>
<repository>
<id>nexus</id>
<name>Team Nexus Repository</name>
<url>http://localhost:8081/nexus/content/groups/public</url>
</repository>
</repositories>
-->
<!-- 设定插件仓库 如有Nexus私服, 取消注释并指向正确的服务器地址.
<pluginRepositories>
<pluginRepository>
<id>nexus</id>
<name>Team Nexus Repository</name>
<url>http://localhost:8081/nexus/content/groups/public</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
-->
<!-- 依赖项定义 -->
<dependencies>
......@@ -37,64 +66,75 @@
<dependency>
<groupId>org.springside</groupId>
<artifactId>springside-core</artifactId>
<version>${springside.version}</version>
</dependency>
<!-- SPRING begin -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<!-- <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.6.9</version> </dependency> -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency>
<!-- SPRING end -->
<!-- PERSISTENCE begin -->
<!-- hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<version>1.0.1.Final</version>
</dependency>
<!-- spring data access -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>${spring-data-jpa.version}</version>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit-dep</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- dbcp connection pool -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>${commons-dbcp.version}</version>
</dependency>
<!-- jdbc driver -->
......@@ -110,152 +150,224 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>opensymphony</groupId>
<artifactId>sitemesh</artifactId>
<version>${sitemesh.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!-- WEB end -->
<!-- CACHE -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
</dependency>
<!-- SECURITY begin -->
<!-- GENERAL UTILS begin -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<!-- GENERAL UTILS end -->
<!-- JSON begin -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- SECURITY end -->
<!-- JSON end -->
<!-- LOGGING begin -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.lazyluke</groupId>
<artifactId>log4jdbc-remix</artifactId>
<version>0.2.7</version>
</dependency>
<!-- LOGGING end -->
<!-- GENERAL UTILS begin -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<!-- GENERAL UTILS end -->
<!-- TEST begin -->
<dependency>
<groupId>org.springside</groupId>
<artifactId>springside-test</artifactId>
<version>${springside.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<!-- selenium 2.0 -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-android-driver</artifactId>
</exclusion>
<exclusion>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-iphone-driver</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-remote-driver</artifactId>
<version>${selenium.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.dbunit</groupId>
<artifactId>dbunit</artifactId>
<version>2.4.8</version>
<scope>test</scope>
</dependency>
<!-- jetty -->
<dependency>
<groupId>org.eclipse.jetty.aggregate</groupId>
<artifactId>jetty-webapp</artifactId>
<version>${jetty.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jsp</artifactId>
<version>${jetty.version}</version>
<scope>test</scope>
</dependency>
<!-- TEST end -->
</dependencies>
<build>
<plugins>
<!-- compiler插件, 设定JDK版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5</version>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
<!-- war打包插件, 设定war包名称不带版本号 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.2</version>
<configuration>
<warName>${project.artifactId}</warName>
</configuration>
</plugin>
<!-- test插件, 仅测试名称为*Test的类,使用支持分组测试的surefire-junit47 driver -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12</version>
<configuration>
<includes>
<include>**/*Test.java</include>
</includes>
<argLine>-Xmx256M</argLine>
</configuration>
<dependencies>
<dependency>
<groupId>org.apache.maven.surefire</groupId>
<artifactId>surefire-junit47</artifactId>
<version>2.12</version>
</dependency>
</dependencies>
</plugin>
<!-- 增加functional test的Source目录 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<id>add-functional-source</id>
......@@ -271,18 +383,12 @@
</execution>
</executions>
</plugin>
<!-- test插件, 设置內存/ClassLoader -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-Xmx256M</argLine>
</configuration>
</plugin>
<!-- cobertura插件, 设置不需要计算覆盖率的类 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<instrumentation>
<excludes>
......@@ -292,23 +398,24 @@
</instrumentation>
</configuration>
</plugin>
<!-- eclipse插件, 设定wtp版本并添加springIDE nature -->
<!-- eclipse插件, 设定wtp版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<downloadSources>true</downloadSources>
<downloadJavadocs>false</downloadJavadocs>
<wtpversion>2.0</wtpversion>
<additionalProjectnatures>
<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
</additionalProjectnatures>
</configuration>
</plugin>
<!-- jetty插件, 设定context path与spring profile -->
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty.version}</version>
<configuration>
<systemProperties>
<systemProperty>
......@@ -323,11 +430,25 @@
</webAppConfig>
</configuration>
</plugin>
<!-- resource插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.5</version>
</plugin>
<!-- clean插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>2.5</version>
</plugin>
</plugins>
</build>
<profiles>
<!-- 仅执行smoke test-->
<!-- 仅执行smoke test -->
<profile>
<id>smoke-test</id>
<build>
......@@ -336,8 +457,13 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- 在parent的profile的基础上进一步定义 -->
<groups>org.springside.modules.test.category.Smoke</groups>
<includes>
<include>**/*IT.java</include>
</includes>
<!-- 支持taglib tld文件查找的必要设置 -->
<useSystemClassLoader>false</useSystemClassLoader>
<!-- 将mvn命令行传入的selenium driver参数传入surefire的JVM -->
<systemPropertyVariables>
<selenium.driver>${selenium.driver}</selenium.driver>
</systemPropertyVariables>
......@@ -346,8 +472,8 @@
</plugins>
</build>
</profile>
<!-- 执行所有functional test-->
<!-- 执行所有functional test -->
<profile>
<id>functional-test</id>
<build>
......@@ -356,7 +482,9 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- 在parent的profile的基础上进一步定义 -->
<includes>
<include>**/*IT.java</include>
</includes>
<!-- 支持taglib tld文件查找的必要设置 -->
<useSystemClassLoader>false</useSystemClassLoader>
<!-- 将mvn命令行传入的selenium driver参数传入surefire的JVM -->
......@@ -369,25 +497,28 @@
</build>
</profile>
<!-- 刷新开发环境数据库 -->
<!-- 刷新开发环境数据库 -->
<profile>
<id>refresh-db</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<configuration>
<target>
<property file="src/main/resources/application.local.properties" />
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
<configuration>
<target>
<property file="src/main/resources/application.development-local.properties" />
<property file="src/main/resources/application.development.properties" />
<property file="src/main/resources/application.properties" />
<property name="sql.type" value="h2" />
<property name="dbunit.datatype" value="org.dbunit.ext.h2.H2DataTypeFactory" />
<taskdef name="dbunit" classname="org.dbunit.ant.DbUnitTask" classpathref="maven.test.classpath" />
<sql driver="${jdbc.driver}" url="${jdbc.url}" userid="${jdbc.username}" password="${jdbc.password}" src="src/main/resources/sql/${sql.type}/schema.sql" onerror="continue">
<sql driver="${jdbc.driver}" url="${jdbc.url}" userid="${jdbc.username}" password="${jdbc.password}"
src="src/main/resources/sql/${sql.type}/schema.sql" onerror="continue">
<classpath refid="maven.test.classpath" />
</sql>
......@@ -396,10 +527,11 @@
<property name="datatypeFactory" value="${dbunit.datatype}" />
</dbconfig>
<classpath refid="maven.test.classpath" />
<operation type="CLEAN_INSERT" src="src/test/resources/data/sample-data.xml" format="flat" transaction="true"/>
<operation type="CLEAN_INSERT" src="src/test/resources/data/sample-data.xml" format="flat"
transaction="true" />
</dbunit>
</target>
</configuration>
</target>
</configuration>
</plugin>
</plugins>
</build>
......
package org.springside.examples.quickstart.dao;
package org.springside.examples.miniweb.dao;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springside.examples.quickstart.entity.Task;
import org.springside.examples.miniweb.entity.Task;
public interface TaskDao extends PagingAndSortingRepository<Task, Long> {
......
package org.springside.examples.miniweb.dao.account;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springside.examples.miniweb.entity.account.Group;
/**
* 权限组对象的Dao interface.
*
* @author calvin
*/
public interface GroupDao extends PagingAndSortingRepository<Group, Long>, GroupDaoCustom {
}
package org.springside.examples.miniweb.dao.account;
/**
* GroupDao的扩展行为interface.
*/
public interface GroupDaoCustom {
/**
* 因为Group中没有建立与User的关联,因此需要以较低效率的方式进行删除User与Group的多对多中间表中的数据.
*/
void deleteWithReference(Long id);
}
package org.springside.examples.miniweb.dao.account;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.stereotype.Component;
import org.springside.examples.miniweb.entity.account.Group;
import org.springside.examples.miniweb.entity.account.User;
/**
* GroupDao的扩展行为实现类.
*/
@Component
public class GroupDaoImpl implements GroupDaoCustom {
private static final String QUERY_USER_BY_GROUPID = "select u from User u left join u.groupList g where g.id=?";
@PersistenceContext
private EntityManager em;
@Override
public void deleteWithReference(Long id) {
//因為Group中沒有与User的关联,只能用笨办法,查询出拥有该权限组的用户, 并删除该用户的权限组.
Group group = em.find(Group.class, id);
List<User> users = em.createQuery(QUERY_USER_BY_GROUPID).setParameter(1, id).getResultList();
for (User u : users) {
u.getGroupList().remove(group);
}
em.remove(group);
}
}
package org.springside.examples.miniweb.dao.account;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springside.examples.miniweb.entity.account.User;
/**
* 用户对象的Dao interface.
*
* @author calvin
*/
public interface UserDao extends PagingAndSortingRepository<User, Long> {
User findByLoginName(String loginName);
}
package org.springside.examples.quickstart.entity;
package org.springside.examples.miniweb.entity;
import javax.persistence.Entity;
......
package org.springside.examples.miniweb.entity.account;
import java.util.List;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.springside.examples.miniweb.entity.IdEntity;
import com.google.common.collect.Lists;
/**
* 权限组.
*
* 注释见{@link User}.
*
* @author calvin
*/
@Entity
@Table(name = "acct_group")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Group extends IdEntity {
private String name;
private List<String> permissionList = Lists.newArrayList();
public Group() {
}
public Group(Long id, String name) {
this.id = id;
this.name = name;
}
@Column(nullable = false, unique = true)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@ElementCollection
@CollectionTable(name = "acct_group_permission", joinColumns = { @JoinColumn(name = "group_id") })
@Column(name = "permission")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public List<String> getPermissionList() {
return permissionList;
}
public void setPermissionList(List<String> permissionList) {
this.permissionList = permissionList;
}
@Transient
public String getPermissionNames() {
List<String> permissionNameList = Lists.newArrayList();
for (String permission : permissionList) {
permissionNameList.add(Permission.parse(permission).displayName);
}
return StringUtils.join(permissionNameList, ",");
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
}
package org.springside.examples.miniweb.entity.account;
import java.util.Map;
import com.google.common.collect.Maps;
/**
* Resource Base Access Control中的资源定义.
*
* @author calvin
*/
public enum Permission {
USER_VIEW("user:view", "查看用戶"), USER_EDIT("user:edit", "修改用户"),
GROUP_VIEW("group:view", "查看权限组"), GROUP_EDIT("group:edit", "修改权限组");
private static Map<String, Permission> valueMap = Maps.newHashMap();
public String value;
public String displayName;
static {
for (Permission permission : Permission.values()) {
valueMap.put(permission.value, permission);
}
}
Permission(String value, String displayName) {
this.value = value;
this.displayName = displayName;
}
public static Permission parse(String value) {
return valueMap.get(value);
}
public String getValue() {
return value;
}
public String getDisplayName() {
return displayName;
}
}
package org.springside.examples.miniweb.entity.account;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.springside.examples.miniweb.entity.IdEntity;
import org.springside.modules.utils.Collections3;
import com.google.common.collect.Lists;
/**
* 用户.
*
* 使用JPA annotation定义ORM关系.
* 使用Hibernate annotation定义JPA未覆盖的部分.
*
* @author calvin
*/
@Entity
//表名与类名不相同时重新定义表名.
@Table(name = "acct_user")
//默认的缓存策略.
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User extends IdEntity {
private String loginName;
private String password;//为简化演示使用明文保存的密码
private String name;
private String email;
private List<Group> groupList = Lists.newArrayList();//有序的关联对象集合
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
//多对多定义
@ManyToMany
@JoinTable(name = "acct_user_group", joinColumns = { @JoinColumn(name = "user_id") }, inverseJoinColumns = { @JoinColumn(name = "group_id") })
//Fecth策略定义
@Fetch(FetchMode.SUBSELECT)
//集合按id排序.
@OrderBy("id")
//集合中对象id的缓存.
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public List<Group> getGroupList() {
return groupList;
}
public void setGroupList(List<Group> groupList) {
this.groupList = groupList;
}
/**
* 用户拥有的权限组名称字符串, 多个权限组名称用','分隔.
*/
//非持久化属性.
@Transient
public String getGroupNames() {
return Collections3.extractToString(groupList, "name", ", ");
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
}
\ No newline at end of file
package org.springside.examples.miniweb.rest;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springside.examples.miniweb.entity.Task;
import org.springside.examples.miniweb.service.TaskManager;
@Controller
@RequestMapping(value = "/api/task")
public class TaskRestController {
@Autowired
private TaskManager taskManager;
@RequestMapping(method = RequestMethod.GET, produces = "application/json")
public @ResponseBody
List<Task> list(Model model) {
return taskManager.getAllTask();
}
}
package org.springside.examples.quickstart.service;
package org.springside.examples.miniweb.service;
import java.util.List;
......@@ -7,8 +7,8 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springside.examples.quickstart.dao.TaskDao;
import org.springside.examples.quickstart.entity.Task;
import org.springside.examples.miniweb.dao.TaskDao;
import org.springside.examples.miniweb.entity.Task;
//Spring Bean的标识.
@Component
......
package org.springside.examples.miniweb.service.account;
import java.util.List;
import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springside.examples.miniweb.dao.account.GroupDao;
import org.springside.examples.miniweb.dao.account.UserDao;
import org.springside.examples.miniweb.entity.account.Group;
import org.springside.examples.miniweb.entity.account.User;
import org.springside.examples.miniweb.service.ServiceException;
/**
* 安全相关实体的管理类,包括用户和权限组.
*
* @author calvin
*/
//Spring Bean的标识.
@Component
//默认将类中的所有public函数纳入事务管理.
@Transactional(readOnly = true)
public class AccountManager {
private static Logger logger = LoggerFactory.getLogger(AccountManager.class);
private UserDao userDao;
private GroupDao groupDao;
private ShiroDbRealm shiroRealm;
//-- User Manager --//
public User getUser(Long id) {
return userDao.findOne(id);
}
@Transactional(readOnly = false)
public void saveUser(User entity) {
userDao.save(entity);
shiroRealm.clearCachedAuthorizationInfo(entity.getLoginName());
}
/**
* 删除用户,如果尝试删除超级管理员将抛出异常.
*/
@Transactional(readOnly = false)
public void deleteUser(Long id) {
if (isSupervisor(id)) {
logger.warn("操作员{}尝试删除超级管理员用户", SecurityUtils.getSubject().getPrincipal());
throw new ServiceException("不能删除超级管理员用户");
}
userDao.delete(id);
}
/**
* 判断是否超级管理员.
*/
private boolean isSupervisor(Long id) {
return id == 1;
}
public List<User> getAllUser() {
return (List<User>) userDao.findAll(new Sort(Direction.ASC, "id"));
}
public User findUserByLoginName(String loginName) {
return userDao.findByLoginName(loginName);
}
//-- Group Manager --//
public Group getGroup(Long id) {
return groupDao.findOne(id);
}
public List<Group> getAllGroup() {
return (List<Group>) groupDao.findAll((new Sort(Direction.ASC, "id")));
}
@Transactional(readOnly = false)
public void saveGroup(Group entity) {
groupDao.save(entity);
shiroRealm.clearAllCachedAuthorizationInfo();
}
@Transactional(readOnly = false)
public void deleteGroup(Long id) {
groupDao.delete(id);
shiroRealm.clearAllCachedAuthorizationInfo();
}
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Autowired
public void setGroupDao(GroupDao groupDao) {
this.groupDao = groupDao;
}
@Autowired(required = false)
public void setShiroRealm(ShiroDbRealm shiroRealm) {
this.shiroRealm = shiroRealm;
}
}
package org.springside.examples.miniweb.service.account;
import java.io.Serializable;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springside.examples.miniweb.entity.account.Group;
import org.springside.examples.miniweb.entity.account.User;
/**
* 自实现用户与权限查询.
* 演示关系,密码用明文存储,因此使用默认 的SimpleCredentialsMatcher.
*/
public class ShiroDbRealm extends AuthorizingRealm {
private AccountManager accountManager;
/**
* 认证回调函数, 登录时调用.
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
User user = accountManager.findUserByLoginName(token.getUsername());
if (user != null) {
return new SimpleAuthenticationInfo(new ShiroUser(user.getLoginName(), user.getName()), user.getPassword(),
getName());
} else {
return null;
}
}
/**
* 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用.
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
ShiroUser shiroUser = (ShiroUser) principals.fromRealm(getName()).iterator().next();
User user = accountManager.findUserByLoginName(shiroUser.getLoginName());
if (user != null) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
for (Group group : user.getGroupList()) {
//基于Permission的权限信息
info.addStringPermissions(group.getPermissionList());
}
return info;
} else {
return null;
}
}
/**
* 更新用户授权信息缓存.
*/
public void clearCachedAuthorizationInfo(String principal) {
SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName());
clearCachedAuthorizationInfo(principals);
}
/**
* 清除所有用户授权信息缓存.
*/
public void clearAllCachedAuthorizationInfo() {
Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
if (cache != null) {
for (Object key : cache.keys()) {
cache.remove(key);
}
}
}
@Autowired
public void setAccountManager(AccountManager accountManager) {
this.accountManager = accountManager;
}
/**
* 自定义Authentication对象,使得Subject除了携带用户的登录名外还可以携带更多信息.
*/
public static class ShiroUser implements Serializable {
private static final long serialVersionUID = -1748602382963711884L;
private String loginName;
private String name;
public ShiroUser(String loginName, String name) {
this.loginName = loginName;
this.name = name;
}
public String getLoginName() {
return loginName;
}
/**
* 本函数输出将作为默认的<shiro:principal/>输出.
*/
@Override
public String toString() {
return loginName;
}
public String getName() {
return name;
}
}
}
package org.springside.examples.miniweb.web;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
/**
* LoginController负责打开登录页面(GET请求)和登录出错页面(POST请求),
* 真正登录的POST请求由Filter完成,
*
* @author calvin
*/
@Controller
public class LoginController {
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login() {
return "login";
}
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String fail(@RequestParam(FormAuthenticationFilter.DEFAULT_USERNAME_PARAM) String userName, Model model) {
model.addAttribute(FormAuthenticationFilter.DEFAULT_USERNAME_PARAM, userName);
return "login";
}
}
package org.springside.examples.quickstart.web;
package org.springside.examples.miniweb.web;
import java.util.List;
......@@ -8,8 +8,8 @@ import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springside.examples.quickstart.entity.Task;
import org.springside.examples.quickstart.service.TaskManager;
import org.springside.examples.miniweb.entity.Task;
import org.springside.examples.miniweb.service.TaskManager;
/**
* Urls:
......
package org.springside.examples.quickstart.web;
package org.springside.examples.miniweb.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
......@@ -7,8 +7,8 @@ import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springside.examples.quickstart.entity.Task;
import org.springside.examples.quickstart.service.TaskManager;
import org.springside.examples.miniweb.entity.Task;
import org.springside.examples.miniweb.service.TaskManager;
/**
* 使用@ModelAttribute, 实现Struts2 Preparable二次绑定的效果。
......
package org.springside.examples.miniweb.web.account;
import java.util.List;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springside.examples.miniweb.entity.account.Group;
import org.springside.examples.miniweb.entity.account.Permission;
import org.springside.examples.miniweb.service.account.AccountManager;
@Controller
@RequestMapping(value = "/account/group")
public class GroupController {
@Autowired
private AccountManager accountManager;
@RequiresPermissions("group:view")
@RequestMapping(value = { "list", "" })
public String list(Model model) {
List<Group> groups = accountManager.getAllGroup();
model.addAttribute("groups", groups);
return "account/groupList";
}
@RequiresPermissions("group:edit")
@RequestMapping(value = "create")
public String createForm(Model model) {
model.addAttribute("group", new Group());
model.addAttribute("allPermissions", Permission.values());
return "account/groupForm";
}
@RequiresPermissions("group:edit")
@RequestMapping(value = "save")
public String save(Group group, RedirectAttributes redirectAttributes) {
accountManager.saveGroup(group);
redirectAttributes.addFlashAttribute("message", "创建权限组" + group.getName() + "成功");
return "redirect:/account/group/";
}
@RequiresPermissions("group:edit")
@RequestMapping(value = "delete/{id}")
public String delete(@PathVariable("id") Long id, RedirectAttributes redirectAttributes) {
accountManager.deleteGroup(id);
redirectAttributes.addFlashAttribute("message", "删除权限组成功");
return "redirect:/account/group/";
}
}
package org.springside.examples.miniweb.web.account;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springside.examples.miniweb.entity.account.Group;
import org.springside.examples.miniweb.entity.account.Permission;
import org.springside.examples.miniweb.service.account.AccountManager;
@Controller
@RequestMapping(value = "/account/group/")
public class GroupDetailController {
@Autowired
private AccountManager accountManager;
@RequiresPermissions("group:edit")
@RequestMapping(value = "update/{id}")
public String updateForm(Model model) {
model.addAttribute("allPermissions", Permission.values());
return "account/groupForm";
}
@RequiresPermissions("group:edit")
@RequestMapping(value = "save/{id}")
public String save(@ModelAttribute("group") Group group, RedirectAttributes redirectAttributes) {
accountManager.saveGroup(group);
redirectAttributes.addFlashAttribute("message", "修改权限组" + group.getName() + "成功");
return "redirect:/account/group/";
}
@ModelAttribute("group")
public Group getGroup(@PathVariable("id") Long id) {
return accountManager.getGroup(id);
}
}
package org.springside.examples.miniweb.web.account;
import java.beans.PropertyEditorSupport;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springside.examples.miniweb.entity.account.Group;
import org.springside.examples.miniweb.service.account.AccountManager;
import org.springside.modules.utils.Collections3;
/**
* 用于转换用户表单中复杂对象Group的checkbox的关联。
*
* @author calvin
*/
@Component
public class GroupListEditor extends PropertyEditorSupport {
@Autowired
private AccountManager accountManager;
/**
* Back From Page
*/
@Override
public void setAsText(String text) throws IllegalArgumentException {
String[] ids = StringUtils.split(text, ",");
List<Group> groups = new ArrayList<Group>();
for (String id : ids) {
Group group = accountManager.getGroup(Long.valueOf(id));
groups.add(group);
}
setValue(groups);
}
/**
* Set to page
*/
@Override
public String getAsText() {
return Collections3.extractToString((List) getValue(), "id", ",");
}
}
package org.springside.examples.miniweb.web.account;
import java.util.List;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springside.examples.miniweb.entity.account.User;
import org.springside.examples.miniweb.service.account.AccountManager;
/**
* Urls:
* List page : GET /account/user/
* Create page : GET /account/user/create
* Create action : POST /account/user/save
* Update page : GET /account/user/update/{id}
* Update action : POST /account/user/save/{id}
* Delete action : POST /account/user/delete/{id}
* CheckLoginName ajax: GET /account/user/checkLoginName?oldLoginName=a&loginName=b
*
* @author calvin
*
*/
@Controller
@RequestMapping(value = "/account/user")
public class UserController {
@Autowired
private AccountManager accountManager;
@Autowired
private GroupListEditor groupListEditor;
@InitBinder
public void initBinder(WebDataBinder b) {
b.registerCustomEditor(List.class, "groupList", groupListEditor);
}
@RequiresPermissions("user:view")
@RequestMapping(value = { "list", "" })
public String list(Model model) {
List<User> users = accountManager.getAllUser();
model.addAttribute("users", users);
return "account/userList";
}
@RequiresPermissions("user:edit")
@RequestMapping(value = "create")
public String createForm(Model model) {
model.addAttribute("user", new User());
model.addAttribute("allGroups", accountManager.getAllGroup());
return "account/userForm";
}
@RequiresPermissions("user:edit")
@RequestMapping(value = "save")
public String save(User user, RedirectAttributes redirectAttributes) {
accountManager.saveUser(user);
redirectAttributes.addFlashAttribute("message", "创建用户" + user.getLoginName() + "成功");
return "redirect:/account/user/";
}
@RequiresPermissions("user:edit")
@RequestMapping(value = "delete/{id}")
public String delete(@PathVariable("id") Long id, RedirectAttributes redirectAttributes) {
accountManager.deleteUser(id);
redirectAttributes.addFlashAttribute("message", "删除用户成功");
return "redirect:/account/user/";
}
@RequiresPermissions("user:edit")
@RequestMapping(value = "checkLoginName")
@ResponseBody
public String checkLoginName(@RequestParam("oldLoginName") String oldLoginName,
@RequestParam("loginName") String loginName) {
if (loginName.equals(oldLoginName)) {
return "true";
} else if (accountManager.findUserByLoginName(loginName) == null) {
return "true";
}
return "false";
}
}
package org.springside.examples.miniweb.web.account;
import java.util.List;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springside.examples.miniweb.entity.account.User;
import org.springside.examples.miniweb.service.account.AccountManager;
/**
* 使用@ModelAttribute, 实现Struts2 Preparable二次绑定的效果。
* 因为@ModelAttribute被默认执行, 而其他的action url中并没有${id},所以需要独立出一个Controller.
*
* @author calvin
*/
@Controller
@RequestMapping(value = "/account/user/")
public class UserDetailController {
@Autowired
private AccountManager accountManager;
@Autowired
private GroupListEditor groupListEditor;
@InitBinder
public void initBinder(WebDataBinder b) {
b.registerCustomEditor(List.class, "groupList", groupListEditor);
}
@RequiresPermissions("user:edit")
@RequestMapping(value = "update/{id}")
public String updateForm(Model model) {
model.addAttribute("allGroups", accountManager.getAllGroup());
return "account/userForm";
}
@RequiresPermissions("user:edit")
@RequestMapping(value = "save/{id}")
public String save(@ModelAttribute("user") User user, RedirectAttributes redirectAttributes) {
accountManager.saveUser(user);
redirectAttributes.addFlashAttribute("message", "修改用户" + user.getLoginName() + "成功");
return "redirect:/account/user/";
}
@ModelAttribute("user")
public User getAccount(@PathVariable("id") Long id) {
return accountManager.getUser(id);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd"
default-lazy-init="true">
<description>Shiro Configuration</description>
<!-- Shiro's main business-tier object for web-enabled applications -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="shiroDbRealm" />
<property name="cacheManager" ref="cacheManager" />
</bean>
<!-- 項目自定义的Realm -->
<bean id="shiroDbRealm" class="org.springside.examples.miniweb.service.account.ShiroDbRealm" depends-on="userDao,groupDao">
<property name="accountManager" ref="accountManager"/>
</bean>
<!-- Shiro Filter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/login" />
<property name="successUrl" value="/account/user/" />
<property name="filterChainDefinitions">
<value>
/login = authc
/logout = logout
/static/** = anon
/** = user
</value>
</property>
</bean>
<!-- 用户授权信息Cache -->
<bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />
<!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- AOP式方法级权限检查 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
</beans>
\ No newline at end of file
......@@ -26,8 +26,6 @@
<property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter"/>
<property name="jpaProperties">
<props>
<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
<prop key="net.sf.ehcache.configurationResourceName">ehcache/ehcache-hibernate-local.xml</prop>
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
</props>
</property>
......@@ -52,11 +50,10 @@
<!-- 使用annotation定义事务 -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
<!-- production/local development环境 -->
<beans profile="production,development">
<!-- production环境 -->
<beans profile="production">
<context:property-placeholder ignore-unresolvable="true"
location="classpath*:/application.properties,
classpath*:/application.local.properties" />
location="classpath*:/application.properties" />
<!-- 数据源配置, 使用DBCP数据库连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
......@@ -79,7 +76,23 @@
<!--<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/ExampleDB" />-->
</beans>
<!-- functional test环境 -->
<!-- local development环境 -->
<beans profile="development">
<context:property-placeholder ignore-resource-not-found="true"
location="classpath*:/application.properties,
classpath*:/application.development.properties,
classpath*:/application.development-local.properties" />
<!-- DBCP连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="defaultAutoCommit" value="false" />
</bean>
</beans>
<beans profile="functional">
<context:property-placeholder ignore-resource-not-found="true"
location="classpath*:/application.properties,
......@@ -95,10 +108,10 @@
<property name="defaultAutoCommit" value="false" />
</bean>
<!-- 初始化数据结构 -->
<!-- 初始化数据结构 -->
<jdbc:initialize-database data-source="dataSource" ignore-failures="ALL">
<jdbc:script location="classpath:sql/h2/schema.sql" />
</jdbc:initialize-database>
</jdbc:initialize-database>
</beans>
<!-- unit test环境 -->
......
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false">
<diskStore path="java.io.tmpdir/hibernate/mini-web" />
<!-- DefaultCache setting. -->
<defaultCache maxEntriesLocalHeap="10000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600"
overflowToDisk="true" maxEntriesLocalDisk="100000" />
<!-- Special objects setting. -->
<cache name="org.springside.examples.miniweb.entity.account.User" maxEntriesLocalHeap="1000" eternal="true"
overflowToDisk="true" maxEntriesLocalDisk="10000" />
<cache name="org.springside.examples.miniweb.entity.account.User.groupList" maxEntriesLocalHeap="1000" eternal="true"
overflowToDisk="true" maxEntriesLocalDisk="10000" />
<cache name="org.springside.examples.miniweb.entity.account.Group" maxEntriesLocalHeap="100" eternal="true"
overflowToDisk="true" maxEntriesLocalDisk="1000" />
<cache name="org.springside.examples.miniweb.entity.account.Group.permissionList" maxEntriesLocalHeap="100"
eternal="true" overflowToDisk="true" maxEntriesLocalDisk="1000" />
</ehcache>
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false">
<cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
properties="peerDiscovery=automatic,multicastGroupAddress=230.0.0.1,multicastGroupPort=4446" />
<cacheManagerPeerListenerFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory" />
<diskStore path="java.io.tmpdir/hibernate/mini-web" />
<!-- DefaultCache setting. -->
<defaultCache maxEntriesLocalHeap="10000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600"
overflowToDisk="true" maxEntriesLocalDisk="100000" />
<!-- Special objects setting. -->
<cache name="org.springside.examples.miniweb.entity.account.User" maxEntriesLocalHeap="1000" eternal="true"
overflowToDisk="true" maxEntriesLocalDisk="10000">
<cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicatePuts=false,replicateUpdatesViaCopy=false" />
</cache>
<cache name="org.springside.examples.miniweb.entity.account.User.groupList" maxEntriesLocalHeap="1000" eternal="true"
overflowToDisk="true" maxEntriesLocalDisk="10000">
<cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicatePuts=false,replicateUpdatesViaCopy=false" />
</cache>
<cache name="org.springside.examples.miniweb.entity.account.Group" maxEntriesLocalHeap="100" eternal="true"
overflowToDisk="true" maxEntriesLocalDisk="1000">
<cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicatePuts=false,replicateUpdatesViaCopy=false" />
</cache>
<cache name="org.springside.examples.miniweb.entity.account.Group.permissionList" maxEntriesLocalHeap="100"
eternal="true" overflowToDisk="true" maxEntriesLocalDisk="1000">
<cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicatePuts=false,replicateUpdatesViaCopy=false" />
</cache>
</ehcache>
......@@ -16,4 +16,4 @@ log4j.appender.RollingFile.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.logger.org.springside.examples.miniweb=INFO
#log4jdbc
log4j.logger.jdbc.sqltiming=INFO
\ No newline at end of file
#log4j.logger.jdbc.sqltiming=INFO
\ No newline at end of file
drop table if exists acct_group;
drop table if exists acct_group_permission;
drop table if exists acct_user;
drop table if exists acct_user_group;
create table acct_group (
id bigint generated by default as identity,
name varchar(255) not null unique,
primary key (id)
) ;
create table acct_group_permission (
group_id bigint not null,
permission varchar(255) not null,
foreign key(group_id) references acct_group(id),
) ;
create table acct_user (
id bigint generated by default as identity,
email varchar(255),
login_name varchar(255) not null unique,
name varchar(255),
password varchar(255),
primary key (id)
) ;
create table acct_user_group (
user_id bigint not null,
group_id bigint not null,
primary key(user_id,group_id),
foreign key(user_id) references acct_user(id),
foreign key(group_id) references acct_group(id)
) ;
drop table if exists task;
create table task (
id bigint generated by default as identity,
title varchar(255) not null,
primary key (id)
) ;
\ No newline at end of file
alter table acct_group_permission
drop foreign key FKAE243466DE3FB930;
alter table acct_user_group
drop foreign key FKFE85CB3EDE3FB930;
alter table acct_user_group
drop foreign key FKFE85CB3E836A7D10;
drop table if exists acct_group;
drop table if exists acct_group_permission;
drop table if exists acct_user;
drop table if exists acct_user_group;
create table acct_group (
id bigint not null auto_increment,
name varchar(255) not null unique,
primary key (id)
) ENGINE=InnoDB;
create table acct_group_permission (
group_id bigint not null,
permission varchar(255) not null
) ENGINE=InnoDB;
create table acct_user (
id bigint not null auto_increment,
email varchar(255),
login_name varchar(255) not null unique,
name varchar(255),
password varchar(255),
primary key (id)
) ENGINE=InnoDB;
create table acct_user_group (
user_id bigint not null,
group_id bigint not null
) ENGINE=InnoDB;
alter table acct_group_permission
add constraint FKAE243466DE3FB930
foreign key (group_id)
references acct_group (id);
alter table acct_user_group
add constraint FKFE85CB3EDE3FB930
foreign key (group_id)
references acct_group (id);
alter table acct_user_group
add constraint FKFE85CB3E836A7D10
foreign key (user_id)
references acct_user (id);
drop table acct_group cascade constraints;
drop table acct_group_permission cascade constraints;
drop table acct_user cascade constraints;
drop table acct_user_group cascade constraints;
drop sequence hibernate_sequence;
create table acct_group (
id number(19,0) not null,
name varchar2(255 char) not null unique,
primary key (id)
);
create table acct_group_permission (
group_id number(19,0) not null,
permission varchar2(255 char) not null
);
create table acct_user (
id number(19,0) not null,
email varchar2(255 char),
login_name varchar2(255 char) not null unique,
name varchar2(255 char),
password varchar2(255 char),
primary key (id)
);
create table acct_user_group (
user_id number(19,0) not null,
group_id number(19,0) not null
);
alter table acct_group_permission
add constraint FKAE243466DE3FB930
foreign key (group_id)
references acct_group;
alter table acct_user_group
add constraint FKFE85CB3EDE3FB930
foreign key (group_id)
references acct_group;
alter table acct_user_group
add constraint FKFE85CB3E836A7D10
foreign key (user_id)
references acct_user;
create sequence hibernate_sequence;
......@@ -15,11 +15,12 @@
<link href="${ctx}/static/bootstrap/2.0.4/css/bootstrap.min.css" type="text/css" rel="stylesheet" />
<link href="${ctx}/static/jquery-validation/1.9.0/validate.css" type="text/css" rel="stylesheet" />
<link href="${ctx}/static/mini-web.css" type="text/css" rel="stylesheet" />
<link href="${ctx}/static/main.css" type="text/css" rel="stylesheet" />
<script src="${ctx}/static/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<script src="${ctx}/static/jquery-validation/1.9.0/jquery.validate.min.js" type="text/javascript"></script>
<script src="${ctx}/static/jquery-validation/1.9.0/messages_cn.js" type="text/javascript"></script>
<sitemesh:head/>
</head>
......
<%@ page language="java" pageEncoding="UTF-8" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="ctx" value="${pageContext.request.contextPath}"/>
<div id="header" class="span12">
<div id="title">
<h1>Mini-Web示例<small>--CRUD管理界面演示</small></h1>
<shiro:user>
<span class="pull-right">Hello, <shiro:principal property="name"/>!!</span>
</shiro:user>
</div>
<div id="menu">
<ul class="nav nav-tabs">
<shiro:user>
<shiro:hasPermission name="user:view">
<li id="user-tab"><a href="${ctx}/account/user/" >帐号列表</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="group:view">
<li id="group-tab"><a href="${ctx}/account/group/">权限组列表</a></li>
</shiro:hasPermission>
<li><a href="${ctx}/logout">退出登录</a></li>
</shiro:user>
<shiro:guest>
<li class="active"><a href="${ctx}/login">登录</a></li>
</shiro:guest>
</ul>
<h1>Mini-Web示例<small>--TodoList应用演示</small></h1>
</div>
</div>
\ No newline at end of file
......@@ -16,31 +16,12 @@
<mvc:default-servlet-handler/>
<!-- 定义首页 -->
<mvc:view-controller path="/" view-name="redirect:/account/user/"/>
<!-- 定义首页转向Task页面 -->
<mvc:view-controller path="/" view-name="redirect:/task/"/>
<!-- 定义JSP -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 支持 Shiro对Controller的方法级AOP安全控制 begin-->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.apache.shiro.authz.UnauthorizedException">error/403</prop>
</props>
</property>
</bean>
<!-- end -->
</beans>
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<c:set var="ctx" value="${pageContext.request.contextPath}"/>
<html>
<head>
<title>帐号管理</title>
<script>
$(document).ready(function() {
$("#group-tab").addClass("active");
$("#inputForm").validate();
});
</script>
</head>
<body>
<form:form id="inputForm" modelAttribute="group" action="${ctx}/account/group/save/${user.id}" method="post" class="form-horizontal">
<input type="hidden" name="id" value="${group.id}"/>
<fieldset>
<legend><small>管理权限组</small></legend>
<div class="control-group">
<label for="name" class="control-label">名称:</label>
<div class="controls">
<input type="text" id="name" name="name" size="50" class="required" value="${group.name}"/>
</div>
</div>
<div class="control-group">
<label for="permissionList" class="control-label">权限列表:</label>
<div class="controls">
<form:checkboxes path="permissionList" items="${allPermissions}" itemLabel="displayName" itemValue="value" />
</div>
</div>
<div class="form-actions">
<input id="submit" class="btn btn-primary" type="submit" value="提交"/>&nbsp;
<input id="cancel" class="btn" type="button" value="返回" onclick="history.back()"/>
</div>
</fieldset>
</form:form>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<c:set var="ctx" value="${pageContext.request.contextPath}"/>
<html>
<head>
<title>帐号管理</title>
<script>
$(document).ready(function() {
//聚焦第一个输入框
$("#group-tab").addClass("active");
});
</script>
</head>
<body>
<h4>权限组列表</h4>
<c:if test="${not empty message}">
<div id="message" class="alert alert-success"><button data-dismiss="alert" class="close">×</button>${message}</div>
</c:if>
<table id="contentTable" class="table table-striped table-bordered table-condensed">
<tr><th>名称</th><th>授权</th><th>操作</th></tr>
<c:forEach items="${groups}" var="group">
<tr>
<td>${group.name}</td>
<td>${group.permissionNames}</td>
<td>
<shiro:hasPermission name="group:edit">
<a href="update/${group.id}" id="editLink-${group.name}">修改</a> <a href="delete/${group.id}">删除</a>
</shiro:hasPermission>
</td>
</tr>
</c:forEach>
</table>
<shiro:hasPermission name="group:edit">
<a class="btn" href="create">创建权限组</a>
</shiro:hasPermission>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<c:set var="ctx" value="${pageContext.request.contextPath}"/>
<html>
<head>
<title>帐号管理</title>
<script>
$(document).ready(function() {
//聚焦第一个输入框
$("#loginName").focus();
//active tab
$("#user-tab").addClass("active");
//为inputForm注册validate函数
$("#inputForm").validate({
rules: {
loginName: {
remote: "${ctx}/account/user/checkLoginName?oldLoginName=" + encodeURIComponent('${user.loginName}')
},
groupList:"required"
},
messages: {
loginName: {
remote: "用户登录名已存在"
},
passwordConfirm: {
equalTo: "输入与上面相同的密码"
}
},
errorContainer: "#messageBox",
errorPlacement: function(error, element) {
if ( element.is(":checkbox") )
error.appendTo ( element.parent().next() );
else
error.insertAfter( element );
}
});
});
</script>
</head>
<body>
<form:form id="inputForm" modelAttribute="user" action="${ctx}/account/user/save/${user.id}" method="post" class="form-horizontal">
<input type="hidden" name="id" value="${user.id}"/>
<fieldset>
<legend><small>管理用户</small></legend>
<div id="messageBox" class="alert alert-error" style="display:none">输入有误,请先更正。</div>
<div class="control-group">
<label for="loginName" class="control-label">登录名:</label>
<div class="controls">
<input type="text" id="loginName" name="loginName" size="50" value="${user.loginName}" class="required"/>
</div>
</div>
<div class="control-group">
<label for="name" class="control-label">用户名:</label>
<div class="controls">
<input type="text" id="name" name="name" size="50" value="${user.name}" class="required"/>
</div>
</div>
<div class="control-group">
<label for="password" class="control-label">密码:</label>
<div class="controls">
<input type="password" id="password" name="password" size="50" value="${user.password}" class="required" minlength="3"/>
</div>
</div>
<div class="control-group">
<label for="passwordConfirm" class="control-label">确认密码:</label>
<div class="controls">
<input type="password" id="passwordConfirm" name="passwordConfirm" size="50" value="${user.password}" equalTo="#password"/>
</div>
</div>
<div class="control-group">
<label for="email" class="control-label">邮箱:</label>
<div class="controls">
<input type="text" id="email" name="email" size="50" value="${user.email}" class="email"/>
</div>
</div>
<div class="control-group">
<label for="groupList" class="control-label">权限组:</label>
<div class="controls">
<form:checkboxes path="groupList" items="${allGroups}" itemLabel="name" itemValue="id" />
</div>
</div>
<div class="form-actions">
<input id="submit" class="btn btn-primary" type="submit" value="提交"/>&nbsp;
<input id="cancel" class="btn" type="button" value="返回" onclick="history.back()"/>
</div>
</fieldset>
</form:form>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="ctx" value="${pageContext.request.contextPath}"/>
<html>
<head>
<title>帐号管理</title>
<script>
$(document).ready(function() {
//聚焦第一个输入框
$("#user-tab").addClass("active");
});
</script>
</head>
<body>
<h4>用户列表</h4>
<c:if test="${not empty message}">
<div id="message" class="alert alert-success"><button data-dismiss="alert" class="close">×</button>${message}</div>
</c:if>
<table id="contentTable" class="table table-striped table-bordered table-condensed">
<thead><tr><th>登录名</th><th>用户名</th><th>邮箱</th><th>权限组<th>操作</th></tr></thead>
<tbody>
<c:forEach items="${users}" var="user">
<tr>
<td>${user.loginName}</td>
<td>${user.name}</td>
<td>${user.email}</td>
<td>${user.groupNames}</td>
<td>
<shiro:hasPermission name="user:edit">
<a href="update/${user.id}" id="editLink-${user.name}">修改</a> <a href="delete/${user.id}">删除</a>
</shiro:hasPermission>
</td>
</tr>
</c:forEach>
</tbody>
</table>
<shiro:hasPermission name="user:edit">
<a class="btn" href="create">创建用户</a>
</shiro:hasPermission>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ page import="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"%>
<%@ page import="org.apache.shiro.authc.ExcessiveAttemptsException"%>
<%@ page import="org.apache.shiro.authc.IncorrectCredentialsException"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<c:set var="ctx" value="${pageContext.request.contextPath}"/>
<html>
<head>
<title>403 - 用户权限不足</title>
</head>
<body>
<div><h1>用户权限不足.</h1></div>
<div><a href="<c:url value="/"/>">返回首页</a></div>
</body>
</html>
......@@ -3,7 +3,7 @@
<%response.setStatus(200);%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<!DOCTYPE html>
<html>
<head>
<title>404 - 页面不存在</title>
......
......@@ -15,8 +15,8 @@
logger.error(ex.getMessage(), ex);
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<!DOCTYPE html>
<html>
<head>
<title>500 - 系统内部错误</title>
</head>
......
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ page import="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"%>
<%@ page import="org.apache.shiro.authc.ExcessiveAttemptsException"%>
<%@ page import="org.apache.shiro.authc.IncorrectCredentialsException"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<c:set var="ctx" value="${pageContext.request.contextPath}"/>
<html>
<head>
<title>登录页</title>
<script>
$(document).ready(function() {
$("#loginForm").validate();
});
</script>
</head>
<body>
<form:form id="loginForm" action="${ctx}/login" method="post" class="form-horizontal">
<%
String error = (String) request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);
if(error != null){
%>
<div class="control-group">
<div class="controls ">
<div class="alert alert-error">
<button class="close" data-dismiss="alert">×</button>
登录失败,请重试.</div>
</div>
</div>
<%
}
%>
<div class="control-group">
<label for="username" class="control-label">名称:</label>
<div class="controls">
<input type="text" id="username" name="username" size="50" value="${username}" class="required span2"/>
</div>
</div>
<div class="control-group">
<label for="password" class="control-label">密码:</label>
<div class="controls">
<input type="password" id="password" name="password" size="50" class="required span2"/>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox inline" for="rememberMe"> <input type="checkbox" id="rememberMe" name="rememberMe"/> 记住我</label>
<input id="submit" class="btn" type="submit" value="登录"/>
<p class="help-block">(管理员<b>admin/admin</b>, 普通用户<b>user/user</b>)</p>
</div>
</div>
</div>
</form:form>
</body>
</html>
......@@ -3,7 +3,7 @@
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<display-name>mini-web</display-name>
<display-name>Mini-Web</display-name>
<!-- Spring ApplicationContext配置文件的路径,可使用通配符,多个路径用,号分隔
此参数用于后面的Spring Context Loader -->
......@@ -11,7 +11,6 @@
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:/applicationContext.xml
classpath*:/applicationContext-shiro.xml
</param-value>
</context-param>
......@@ -55,20 +54,6 @@
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Shiro Security filter-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- SiteMesh Web-Page Layout filter-->
<filter>
<filter-name>sitemeshFilter</filter-name>
......
/* general */
.prepend-top {margin-top:1.5em;}
/* header */
#header h1 {
color: #658A16;
}
#header h1 small {
color: #BACE87;
}
/* footer */
#footer {
margin-top: 15px;
padding: 15px 0px 0px 0px;
font-size: 95%;
text-align: center;
border-top: 2px solid #658A16;
}
#footer a {color: #999;}
\ No newline at end of file
package org.springside.examples.miniweb.functional;
import static org.junit.Assert.*;
import java.net.URL;
import java.sql.Driver;
import org.eclipse.jetty.server.Server;
import org.junit.BeforeClass;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -46,6 +43,7 @@ public class BaseFunctionalTestCase {
baseUrl = propertiesLoader.getProperty("baseUrl", MiniWebServer.BASE_URL);
//如果是目标地址是localhost,则启动嵌入式jetty。如果指向远程地址,则不需要启动Jetty.
Boolean isEmbedded = new URL(baseUrl).getHost().equals("localhost");
if (isEmbedded) {
......@@ -56,7 +54,6 @@ public class BaseFunctionalTestCase {
reloadSampleData();
createSeleniumOnce();
loginAsAdminIfNecessary();
}
/**
......@@ -76,6 +73,7 @@ public class BaseFunctionalTestCase {
/**
* 构造数据源,仅构造一次.
* 连接参数从配置文件中读取,可指向本地的开发环境,也可以指向远程的测试服务器。
*/
protected static void buildDataSourceOnce() throws ClassNotFoundException {
if (dataSource == null) {
......@@ -85,7 +83,6 @@ public class BaseFunctionalTestCase {
dataSource.setUrl(propertiesLoader.getProperty("jdbc.url"));
dataSource.setUsername(propertiesLoader.getProperty("jdbc.username"));
dataSource.setPassword(propertiesLoader.getProperty("jdbc.password"));
}
}
......@@ -97,10 +94,9 @@ public class BaseFunctionalTestCase {
}
/**
* 创建Selenium.
* 创建Selenium,,仅构造一次.
*/
protected static void createSeleniumOnce() throws Exception {
if (s == null) {
//根据配置创建Selenium driver.
String driverName = propertiesLoader.getProperty("selenium.driver");
......@@ -111,30 +107,4 @@ public class BaseFunctionalTestCase {
s.setStopAtShutdown();
}
}
/**
* 登录管理员, 如果用户还没有登录.
*/
protected static void loginAsAdminIfNecessary() {
s.open("/account/user");
if ("Mini-Web示例:登录页".equals(s.getTitle())) {
s.type(By.name("username"), "admin");
s.type(By.name("password"), "admin");
s.check(By.name("rememberMe"));
s.click(By.id("submit"));
assertEquals("Mini-Web示例:帐号管理", s.getTitle());
}
}
/**
* 登录特定用户.
*/
protected static void login(String user, String password) {
s.open("/logout");
s.type(By.name("username"), user);
s.type(By.name("password"), password);
s.click(By.id("submit"));
}
}
\ No newline at end of file
package org.springside.examples.miniweb.functional;
import org.openqa.selenium.By;
/**
* 定义页面元素的常量.
*/
public class Gui {
public static final By MENU_USER = By.linkText("帐号列表");
public static final By MENU_GROUP = By.linkText("权限组列表");
//定义表格内容,避免表格内容顺序变动引起case的大崩溃。
public enum UserColumn {
LOGIN_NAME, NAME, EMAIL, GROUPS, OPERATIONS
}
public enum GroupColumn {
NAME, PERMISSIONS, OPERATIONS
}
}
package org.springside.examples.quickstart.functional;
package org.springside.examples.miniweb.functional;
import org.junit.Test;
import org.junit.experimental.categories.Category;
......
package org.springside.examples.miniweb.functional.account;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.springside.examples.miniweb.data.AccountData;
import org.springside.examples.miniweb.entity.account.Group;
import org.springside.examples.miniweb.functional.BaseFunctionalTestCase;
import org.springside.examples.miniweb.functional.Gui;
import org.springside.examples.miniweb.functional.Gui.GroupColumn;
import org.springside.modules.utils.Collections3;
/**
* 权限组管理的功能测试,测 试页面JavaScript及主要用户故事流程.
*
* @author calvin
*/
public class GroupManagerIT extends BaseFunctionalTestCase {
/**
* 查看权限组列表.
*/
@Test
public void viewGroupList() {
s.click(Gui.MENU_GROUP);
WebElement table = s.findElement(By.xpath("//table[@id='contentTable']"));
assertEquals("管理员", s.getTable(table, 1, GroupColumn.NAME.ordinal()));
assertEquals("查看用戶,修改用户,查看权限组,修改权限组", s.getTable(table, 1, GroupColumn.PERMISSIONS.ordinal()));
}
/**
* 创建权限组.
*/
@Test
public void createGroup() {
s.click(Gui.MENU_GROUP);
s.click(By.linkText("创建权限组"));
//生成测试数据
Group group = AccountData.getRandomGroupWithPermissions();
//输入数据
s.type(By.id("name"), group.getName());
List<WebElement> checkBoxes = s.findElements(By.name("permissionList"));
for (String permission : group.getPermissionList()) {
for (WebElement checkBox : checkBoxes) {
if (permission.equals(s.getValue(checkBox))) {
s.check(checkBox);
}
}
}
s.click(By.id("submit"));
//校验结果
assertTrue(s.isTextPresent("创建权限组" + group.getName() + "成功"));
verifyGroup(group);
}
private void verifyGroup(Group group) {
s.click(Gui.MENU_GROUP);
s.click(By.id("editLink-" + group.getName()));
assertEquals(group.getName(), s.getValue(By.id("name")));
List<WebElement> checkBoxes = s.findElements(By.name("permissionList"));
for (String permission : group.getPermissionList()) {
for (WebElement checkBox : checkBoxes) {
if (permission.equals(s.getValue(checkBox))) {
assertTrue(s.isChecked(checkBox));
}
}
}
List<String> uncheckPermissionList = Collections3.subtract(AccountData.getDefaultPermissionList(),
group.getPermissionList());
for (String permission : uncheckPermissionList) {
for (WebElement checkBox : checkBoxes) {
if (permission.equals(s.getValue(checkBox))) {
assertFalse(s.isChecked(checkBox));
}
}
}
}
}
package org.springside.examples.miniweb.functional.account;
import static org.junit.Assert.*;
import org.junit.Test;
import org.openqa.selenium.By;
import org.springside.examples.miniweb.functional.BaseFunctionalTestCase;
import org.springside.examples.miniweb.functional.Gui;
import org.springside.examples.miniweb.functional.Gui.UserColumn;
/**
* 系统安全控制的功能测试, 测试主要用户故事.
*
* @author calvin
*/
public class SecurityIT extends BaseFunctionalTestCase {
/**
* 测试匿名用户访问系统时的行为.
*/
@Test
public void checkAnonymous() {
//访问退出登录页面,退出之前的登录
s.open("/logout");
assertEquals("Mini-Web示例:登录页", s.getTitle());
//访问任意页面会跳转到登录界面
s.open("/account/user");
assertEquals("Mini-Web示例:登录页", s.getTitle());
}
/**
* 只有用户权限组的操作员访问系统时的受限行为.
*/
@Test
public void checkUserPermission() {
//访问退出登录页面,退出之前的登录
s.open("/logout");
assertEquals("Mini-Web示例:登录页", s.getTitle());
//登录普通用户
s.type(By.name("username"), "user");
s.type(By.name("password"), "user");
s.click(By.id("submit"));
//校验用户权限组的操作单元格只有查看
s.click(Gui.MENU_USER);
assertEquals("", s.getTable(By.id("contentTable"), 1, UserColumn.OPERATIONS.ordinal()));
//强行访问无权限的url
s.open("/account/user/update/1");
assertTrue(s.getTitle().contains("403"));
//重新退出
s.open("/logout");
}
}
package org.springside.examples.miniweb.functional.account;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.springside.examples.miniweb.data.AccountData;
import org.springside.examples.miniweb.entity.account.Group;
import org.springside.examples.miniweb.entity.account.User;
import org.springside.examples.miniweb.functional.BaseFunctionalTestCase;
import org.springside.examples.miniweb.functional.Gui;
import org.springside.examples.miniweb.functional.Gui.UserColumn;
import org.springside.modules.test.category.Smoke;
import org.springside.modules.utils.Collections3;
/**
* 用户管理的功能测试, 测试页面JavaScript及主要用户故事流程.
*
* @author calvin
*/
public class UserManagerIT extends BaseFunctionalTestCase {
/**
* 查看用户列表.
*/
@Test
@Category(Smoke.class)
public void viewUserList() {
s.click(Gui.MENU_USER);
WebElement table = s.findElement(By.id("contentTable"));
assertEquals("admin", s.getTable(table, 0, UserColumn.LOGIN_NAME.ordinal()));
assertEquals("Admin", s.getTable(table, 0, UserColumn.NAME.ordinal()));
assertEquals("管理员, 用户", s.getTable(table, 0, UserColumn.GROUPS.ordinal()));
}
/**
* 创建用户.
*/
@Category(Smoke.class)
@Test
public void createUser() {
//打开新增用户页面
s.click(Gui.MENU_USER);
s.click(By.linkText("创建用户"));
//生成待输入的测试用户数据
User user = AccountData.getRandomUserWithOneGroup();
//输入数据
s.type(By.id("loginName"), user.getLoginName());
s.type(By.id("name"), user.getName());
s.type(By.id("password"), user.getPassword());
s.type(By.id("passwordConfirm"), user.getPassword());
List<WebElement> checkBoxes = s.findElements(By.name("groupList"));
for (Group group : user.getGroupList()) {
for (WebElement checkBox : checkBoxes) {
if (String.valueOf(group.getId()).equals(s.getValue(checkBox))) {
s.check(checkBox);
}
}
}
s.click(By.id("submit"));
//校验结果
assertTrue(s.isTextPresent("创建用户" + user.getLoginName() + "成功"));
verifyUser(user);
}
/**
* 校验用户数据的工具函数.
*/
private void verifyUser(User user) {
s.click(By.id("editLink-" + user.getLoginName()));
assertEquals(user.getLoginName(), s.getValue(By.id("loginName")));
assertEquals(user.getName(), s.getValue(By.id("name")));
List<WebElement> checkBoxes = s.findElements(By.name("groupList"));
for (Group group : user.getGroupList()) {
for (WebElement checkBox : checkBoxes) {
if (String.valueOf(group.getId()).equals(s.getValue(checkBox))) {
assertTrue(s.isChecked(checkBox));
}
}
}
List<Group> uncheckGroupList = Collections3.subtract(AccountData.getDefaultGroupList(), user.getGroupList());
for (Group group : uncheckGroupList) {
for (WebElement checkBox : checkBoxes) {
if (String.valueOf(group.getId()).equals(s.getValue(checkBox))) {
assertFalse(s.isChecked(checkBox));
}
}
}
}
/**
* 创建用户时的输入校验测试.
*/
@Test
public void inputInValidateUser() {
s.click(Gui.MENU_USER);
s.click(By.linkText("创建用户"));
s.type(By.id("loginName"), "admin");
s.type(By.id("name"), "");
s.type(By.id("password"), "a");
s.type(By.id("passwordConfirm"), "abc");
s.type(By.id("email"), "abc");
s.click(By.id("submit"));
assertEquals("用户登录名已存在", s.getText(By.xpath("//fieldset/div[2]/div/label")));
assertEquals("必选字段", s.getText(By.xpath("//fieldset/div[3]/div/label")));
assertEquals("请输入一个长度最少是 3 的字符串", s.getText(By.xpath("//fieldset/div[4]/div/label")));
assertEquals("输入与上面相同的密码", s.getText(By.xpath("//fieldset/div[5]/div/label")));
assertEquals("请输入正确格式的电子邮件", s.getText(By.xpath("//fieldset/div[6]/div/label")));
}
}
package org.springside.examples.miniweb.dao.account;
import static org.junit.Assert.*;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springside.examples.miniweb.data.AccountData;
import org.springside.examples.miniweb.entity.account.Group;
import org.springside.examples.miniweb.entity.account.User;
import org.springside.modules.test.spring.SpringTransactionalTestCase;
/**
* GroupDao的测试用例, 测试ORM映射及特殊的DAO操作.
*
* @author calvin
*/
@ContextConfiguration(locations = { "/applicationContext.xml" })
public class GroupDaoTest extends SpringTransactionalTestCase {
@Autowired
private GroupDao groupDao;
@Autowired
private UserDao userDao;
@PersistenceContext
private EntityManager em;
/**
* 测试删除权限组时删除用户-权限组的中间表.
* @throws Exception
*/
@Test
public void deleteGroup() throws Exception {
//新增测试权限组并与admin用户绑定.
Group group = AccountData.getRandomGroup();
groupDao.save(group);
em.flush();
User user = userDao.findOne(1L);
user.getGroupList().add(group);
userDao.save(user);
em.flush();
int oldJoinTableCount = countRowsInTable("ACCT_USER_GROUP");
int oldUserTableCount = countRowsInTable("ACCT_USER");
//删除用户权限组, 中间表将减少1条记录,而用户表应该不受影响.
groupDao.deleteWithReference(group.getId());
em.flush();
user = userDao.findOne(1L);
int newJoinTableCount = countRowsInTable("ACCT_USER_GROUP");
int newUserTableCount = countRowsInTable("ACCT_USER");
assertEquals(1, oldJoinTableCount - newJoinTableCount);
assertEquals(0, oldUserTableCount - newUserTableCount);
}
@Test
public void crudEntityWithGroup() {
//新建并保存带权限组的用户
Group group = AccountData.getRandomGroupWithPermissions();
groupDao.save(group);
em.flush();
//获取用户
group = groupDao.findOne(group.getId());
assertTrue(group.getPermissionList().size() > 0);
}
}
package org.springside.examples.miniweb.dao.account;
import static org.junit.Assert.*;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springside.examples.miniweb.data.AccountData;
import org.springside.examples.miniweb.entity.account.User;
import org.springside.modules.test.spring.SpringTransactionalTestCase;
/**
* UserDao的测试用例, 测试ORM映射及特殊的DAO操作.
*
* 默认在每个测试函数后进行回滚.
*
* @author calvin
*/
@ContextConfiguration(locations = { "/applicationContext.xml" })
public class UserDaoTest extends SpringTransactionalTestCase {
@Autowired
private UserDao entityDao;
@PersistenceContext
private EntityManager em;
@Test
//如果你需要真正插入数据库,将Rollback设为false
//@Rollback(false)
public void crudEntityWithGroup() {
//新建并保存带权限组的用户
User user = AccountData.getRandomUserWithOneGroup();
entityDao.save(user);
em.flush();
//获取用户
user = entityDao.findOne(user.getId());
assertEquals(1, user.getGroupList().size());
//删除用户的权限组
user.getGroupList().remove(0);
entityDao.save(user);
em.flush();
user = entityDao.findOne(user.getId());
assertEquals(0, user.getGroupList().size());
//删除用户
entityDao.delete(user.getId());
em.flush();
user = entityDao.findOne(user.getId());
assertNull(user);
}
//期望抛出ConstraintViolationException的异常.
@Test(expected = org.springframework.dao.DataIntegrityViolationException.class)
public void savenUserNotUnique() {
User user = AccountData.getRandomUser();
user.setLoginName("admin");
entityDao.save(user);
em.flush();
}
}
\ No newline at end of file
package org.springside.examples.miniweb.data;
import java.util.List;
import org.springside.examples.miniweb.entity.account.Group;
import org.springside.examples.miniweb.entity.account.Permission;
import org.springside.examples.miniweb.entity.account.User;
import org.springside.modules.test.data.RandomData;
import com.google.common.collect.Lists;
/**
* Account相关实体测试数据生成.
*
* @author calvin
*/
public class AccountData {
public static final String DEFAULT_PASSWORD = "123456";
private static List<Group> defaultGroupList = null;
private static List<String> defaultPermissionList = null;
public static User getRandomUser() {
String userName = RandomData.randomName("User");
User user = new User();
user.setLoginName(userName);
user.setName(userName);
user.setPassword(DEFAULT_PASSWORD);
user.setEmail(userName + "@springside.org.cn");
return user;
}
public static User getRandomUserWithOneGroup() {
User user = getRandomUser();
user.getGroupList().add(getRandomDefaultGroup());
return user;
}
public static Group getRandomGroup() {
Group group = new Group();
group.setName(RandomData.randomName("Group"));
return group;
}
public static Group getRandomGroupWithPermissions() {
Group group = getRandomGroup();
group.getPermissionList().addAll(getRandomDefaultPermissionList());
return group;
}
public static List<Group> getDefaultGroupList() {
if (defaultGroupList == null) {
defaultGroupList = Lists.newArrayList();
defaultGroupList.add(new Group(1L, "管理员"));
defaultGroupList.add(new Group(2L, "用户"));
}
return defaultGroupList;
}
public static Group getRandomDefaultGroup() {
return RandomData.randomOne(getDefaultGroupList());
}
public static List<String> getDefaultPermissionList() {
if (defaultPermissionList == null) {
defaultPermissionList = Lists.newArrayList();
for (Permission permission : Permission.values()) {
defaultPermissionList.add(permission.value);
}
}
return defaultPermissionList;
}
public static List<String> getRandomDefaultPermissionList() {
return RandomData.randomSome(getDefaultPermissionList());
}
}
package org.springside.examples.quickstart.data;
package org.springside.examples.miniweb.data;
import org.springside.examples.quickstart.entity.Task;
import org.springside.examples.miniweb.entity.Task;
import org.springside.modules.test.data.RandomData;
/**
......
package org.springside.examples.quickstart.service;
package org.springside.examples.miniweb.service;
import org.junit.Before;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springside.examples.quickstart.dao.TaskDao;
import org.springside.examples.quickstart.service.TaskManager;
import org.springside.examples.miniweb.dao.TaskDao;
import org.springside.examples.miniweb.service.TaskManager;
/**
* TaskManager的测试用例, 测试Service层的业务逻辑.
......
package org.springside.examples.miniweb.service.account;
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springside.examples.miniweb.dao.account.UserDao;
import org.springside.examples.miniweb.service.ServiceException;
import org.springside.modules.test.security.shiro.ShiroTestUtils;
/**
* SecurityEntityManager的测试用例, 测试Service层的业务逻辑.
*
* @author calvin
*/
public class AccountManagerTest {
private AccountManager accountManager;
@Mock
private UserDao mockUserDao;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
ShiroTestUtils.mockSubject("foo");
accountManager = new AccountManager();
accountManager.setUserDao(mockUserDao);
}
@After
public void tearDown() {
ShiroTestUtils.clearSubject();
}
@Test
public void deleteUser() {
//正常删除用户.
accountManager.deleteUser(2L);
//删除超级管理用户抛出异常.
try {
accountManager.deleteUser(1L);
fail("expected ServicExcepton not be thrown");
} catch (ServiceException e) {
//expected exception
}
}
}
......@@ -3,3 +3,5 @@ baseUrl=http://localhost:8082/mini-web
#selenium settings, options include firefox,ie,chrome,remote:localhost:4444:firefox
selenium.driver=firefox
#(optional)override jdbc url for functional test
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<ACCT_GROUP ID="1" NAME="管理员" />
<ACCT_GROUP ID="2" NAME="用户" />
<ACCT_GROUP_PERMISSION GROUP_ID="1" PERMISSION="user:view" />
<ACCT_GROUP_PERMISSION GROUP_ID="1" PERMISSION="user:edit" />
<ACCT_GROUP_PERMISSION GROUP_ID="1" PERMISSION="group:view" />
<ACCT_GROUP_PERMISSION GROUP_ID="1" PERMISSION="group:edit" />
<ACCT_GROUP_PERMISSION GROUP_ID="2" PERMISSION="user:view" />
<ACCT_GROUP_PERMISSION GROUP_ID="2" PERMISSION="group:view" />
<ACCT_USER ID="1" EMAIL="admin@springside.org.cn" LOGIN_NAME="admin" NAME="Admin" PASSWORD="admin" />
<ACCT_USER ID="2" EMAIL="user@springside.org.cn" LOGIN_NAME="user" NAME="User" PASSWORD="user" />
<ACCT_USER ID="3" EMAIL="jack@springside.org.cn" LOGIN_NAME="user2" NAME="Jack" PASSWORD="user2" />
<ACCT_USER ID="4" EMAIL="kate@springside.org.cn" LOGIN_NAME="user3" NAME="Kate" PASSWORD="user3" />
<ACCT_USER ID="5" EMAIL="sawyer@springside.org.cn" LOGIN_NAME="user4" NAME="Sawyer" PASSWORD="user4" />
<ACCT_USER ID="6" EMAIL="ben@springside.org.cn" LOGIN_NAME="user5" NAME="Ben" PASSWORD="user5" />
<ACCT_USER_GROUP USER_ID="1" GROUP_ID="1" />
<ACCT_USER_GROUP USER_ID="1" GROUP_ID="2" />
<ACCT_USER_GROUP USER_ID="2" GROUP_ID="2" />
<ACCT_USER_GROUP USER_ID="3" GROUP_ID="2" />
<ACCT_USER_GROUP USER_ID="4" GROUP_ID="2" />
<ACCT_USER_GROUP USER_ID="5" GROUP_ID="2" />
<ACCT_USER_GROUP USER_ID="6" GROUP_ID="2" />
<TASK ID="1" TITLE="Study PlayFramework 2.0" />
<TASK ID="2" TITLE="Study Grails2.0" />
<TASK ID="3" TITLE="Try SpringFuse" />
<TASK ID="4" TITLE="Try Spring Roo" />
<TASK ID="5" TITLE="Release SprignSide 4.0" />
</dataset>
......@@ -14,7 +14,8 @@
<packaging>pom</packaging>
<modules>
<module>quickstart</module>
<module>mini-web</module>
<module>mini-service</module>
<module>showcase</module>
</modules>
</project>
\ No newline at end of file
eclipse.preferences.version=1
encoding/<project>=UTF-8
@echo off
echo [INFO] Use maven eclipse-plugin download jars and generate eclipse project files.
echo [Info] Please add "-Declipse.workspace=<path-to-eclipse-workspace>" at end of mvn command.
cd %~dp0
cd ..
call mvn eclipse:clean eclipse:eclipse
cd bin
pause
\ No newline at end of file
@echo off
echo [INFO] Use maven jetty-plugin run the project.
cd %~dp0
cd ..
set MAVEN_OPTS=%MAVEN_OPTS% -XX:MaxPermSize=128m
call mvn jetty:run -Djetty.port=8080
cd bin
pause
\ No newline at end of file
@echo off
echo [INFO] Re-create the schema and provision the sample data.
cd %~dp0
cd ..
call mvn antrun:run -Prefresh-db
cd bin
pause
\ No newline at end of file
@echo off
echo [INFO] run smoking functional test.
cd %~dp0
cd ..
set MAVEN_OPTS=%MAVEN_OPTS% -XX:MaxPermSize=128m
call mvn clean test -Psmoke-test
cd bin
pause
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springside.examples</groupId>
<artifactId>quickstart</artifactId>
<version>4.0.0.RC4-SNAPSHOT</version>
<packaging>war</packaging>
<name>Springside :: Example :: QuickStart</name>
<properties>
<!-- 主要依赖库的版本定义 -->
<springside.version>4.0.0.RC4-SNAPSHOT</springside.version>
<spring.version>3.1.2.RELEASE</spring.version>
<hibernate.version>4.1.4.Final</hibernate.version>
<spring-data-jpa.version>1.1.0.RELEASE</spring-data-jpa.version>
<commons-dbcp.version>1.4</commons-dbcp.version>
<sitemesh.version>2.4.2</sitemesh.version>
<guava.version>12.0</guava.version>
<commons-lang3.version>3.1</commons-lang3.version>
<jackson.version>2.0.4</jackson.version>
<slf4j.version>1.6.6</slf4j.version>
<log4j.version>1.2.17</log4j.version>
<jetty.version>7.6.4.v20120524</jetty.version>
<h2.version>1.3.167</h2.version>
<junit.version>4.10</junit.version>
<mockito.version>1.9.0</mockito.version>
<selenium.version>2.24.1</selenium.version>
<!-- Plugin的属性定义 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jdk.version>1.6</jdk.version>
<!-- 项目属性 -->
<jdbc.driver.groupId>com.h2database</jdbc.driver.groupId>
<jdbc.driver.artifactId>h2</jdbc.driver.artifactId>
<jdbc.driver.version>${h2.version}</jdbc.driver.version>
</properties>
<!-- 设定仓库 如有Nexus私服, 取消注释并指向正确的服务器地址.
<repositories>
<repository>
<id>nexus</id>
<name>Team Nexus Repository</name>
<url>http://localhost:8081/nexus/content/groups/public</url>
</repository>
</repositories>
-->
<!-- 设定插件仓库 如有Nexus私服, 取消注释并指向正确的服务器地址.
<pluginRepositories>
<pluginRepository>
<id>nexus</id>
<name>Team Nexus Repository</name>
<url>http://localhost:8081/nexus/content/groups/public</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
-->
<!-- 依赖项定义 -->
<dependencies>
<!-- SPRINGSIDE -->
<dependency>
<groupId>org.springside</groupId>
<artifactId>springside-core</artifactId>
<version>${springside.version}</version>
</dependency>
<!-- SPRING begin -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.6.9</version> </dependency> -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency>
<!-- SPRING end -->
<!-- PERSISTENCE begin -->
<!-- hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<version>1.0.1.Final</version>
</dependency>
<!-- spring data access -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>${spring-data-jpa.version}</version>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit-dep</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- dbcp connection pool -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>${commons-dbcp.version}</version>
</dependency>
<!-- jdbc driver -->
<dependency>
<groupId>${jdbc.driver.groupId}</groupId>
<artifactId>${jdbc.driver.artifactId}</artifactId>
<version>${jdbc.driver.version}</version>
<scope>runtime</scope>
</dependency>
<!-- PERSISTENCE end -->
<!-- WEB begin -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>opensymphony</groupId>
<artifactId>sitemesh</artifactId>
<version>${sitemesh.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!-- WEB end -->
<!-- GENERAL UTILS begin -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<!-- GENERAL UTILS end -->
<!-- JSON begin -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- JSON end -->
<!-- LOGGING begin -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.lazyluke</groupId>
<artifactId>log4jdbc-remix</artifactId>
<version>0.2.7</version>
</dependency>
<!-- LOGGING end -->
<!-- TEST begin -->
<dependency>
<groupId>org.springside</groupId>
<artifactId>springside-test</artifactId>
<version>${springside.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<!-- selenium 2.0 -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-android-driver</artifactId>
</exclusion>
<exclusion>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-iphone-driver</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-remote-driver</artifactId>
<version>${selenium.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.dbunit</groupId>
<artifactId>dbunit</artifactId>
<version>2.4.8</version>
<scope>test</scope>
</dependency>
<!-- jetty -->
<dependency>
<groupId>org.eclipse.jetty.aggregate</groupId>
<artifactId>jetty-webapp</artifactId>
<version>${jetty.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jsp</artifactId>
<version>${jetty.version}</version>
<scope>test</scope>
</dependency>
<!-- TEST end -->
</dependencies>
<build>
<plugins>
<!-- compiler插件, 设定JDK版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5</version>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
<!-- war打包插件, 设定war包名称不带版本号 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.2</version>
<configuration>
<warName>${project.artifactId}</warName>
</configuration>
</plugin>
<!-- test插件, 仅测试名称为*Test的类,使用支持分组测试的surefire-junit47 driver -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12</version>
<configuration>
<includes>
<include>**/*Test.java</include>
</includes>
<argLine>-Xmx256M</argLine>
</configuration>
<dependencies>
<dependency>
<groupId>org.apache.maven.surefire</groupId>
<artifactId>surefire-junit47</artifactId>
<version>2.12</version>
</dependency>
</dependencies>
</plugin>
<!-- 增加functional test的Source目录 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<id>add-functional-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>src/test/functional</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<!-- cobertura插件, 设置不需要计算覆盖率的类 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<instrumentation>
<excludes>
<exclude>**/entity/**/*.class</exclude>
<exclude>**/*Controller.class</exclude>
</excludes>
</instrumentation>
</configuration>
</plugin>
<!-- eclipse插件, 设定wtp版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<downloadSources>true</downloadSources>
<downloadJavadocs>false</downloadJavadocs>
<wtpversion>2.0</wtpversion>
</configuration>
</plugin>
<!-- jetty插件, 设定context path与spring profile -->
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<configuration>
<systemProperties>
<systemProperty>
<name>spring.profiles.active</name>
<value>development</value>
</systemProperty>
</systemProperties>
<useTestClasspath>true</useTestClasspath>
<webAppConfig>
<contextPath>/${project.artifactId}</contextPath>
</webAppConfig>
</configuration>
</plugin>
<!-- resource插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.5</version>
</plugin>
<!-- clean插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>2.5</version>
</plugin>
</plugins>
</build>
<profiles>
<!-- 仅执行smoke test -->
<profile>
<id>smoke-test</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<groups>org.springside.modules.test.category.Smoke</groups>
<includes>
<include>**/*IT.java</include>
</includes>
<!-- 支持taglib tld文件查找的必要设置 -->
<useSystemClassLoader>false</useSystemClassLoader>
<!-- 将mvn命令行传入的selenium driver参数传入surefire的JVM -->
<systemPropertyVariables>
<selenium.driver>${selenium.driver}</selenium.driver>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<!-- 执行所有functional test -->
<profile>
<id>functional-test</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*IT.java</include>
</includes>
<!-- 支持taglib tld文件查找的必要设置 -->
<useSystemClassLoader>false</useSystemClassLoader>
<!-- 将mvn命令行传入的selenium driver参数传入surefire的JVM -->
<systemPropertyVariables>
<selenium.driver>${selenium.driver}</selenium.driver>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<!-- 刷新开发环境数据库 -->
<profile>
<id>refresh-db</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
<configuration>
<target>
<property file="src/main/resources/application.development-local.properties" />
<property file="src/main/resources/application.development.properties" />
<property file="src/main/resources/application.properties" />
<property name="sql.type" value="h2" />
<property name="dbunit.datatype" value="org.dbunit.ext.h2.H2DataTypeFactory" />
<taskdef name="dbunit" classname="org.dbunit.ant.DbUnitTask" classpathref="maven.test.classpath" />
<sql driver="${jdbc.driver}" url="${jdbc.url}" userid="${jdbc.username}" password="${jdbc.password}"
src="src/main/resources/sql/${sql.type}/schema.sql" onerror="continue">
<classpath refid="maven.test.classpath" />
</sql>
<dbunit driver="${jdbc.driver}" url="${jdbc.url}" userid="${jdbc.username}" password="${jdbc.password}">
<dbconfig>
<property name="datatypeFactory" value="${dbunit.datatype}" />
</dbconfig>
<classpath refid="maven.test.classpath" />
<operation type="CLEAN_INSERT" src="src/test/resources/data/sample-data.xml" format="flat"
transaction="true" />
</dbunit>
</target>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
package org.springside.examples.quickstart.entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
/**
* 统一定义id的entity基类.
*
* 基类统一定义id的属性名称、数据类型、列名映射及生成策略.
* 子类可重载getId()函数重定义id的列名映射和生成策略.
*
* @author calvin
*/
//JPA 基类的标识
@MappedSuperclass
public abstract class IdEntity {
protected Long id;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
//@GeneratedValue(strategy = GenerationType.SEQUENCE)
//@GeneratedValue(generator = "system-uuid")
//@GenericGenerator(name = "system-uuid", strategy = "uuid")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
package org.springside.examples.quickstart.service;
/**
* Service层公用的Exception.
*
* 继承自RuntimeException, 从由Spring管理事务的函数中抛出时会触发事务回滚.
*
* @author calvin
*/
public class ServiceException extends RuntimeException {
private static final long serialVersionUID = 3583566093089790852L;
public ServiceException() {
super();
}
public ServiceException(String message) {
super(message);
}
public ServiceException(Throwable cause) {
super(cause);
}
public ServiceException(String message, Throwable cause) {
super(message, cause);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm/orm_2_0.xsd" version="2.0">
</entity-mappings>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0">
<!-- Keep this file empty, Spring will scan the @Entity classes -->
<persistence-unit name="defaultPU" transaction-type="RESOURCE_LOCAL">
<mapping-file>META-INF/orm.xml</mapping-file>
</persistence-unit>
</persistence>
\ No newline at end of file
#h2 database settings
jdbc.driver=org.h2.Driver
jdbc.url=jdbc:h2:file:~/.h2/quickstart;AUTO_SERVER=TRUE
jdbc.username=sa
jdbc.password=
#dbcp settings
dbcp.maxIdle=5
dbcp.maxActive=40
<?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:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"
default-lazy-init="true">
<description>Spring公共配置 </description>
<!-- 使用annotation 自动注册bean, 并保证@Required、@Autowired的属性被注入 -->
<context:component-scan base-package="org.springside.examples.quickstart">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- Jpa Entity Manager 配置 -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="persistenceUnitName" value="defaultPU"/>
<property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter"/>
<property name="jpaProperties">
<props>
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
</props>
</property>
</bean>
<bean id="hibernateJpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform">
<bean factory-method="getDialect" class="org.springside.modules.orm.Hibernates">
<constructor-arg ref="dataSource"/>
</bean>
</property>
</bean>
<!-- Spring Data Jpa配置 -->
<jpa:repositories base-package="org.springside.examples.quickstart" transaction-manager-ref="transactionManager" entity-manager-factory-ref="entityManagerFactory"/>
<!-- Jpa 事务配置 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!-- 使用annotation定义事务 -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
<!-- production环境 -->
<beans profile="production">
<context:property-placeholder ignore-unresolvable="true"
location="classpath*:/application.properties" />
<!-- 数据源配置, 使用DBCP数据库连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- Connection Info -->
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- Connection Pooling Info -->
<property name="maxActive" value="${dbcp.maxActive}" />
<property name="maxIdle" value="${dbcp.maxIdle}" />
<property name="defaultAutoCommit" value="false" />
<!-- 连接Idle一个小时后超时 -->
<property name="timeBetweenEvictionRunsMillis" value="3600000" />
<property name="minEvictableIdleTimeMillis" value="3600000" />
</bean>
<!-- 数据源配置,使用应用服务器的数据库连接池 -->
<!--<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/ExampleDB" />-->
</beans>
<!-- local development环境 -->
<beans profile="development">
<context:property-placeholder ignore-resource-not-found="true"
location="classpath*:/application.properties,
classpath*:/application.development.properties,
classpath*:/application.development-local.properties" />
<!-- DBCP连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="defaultAutoCommit" value="false" />
</bean>
</beans>
<beans profile="functional">
<context:property-placeholder ignore-resource-not-found="true"
location="classpath*:/application.properties,
classpath*:/application.functional.properties,
classpath*:/application.functional-local.properties" />
<!-- DBCP连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="defaultAutoCommit" value="false" />
</bean>
<!-- 初始化数据表结构 -->
<jdbc:initialize-database data-source="dataSource" ignore-failures="ALL">
<jdbc:script location="classpath:sql/h2/schema.sql" />
</jdbc:initialize-database>
</beans>
<!-- unit test环境 -->
<beans profile="test">
<context:property-placeholder ignore-resource-not-found="true"
location="classpath*:/application.properties,
classpath*:/application.test.properties" />
<!-- 嵌入式内存中数据库 -->
<jdbc:embedded-database id="dataSource" type="H2">
<jdbc:script location="classpath:sql/h2/schema.sql" />
</jdbc:embedded-database>
<!-- 初始化默认数据 -->
<bean class="org.springside.modules.test.data.DataInitializer" lazy-init="false">
<property name="dataSource" ref="dataSource"/>
<property name="dataFile" value="/data/sample-data.xml" />
</bean>
</beans>
</beans>
\ No newline at end of file
# Output pattern : date [thread] priority category - message
log4j.rootLogger=WARN, Console, RollingFile
#Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
#RollingFile
log4j.appender.RollingFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.RollingFile.File=logs/quickstart.log
log4j.appender.RollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.RollingFile.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
#Project defalult level
log4j.logger.org.springside.examples.quickstart=INFO
#log4jdbc
#log4j.logger.jdbc.sqltiming=INFO
\ No newline at end of file
drop table if exists task;
create table task (
id bigint generated by default as identity,
title varchar(255) not null,
primary key (id)
) ;
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<decorators defaultdir="/WEB-INF/layouts/">
<excludes>
<pattern>/static/*</pattern>
</excludes>
<decorator name="default" page="default.jsp">
<pattern>/*</pattern>
</decorator>
</decorators>
<%@ page contentType="text/html;charset=UTF-8"%>
<%@ taglib prefix="sitemesh" uri="http://www.opensymphony.com/sitemesh/decorator" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<c:set var="ctx" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<title>QuickStart示例:<sitemesh:title/></title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta http-equiv="Cache-Control" content="no-store" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<link href="${ctx}/static/bootstrap/2.0.4/css/bootstrap.min.css" type="text/css" rel="stylesheet" />
<link href="${ctx}/static/jquery-validation/1.9.0/validate.css" type="text/css" rel="stylesheet" />
<link href="${ctx}/static/quickstart.css" type="text/css" rel="stylesheet" />
<script src="${ctx}/static/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<script src="${ctx}/static/jquery-validation/1.9.0/jquery.validate.min.js" type="text/javascript"></script>
<script src="${ctx}/static/jquery-validation/1.9.0/messages_cn.js" type="text/javascript"></script>
<sitemesh:head/>
</head>
<body>
<div class="container">
<%@ include file="/WEB-INF/layouts/header.jsp"%>
<div id="content" class="span12">
<sitemesh:body/>
</div>
<%@ include file="/WEB-INF/layouts/footer.jsp"%>
</div>
<script src="${ctx}/static/bootstrap/2.0.4/js/bootstrap.min.js" type="text/javascript"></script>
</body>
</html>
\ No newline at end of file
<%@ page language="java" pageEncoding="UTF-8" %>
<div id="footer" class="span12">
Copyright &copy; 2005-2012 <a href="http://www.springside.org.cn">springside.org.cn</a>
</div>
<%@ page language="java" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="ctx" value="${pageContext.request.contextPath}"/>
<div id="header" class="span12">
<div id="title">
<h1>QuickStart示例<small>--TodoList应用演示</small></h1>
</div>
</div>
\ No newline at end of file
<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<!-- 自动扫描且只扫描@Controller -->
<context:component-scan base-package="org.springside.examples.quickstart" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<mvc:annotation-driven />
<mvc:default-servlet-handler/>
<!-- 定义首页转向Task页面 -->
<mvc:view-controller path="/" view-name="redirect:/task/"/>
<!-- 定义JSP -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
<%@ page contentType="text/html;charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%response.setStatus(200);%>
<!DOCTYPE html>
<html>
<head>
<title>404 - 页面不存在</title>
</head>
<body>
<div>
<div><h1>页面不存在.</h1></div>
<div><a href="<c:url value="/"/>">返回首页</a></div>
</div>
</body>
</html>
\ No newline at end of file
<%@ page contentType="text/html;charset=UTF-8" isErrorPage="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page import="org.slf4j.Logger,org.slf4j.LoggerFactory" %>
<%response.setStatus(200);%>
<%
Throwable ex = null;
if (exception != null)
ex = exception;
if (request.getAttribute("javax.servlet.error.exception") != null)
ex = (Throwable) request.getAttribute("javax.servlet.error.exception");
//记录日志
Logger logger = LoggerFactory.getLogger("500.jsp");
logger.error(ex.getMessage(), ex);
%>
<!DOCTYPE html>
<html>
<head>
<title>500 - 系统内部错误</title>
</head>
<body>
<div><h1>系统发生内部错误.</h1></div>
<div><a href="<c:url value="/"/>">返回首页</a></div>
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<web-app 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"
version="2.5">
<display-name>QuickStart</display-name>
<!-- Spring ApplicationContext配置文件的路径,可使用通配符,多个路径用,号分隔
此参数用于后面的Spring Context Loader -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:/applicationContext.xml
</param-value>
</context-param>
<!-- 設定Spring Context的默认Profile -->
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>production</param-value>
</context-param>
<!--Spring的ApplicationContext 载入 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Filter 定义 -->
<!-- Character Encoding filter -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Open Entity Manager in View filter -->
<filter>
<filter-name>openEntityManagerInViewFilter</filter-name>
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>openEntityManagerInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- SiteMesh Web-Page Layout filter-->
<filter>
<filter-name>sitemeshFilter</filter-name>
<filter-class>com.opensymphony.sitemesh.webapp.SiteMeshFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>sitemeshFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Spring MVC Servlet -->
<servlet>
<servlet-name>springServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- session超时定义,单位为分钟 -->
<session-config>
<session-timeout>20</session-timeout>
</session-config>
<!-- 出错页面定义 -->
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/WEB-INF/views/error/500.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/views/error/500.jsp</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/views/error/404.jsp</location>
</error-page>
</web-app>
/*!
* Bootstrap Responsive v2.0.4
*
* Copyright 2012 Twitter, Inc
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Designed and built with all the love in the world @twitter by @mdo and @fat.
*/
.clearfix {
*zoom: 1;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both;
}
.hide-text {
font: 0/0 a;
color: transparent;
text-shadow: none;
background-color: transparent;
border: 0;
}
.input-block-level {
display: block;
width: 100%;
min-height: 28px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.hidden {
display: none;
visibility: hidden;
}
.visible-phone {
display: none !important;
}
.visible-tablet {
display: none !important;
}
.hidden-desktop {
display: none !important;
}
@media (max-width: 767px) {
.visible-phone {
display: inherit !important;
}
.hidden-phone {
display: none !important;
}
.hidden-desktop {
display: inherit !important;
}
.visible-desktop {
display: none !important;
}
}
@media (min-width: 768px) and (max-width: 979px) {
.visible-tablet {
display: inherit !important;
}
.hidden-tablet {
display: none !important;
}
.hidden-desktop {
display: inherit !important;
}
.visible-desktop {
display: none !important ;
}
}
@media (max-width: 480px) {
.nav-collapse {
-webkit-transform: translate3d(0, 0, 0);
}
.page-header h1 small {
display: block;
line-height: 18px;
}
input[type="checkbox"],
input[type="radio"] {
border: 1px solid #ccc;
}
.form-horizontal .control-group > label {
float: none;
width: auto;
padding-top: 0;
text-align: left;
}
.form-horizontal .controls {
margin-left: 0;
}
.form-horizontal .control-list {
padding-top: 0;
}
.form-horizontal .form-actions {
padding-right: 10px;
padding-left: 10px;
}
.modal {
position: absolute;
top: 10px;
right: 10px;
left: 10px;
width: auto;
margin: 0;
}
.modal.fade.in {
top: auto;
}
.modal-header .close {
padding: 10px;
margin: -10px;
}
.carousel-caption {
position: static;
}
}
@media (max-width: 767px) {
body {
padding-right: 20px;
padding-left: 20px;
}
.navbar-fixed-top,
.navbar-fixed-bottom {
margin-right: -20px;
margin-left: -20px;
}
.container-fluid {
padding: 0;
}
.dl-horizontal dt {
float: none;
width: auto;
clear: none;
text-align: left;
}
.dl-horizontal dd {
margin-left: 0;
}
.container {
width: auto;
}
.row-fluid {
width: 100%;
}
.row,
.thumbnails {
margin-left: 0;
}
[class*="span"],
.row-fluid [class*="span"] {
display: block;
float: none;
width: auto;
margin-left: 0;
}
.input-large,
.input-xlarge,
.input-xxlarge,
input[class*="span"],
select[class*="span"],
textarea[class*="span"],
.uneditable-input {
display: block;
width: 100%;
min-height: 28px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.input-prepend input,
.input-append input,
.input-prepend input[class*="span"],
.input-append input[class*="span"] {
display: inline-block;
width: auto;
}
}
@media (min-width: 768px) and (max-width: 979px) {
.row {
margin-left: -20px;
*zoom: 1;
}
.row:before,
.row:after {
display: table;
content: "";
}
.row:after {
clear: both;
}
[class*="span"] {
float: left;
margin-left: 20px;
}
.container,
.navbar-fixed-top .container,
.navbar-fixed-bottom .container {
width: 724px;
}
.span12 {
width: 724px;
}
.span11 {
width: 662px;
}
.span10 {
width: 600px;
}
.span9 {
width: 538px;
}
.span8 {
width: 476px;
}
.span7 {
width: 414px;
}
.span6 {
width: 352px;
}
.span5 {
width: 290px;
}
.span4 {
width: 228px;
}
.span3 {
width: 166px;
}
.span2 {
width: 104px;
}
.span1 {
width: 42px;
}
.offset12 {
margin-left: 764px;
}
.offset11 {
margin-left: 702px;
}
.offset10 {
margin-left: 640px;
}
.offset9 {
margin-left: 578px;
}
.offset8 {
margin-left: 516px;
}
.offset7 {
margin-left: 454px;
}
.offset6 {
margin-left: 392px;
}
.offset5 {
margin-left: 330px;
}
.offset4 {
margin-left: 268px;
}
.offset3 {
margin-left: 206px;
}
.offset2 {
margin-left: 144px;
}
.offset1 {
margin-left: 82px;
}
.row-fluid {
width: 100%;
*zoom: 1;
}
.row-fluid:before,
.row-fluid:after {
display: table;
content: "";
}
.row-fluid:after {
clear: both;
}
.row-fluid [class*="span"] {
display: block;
float: left;
width: 100%;
min-height: 28px;
margin-left: 2.762430939%;
*margin-left: 2.709239449638298%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.row-fluid [class*="span"]:first-child {
margin-left: 0;
}
.row-fluid .span12 {
width: 99.999999993%;
*width: 99.9468085036383%;
}
.row-fluid .span11 {
width: 91.436464082%;
*width: 91.38327259263829%;
}
.row-fluid .span10 {
width: 82.87292817100001%;
*width: 82.8197366816383%;
}
.row-fluid .span9 {
width: 74.30939226%;
*width: 74.25620077063829%;
}
.row-fluid .span8 {
width: 65.74585634900001%;
*width: 65.6926648596383%;
}
.row-fluid .span7 {
width: 57.182320438000005%;
*width: 57.129128948638304%;
}
.row-fluid .span6 {
width: 48.618784527%;
*width: 48.5655930376383%;
}
.row-fluid .span5 {
width: 40.055248616%;
*width: 40.0020571266383%;
}
.row-fluid .span4 {
width: 31.491712705%;
*width: 31.4385212156383%;
}
.row-fluid .span3 {
width: 22.928176794%;
*width: 22.874985304638297%;
}
.row-fluid .span2 {
width: 14.364640883%;
*width: 14.311449393638298%;
}
.row-fluid .span1 {
width: 5.801104972%;
*width: 5.747913482638298%;
}
input,
textarea,
.uneditable-input {
margin-left: 0;
}
input.span12,
textarea.span12,
.uneditable-input.span12 {
width: 714px;
}
input.span11,
textarea.span11,
.uneditable-input.span11 {
width: 652px;
}
input.span10,
textarea.span10,
.uneditable-input.span10 {
width: 590px;
}
input.span9,
textarea.span9,
.uneditable-input.span9 {
width: 528px;
}
input.span8,
textarea.span8,
.uneditable-input.span8 {
width: 466px;
}
input.span7,
textarea.span7,
.uneditable-input.span7 {
width: 404px;
}
input.span6,
textarea.span6,
.uneditable-input.span6 {
width: 342px;
}
input.span5,
textarea.span5,
.uneditable-input.span5 {
width: 280px;
}
input.span4,
textarea.span4,
.uneditable-input.span4 {
width: 218px;
}
input.span3,
textarea.span3,
.uneditable-input.span3 {
width: 156px;
}
input.span2,
textarea.span2,
.uneditable-input.span2 {
width: 94px;
}
input.span1,
textarea.span1,
.uneditable-input.span1 {
width: 32px;
}
}
@media (min-width: 1200px) {
.row {
margin-left: -30px;
*zoom: 1;
}
.row:before,
.row:after {
display: table;
content: "";
}
.row:after {
clear: both;
}
[class*="span"] {
float: left;
margin-left: 30px;
}
.container,
.navbar-fixed-top .container,
.navbar-fixed-bottom .container {
width: 1170px;
}
.span12 {
width: 1170px;
}
.span11 {
width: 1070px;
}
.span10 {
width: 970px;
}
.span9 {
width: 870px;
}
.span8 {
width: 770px;
}
.span7 {
width: 670px;
}
.span6 {
width: 570px;
}
.span5 {
width: 470px;
}
.span4 {
width: 370px;
}
.span3 {
width: 270px;
}
.span2 {
width: 170px;
}
.span1 {
width: 70px;
}
.offset12 {
margin-left: 1230px;
}
.offset11 {
margin-left: 1130px;
}
.offset10 {
margin-left: 1030px;
}
.offset9 {
margin-left: 930px;
}
.offset8 {
margin-left: 830px;
}
.offset7 {
margin-left: 730px;
}
.offset6 {
margin-left: 630px;
}
.offset5 {
margin-left: 530px;
}
.offset4 {
margin-left: 430px;
}
.offset3 {
margin-left: 330px;
}
.offset2 {
margin-left: 230px;
}
.offset1 {
margin-left: 130px;
}
.row-fluid {
width: 100%;
*zoom: 1;
}
.row-fluid:before,
.row-fluid:after {
display: table;
content: "";
}
.row-fluid:after {
clear: both;
}
.row-fluid [class*="span"] {
display: block;
float: left;
width: 100%;
min-height: 28px;
margin-left: 2.564102564%;
*margin-left: 2.510911074638298%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.row-fluid [class*="span"]:first-child {
margin-left: 0;
}
.row-fluid .span12 {
width: 100%;
*width: 99.94680851063829%;
}
.row-fluid .span11 {
width: 91.45299145300001%;
*width: 91.3997999636383%;
}
.row-fluid .span10 {
width: 82.905982906%;
*width: 82.8527914166383%;
}
.row-fluid .span9 {
width: 74.358974359%;
*width: 74.30578286963829%;
}
.row-fluid .span8 {
width: 65.81196581200001%;
*width: 65.7587743226383%;
}
.row-fluid .span7 {
width: 57.264957265%;
*width: 57.2117657756383%;
}
.row-fluid .span6 {
width: 48.717948718%;
*width: 48.6647572286383%;
}
.row-fluid .span5 {
width: 40.170940171000005%;
*width: 40.117748681638304%;
}
.row-fluid .span4 {
width: 31.623931624%;
*width: 31.5707401346383%;
}
.row-fluid .span3 {
width: 23.076923077%;
*width: 23.0237315876383%;
}
.row-fluid .span2 {
width: 14.529914530000001%;
*width: 14.4767230406383%;
}
.row-fluid .span1 {
width: 5.982905983%;
*width: 5.929714493638298%;
}
input,
textarea,
.uneditable-input {
margin-left: 0;
}
input.span12,
textarea.span12,
.uneditable-input.span12 {
width: 1160px;
}
input.span11,
textarea.span11,
.uneditable-input.span11 {
width: 1060px;
}
input.span10,
textarea.span10,
.uneditable-input.span10 {
width: 960px;
}
input.span9,
textarea.span9,
.uneditable-input.span9 {
width: 860px;
}
input.span8,
textarea.span8,
.uneditable-input.span8 {
width: 760px;
}
input.span7,
textarea.span7,
.uneditable-input.span7 {
width: 660px;
}
input.span6,
textarea.span6,
.uneditable-input.span6 {
width: 560px;
}
input.span5,
textarea.span5,
.uneditable-input.span5 {
width: 460px;
}
input.span4,
textarea.span4,
.uneditable-input.span4 {
width: 360px;
}
input.span3,
textarea.span3,
.uneditable-input.span3 {
width: 260px;
}
input.span2,
textarea.span2,
.uneditable-input.span2 {
width: 160px;
}
input.span1,
textarea.span1,
.uneditable-input.span1 {
width: 60px;
}
.thumbnails {
margin-left: -30px;
}
.thumbnails > li {
margin-left: 30px;
}
.row-fluid .thumbnails {
margin-left: 0;
}
}
@media (max-width: 979px) {
body {
padding-top: 0;
}
.navbar-fixed-top,
.navbar-fixed-bottom {
position: static;
}
.navbar-fixed-top {
margin-bottom: 18px;
}
.navbar-fixed-bottom {
margin-top: 18px;
}
.navbar-fixed-top .navbar-inner,
.navbar-fixed-bottom .navbar-inner {
padding: 5px;
}
.navbar .container {
width: auto;
padding: 0;
}
.navbar .brand {
padding-right: 10px;
padding-left: 10px;
margin: 0 0 0 -5px;
}
.nav-collapse {
clear: both;
}
.nav-collapse .nav {
float: none;
margin: 0 0 9px;
}
.nav-collapse .nav > li {
float: none;
}
.nav-collapse .nav > li > a {
margin-bottom: 2px;
}
.nav-collapse .nav > .divider-vertical {
display: none;
}
.nav-collapse .nav .nav-header {
color: #999999;
text-shadow: none;
}
.nav-collapse .nav > li > a,
.nav-collapse .dropdown-menu a {
padding: 6px 15px;
font-weight: bold;
color: #999999;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
.nav-collapse .btn {
padding: 4px 10px 4px;
font-weight: normal;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.nav-collapse .dropdown-menu li + li a {
margin-bottom: 2px;
}
.nav-collapse .nav > li > a:hover,
.nav-collapse .dropdown-menu a:hover {
background-color: #222222;
}
.nav-collapse.in .btn-group {
padding: 0;
margin-top: 5px;
}
.nav-collapse .dropdown-menu {
position: static;
top: auto;
left: auto;
display: block;
float: none;
max-width: none;
padding: 0;
margin: 0 15px;
background-color: transparent;
border: none;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
.nav-collapse .dropdown-menu:before,
.nav-collapse .dropdown-menu:after {
display: none;
}
.nav-collapse .dropdown-menu .divider {
display: none;
}
.nav-collapse .navbar-form,
.nav-collapse .navbar-search {
float: none;
padding: 9px 15px;
margin: 9px 0;
border-top: 1px solid #222222;
border-bottom: 1px solid #222222;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
-moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
}
.navbar .nav-collapse .nav.pull-right {
float: none;
margin-left: 0;
}
.nav-collapse,
.nav-collapse.collapse {
height: 0;
overflow: hidden;
}
.navbar .btn-navbar {
display: block;
}
.navbar-static .navbar-inner {
padding-right: 10px;
padding-left: 10px;
}
}
@media (min-width: 980px) {
.nav-collapse.collapse {
height: auto !important;
overflow: visible !important;
}
}
/*!
* Bootstrap Responsive v2.0.4
*
* Copyright 2012 Twitter, Inc
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Designed and built with all the love in the world @twitter by @mdo and @fat.
*/.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}@media(max-width:767px){.visible-phone{display:inherit!important}.hidden-phone{display:none!important}.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}}@media(min-width:768px) and (max-width:979px){.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:18px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-group>label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.modal{position:absolute;top:10px;right:10px;left:10px;width:auto;margin:0}.modal.fade.in{top:auto}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:auto;margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;content:""}.row:after{clear:both}[class*="span"]{float:left;margin-left:20px}.container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:28px;margin-left:2.762430939%;*margin-left:2.709239449638298%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:99.999999993%;*width:99.9468085036383%}.row-fluid .span11{width:91.436464082%;*width:91.38327259263829%}.row-fluid .span10{width:82.87292817100001%;*width:82.8197366816383%}.row-fluid .span9{width:74.30939226%;*width:74.25620077063829%}.row-fluid .span8{width:65.74585634900001%;*width:65.6926648596383%}.row-fluid .span7{width:57.182320438000005%;*width:57.129128948638304%}.row-fluid .span6{width:48.618784527%;*width:48.5655930376383%}.row-fluid .span5{width:40.055248616%;*width:40.0020571266383%}.row-fluid .span4{width:31.491712705%;*width:31.4385212156383%}.row-fluid .span3{width:22.928176794%;*width:22.874985304638297%}.row-fluid .span2{width:14.364640883%;*width:14.311449393638298%}.row-fluid .span1{width:5.801104972%;*width:5.747913482638298%}input,textarea,.uneditable-input{margin-left:0}input.span12,textarea.span12,.uneditable-input.span12{width:714px}input.span11,textarea.span11,.uneditable-input.span11{width:652px}input.span10,textarea.span10,.uneditable-input.span10{width:590px}input.span9,textarea.span9,.uneditable-input.span9{width:528px}input.span8,textarea.span8,.uneditable-input.span8{width:466px}input.span7,textarea.span7,.uneditable-input.span7{width:404px}input.span6,textarea.span6,.uneditable-input.span6{width:342px}input.span5,textarea.span5,.uneditable-input.span5{width:280px}input.span4,textarea.span4,.uneditable-input.span4{width:218px}input.span3,textarea.span3,.uneditable-input.span3{width:156px}input.span2,textarea.span2,.uneditable-input.span2{width:94px}input.span1,textarea.span1,.uneditable-input.span1{width:32px}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;content:""}.row:after{clear:both}[class*="span"]{float:left;margin-left:30px}.container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:28px;margin-left:2.564102564%;*margin-left:2.510911074638298%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145300001%;*width:91.3997999636383%}.row-fluid .span10{width:82.905982906%;*width:82.8527914166383%}.row-fluid .span9{width:74.358974359%;*width:74.30578286963829%}.row-fluid .span8{width:65.81196581200001%;*width:65.7587743226383%}.row-fluid .span7{width:57.264957265%;*width:57.2117657756383%}.row-fluid .span6{width:48.717948718%;*width:48.6647572286383%}.row-fluid .span5{width:40.170940171000005%;*width:40.117748681638304%}.row-fluid .span4{width:31.623931624%;*width:31.5707401346383%}.row-fluid .span3{width:23.076923077%;*width:23.0237315876383%}.row-fluid .span2{width:14.529914530000001%;*width:14.4767230406383%}.row-fluid .span1{width:5.982905983%;*width:5.929714493638298%}input,textarea,.uneditable-input{margin-left:0}input.span12,textarea.span12,.uneditable-input.span12{width:1160px}input.span11,textarea.span11,.uneditable-input.span11{width:1060px}input.span10,textarea.span10,.uneditable-input.span10{width:960px}input.span9,textarea.span9,.uneditable-input.span9{width:860px}input.span8,textarea.span8,.uneditable-input.span8{width:760px}input.span7,textarea.span7,.uneditable-input.span7{width:660px}input.span6,textarea.span6,.uneditable-input.span6{width:560px}input.span5,textarea.span5,.uneditable-input.span5{width:460px}input.span4,textarea.span4,.uneditable-input.span4{width:360px}input.span3,textarea.span3,.uneditable-input.span3{width:260px}input.span2,textarea.span2,.uneditable-input.span2{width:160px}input.span1,textarea.span1,.uneditable-input.span1{width:60px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:18px}.navbar-fixed-bottom{margin-top:18px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 9px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#999;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:6px 15px;font-weight:bold;color:#999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#222}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:block;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:9px 15px;margin:9px 0;border-top:1px solid #222;border-bottom:1px solid #222;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}}
/*
* Translated default messages for the jQuery validation plugin.
* Locale: CN
*/
jQuery.extend(jQuery.validator.messages, {
required: "必选字段",
remote: "请修正该字段",
email: "请输入正确格式的电子邮件",
url: "请输入合法的网址",
date: "请输入合法的日期",
dateISO: "请输入合法的日期 (ISO).",
number: "请输入合法的数字",
digits: "只能输入整数",
creditcard: "请输入合法的信用卡号",
equalTo: "请再次输入相同的值",
accept: "请输入拥有合法后缀名的字符串",
maxlength: jQuery.validator.format("请输入一个长度最多是 {0} 的字符串"),
minlength: jQuery.validator.format("请输入一个长度最少是 {0} 的字符串"),
rangelength: jQuery.validator.format("请输入一个长度介于 {0} 和 {1} 之间的字符串"),
range: jQuery.validator.format("请输入一个介于 {0} 和 {1} 之间的值"),
max: jQuery.validator.format("请输入一个最大为 {0} 的值"),
min: jQuery.validator.format("请输入一个最小为 {0} 的值")
});
\ No newline at end of file
label.error {
background:url("images/unchecked.gif") no-repeat 0px 0px;
padding-left: 16px;
padding-bottom: 2px;
font-weight: bold;
color: #EA5200;
}
\ No newline at end of file
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册