Initial commit

This commit is contained in:
2026-05-29 17:49:25 +09:00
commit 330105cb27
1081 changed files with 148694 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
// PDF
:pdf-fontsdir: {asciidoctorconfigdir}/fonts
:pdf-themesdir: {asciidoctorconfigdir}/themes
:pdf-theme: ko_KR
// DOCTYPE, TOC
:doctype: book
:chapter-signifier:
:sectnums:
:sectanchors:
:toc: left
:toclevels: 3
:source-highlighter: highlight.js
// ICON, IMAGE
:icons: font
:stylesheet: css/sdl-manual.css
:linkcss:
:imagesdir: {asciidoctorconfigdir}/img
:data-uri:
+13
View File
@@ -0,0 +1,13 @@
= Appendix
:leveloffset: +1
include::QuartzClustering.adoc[leveloffset]
include::Properties암호화툴사용방법.adoc[leveloffset]
include::uiSDL.adoc[leveloffset]
include::RedisConfig.adoc[leveloffset]
include::D-KMS 암호화 적용.adoc[leveloffset]
+90
View File
@@ -0,0 +1,90 @@
= D-KMS 암호화 적용
SDL(표준개발라이브러리)에 D-KMS 암호화를 적용하는 방법에 대해 설명한다.
== 사전 준비
=== KMS 포털에서 암호화 권한 신청
. KMS 포털에서 계정을 인증하고 암호화 권한 신청, 결재
. 암호화 권한 결재 완료되면 D-KMS SDK 및 가이드 전송됨
. 전달 받은 SDK 설치 및 코드 적용
== 코드 적용
=== Credential 생성 및 환경변수 추가
. D-KMS 가이드를 참고하여 Credential을 생성하고 시스템 환경변수에 추가한다.
=== SDL 수정
. D-KMS 암호화 라이브러리 추가
- https://code.sec.samsung.net/confluence/pages/viewpage.action?pageId=343332410[D-KMS 가이드]를 참고하여 라이브러리를 추가한다.
. src/main/resources-{local|dev|prod} 에 dkms.properties 파일을 추가한다. (배포된 sdl-appendix 내 참조)
IMPORTANT: - dkms.task-id 프로퍼티 값을 D-KMS 신청시 부여받은 값으로 수정해야한다. +
- 기타 다른 프로퍼티 값도 제대로 설정되었는지 확인한다.
[start=3]
. mybatis typehandler 추가
- 암호화 대상이 되는 컬럼에 대해 typehandler 를 적용한다.
- com.samsung.dkms.handler 패키지 생성
- EncryptionTypeHandler.java, NameEncryptionTypeHandler.java, EmailEncryptionTypeHandler.java, PhoneEncryptionTypeHandler.java, AddressEncryptionTypeHandler.java, BirthdayEncryptionTypeHandler.java 파일을 복사/붙여넣기 한다. (배포된 sdl-appendix 내 참조)
. mybatis-config.xml 수정
- mapper xml 에 적용이 용이하도록 typehandler의 type alias 를 추가한다.
+
image::dkms_typehandler_alias.png[]
[start=5]
. mapper xml 에 mybatis typehandler 적용
- 암호화가 필요한 컬럼에 해당하는 핸들러를 선택 적용한다.
- mapper xml에 typehandler 적용 방법은 샘플파일(mapper-mybatis-user.xml, 배포된 sdl-appendix 내 참조)을 참고한다. (`*""유무에 유의*`)
* select resultMap 적용 예
+
image::dkms_typehandler_select_resultmap.png[]
* insert, update 적용 예
+
image::dkms_typehandler_insert.png[]
image::dkms_typehandler_update.png[]
* 수정대상 mapper xml (배포판 기준)
+
----
mapper-mybatis-common.xml
mapper-mybatis-sample-approval-internal.xml
mapper-mybatis-sample-approval-knox.xml
mapper-mybatis-sample.xml
mapper-mybatis-sys-resource.xml
mapper-mybatis-user-sync.xml
mapper-mybatis-approval.xml
mapper-mybatis-user-role.xml
mapper-mybatis-user.xml
mapper-mybatis-workgroup-role.xml
mapper-mybatis-comment.xml
mapper-mybatis-post.xml
mapper-mybatis-email.xml
mapper-mybatis-mail-group.xml
mapper-mybatis-history.xml
mapper-mybatis-access-log.xml
mapper-mybatis-admin-address.xml
mapper-mybatis-knox-department-sync.xml
mapper-mybatis-knox-department.xml
mapper-mybatis-terms-use.xml
----
=== 테이블 수정 및 데이터 마이그레이션
. 테이블 컬럼 사이즈 변경
- 암호화 대상 컬럼의 사이즈를 최소 255byte 가 되도록 변경한다.
. 기존 데이터 암호화를 위한 마이그레이션은 D-KMS 가이드를 참고한다.
CAUTION: *SQL기반의 Database의 경우, 개인정보가 암호화됨으로써 아래와 같은 SQL문에 영향이 발생* (D-KMS 소개자료 중 발췌) +
Nondeterministic Encryption으로 인해 Equality 비교 불가 +
: WHERE, GROUP BY, JOIN (ON), DISTINCT, HAVING, ORDER BY +
-> 일반적으로 Application Level에서 Filtering, Grouping, Joining 구현 필요 +
+
*대안: 별도 Column에 Hash 데이터를 추가 저장* +
Hash로 인해 영향 받는 SQL문: WHERE (비교에 사용하는 데이터 역시 Hash 필요. Equality 비교는 가능하나 LIKE 또는 대소비교 불가), ORDER BY +
-> 단, 모든 데이터가 Hash되어있지 않은 경우 GROUP BY에 영향을 줄 수 있음
@@ -0,0 +1,170 @@
= Properties 암호화 툴 사용 방법
sdl-encrypt는 Spring Framework에서 사용하는 properties를 http://www.jasypt.org/[Jasypt]를 이용해 쉽게 암호화 하기 위한 모듈이다.
== 사용 방법
=== 파일다운로드
sdl-encrypt-1.0.0.jar 파일을 다운로드 받아서 특정 디렉토리에 복사한다. (배포된 sdl-appendix 내 참조)
sdl-encypt-1.0.0.jar 파일을 실행하기 위해서는 JDK 8 이상의 버전이 설치 되어 있어야 한다.
----
C:\encrypt>dir
C 드라이브의 볼륨에는 이름이 없습니다.
볼륨 일련 번호: 1841-973A
C:\encrypt 디렉터리
2022-08-09 오후 02:17 <DIR> .
2022-08-09 오후 02:17 <DIR> ..
2022-08-09 오후 02:15 9,114,144 sdl-encrypt-1.0.0.jar
----
'''
=== 암호화 key 파일 생성
같은 폴더에 암호화를 위한 key 파일을 sdl-encrypt-1.0.0.jar 파일을 있는 폴더에 생성한다.
key파일의 이름은 *jasypt.key* 로 한다. jasypt.key 파일은 어플리케이션 실행 시 서버에서 읽어 복호화 할때 필요하다.
*jasypt.key 파일의 내용이 유출 될 경우 문제가 생길 수 있으니 형상관리에 저장하지 않도록 한다.*
*jasypt.key 샘플*
----
fhasiuk23dfjhasoijcnaoijfoase0342ruq0
----
jasypt.key 파일의 내용은 프로젝트에서 랜덤한 문자열로 생성한다.
'''
=== Properties 파일 생성
config.properties 파일에 암호화가 필요한 properties를 작성해 sdl-encrypt-1.0.0.jar 파일이 있는 폴더에 생성한다.
*config.properties 파일 샘플*
----
# Datasource(Oracle)
datasource.driver=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
datasource.url=jdbc:log4jdbc:oracle:thin:@10.127.72.226:1521:XE
datasource.username=sdl5
datasource.password=dlatl#00
----
'''
=== 암호화 하기
실행 전에 위의 모든 파일들이 같은 폴더에 위치하는지 확인한다.
----
C:\encrypt>dir
C 드라이브의 볼륨에는 이름이 없습니다.
볼륨 일련 번호: 1841-973A
C:\encrypt 디렉터리
2022-08-09 오후 02:29 <DIR> .
2022-08-09 오후 02:29 <DIR> ..
2022-08-08 오후 05:14 407 config.properties
2022-08-09 오후 02:29 37 jasypt.key
2022-08-09 오후 02:15 9,114,144 sdl-encrypt-1.0.0.jar
3개 파일 9,114,588 바이트
----
*java -jar sdl-encrypt-1.0.0.jar* 실행
----
C:\encrypt>java -jar sdl-encrypt-1.0.0.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.2)
2022-08-09 14:30:33.152 INFO 47544 --- [ main] com.samsung.EncryptApplication : Starting EncryptApplication v1.0.0 using Java 1.8.0_341 on DESKTOP-BBNYDORY with PID 47544 (C:\encrypt\sdl-encrypt-1.0.0.jar started by bbnydory in C:\encrypt)
2022-08-09 14:30:33.157 INFO 47544 --- [ main] com.samsung.EncryptApplication : No active profile set, falling back to 1 default profile: "default"
2022-08-09 14:30:33.456 INFO 47544 --- [ main] ptablePropertiesBeanFactoryPostProcessor : Post-processing PropertySource instances
2022-08-09 14:30:33.458 INFO 47544 --- [ main] c.u.j.EncryptablePropertySourceConverter : Skipping PropertySource configurationProperties [class org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertySource
2022-08-09 14:30:33.461 INFO 47544 --- [ main] c.u.j.EncryptablePropertySourceConverter : Converting PropertySource systemProperties [org.springframework.core.env.PropertiesPropertySource] to EncryptableMapPropertySourceWrapper
2022-08-09 14:30:33.461 INFO 47544 --- [ main] c.u.j.EncryptablePropertySourceConverter : Converting PropertySource systemEnvironment [org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor$OriginAwareSystemEnvironmentPropertySource] to EncryptableSystemEnvironmentPropertySourceWrapper
2022-08-09 14:30:33.463 INFO 47544 --- [ main] c.u.j.EncryptablePropertySourceConverter : Converting PropertySource random [org.springframework.boot.env.RandomValuePropertySource] to EncryptablePropertySourceWrapper
2022-08-09 14:30:33.464 INFO 47544 --- [ main] c.u.j.EncryptablePropertySourceConverter : Converting PropertySource Config resource 'class path resource [application.properties]' via location 'optional:classpath:/' [org.springframework.boot.env.OriginTrackedMapPropertySource] to EncryptableMapPropertySourceWrapper
2022-08-09 14:30:33.500 INFO 47544 --- [ main] c.u.j.filter.DefaultLazyPropertyFilter : Property Filter custom Bean not found with name 'encryptablePropertyFilter'. Initializing Default Property Filter
2022-08-09 14:30:33.551 INFO 47544 --- [ main] c.u.j.r.DefaultLazyPropertyResolver : Property Resolver custom Bean not found with name 'encryptablePropertyResolver'. Initializing Default Property Resolver
2022-08-09 14:30:33.553 INFO 47544 --- [ main] c.u.j.d.DefaultLazyPropertyDetector : Property Detector custom Bean not found with name 'encryptablePropertyDetector'. Initializing Default Property Detector
2022-08-09 14:30:33.611 INFO 47544 --- [ main] com.samsung.EncryptApplication : Started EncryptApplication in 0.765 seconds (JVM running for 1.669)
2022-08-09 14:30:34.114 INFO 47544 --- [ main] com.samsung.EncryptService : datasource.username=ENC(fOdjJKVr6fkmdS46JlsCRw==)
2022-08-09 14:30:34.115 INFO 47544 --- [ main] com.samsung.EncryptService : datasource.password=ENC(xfWl/qeA7tIMQ10UyU7IInz+SyY6ovX9)
2022-08-09 14:30:34.117 INFO 47544 --- [ main] com.samsung.EncryptService : datasource.url=ENC(Ocp24DAK1cq0f8TezLyMpcedSu6nLdTACSIYyMoCnuanSJT5x76lw8yM0reAeu4qBrBF+yvItVyCBmtPlIv70A==)
2022-08-09 14:30:34.119 INFO 47544 --- [ main] com.samsung.EncryptService : datasource.driver=ENC(GJ527jBO/7Xjilg8WbQHk55GwVUAvc6fIa1Yai1o68WeimiE5BpHNj3e7YnjhtQR)
----
'''
=== 확인하기
암호화가 정상적으로 실행 됐다면 같은 폴더에 config-enc.properties 파일이 생성된 것을 확인 할 수 있다.
----
C:\encrypt>dir
C 드라이브의 볼륨에는 이름이 없습니다.
볼륨 일련 번호: 1841-973A
C:\encrypt 디렉터리
2022-08-09 오후 02:30 <DIR> .
2022-08-09 오후 02:30 <DIR> ..
2022-08-09 오후 02:30 305 config-enc.properties
2022-08-08 오후 05:14 407 config.properties
2022-08-09 오후 02:29 37 jasypt.key
2022-08-09 오후 02:15 9,114,144 sdl-encrypt-1.0.0.jar
4개 파일 9,114,893 바이트
----
config-enc.properties 파일내용
----
datasource.username=ENC(fOdjJKVr6fkmdS46JlsCRw==)
datasource.password=ENC(xfWl/qeA7tIMQ10UyU7IInz+SyY6ovX9)
datasource.url=ENC(Ocp24DAK1cq0f8TezLyMpcedSu6nLdTACSIYyMoCnuanSJT5x76lw8yM0reAeu4qBrBF+yvItVyCBmtPlIv70A==)
datasource.driver=ENC(GJ527jBO/7Xjilg8WbQHk55GwVUAvc6fIa1Yai1o68WeimiE5BpHNj3e7YnjhtQR)
----
'''
== 고급 사용
sdl-encrypt 모듈을 Spring Boot 프로젝트로 되어 있고 application.properties 파일에 암호화 key파일, Properties 파일, 출력파일에 대한 경로가 작성되어 있다.
application.properties 내용
[source,properties]
----
algorithm=PBEWithMD5AndDES
keyObtentionIterations=1000
pollSize=1
stringOutputType=base64
keyFilePath=./jasypt.key
propertiesFilePath=./config.properties
outputPropertiesFilePath=./config-enc.properties
----
위 내용의 변경이 필요하면
sdl-encrypt-1.0.0.jar 파일이 있는 위치에 application.properties 파일을 새로 작성하고 위의 값들을 오버라이딩해서 사용할 수 있다.
+221
View File
@@ -0,0 +1,221 @@
= Quartz Clustering with JDBC-JobStore
== JDBC-JobStore DB 초기화
quartz db schema (배포된 sdl-appendix 내 참조) 를 사용하는 DBMS에 맞는 SQL을 실행해 JDBC-JobStore를 사용하기 위한 Table을 생성한다.
CAUTION: Tibero DB는 미지원
== application.properties
properties 파일 내용 중 Quartz JDBC-JobStore 관련 설정이다.
[source, properties]
----
# Spring Quartz 설정
spring.quartz.job-store-type=jdbc
spring.quartz.jdbc.initialize-schema=always
spring.quartz.datasource.driver-class-name=org.postgresql.Driver
spring.quartz.datasource.jdbcUrl=jdbc:postgresql://10.40.87.189:5445/SDL
spring.quartz.datasource.username=sdl5
spring.quartz.datasource.password=dlatl#123
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate <1>
spring.quartz.properties.org.quartz.jobStore.isClustered=true
spring.quartz.properties.org.quartz.jobStore.misfireThreshold=60000
spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=15000
spring.quartz.properties.org.quartz.scheduler.instanceName=sdl-prod <2>
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO <3>
spring.quartz.properties.org.quartz.scheduler.rmi.export=false
spring.quartz.properties.org.quartz.scheduler.rmi.proxy=false
spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
spring.quartz.properties.org.quartz.threadPool.threadCount=10 <4>
spring.quartz.properties.org.quartz.threadPool.threadPriority=5
spring.quartz.properties.org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
spring.quartz.properties.org.quartz.scheduler-name=QuartzScheduler
----
<1> DB 에 따라 Delegate 를 변경한다.
+
org.quartz.impl.jdbcjobstore.oracle.OracleDelegate, +
org.quartz.impl.jdbcjobstore.MSSQLDelegate, +
org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
<2> Quartz Scheduler 를 구별하는 Property 값으로 Clustering 할 서버에서는 같은 값을 세팅한다.
<3> Unique 한 값이어야 하며 AUTO 일 경우 자동 생성된다.
<4> Job 을 실행하기 위한 Thread 수
기타 자세한 Property 값은 http://www.quartz-scheduler.org/documentation/quartz-2.3.0/configuration/ConfigJDBCJobStoreClustering.html[Quartz 메뉴얼, window=_blank]을 참고 한다.
== QuartzClusteringConfig
QuartzClusteringConfig 파일은 Spring 에 등록된 CronTriggerFactoryBean 을 실행 하도록 SchedulerFactoryBean이 정의되어 있다.
[source, java]
----
@Bean
public SchedulerFactoryBean setSchedulerFactoryBean(ApplicationContext applicationContext) {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
schedulerFactoryBean.setJobFactory(jobFactory); <1>
schedulerFactoryBean.setDataSource(quartzDataSource()); <2>
schedulerFactoryBean.setApplicationContext(applicationContext);
schedulerFactoryBean.setTransactionManager(quartzTransactionManager()); <3>
schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(true);
schedulerFactoryBean.setStartupDelay(60);
schedulerFactoryBean.setOverwriteExistingJobs(true);
Properties properties = new Properties();
properties.putAll(quartzProperties.getProperties()); <4>
schedulerFactoryBean.setQuartzProperties(properties);
schedulerFactoryBean.setTriggers(
knoxSyncBatchConfig.knoxApprovalSyncTrigger().getObject(),
userBatchConfig.batchUserLongTermCheckTrigger().getObject(),
userBatchConfig.batchUserAuthExpiredTrigger().getObject(),
sysUseLogBatchConfig.batchSysUseLogTrigger().getObject(),
sysUseLogBatchConfig.batchMenuUseHistoryTrigger().getObject()
); <5>
return schedulerFactoryBean;
}
@Bean
@ConfigurationProperties(prefix = "spring.quartz.datasource")
@QuartzDataSource <2>
public DataSource quartzDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean
@QuartzTransactionManager <3>
public DataSourceTransactionManager quartzTransactionManager() {
return new DataSourceTransactionManager(quartzDataSource());
}
----
<1> Spring Bean을 AutoWiring
<2> Job 정보를 조회, 저회 저장할 때 사용하는 Datasource (resources-dev, resources-prod/application.properties 참조)
<3> Quartz 전용 TransactionManager
<4> Quartz 설정 파일 세팅
<5> SchedulerFactoryBean 에서 실행 할 Trigger
== JobDetail 및 Trigger Bean 등록
아래는 KnoxSyncBatchConfig에 수행할 Job 클래스를 설정한 JobDetail Bean과 Trigger Bean을 등록한 예이다.
.KnoxSyncBatchConfig.java
[source, java]
----
@Value("${knox.approval.sync.cron}")
private String knoxApprovalSyncCron;
@Bean
public JobDetail knoxApprovalSyncJobDetail() {
return JobBuilder
.newJob(KnoxSyncBatchExecutor.class) <1>
.withIdentity("knoxApprovalSyncJob")
.withDescription("Knox Approval Sync Batch")
.storeDurably(true)
.build();
}
@Bean
public CronTriggerFactoryBean knoxApprovalSyncTrigger() {
CronTriggerFactoryBean approvalTrigger = new CronTriggerFactoryBean();
approvalTrigger.setJobDetail(knoxApprovalSyncJobDetail());
approvalTrigger.setCronExpression(knoxApprovalSyncCron);
return approvalTrigger;
}
----
<1> 배치잡 실행시 QuartzJobBean을 상속 받은 KnoxSyncBatchExecutor의 executeInternal 메소드가 실행된다.
== Job 구현
QuartzJobBean은 Spring Bean을 DI Job의 구현체에서 사용할 수 있도록 Job을 구현한 추상 클래스이며,
QuartzJobBean을 상속 받아 executeInternal 메소드를 오버라이드하여 실행할 배치 Service의 메소드를 호출한다.
.KnoxSyncBatchExecutor.java
[source,java]
----
@Component
@Log4j2
public class KnoxSyncBatchExecutor extends QuartzJobBean {
@Value("${node-id}")
private String nodeId;
@Autowired
private KnoxApprovalSyncService knoxApprovalSyncService;
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
JobDetail jobDetail = jobExecutionContext.getJobDetail();
log.info(new StringBuilder()
.append("nodeId : ").append(nodeId).append(", ")
.append("jobName : ").append(jobDetail.getKey().getName()).append(", ")
.append("description : ").append(jobDetail.getDescription())
);
knoxApprovalSyncService.synchronizeKnox();
}
}
----
CAUTION: UserBatchExecutor의 경우 JobKey(JobBuilder.withIdentity 설정 값)를 이용해 분기 처리 하고 있으므로 Job 등록시 Name 명 지정에 주의하도록 한다.
.UserBatchConfig.java
[source, java]
----
/**
* 장기 미사용자 관리 Job
*/
@Bean
public JobDetail batchUserLongTermCheckJob() {
return JobBuilder
.newJob(UserBatchExecutor.class)
.withIdentity("batchUserLongTermCheck")
.withDescription("User LongTerm Check Batch")
.storeDurably(true)
.build();
}
/**
* 만료 권한 삭제 Job
*/
@Bean
public JobDetail batchUserAuthExpiredJob() {
return JobBuilder
.newJob(UserBatchExecutor.class)
.withIdentity("batchUserAuthExpired")
.withDescription("User Auth Expired Batch")
.storeDurably(true)
.build();
}
----
.UserBatchExecutor.java
[source, java]
----
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) {
JobDetail jobDetail = jobExecutionContext.getJobDetail();
String jobName = jobDetail.getKey().getName();
log.info(new StringBuilder()
.append("nodeId : ").append(nodeId).append(", ")
.append("jobName : ").append(jobName).append(", ")
.append("description : ").append(jobDetail.getDescription())
);
switch (jobName) {
case "batchUserLongTermCheck" -> userBatchService.execUserLongTermCheck();
case "batchUserAuthExpired" -> userBatchService.execUserAuthValidCheck();
case "batchUserAuthExpiredMailing" -> {
try {
userBatchService.execUserAuthValidAlarmMail();
} catch (IOException | MessagingException e) {
log.error(e.getMessage(), e);
}
}
default -> {
}
}
}
----
+46
View File
@@ -0,0 +1,46 @@
= Redis 설정
== RedisConfig
SDL은 어플리케이션 속도 향상을 위해 자주 사용하는 Data를 Cache하고 있다.
기본적으로 Local, Dev 환경에서는 Spring에서 제공하고 있는 ConcurrentMapCacheManager를 사용해
Data를 Cache하고 있지만 Clustering 환경에서 Cache Replicated를 위해서
Redis를 사용한다.
IMPORTANT: Ehcache 3부터 RMI, JMS 방식이 Cache 동기화 지원이 중단 됐으니 Cache 복제가 필요한 경우 Redis를 반드시 사용해야 한다.
application.properties
----
spring.cache.type=redis
spring.data.redis.host=redis 서버 주소
spring.data.redis.port=redis 포트
----
RedisConfig
----
@Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
return (builder) -> builder
.withCacheConfiguration("message-all",
RedisCacheConfiguration.defaultCacheConfig())
.withCacheConfiguration("message",
RedisCacheConfiguration.defaultCacheConfig())
.withCacheConfiguration("menu-all",
RedisCacheConfiguration.defaultCacheConfig())
.withCacheConfiguration("menu-label-all",
RedisCacheConfiguration.defaultCacheConfig())
.withCacheConfiguration("menu-full-path-all",
RedisCacheConfiguration.defaultCacheConfig())
.withCacheConfiguration("page-full-path-all",
RedisCacheConfiguration.defaultCacheConfig())
.withCacheConfiguration("api-full-path-all",
RedisCacheConfiguration.defaultCacheConfig())
.withCacheConfiguration("api-user",
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(300)))
.withCacheConfiguration("api-user-menu",
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(300)))
.withCacheConfiguration("page-all-by-menu-auth",
RedisCacheConfiguration.defaultCacheConfig());
}
----
각 Cache별 필요한 세팅은 redisCacheManagerBuilderCustomizer 메소드에서 할수 있으니 시스템 환경에 맞춰 적절하게 수정한다.
+272
View File
@@ -0,0 +1,272 @@
= UI 라이브러리(uiSDL)
== UI 라이브러리(uiSDL)란?
UI라이브러리(uiSDL)은 웹 UI 개발 리드타임 단축을 위하여 실무에 유용한 컴포넌트들을 담은 UI라이브러리입니다. +
Vue2 및 Vue3 용 라이브러리가 나뉘어져 있습니다.
NOTE: UI라이브러리 시스템 접속 +
vue2 : http://uisdl.scp.samsung.net/v2/[window=_blnak] +
vue3 : http://uisdl.scp.samsung.net/v3/[window=_blnak]
IMPORTANT: 접속이 안되는 경우 window host 파일에 아래와 같이 추가해 주시기 바랍니다 +
10.195.53.147 uisdl.scp.samsung.net
== 컴포넌트 주요 특징
- 삼성전자 웹 시스템 개발 시 빠르게 웹 화면을 개발할 수 있도록 사용빈도가 높은 UI 환경 구성 요소들을 오픈 소스 Framework인 Vue.js와 Bootstrap 기반으로 제작하여 제공합니다.
- 시스템의 특성에 맞게 제작이 가능하도록 화면의 구조인 레이아웃 6종과 단일 UI구성요소인 컴포넌트 37종으로 구성되어 있습니다
+
image::uisdl_1.png[]
- 각 페이지 화면의 우측 상단에 개발과 디자인 탭으로 구분되어 있습니다.
+
개발 탭에서는 개발 가이드와 간단한 기능 테스트를 해볼 수 있는 Code Demo 가 있고, 디자인 탭에서는 전사 UX 표준을 바탕으로 디자인 원칙과 구성요소들을 확인할 수 있습니다.
+
image::uisdl_2.png[]
== 컴포넌트 개발 환경
* VueJS 버전 3
* Used Open-source
** bootstrap: 5.3.0 or Higher
** vue-datepicker-next: 1.0.3 or Higher
== 컴포넌트 적용 방법
. Install from Local file
+
로컬 파일로부터 컴포넌트를 설치할 수 있습니다. 아래의 경로에서 파일을 다운로드 받습니다.
+
http://uisdl.scp.samsung.net/v3/download/sdl-component-3.0.0.tgz[sdl-component-3.0.0.tgz 다운로드]
+
해당 파일을 로컬의 임의의 위치로 복사한 다음, NPM 명령어로 컴포넌트를 설치합니다. 설치하실 프로젝트 루트 폴더로 이동한 다음, 아래 명령어를 커맨드 창에서 입력합니다.
+
[source, shell]
----
npm install --save ./{your-path}/sdl-component-{version}.tgz
----
. Import bootstrap
+
'bootstrap' 관련 스타일과 스크립트를 Entry Point 파일에 Import 합니다.
+
[source, javascript]
----
// bootstrap css등록 및 popper가 포함된 bootstrap bundle js 임포트
import 'bootstrap/dist/css/bootstrap.min.css';
import 'bootstrap/dist/js/bootstrap.bundle.min';
----
. Import sdl-component
+
'sdl-component' 관련 스타일과 스크립트를 Entry Point 파일에 Import 하고
+
[source, javascript]
----
import 'sdl-component/src/assets/css/custom.css';
import SdlComponent from 'sdl-component';
----
+
전역 Vue(app)에서 사용할 수 있도록 Plugin을 등록합니다.
+
[source, javascript]
----
app.use(SdlComponent);
----
. Complete
+
이제 시스템 전역에서 'sdl-component'를 사용하실 수 있습니다
== UI 라이브러리 목록
활용빈도, 사용성을 고려하여 사내시스템 개발에 필요한 Library를 제공합니다.
* 레이아웃
+
화면의 구조를 정의하는 레이아웃 유형
+
[cols="7,33,60",options="header"]
|===
|No |Library |설명
|1
|GNB + Contents
|GNB와 콘텐츠로 구성된 화면 형태
|2
|GNB + LNB + Contents
|GNB, LNB, 콘텐츠로 구성된 화면 형태
|3
|GNB + LNB + Contents + Footer
|GNB, LNB, Footer, 콘텐츠로 구성된 화면 형태
|4
|GNB + LNB + Aside + Contents + Footer
|GNB, LNB, Footer, Aside, 콘텐츠로 구성된 화면 형태
|5
|Popup
|현재 화면에서 추가적으로 띄우는 레이어 형태의 창으로, 사용 유형에 따라 구별하여 사용
|6
|Center Frame
|본 화면의 상하좌우 중앙에 콘텐츠 박스가 위치
|===
* 컴포넌트
+
화면을 구성하는 요소 단위
+
[cols="7,33,60",options="header"]
|===
|No |Library |설명
|1
|GNB
|(Global Navigation Bar) 시스템 명과 메뉴를 포함한 네비게이션으로 화면의 최 상단에 구성
|2
|LNB
|(Left Navigation Bar) 서브메뉴라고 불리며 화면의 좌측에 구성
|3
|Aside
|자주 사용하는 기능을 빠르게 접근(Quick Access)할 수 있도록 제공하는 영역
|4
|Footer
|Copyright, 사이트 맵, 이용약관 등의 정보를 포함하며, 화면의 하단에 구성
|5
|Card List
|한 레이아웃에서 여러 개의 카드를 함께 사용할 때 사용
|6
|Search Grid
|검색조건을 입력/선택하는 조회 영역과 데이터결과를 호출하는 리스트영역으로 구성할 때 사용
|7
|Form
|여러 개의 컨트롤(Input Box, Radio Button, Dropdown Box 등)를 활용하여 하나의 입력 폼으로 구성할 때 사용
|8
|Form Detail
|폼 화면에서 입력한 정보를 화면에 표시할 때 사용
|9
|Board Detail
|게시판 게시물의 기본적인 상세 화면 
|10
|Tree Detail
|계층 구조를 나타내는 트리 요소와 트리에서 선택한 정보를 페이지 전환 없이 즉각적으로 보여줄 때 사용
|11
|Create Board
|게시판 게시물의 기본적인 입력 화면
|12
|Approval
|시스템에서 Knox 결재나 시스템 내부 결재시 사용
|13
|Reply
|간단하게 사용자 간의 의사소통을 할 수 있는 폼 형태로 구성
|14
|Notice
|사용자에게 시스템 내 서비스 관련 정보들을 공지 시 사용
|15
|Alert
|공지나 안내 사항 같은 부가적인 정보, 경고 상태 혹은 사용자의 행동에 대한 즉각적인 피드백이 필요할 때 사용
|16
|Badge
|알림 목적으로 사용하거나, 데이터를 시각적으로 강조하고 싶을 때 사용
|17
|Button
|사용자가 클릭하여 기능을 수행할 수 있는 그래픽 요소
|18
|Card
|다양한 정보들을 그룹화하여 하나의 카드 모양 안에 구성할 때 사용
|19
|Collapse(Toggle)
|사용자가 두개의 반대되는 상태 중에서 선택할 수 있는 온오프 스위치
|20
|Tooltip
|페이지 요소 또는 기능에 대한 추가 정보를 제공하는 간단하고 유용한 메시지
|21
|Text Input
|입력 폼 요소 중 한 줄의 비교적 짧은 데이터를 입력할 경우 사용
|22
|Textarea
|입력 폼 요소 중 데이터의 내용이 일정하지 않거나, 여러 줄의 데이터를 입력할 경우 사용
|23
|Radio
|두개 이상의 항목 중 하나를 선택할 때 사용
|24
|Select
|사용자가 옵션 중 하나 또는 다중으로 선택할 수 있을 때 사용
|25
|Checkbox
|여러 개의 항목 중 하나 이상을 다중 선택할 때 사용하거나 On/Off의 Toggle 경우에 사용
|26
|Table
|데이터/정보를 열과 행으로 구분하여 사용자가 쉽게 읽고 이해할 수 있도록 제공
|27
|Progress
|사용자 액션에 대한 즉각 데이터가 표시가 어려운 경우(데이터의 로딩 처리 시간이 긴 경우) 작업의 진행 상태에 대한 정확한 피드백을 제공하기 위해 사용
|28
|Spinner
|사용자 액션에 대한 즉각 데이터가 표시가 어려운 경우(데이터의 로딩 처리 시간이 긴 경우) 작업의 진행 상태에 대한 정확한 피드백을 제공하지 못할 경우 사용
|29
|Navbar
|시스템 명과 메뉴를 포함한 네비게이션 영역으로 주로 상단(GNB)에 제공
|30
|File Attachment
|사용자가 첨부파일을 다운받거나 업로드할 경우에 사용
|31
|Tree
|목록의 계층 구조를 표현하기 위해 사용하는 요소
|32
|Datepicker
|날짜를 선택하고 그에 해당하는 데이터를 얻을 수 있도록 위젯 형태로 제공
|33
|Breadcrumb
|시스템 내 사용자의 메뉴의 이동경로를 의미하며 현재 사용자의 위치를 보여줄 때 사용
|34
|Pagination
|많은 양의 데이터를 여러 페이지로 나누고, 이전, 다음 페이지 혹은 특정 페이지로 이동할 수 있는 일련의 링크를 목록 하단에 배치하여 제공
|35
|Tab(Basic, Progress)
|페이지 이동 및 시점 전환 없이 그룹화된 콘텐츠를 제공할 때 사용
|36
|Jumbotron
|어떤 특별한 내용이나 정보를 눈에 띄게 보여주기 위한 박스 형태의 공간
|37
|Carousel
|한 공간에 여러 개의 콘텐츠를 슬라이드 형태로 제공
|===
+460
View File
@@ -0,0 +1,460 @@
/* @import "https://fonts.googleapis.com/css?family=Karla:400,700|Montserrat:400,700"; !normalize.css v2.1.2 | MIT License | git.io/normalize*/
@font-face {
font-family: 'D2 coding';
font-style: normal;
font-weight: 400;
src: url('../fonts/D2Coding.eot');
src: local('※'), local('D2Coding'),
url('../fonts/D2Coding.eot?#iefix') format('embedded-opentype'),
url('../fonts/D2Coding.woff') format('woff');
}
@font-face {
font-family: 'D2 coding';
font-style: normal;
font-weight: 700;
src: url('../fonts/D2CodingBold.eot');
src: local('※'), local('D2Coding Bold'),
url('../fonts/D2CodingBold.eot?#iefix') format('embedded-opentype'),
url('../fonts/D2CodingBold.woff') format('woff');
}
article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}
audio,video{display:inline-block}
audio:not([controls]){display:none;height:0}
html{font-family:"D2 coding";-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}
a{background:none}
a:focus{outline:thin dotted}
a:active,a:hover{outline:0}
h1{font-size:2em;margin:.67em 0}
abbr[title]{border-bottom:1px dotted}
b,strong{font-weight:bold}
dfn{font-style:italic}
hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}
mark{background:#ff0;color:#000}
code,kbd,pre,samp{font-family:"D2 coding";font-size:1em}
pre{white-space:pre-wrap}
q{quotes:"\201C" "\201D" "\2018" "\2019"}
small{font-size:80%}
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
sup{top:-.5em}
sub{bottom:-.25em}
img{border:0}
svg:not(:root){overflow:hidden}
figure{margin:0}
fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
legend{border:0;padding:0}
button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
button,input{line-height:normal}
button,select{text-transform:none}
button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}
button[disabled],html input[disabled]{cursor:default}
input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
textarea{overflow:auto;vertical-align:top}
table{border-collapse:collapse;border-spacing:0}
*,*::before,*::after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
html,body{font-size:14px}
body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"D2 coding";font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto;tab-size:4;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
a:hover{cursor:pointer}
img,object,embed{max-width:100%;height:auto}
object,embed{height:100%}
img{-ms-interpolation-mode:bicubic}
.left{float:left!important}
.right{float:right!important}
.text-left{text-align:left!important}
.text-right{text-align:right!important}
.text-center{text-align:center!important}
.text-justify{text-align:justify!important}
.hide{display:none}
img,object,svg{display:inline-block;vertical-align:middle}
textarea{height:auto;min-height:50px}
select{width:100%}
.center{margin-left:auto;margin-right:auto}
.stretch{width:100%}
.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;font-weight:800;margin-top:0;margin-bottom:.25em}
div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr}
a{color:#2156a5;text-decoration:underline;line-height:inherit}
a:hover,a:focus{color:#1d4b8f}
a img{border:0}
p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
p aside{font-size:.875em;line-height:1.35;font-style:italic}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-weight:300;font-style:normal;color:inherit;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
h1{font-size:2.125em}
h2{font-size:1.6875em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
h4,h5{font-size:1.125em}
h6{font-size:1em}
hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0}
em,i{font-style:italic;line-height:inherit}
strong,b{font-weight:bold;line-height:inherit}
small{font-size:60%;line-height:inherit}
code{font-family:D2 coding,Monaco,Menlo,Consolas,courier new,monospace;font-weight:400;color:rgba(0,0,0,.9)}
ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
ul,ol{margin-left:1.5em}
ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em}
ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}
ul.square{list-style-type:square}
ul.circle{list-style-type:circle}
ul.disc{list-style-type:disc}
ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
dl dt{margin-bottom:.3125em;font-weight:bold}
dl dd{margin-bottom:1.25em}
abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help}
abbr{text-transform:none}
blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)}
blockquote cite::before{content:"\2014 \0020"}
blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)}
blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
table{background:#fff;margin-bottom:1.25em;border:solid 1px #cacaca;border-spacing:0}
table thead,table tfoot{background:#f7f8f7;font-weight:700}
table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:#000;text-align:left}
table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:#000}
table tr td{word-break:break-all}
table tr.even,table tr.alt{background:#fafafa}
table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6}
body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;tab-size:4}
h1,h2,h3,#toctitle,.sidebarblock > .content > .title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
.clearfix:before,.clearfix:after,.float-group:before,.float-group:after{content:" ";display:table}
.clearfix:after,.float-group:after{clear:both}
:not(pre) > code{font-size:.8525em;font-style:normal!important;letter-spacing:0;padding:.1em .3em .2em;background-color:rgba(27,31,35,.05);border-radius:4px;text-rendering:optimizeSpeed}
pre,pre > code{line-height:1.85;color:rgba(0,0,0,.9);font-family:D2 coding,Monaco,Menlo,Consolas,courier new,monospace;font-weight:400;text-rendering:optimizeSpeed;word-break:normal}
pre{overflow:auto}
em em{font-style:normal}
strong strong{font-weight:400}
.keyseq{color:#6b625c}
kbd{font-family:D2 coding,Monaco,Menlo,Consolas,courier new,monospace;display:inline-block;color:#000;font-size:.65em;line-height:1.45;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
.keyseq kbd:first-child{margin-left:0}
.keyseq kbd:last-child{margin-right:0}
.menuseq,.menu{color:#191715}
b.button:before,b.button:after{position:relative;top:-1px;font-weight:400}
b.button:before{content:"[";padding:0 3px 0 2px}
b.button:after{content:"]";padding:0 2px 0 3px}
p a > code:hover{color:rgba(0,0,0,.9)}
#toc{border-bottom:1px solid #ddddd8;padding-bottom:.5em}
#toc > ul{margin-left:.125em}
#toc ul.sectlevel0 > li > a{font-style:italic}
#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
#toc ul{list-style-type:none}
#toc li{line-height:1.3334}
#toc a{text-decoration:none}
#toc a:active{text-decoration:underline}
#toctitle{color:#0b0a0a;font-size:1.2em}
body.toc2{padding-top:90px;text-rendering:optimizeLegibility}
#content #toc{border-style:solid;border-width:1px;border-color:#d7d7d7;margin-bottom:1.25em;padding:1.25em;background:#f1f1f1;-webkit-border-radius:4px;border-radius:4px}
#content #toc > :first-child{margin-top:0}
#content #toc > :last-child{margin-bottom:0}
#footer{padding-bottom:2rem}
#footer #footer-text{padding:2rem 0;border-top:1px solid #efefed}
#footer-text{color:rgba(0,0,0,.6);line-height:1.44}
.sect1{padding-bottom:.625em}
.sect1 + .sect1{border-top:1px solid #efefed}
#content h1 > a.anchor,h2 > a.anchor,h3 > a.anchor,#toctitle > a.anchor,.sidebarblock > .content > .title > a.anchor,h4 > a.anchor,h5 > a.anchor,h6 > a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;margin-top:.1rem;display:block;visibility:hidden;text-align:center;font-weight:400;color:rgba(0,0,0,.2)}
#content h1 > a.anchor:hover,h2 > a.anchor:hover,h3 > a.anchor:hover,#toctitle > a.anchor:hover,.sidebarblock > .content > .title > a.anchor:hover,h4 > a.anchor:hover,h5 > a.anchor:hover,h6 > a.anchor:hover{color:#097dff;text-decoration:none}
#content h1 > a.anchor:before,h2 > a.anchor:before,h3 > a.anchor:before,#toctitle > a.anchor:before,.sidebarblock > .content > .title > a.anchor:before,h4 > a.anchor:before,h5 > a.anchor:before,h6 > a.anchor:before{content:"\0023";font-size:.85em;display:block;padding-top:.1em}
#content h1:hover > a.anchor,#content h1 > a.anchor:hover,h2:hover > a.anchor,h2 > a.anchor:hover,h3:hover > a.anchor,#toctitle:hover > a.anchor,.sidebarblock > .content > .title:hover > a.anchor,h3 > a.anchor:hover,#toctitle > a.anchor:hover,.sidebarblock > .content > .title > a.anchor:hover,h4:hover > a.anchor,h4 > a.anchor:hover,h5:hover > a.anchor,h5 > a.anchor:hover,h6:hover > a.anchor,h6 > a.anchor:hover{visibility:visible}
#content h1 > a.link,h2 > a.link,h3 > a.link,#toctitle > a.link,.sidebarblock > .content > .title > a.link,h4 > a.link,h5 > a.link,h6 > a.link{color:#000;text-decoration:none}
#content h1 > a.link:hover,h2 > a.link:hover,h3 > a.link:hover,#toctitle > a.link:hover,.sidebarblock > .content > .title > a.link:hover,h4 > a.link:hover,h5 > a.link:hover,h6 > a.link:hover{color:#262321}
.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
.admonitionblock td.content > .title,.audioblock > .title,.exampleblock > .title,.imageblock > .title,.listingblock > .title,.literalblock > .title,.stemblock > .title,.openblock > .title,.paragraph > .title,.quoteblock > .title,table.tableblock > .title,.verseblock > .title,.videoblock > .title,.dlist > .title,.olist > .title,.ulist > .title,.qlist > .title,.hdlist > .title{text-rendering:optimizeLegibility;text-align:left;font-size:1rem}
table.tableblock > caption.title{white-space:nowrap;overflow:visible;max-width:0;padding:.6rem 0}
table.tableblock #preamble > .sectionbody > .paragraph:first-of-type p{font-size:inherit}
.admonitionblock > table{border-collapse:separate;border:0;background:0 0;width:100%}
.admonitionblock > table td.icon{text-align:center;vertical-align:top;padding-top:.8em;width:80px}
.admonitionblock > table td.icon img{max-width:initial}
.admonitionblock > table td.icon .title{font-weight:700;text-transform:uppercase}
.admonitionblock > table td.content{padding-left:0;padding-right:1.25em;border-left:1px solid #ddddd8}
.admonitionblock > table td.content > :last-child > :last-child{margin-bottom:0}
.exampleblock > .content{border-style:solid;border-width:0;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#f1f1f1;border-radius:4px}
.exampleblock > .content > :first-child{margin-top:0}
.exampleblock > .content > :last-child{margin-bottom:0}
.sidebarblock{border-style:solid;border-width:0;border-color:#d7d7d7;margin-bottom:1.25em;padding:1.25em;background:#f1f1f1;border-radius:4px;overflow:scroll}
.sidebarblock > :first-child{margin-top:0}
.sidebarblock > :last-child{margin-bottom:0}
.sidebarblock > .content > .title{color:#0b0a0a;margin-top:0;text-align:center}
.exampleblock > .content > :last-child > :last-child,.exampleblock > .content .olist > ol > li:last-child > :last-child,.exampleblock > .content .ulist > ul > li:last-child > :last-child,.exampleblock > .content .qlist > ol > li:last-child > :last-child,.sidebarblock > .content > :last-child > :last-child,.sidebarblock > .content .olist > ol > li:last-child > :last-child,.sidebarblock > .content .ulist > ul > li:last-child > :last-child,.sidebarblock > .content .qlist > ol > li:last-child > :last-child{margin-bottom:0}
.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class=highlight],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#f2f2f2;color:#222;border-radius:4px}
.sidebarblock .literalblock pre,.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class=highlight],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint{background:#f2f2f2;color:#222}
.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class],.listingblock pre:not(.highlight){padding:1em 1.5rem;font-size:.9285em}
.literalblock pre.nowrap,.literalblock pre[class].nowrap,.listingblock pre.nowrap,.listingblock pre[class].nowrap{overflow-x:auto}
.literalblock.output pre{color:#f5f5f5;background-color:rgba(0,0,0,.9)}
.listingblock{white-space:nowrap}
.listingblock pre.highlightjs{padding:.2rem 0}
.listingblock pre.highlightjs > code{padding:1em 1.5rem;border-radius:4px}
.listingblock > .content{position:relative}
.listingblock code[data-lang]:before{content:attr(data-lang);position:absolute;font-size:.8em;font-weight:700;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:#999;display:block}
.listingblock.terminal pre .command:before{content:attr(data-prompt);padding-right:.5em;color:#999}
.listingblock.terminal pre .command:not([data-prompt]):before{content:"$"}
table.pyhltable{border-collapse:separate;border:0;margin-bottom:0;background:0 0}
table.pyhltable td{vertical-align:top;padding-top:0;padding-bottom:0;line-height:1.45}
table.pyhltable td.code{padding-left:.75em;padding-right:0}
pre.pygments .lineno,table.pyhltable td:not(.code){color:#999;padding-left:0;padding-right:.5em;border-right:1px solid #ddddd8}
pre.pygments .lineno{display:block;margin-right:.25em}
table.pyhltable .linenodiv{background:0 0!important;padding-right:0!important}
.quoteblock{margin:0 1em 1.25em 1.5em;display:block;text-align:left;padding-left:20px}
.quoteblock blockquote,.quoteblock blockquote p{color:rgba(0,0,0,.85);line-height:1.75;letter-spacing:0}
.quoteblock blockquote{margin:0;padding:0;border:0;position:relative}
.quoteblock blockquote:before{content:"\201c";font-size:2.75em;font-weight:700;line-height:.6em;margin-left:0;margin-right:1rem;margin-top:.8rem;color:rgba(0,0,0,.1);position:absolute;top:0;left:-30px}
.quoteblock blockquote > .paragraph:last-child p{margin-bottom:0}
.quoteblock .attribution{margin-right:.5ex}
.quoteblock .quoteblock{margin-left:0;margin-right:0;padding:.5em 0;border-left:3px solid rgba(0,0,0,.6)}
.quoteblock .quoteblock blockquote{padding:0 0 0 .75em}
.quoteblock .quoteblock blockquote:before{display:none}
.verseblock{margin:0 1em 1.25em 0;background-color:#f1f1f1;padding:1rem 1.4rem;border-radius:4px}
.verseblock pre{font-family:D2 coding,Monaco,Menlo,Consolas,courier new,monospace;font-size:.9rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
.verseblock pre strong{font-weight:400}
.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
.quoteblock .attribution br,.verseblock .attribution br{display:none}
.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
.quoteblock.abstract{margin:0 0 1.25em;display:block}
.quoteblock.abstract blockquote,.quoteblock.abstract blockquote p{text-align:left;word-spacing:0}
.quoteblock.abstract blockquote:before,.quoteblock.abstract blockquote p:first-of-type:before{display:none}
table.tableblock{max-width:100%;border-collapse:separate;overflow-x:scroll}
table.tableblock td > .paragraph:last-child p > p:last-child,table.tableblock th > p:last-child,table.tableblock td > p:last-child{margin-bottom:0}
table.tableblock,th.tableblock,td.tableblock{border:0 solid #cacaca}
table.grid-all th.tableblock,table.grid-all td.tableblock{border-width:0 1px 1px 0}
table.grid-all tfoot > tr > th.tableblock,table.grid-all tfoot > tr > td.tableblock{border-width:1px 1px 0 0}
table.grid-cols th.tableblock,table.grid-cols td.tableblock{border-width:0 1px 0 0}
table.grid-all * > tr > .tableblock:last-child,table.grid-cols * > tr > .tableblock:last-child{border-right-width:0}
table.grid-rows th.tableblock,table.grid-rows td.tableblock{border-width:0 0 1px}
table.grid-all tbody > tr:last-child > th.tableblock,table.grid-all tbody > tr:last-child > td.tableblock,table.grid-all thead:last-child > tr > th.tableblock,table.grid-rows tbody > tr:last-child > th.tableblock,table.grid-rows tbody > tr:last-child > td.tableblock,table.grid-rows thead:last-child > tr > th.tableblock{border-bottom-width:0}
table.grid-rows tfoot > tr > th.tableblock,table.grid-rows tfoot > tr > td.tableblock{border-width:1px 0 0}
table.frame-all{border-width:1px}
table.frame-sides{border-width:0 1px}
table.frame-topbot{border-width:1px 0}
th.halign-left,td.halign-left{text-align:left}
th.halign-right,td.halign-right{text-align:right}
th.halign-center,td.halign-center{text-align:center}
th.valign-top,td.valign-top{vertical-align:top}
th.valign-bottom,td.valign-bottom{vertical-align:bottom}
th.valign-middle,td.valign-middle{vertical-align:middle}
table thead th,table tfoot th{font-weight:700}
tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7}
tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:#34302d;font-weight:700}
p.tableblock{font-size:1em}
td > div.verse{white-space:pre}
ol{margin-left:1.75em}
ul li ol{margin-left:1.5em}
dl dd{margin-left:1.125em}
dl dd:last-child,dl dd:last-child > :last-child{margin-bottom:0}
ol > li p,ul > li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
ul.unstyled,ol.unnumbered,ul.checklist,ul.none{list-style-type:none}
ul.unstyled,ol.unnumbered,ul.checklist{margin-left:.625em}
ul.checklist li > p:first-child > .fa-square-o:first-child,ul.checklist li > p:first-child > .fa-check-square-o:first-child{width:1em;font-size:.85em}
ul.checklist li > p:first-child > input[type=checkbox]:first-child{width:1em;position:relative;top:1px}
ul.inline{margin:0 auto .625em;margin-left:-1.375em;margin-right:0;padding:0;list-style:none;overflow:hidden}
ul.inline > li{list-style:none;float:left;margin-left:1.375em;display:block}
ul.inline > li > *{display:block}
.unstyled dl dt{font-weight:400;font-style:normal}
ol.arabic{list-style-type:decimal}
ol.decimal{list-style-type:decimal-leading-zero}
ol.loweralpha{list-style-type:lower-alpha}
ol.upperalpha{list-style-type:upper-alpha}
ol.lowerroman{list-style-type:lower-roman}
ol.upperroman{list-style-type:upper-roman}
ol.lowergreek{list-style-type:lower-greek}
.hdlist > table,.colist > table{border:0;background:0 0}
.hdlist > table > tbody > tr,.colist > table > tbody > tr{background:0 0}
td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
td.hdlist1{font-weight:700;padding-bottom:1.25em}
.literalblock + .colist,.listingblock + .colist{margin-top:-.5em}
.colist > table tr > td:first-of-type{padding:0 .75em;line-height:1}
.colist > table tr > td:first-of-type img{max-width:initial}
.colist > table tr > td:last-of-type{padding:.25em 0}
.thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd}
.imageblock.left,.imageblock[style*="float: left"]{margin:.25em .625em 1.25em 0}
.imageblock.right,.imageblock[style*="float: right"]{margin:.25em 0 1.25em .625em}
.imageblock > .title{margin-bottom:0}
.imageblock.thumb,.imageblock.th{border-width:6px}
.imageblock.thumb > .title,.imageblock.th > .title{padding:0 .125em}
.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
.image.left{margin-right:.625em}
.image.right{margin-left:.625em}
a.image{text-decoration:none;display:inline-block}
a.image object{pointer-events:none}
sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
sup.footnote a,sup.footnoteref a{text-decoration:none}
sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;text-indent:-1.05em;margin-bottom:.2em}
#footnotes .footnote a:first-of-type{font-weight:700;text-decoration:none}
#footnotes .footnote:last-of-type{margin-bottom:0}
#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
.gist .file-data > table{border:0;background:#fff;width:100%;margin-bottom:0}
.gist .file-data > table td.line-data{width:99%}
div.unbreakable{page-break-inside:avoid}
.big{font-size:larger}
.small{font-size:smaller}
.underline{text-decoration:underline}
.overline{text-decoration:overline}
.line-through{text-decoration:line-through}
.aqua{color:#00bfbf}
.aqua-background{background-color:#00fafa}
.black{color:#000}
.black-background{background-color:#000}
.blue{color:#0000bf}
.blue-background{background-color:#0000fa}
.fuchsia{color:#bf00bf}
.fuchsia-background{background-color:#fa00fa}
.gray{color:#606060}
.gray-background{background-color:#7d7d7d}
.green{color:#006000}
.green-background{background-color:#007d00}
.lime{color:#00bf00}
.lime-background{background-color:#00fa00}
.maroon{color:#600000}
.maroon-background{background-color:#7d0000}
.navy{color:#000060}
.navy-background{background-color:#00007d}
.olive{color:#606000}
.olive-background{background-color:#7d7d00}
.purple{color:#600060}
.purple-background{background-color:#7d007d}
.red{color:#bf0000}
.red-background{background-color:#fa0000}
.silver{color:#909090}
.silver-background{background-color:#bcbcbc}
.teal{color:#006060}
.teal-background{background-color:#007d7d}
.white{color:#bfbfbf}
.white-background{background-color:#fafafa}
.yellow{color:#bfbf00}
.yellow-background{background-color:#fafa00}
span.icon > .fa{cursor:default}
.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;cursor:default}
.admonitionblock td.icon .icon-note:before{content:"\f05a";color:#8979ca}
.admonitionblock td.icon .icon-tip:before{content:"\f0eb";color:#0077b9}
.admonitionblock td.icon .icon-warning:before{content:"\f071";color:#d88400}
.admonitionblock td.icon .icon-caution:before{content:"\f06d";color:#bf3400}
.admonitionblock td.icon .icon-important:before{content:"\f06a";color:#bf0000}
.conum[data-value]{display:inline-block;color:#000!important;background-color:#ffe157;-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-style:normal;font-weight:700}
.conum[data-value] *{color:#fff!important}
.conum[data-value] + b{display:none}
.conum[data-value]:after{content:attr(data-value)}
pre .conum[data-value]{position:relative;top:0;color:#000!important;background-color:#ffe157;font-size:12px}
b.conum *{color:inherit!important}
.conum:not([data-value]):empty{display:none}
.admonitionblock{background-color:#ebe7f8;padding:.8em 0;margin:30px 0;width:auto;border-radius:4px;overflow-x:auto}
.admonitionblock.important{border-left:0 solid #e20000;background-color:#f9ebeb}
.admonitionblock.warning{border-left:0 solid #d88400;background-color:#fff9e4}
.admonitionblock.tip{border-left:0 solid #0077b9;background-color:#e9f1f6}
.admonitionblock.caution{border-left:0 solid #e20000;background-color:#f9ebeb}
.admonitionblock .exampleblock > .content{border:0;background-color:#fff}
#toc a:hover{text-decoration:underline}
.admonitionblock > table{margin-bottom:0}
.admonitionblock > table td.content{border-left:none}
@media print {
#tocbot a.toc-link.node-name--H4{display:none}
}
.is-collapsible{max-height:1000px;overflow:hidden;transition:all 200ms ease-in-out}
.is-collapsed{max-height:0}
#index-link{display:none}
ul li > p > a > code{color:#097dff}
ul li > p > a:hover > code{color:#097dff}
div.back-action,#toc.toc2 div.back-action{padding:.8rem 0 0 5px}
div.back-action a,#toc.toc2 div.back-action a{position:relative;display:inline-block;padding:.6rem 1.2rem;padding-left:35px}
div.back-action a span,#toc.toc2 div.back-action a span{position:absolute;left:5px;top:5px;display:block;color:#333;height:26px;width:26px;border-radius:13px}
div.back-action a i,#toc.toc2 div.back-action a i{position:absolute;top:10px;left:5px}
div.back-action a:hover span,#toc.toc2 div.back-action a:hover span{color:#000}
#tocbot.desktop-toc{padding-top:.8rem}
#header-spring{position:absolute;text-rendering:optimizeLegibility;top:0;left:0;right:0;height:90px;margin:0 1rem;padding:0 1rem;border-bottom:1px solid #ddddd8;border-top:3px solid #9742ec}
#header-spring h1{margin:0;padding:0;font-size:22px;text-align:left;line-height:86px;padding-left:.6rem}
#header-spring h1 svg{width:200px}
#header-spring h1 svg .st0{fill:#6bb344}
#header-spring h1 svg .st2{fill:#444}
body.book #header-spring{position:relative;top:auto;left:auto;right:auto;margin:0}
body.book #header > h1:only-child{border:0;padding-bottom:1.2rem;font-size:1.8rem}
body.book #header,body.book #content,body.book #footnotes,body.book #footer{margin:0 auto}
body.toc2 #header-spring{position:absolute;left:0;right:0;top:0}
body.toc2 #header > h1:only-child{font-size:2.2rem}
body.toc2 #header,body.toc2 #content,body.toc2 #footnotes,body.toc2 #footer{margin:0 auto}
body.toc2 #content{padding-top:2rem}
#header,#content,#footnotes,#footer{width:100%;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
#header:before,#header:after,#content:before,#content:after,#footnotes:before,#footnotes:after,#footer:before,#footer:after{content:" ";display:table}
#header:after,#content:after,#footnotes:after,#footer:after{clear:both}
#content{margin-top:1.25em}
#content:before{content:none}
#header > h1:first-child{margin-top:2.55rem;margin-bottom:.5em}
#header > h1:first-child + #toc{margin-top:8px;border-top:0}
#header > h1:only-child,body.toc2 #header > h1:nth-last-child(2){border-bottom:1px solid #ddddd8;padding-bottom:8px;display:none}
#header .details{border-bottom:1px solid #ddddd8;line-height:1.45;padding-top:0;padding-bottom:2.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap}
#header .details span:first-child{margin-left:-.125em}
#header .details span.email a{color:rgba(0,0,0,.85)}
#header .details br{display:none}
#header .details br + span:before{content:"\00a0\2013\00a0"}
#header .details br + span.author:before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
#header .details br + span#revremark:before{content:"\00a0|\00a0"}
#header #revnumber{text-transform:capitalize}
#header #revnumber:after{content:"\00a0"}
#content > h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #ddddd8;padding-bottom:8px;margin-top:0;padding-top:1.5rem;margin-bottom:1.25rem}
h1{font-size:2.2rem;letter-spacing:-1px}
h1,h2,h3,h4,h5,h6{font-weight:700}
h1:focus,h2:focus,h3:focus,h4:focus,h5:focus,h6:focus{box-shadow:none;outline:none}
h2,h3,h4,h5,h6{padding:.8rem 0 .4rem}
h1{font-size:1.75em}
h2{font-size:1.6rem;letter-spacing:-1px}
h3{font-size:1.5rem}
h4{font-size:1.4rem}
h5{font-size:1.3rem}
h6{font-size:1.2rem}
pre.highlight{background:#f2f2f2;color:#666;border-radius:4px}
pre.highlight code{color:#222}
pre.highlight a,#toc.toc2 a{color:#000;font-size:1rem}
pre.highlight ul.sectlevel1,#toc.toc2 ul.sectlevel1{padding-left:.2rem}
pre.highlight ul.sectlevel1 li,#toc.toc2 ul.sectlevel1 li{line-height:1.4rem}
::selection{background-color:#faefff}
.literalblock pre::selection,.listingblock pre[class=highlight]::selection,.highlight::selection,pre::selection,.highlight code::selection,.highlight code span::selection{background:rgba(0,0,0,.2)!important}
body.book #header{margin-bottom:2rem}
body.toc2 #header{margin-bottom:0}
.desktop-toc{display:none}
.admonitionblock td.icon{display:none}
.admonitionblock > table td.content{padding-left:1.25em}
.brand-symbol{width:24px;height:24px;vertical-align:sub;margin-right:4px;}
@media only screen and (min-width:768px) {
#toctitle{font-size:1.375em}
.sect1{padding-bottom:1.25em}
.mobile-toc{display:none}
.desktop-toc{display:block}
.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}
.admonitionblock td.icon{display:table-cell}
.admonitionblock > table td.content{padding-left:0}
body.toc2{padding-right:0}
body.toc2 #toc.toc2{position:absolute;margin-top:0!important;width:15em;top:0;border-top-width:0!important;border-bottom-width:0!important;margin-left:-15.9375em;z-index:1000;padding:1.25em 1em 1.25em 1.25em;overflow:auto}
body.toc2 #toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
body.toc2 #toc.toc2 > ul{font-size:.9em;margin-bottom:0}
body.toc2 #toc.toc2 ul ul{margin-left:0;padding-left:1em}
body.toc2 #toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
body.toc2 #header,body.toc2 #content,body.toc2 #footnotes,body.toc2 #footer{padding-left:15.9375em;padding-right:1.5rem;max-width:none}
body.book #header-spring h1{max-width:1400px;margin:0 auto}
body.book #header,body.book #content,body.book #footnotes,body.book #footer{max-width:1400px}
body.is-position-fixed #toc.toc2{position:fixed;height:100%}
h1,h2,h3,#toctitle,.sidebarblock > .content > .title,h4,h5,h6{line-height:1.2}
h1{font-size:1.75em}
h2{font-size:1.6em}
h3,#toctitle,.sidebarblock > .content > .title{font-size:1.5em}
h4{font-size:1.4em}
h5{font-size:1.2em}
h6{font-size:1.2em}
#tocbot a.toc-link.node-name--H1{font-style:italic}
#tocbot ol{margin:0;padding:0;padding-left:.6rem}
#tocbot ol li{list-style:none;padding:0;margin:0;display:block}
#tocbot{z-index:999}
#tocbot .toc-link{position:relative;display:block;z-index:999;padding-right:5px;padding-top:4px;padding-bottom:4px}
#tocbot .is-active-link{padding-right:3px;border-right:3px solid #9742ec}
}
@media only screen and (min-width:768px) {
#tocbot > ul.toc-list{margin-bottom:.5em;margin-left:.125em}
#tocbot ul.sectlevel0,#tocbot a.toc-link.node-name--H1 + ul{padding-left:0}
#tocbot a.toc-link{height:100%}
.is-collapsible{max-height:3000px;overflow:hidden}
.is-collapsed{max-height:0}
.is-active-link{font-weight:700}
}
@media only screen and (min-width:768px) {
body.toc2 #header,body.toc2 #content,body.toc2 #footer{background-repeat:repeat-y;background-position:14em 0;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAMAAAAoyzS7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQwIDc5LjE2MDQ1MSwgMjAxNy8wNS8wNi0wMTowODoyMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTggKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RDE0NUNENzNGMTVGMTFFODk5RjI5ODk3QURGRjcxMkEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RDE0NUNENzRGMTVGMTFFODk5RjI5ODk3QURGRjcxMkEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpEMTQ1Q0Q3MUYxNUYxMUU4OTlGMjk4OTdBREZGNzEyQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpEMTQ1Q0Q3MkYxNUYxMUU4OTlGMjk4OTdBREZGNzEyQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PjmGxxYAAAAGUExURd3d2AAAAJlCnKAAAAAMSURBVHjaYmAACDAAAAIAAU9tWeEAAAAASUVORK5CYII=)}
}
@media only screen and (min-width:1280px) {
body.toc2{padding-right:0}body.toc2 #toc.toc2{width:25em;left:auto;margin-left:-26.9375em}body.toc2 #toc.toc2 #toctitle{font-size:1.375em}body.toc2 #toc.toc2 > ul{font-size:.95em}body.toc2 #toc.toc2 ul ul{padding-left:1.25em}body.toc2 body.toc2.toc-right{padding-left:0;padding-right:20em}body.toc2 #header,body.toc2 #content,body.toc2 #footnotes,body.toc2 #footer{padding-left:26.9375em;padding-right:1.5rem;max-width:2000px}body.toc2 #header-spring h1{margin:0 auto;max-width:2000px}.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}body.toc2 #header,body.toc2 #content,body.toc2 #footer{background-repeat:repeat-y;background-position:24em 0;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAMAAAAoyzS7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQwIDc5LjE2MDQ1MSwgMjAxNy8wNS8wNi0wMTowODoyMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTggKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RDE0NUNENzNGMTVGMTFFODk5RjI5ODk3QURGRjcxMkEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RDE0NUNENzRGMTVGMTFFODk5RjI5ODk3QURGRjcxMkEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpEMTQ1Q0Q3MUYxNUYxMUU4OTlGMjk4OTdBREZGNzEyQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpEMTQ1Q0Q3MkYxNUYxMUU4OTlGMjk4OTdBREZGNzEyQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PjmGxxYAAAAGUExURd3d2AAAAJlCnKAAAAAMSURBVHjaYmAACDAAAAIAAU9tWeEAAAAASUVORK5CYII=)}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Some files were not shown because too many files have changed in this diff Show More