星期四, 6月 28, 2007

Spring Acegi Security 備忘記

Acegi 發音為 "Ah-see-gee", Acegi Security 主要應用於 Spring 的認證及授權服務.
Acegi Security 亦解決企業在 J2EE 應用程式安全方面的問題,
能夠對網站 URL 進行用戶認證及授權, 用戶資料來源可以設定成由 memory, files 或 資料庫提供,
結合 CAS (Central Authentication Service) Server, 更能提供 Single Sign On 的功能,


這次備忘記開始時記載如何設定 Tomcat 使用 SSL 服務, [以備日後整合 CAS server]
然後從實際的 Eclipse project 實作中,
實作如何使用 Acegi Security 對 Spring 的 bean method 進行簡單的認證及授權.
用戶資料由 properties file 提供, 密碼經由 Md5PasswordEncoder 產生.

開始備忘記:
[1] 安裝 Eclipse WTP
[2] 安裝 tomcat 及 SSL
[3] Eclipse 建立 Acegi project


[1] 安裝 Eclipse WTP:
下載 wtp-all-in-one-sdk-R-1.5.4-win32.zip
http://download.eclipse.org/webtools/downloads/drops/R1.5/R-1.5.4-200705021353/
解壓縮至 C:\wtp-all-in-one-sdk-R-1.5.4-win32


[2] 安裝 tomcat 及 SSL:
下載 apache-tomcat-5.5.23.zip
http://tomcat.apache.org/download-55.cgi
解壓縮至 C:\apache-tomcat-5.5.23


設定 Eclipse 使用 tomcat 為 web server:
Eclipse
: Window -> Show View -> Servers
右鍵點擊 Servers panel -> New -> Server ->> Apache -> Tomcat v5.5 Server -> Next
Tomcat installation directory 選擇 C:\apache-tomcat-5.5.23
JRE 選擇 jre1.6.0_01
按 Finish 完成.


建立 KeyStore:
建立及進入目錄 C:\keytool
輸入
C:\keytool>keytool -genkey -alias joeytaProject -keyalg RSA -keystore c:\keytool\keystore.jks
輸入 keystore 密碼: joeytaPassword
你的名字與姓氏為何? localhost
其它按 <Enter> 鍵


CN=localhost, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown 正確嗎?
[否]: y


輸入 <tomcat> 的主密碼 (RETURN 如果各 keystore 密碼相同):
按 <Enter> 鍵

新增 Tomcat SSL 設定:
修改 Eclipse:Server -> Tomcat v5.5 Server @ localhost-config -> server.xml


將內文:
<!-- Define a SSL HTTP/1.1 Connector on port 8443 -->
<!--
<Connector port="8443" maxHttpHeaderSize="8192"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" disableUploadTimeout="true"
acceptCount="100" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS" />
-->


修改成:
<!-- Define a SSL HTTP/1.1 Connector on port 8443 -->
<Connector port="8443" maxHttpHeaderSize="8192"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" disableUploadTimeout="true"
acceptCount="100" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="c:\keytool\keystore.jks"
keyAlias="joeytaProject"
keystorePass="joeytaPassword" />


[3] Eclipse 建立 Acegi project:
Eclipse: File -> New -> Other ->> Web -> Dynamic Web Project -> Next
Project Name: FirstAcegi
按 Finish 完成.

下載並解壓縮 spring-framework-2.0.6-with-dependencies.zip
http://www.springframework.org/download


下載並解壓縮 acegi-security-1.0.4.zip
http://sourceforge.net/project/showfiles.php?group_id=104215


將以下 jars 複製至 Eclipse:FirstAcegi/WebContent/WEB-INF/lib 裡:
spring-framework-2.0.6\dist\spring.jar
spring-framework-2.0.6\dist\spring-aspects.jar
spring-framework-2.0.6\dist\spring-mock.jar
spring-framework-2.0.6\lib\log4j\log4j-1.2.14.jar
spring-framework-2.0.6\lib\ehcache\ehcache-1.2.4.jar
spring-framework-2.0.6\lib\jakarta-commons\commons-collections.jar
spring-framework-2.0.6\lib\jakarta-commons\commons-codec.jar


acegi-security-1.0.4\acegi-security-1.0.4.jar



<!------------- web.xml ---------------------->
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="
http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="
http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">

<display-name>FirstAcegi</display-name>


<context-param>
<param-name>webAppRootKey</param-name>
<param-value>FirstAcegi.root</param-value>
</context-param>


<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext.xml
</param-value>
</context-param>

<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/classes/log4j.properties</param-value>
</context-param>

<context-param>
<param-name>log4jExposeWebAppRoot</param-name>
<param-value>false</param-value>
</context-param>

<filter>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>org.acegisecurity.util.FilterChainProxy</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>


<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>


<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>

<listener>
<listener-class>org.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>
</listener>


<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
<!------------- web.xml ---------------------->

<!-------------- applicationContext.xml ------------->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "
http://www.springframework.org/dtd/spring-beans.dtd">
<beans>


<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,rememberMeProcessingFilter,exceptionTranslationFilter
</value>
</property>
</bean>
<!-- 這裡的 /** 為 URL, 可分別對不同的 URL 加入不同的 filter -->


<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="daoAuthenticationProvider" />
<ref local="rememberMeAuthenticationProvider"/>
</list>
</property>
</bean>

<bean id="passwordEncoder" class="org.acegisecurity.providers.encoding.Md5PasswordEncoder"/>

<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="inMemoryDaoImpl" />
<property name="passwordEncoder"><ref local="passwordEncoder"/></property>
</bean>

<bean id="loggerListener" class="org.acegisecurity.event.authentication.LoggerListener"/>

<bean id="inMemoryDaoImpl" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userProperties">
<bean class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="/WEB-INF/users.properties" />
</bean>
</property>
<!--
<property name="userMap">
<value>
joeyta=joeytaPassword,ROLE_ADMIN
joey=joeyPassword,ROLE_USER
joe=joePassword,ROLE_USER
jane=janePassword,disabled,ROLE_USER
</value>
</property>
-->
</bean>
<!--
由於上面設定 passwordEncoder 由 Md5PasswordEncoder 做 Hashing,
故 /WEB-INF/users.properties 由 GenUsers.java 產生.
-->


<bean id="accessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions" value="false" />
<property name="decisionVoters">
<list>
<bean class="org.acegisecurity.vote.RoleVoter" />
</list>
</property>
</bean>


<bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter" />


<bean id="rememberMeProcessingFilter" class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
<property name="authenticationManager"><ref local="authenticationManager"/></property>
<property name="rememberMeServices"><ref local="rememberMeServices"/></property>
</bean>
<!-- 在 acegilogin.jsp 裡的 checkbox value 加入 _acegi_security_remember_me, 選擇後就可以使用 cookie 自動登入 -->

<bean id="rememberMeServices" class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService"><ref local="inMemoryDaoImpl"/></property>
<property name="key"><value>springRocks</value></property>
</bean>

<bean id="rememberMeAuthenticationProvider" class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
<property name="key"><value>springRocks</value></property>
</bean>

<bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
<constructor-arg value="/"/>
<constructor-arg>
<list>
<ref bean="rememberMeServices"/>
<bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
</list>
</constructor-arg>
</bean>
<!-- 欄截 url 為 /j_acegi_logout, 處理登出動作 -->

<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager" ref="authenticationManager" />
<property name="authenticationFailureUrl" value="/accessDenied.jsp" />
<property name="defaultTargetUrl" value="/secure/loginsuccess.jsp" />
<property name="filterProcessesUrl" value="/j_acegi_security_check" />
<property name="rememberMeServices">
<ref local="rememberMeServices"/>
</property>
</bean>

<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl" value="/acegilogin.jsp" />
<property name="forceHttps" value="false" />
</bean>
</property>
<property name="accessDeniedHandler">
<bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
<property name="errorPage" value="/accessDenied.jsp" />
</bean>
</property>
</bean>


<!--
<bean id="filterSecurityInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager" />
<property name="accessDecisionManager" ref="accessDecisionManager" />
<property name="objectDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/secure/**=ROLE_ADMIN,ROLE_USER
</value>
</property>
</bean>
-->

<bean name="sayHello" class="test.joeyta.SayHello" />


<bean id="methodSecurityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager">
<ref bean="authenticationManager"/>
</property>
<property name="accessDecisionManager">
<ref bean="accessDecisionManager"/>
</property>
<property name="objectDefinitionSource">
<value>test.joeyta.ISayHello.say=ROLE_ADMIN</value>
</property>
</bean>

<bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>sayHello</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>methodSecurityInterceptor</value>
</list>
</property>
</bean>


</beans>
<!-------------- applicationContext.xml ------------->

############ users.properties ##################
#Generated by GeneUsers.java
#Wed Jun 27 17:13:43 CST 2007
joey=eb7fb487fccce00e33f945d53d6d653b,ROLE_USER
joe=00f5c18c7dbe1424d32b253e695762a2,ROLE_USER
jane=a7ef041dae153f8426b739227fe77427,disabled,ROLE_USER
joeyta=c9cad4b3e956e94c8ded8c14a99c04c3,ROLE_ADMIN
############ users.properties ##################

/************** GenUsers *******************/
import java.io.FileOutputStream;
import java.util.Properties;


import org.acegisecurity.providers.encoding.Md5PasswordEncoder;



public class GenUsers {


public static void main(String[] args) {
Md5PasswordEncoder md5 = new Md5PasswordEncoder();

Properties property = new Properties();
property.setProperty("joeyta", md5.encodePassword("joeytaPassword", null)+",ROLE_ADMIN");
property.setProperty("joey", md5.encodePassword("joeyPassword", null)+",ROLE_USER");
property.setProperty("joe", md5.encodePassword("joePassword", null)+",ROLE_USER");
property.setProperty("jane", md5.encodePassword("janePassword", null)+",disabled,ROLE_USER");

try {
property.store(new FileOutputStream("WebContent/WEB-INF/users.properties"),"Generated by GeneUsers.java");
System.out.println("WebContent/WEB-INF/users.properties generated");
} catch (Exception e) {
System.err.println("Failure:"+e);
e.printStackTrace();
}

}


}
/************** GenUsers *******************/

/************** ISayHello *******************/
package test.joeyta;


public interface ISayHello {
public String say(String name);
}
/************** ISayHello *******************/

/************** SayHello *******************/
package test.joeyta;


public class SayHello implements ISayHello {
public String say(String name){
return "Hello, " + name;
}
}
/************** SayHello *******************/

<%!---------------- index.jsp ------------------%>
<%@ page session="false"%>


<%
response.sendRedirect(request.getContextPath()+"/sayHello.jsp");
%>
<%!---------------- index.jsp ------------------%>

<%!---------------- sayHello.jsp ------------------%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"
import="test.joeyta.*,org.springframework.web.context.support.WebApplicationContextUtils"
%>


<a href="<%=request.getContextPath()%>/j_acegi_logout">Logout</a> <br />


<%
ISayHello sayHello = (ISayHello)WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()).getBean("sayHello");
out.println(sayHello.say("Joeyta"));
%>
<%!---------------- sayHello.jsp ------------------%>

<%!---------------- acegilogin.jsp ------------------%>
<%@ page import="org.acegisecurity.ui.AbstractProcessingFilter" %>
<%@ page import="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter" %>
<%@ page import="org.acegisecurity.AuthenticationException" %>


<html>
<head>
<title>Login</title>
</head>


<body>
<h1>Login</h1>


<P>Valid users:
<P>
<P>username <b>joeyta</b>, password <b>joeytaPassword</b>
<P>username <b>joey</b>, password <b>joeyPassword</b>
<P>username <b>joe</b>, password <b>joePassword</b>
<p>username <b>jane</b>, password <b>janePassword</b> (user disabled)
<p>


<form action="<%=request.getContextPath()%>/j_acegi_security_check" method="POST">
<table>
<tr>
<td>User:</td>
<td><input type='text' name='j_username' /></td>
</tr>
<tr>
<td>Password:</td>
<td><input type='password' name='j_password' /></td>
</tr>
<tr>
<td><input type="checkbox" name="_acegi_security_remember_me"></td>
<td>Don't ask for my password for next time</td>
</tr>


<tr><td colspan='2'><input name="submit" type="submit"></td></tr>
<tr><td colspan='2'><input name="reset" type="reset"></td></tr>
</table>


</form>


</body>
</html>
<%!---------------- acegilogin.jsp ------------------%>

<%!---------------- accessDenied.jsp ------------------%>
<%@ page import="org.acegisecurity.context.SecurityContextHolder" %>
<%@ page import="org.acegisecurity.Authentication" %>
<%@ page import="org.acegisecurity.ui.AccessDeniedHandlerImpl" %>


<h1>Sorry, access is denied</h1>


<a href="<%=request.getContextPath()%>/j_acegi_logout">Logout</a>
<p>
<%= request.getAttribute(AccessDeniedHandlerImpl.ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY)%>


<p>


<% Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) { %>
Authentication object as a String: <%= auth.toString() %><BR><BR>
<% } %>
<%!---------------- accessDenied.jsp ------------------%>



項目結構如下圖所示:


開啟 IE 輸入 https://localhost:8443/FirstAcegi/index.jsp
出現如下圖所示:


按<是>後如下圖所示:
輸入用戶名 joeyta , 密碼 joeytaPassword


由於在 applicationContext.xml 裡定義如下:
<bean name="sayHello" class="test.joeyta.SayHello" />


<bean id="methodSecurityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager">
<ref bean="authenticationManager"/>
</property>
<property name="accessDecisionManager">
<ref bean="accessDecisionManager"/>
</property>
<property name="objectDefinitionSource">
<value>test.joeyta.ISayHello.say=ROLE_ADMIN</value>
</property>
</bean>

<bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>sayHello</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>methodSecurityInterceptor</value>
</list>
</property>
</bean>

並在 sayHello.jsp 源碼裡可以了解到,
當執行下面這兩句時要求進行確認, 進入登入頁面進行認證.
ISayHello sayHello = (ISayHello)WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()).getBean("sayHello");
out.println(sayHello.say("Joeyta"));



按送出查詢後如下圖所示:


請由以下連結下載項目:
http://blog.matrix.org.cn/joeyta/resource/FirstAcegi.zip
由於 spring.jar 太大了, 請自行加入.


參考資料:
http://acegisecurity.org/docbook/acegi.html


http://caterpillar.onlyfun.net/Gossip/AcegiGossip/AcegiGossip.html


Free Text Editor 備忘記

由於習慣使用 UltraEditor 作為 text editor, 最近卻不能使用了.
故前陣子試用了一些 free text editor.
就決定把它記錄下來, 日後參考.

開始備忘記:

Notepad++:
下載 npp.4.1.2.bin.zip
http://sourceforge.net/project/showfiles.php?group_id=95717&package_id=102072
解壓縮後 執行 npp.4.1.2.bin/notepad++.exe 直接使用


或下載 npp.4.1.2.Installer.exe 直接執行安裝


下載 plugin:
http://sourceforge.net/projects/npp-plugins/
http://sourceforge.net/project/showfiles.php?group_id=189927


Explorer_1_4_dll.zip
DBGpPlugin_0_1a_dll.zip
HexEditor_0_8_3_dll.zip
LightExplorer_1_3.zip
NppDocUpdater_1_0_dll.zip
SearchInFiles_1_11.zip
NppExec_02beta3_dll.zip
SpellChecker_1_1_dll.zip
FunctionList_1_2_dll.zip


解壓後將 *.dll 複製至安裝目錄下的 plugins 目錄.
如 C:\Program Files\Notepad++\plugins


以下是作者的教學連結:
http://notepad-plus.sourceforge.net/tw/site.htm


備忘:最近才開始使用它, 好後悔沒早點使用它.



Crimson Editor:
下載 cedt370r.exe 直接安裝
http://www.crimsoneditor.com/
http://robotics.snu.ac.kr/pds/CrimsonEditor/cedt370r.exe


備忘:這套 Editor 其實使用了很久, 感覺不錯.



MadEdit:
下載 MadEdit-0.2.7.20070410-win32-bin.7z
http://sourceforge.net/project/showfiles.php?group_id=123568


解壓縮後 執行 MadEdit-0.2.7/MadEdit.exe


備忘:功能一般, 這套 Editor 還有很大的發展空間.



Vim:
下載 gvim71.zip
http://www.vim.org/
http://www.vim.org/download.php
解壓縮後 執行 vim71/gvim.exe 直接使用


備忘:這套 Editor 雖要使用 linux vi 的經驗.



AkelPad:
下載 AkelPad-3.3.2-setup.exe
http://akelpad.sourceforge.net/en/download.php


備忘:這套只比 notepad 多一點功能.


Spring Compass 備忘記

以往實作搜尋引擎, 首選 Lucene, 實作起來並不輕鬆,
Opensymphony Compass 是 Lucene 的開源框架,
如果對 Hibernate 有基本認識, 搭配 Spring framework, 使用 Compass 就會特別輕鬆,
但現時的 Compass 只能對單個 entity 作操作.

這編備忘記主要是以簡單的 Spring Web MVC 作為開始,
配合 Compass, 以新增及搜尋 User 作為簡單例子.

開始備忘記:
[1] 安裝 JDK 6u1
[2] 安裝 Eclipse WTP:
[3] 安裝 tomcat
[4] 下載 spring 及 OpenSymphony Compass
[5] Eclipse 實作


[1] 安裝 JDK 6u1:
下載 jdk-6u1-windows-i586-p.exe
http://java.sun.com/javase/downloads/index.jsp
安裝至 C:\jdk1.6.0_01\
新增環境變數 JAVA_HOME=C:\jdk1.6.0_01
C:\jdk1.6.0_01\bin 加入至 PATH 中
C:\jdk1.6.0_01\lib\dt.jar 及 C:\jdk1.6.0_01\lib\tools.jar 加入至 CLASSPATH 中
執行 C:\>java -version
輸出 java version "1.6.0_01" 即安裝成功
java version "1.6.0_01"
Java(TM) SE Runtime Environment (build 1.6.0_01-b06)
Java HotSpot(TM) Client VM (build 1.6.0_01-b06, mixed mode, sharing)


[2] 安裝 Eclipse WTP:
下載 wtp-all-in-one-sdk-R-1.5.4-win32.zip
http://download.eclipse.org/webtools/downloads/drops/R1.5/R-1.5.4-200705021353/
解壓縮至 C:\wtp-all-in-one-sdk-R-1.5.4-win32


執行 C:\wtp-all-in-one-sdk-R-1.5.4-win32\eclipse\eclipse.exe 啟動 eclipse
如下圖所示:


[3] 安裝 tomcat:
下載 apache-tomcat-5.5.23.zip
http://tomcat.apache.org/download-55.cgi
解壓縮至 C:\apache-tomcat-5.5.23


設定 Eclipse 使用 tomcat 為 web server:
Eclipse: Window -> Show View -> Servers
右鍵點擊 Servers panel -> New -> Server ->> Apache -> Tomcat v5.5 Server -> Next
Tomcat installation directory 選擇 C:\apache-tomcat-5.5.23
JRE 選擇 jre1.6.0_01
按 Finish 完成, 如下圖所示


[4] 下載 spring 及 OpenSymphony Compass:
下載 spring-framework-2.0.6-with-dependencies.zip
http://sourceforge.net/project/showfiles.php?group_id=73357&package_id=173644


下載 compass-1.1.zip
http://www.opensymphony.com/compass/download.action


[5] Eclipse 實作:
Eclipse: File -> New -> Other ->> Web -> Dynamic Web Project -> Next
Project Name: FirstCompass
按 Finish 完成.


複製以下檔案至 Project FirstCompass/WebContent/WEB-INF/lib
spring-framework-2.0.6/dist/spring.jar
C:\apache-tomcat-5.5.23\webapps\jsp-examples\WEB-INF\lib\jstl.jar
C:\apache-tomcat-5.5.23\webapps\jsp-examples\WEB-INF\lib\standard.jar
compass-1.1.1\dist\commons-logging.jar
compass-1.1.1\dist\compass.jar
compass-1.1.1\dist\lucene\lucene-core.jar



<!-------------------- web.xml ------------------->
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="
http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="
http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">


<display-name>First Compass</display-name>


<description>spring compass sample application</description>


<context-param>
<param-name>webAppRootKey</param-name>
<param-value>FirstCompass.root</param-value>
</context-param>


<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/mvc-config.xml,/WEB-INF/beans-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>


<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>


<session-config>
<session-timeout>10</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
<!-------------------- web.xml ------------------->



<!-------------------- mvc-config.xml ------------------->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "
http://www.springframework.org/dtd/spring-beans-2.0.dtd">


<beans>
<bean id="urlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/AddUser.do">addUserController</prop>
<prop key="/SearchUser.do">searchUserController</prop>
</props>
</property>
</bean>

<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass">
<value>
org.springframework.web.servlet.view.JstlView
</value>
</property>
<property name="prefix">
<value>/WEB-INF/jsp/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>

<bean id="addUserController"
class="test.joeyta.AddUserController">
<property name="successView">
<value>searchUser</value>
</property>
<property name="formView">
<value>addUser</value>
</property>
</bean>


<bean id="searchUserController"
class="test.joeyta.SearchUserController">
<property name="nextView">
<value>listUser</value>
</property>
<property name="previousView">
<value>searchUser</value>
</property>
</bean>


</beans>
<!-------------------- mvc-config.xml ------------------->



<!-------------------- beans-config.xml ------------------->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "
http://www.springframework.org/dtd/spring-beans-2.0.dtd">


<beans>


<bean id="compass"
class="org.compass.spring.LocalCompassBean">

<property name="resourceLocations">
<list>
<value>classpath:test/joeyta/library.cmd.xml</value>
<value>classpath:test/joeyta/User.cpm.xml</value>
</list>
</property>
<property name="compassSettings">
<props>
<prop key="compass.engine.connection">
target/testindex
</prop>
<prop key="compass.transaction.factory">
org.compass.core.transaction.LocalTransactionFactory
</prop>
</props>
</property>
</bean>


<bean id="userCompassDao" class="test.joeyta.UserCompassDao">
<property name="compass">
<ref local="compass" />
</property>
</bean>


</beans>
<!-------------------- beans-config.xml ------------------->
上面的 compass.engine.connection 使用了記憶體儲存 index 及 entity.
這裡可簡單的改成 filesystem 或 database, 詳情可參考 documentation.


<!-------------------- library.cmd.xml ------------------->
<?xml version="1.0"?>
<!DOCTYPE compass-core-meta-data PUBLIC
"-//Compass/Compass Core Meta Data DTD 1.0//EN"
"
http://www.opensymphony.com/compass/dtd/compass-core-meta-data.dtd">


<compass-core-meta-data>


<meta-data-group id="library" displayName="Library Meta Data">

<description>Library Meta Data</description>

<alias id="user" displayName="User">
<description>User alias</description>
<name>user</name>
</alias>

<meta-data id="name" displayName="Name">
<description>Name associated with an entity</description>
<name>name</name>
</meta-data>


<meta-data id="tel" displayName="Tel">
<description>Tel associated with an entity</description>
<name>tel</name>
</meta-data>

<meta-data id="address" displayName="Address">
<description>Address associated with an entity</description>
<name>address</name>
</meta-data>

</meta-data-group>

</compass-core-meta-data>
<!-------------------- library.cmd.xml ------------------->



<!-------------------- User.cpm.xml ------------------->
<?xml version="1.0"?>
<!DOCTYPE compass-core-mapping PUBLIC
"-//Compass/Compass Core Mapping DTD 1.0//EN"
"
http://www.opensymphony.com/compass/dtd/compass-core-mapping.dtd">


<compass-core-mapping package="test.joeyta">


<class name="User" alias="${library.user}">

<id name="id" />


<property name="name">
<meta-data>${library.name}</meta-data>
</property>

<property name="tel">
<meta-data>${library.tel}</meta-data>
</property>


<property name="address">
<meta-data>${library.address}</meta-data>
</property>


</class>

</compass-core-mapping>
<!-------------------- User.cpm.xml ------------------->
上例中使用 name, tel, address 作為 index.



/****************** User.java ********************/
package test.joeyta;


public class User {
private String id;


private String name;


private String tel;


private String address;


public String getAddress() {
return address;
}


public void setAddress(String address) {
this.address = address;
}


public String getId() {
return id;
}


public void setId(String id) {
this.id = id;
}


public String getName() {
return name;
}


public void setName(String name) {
this.name = name;
}


public String getTel() {
return tel;
}


public void setTel(String tel) {
this.tel = tel;
}


}
/****************** User.java ********************/



/************** IUserCompassDao.java **************/
package test.joeyta;


import java.util.List;


public interface IUserCompassDao {
public void save(User user);

public List find(String sKeyword);

}
/************** IUserCompassDao.java **************/



/************** UserCompassDao.java **************/
package test.joeyta;


import java.util.ArrayList;
import java.util.List;


import org.compass.core.CompassCallback;
import org.compass.core.CompassException;
import org.compass.core.CompassHit;
import org.compass.core.CompassHits;
import org.compass.core.CompassSession;
import org.compass.spring.CompassDaoSupport;


public class UserCompassDao extends CompassDaoSupport implements
IUserCompassDao {


public void save(User user) {
getCompassTemplate().save(user);
}


public List find(final String sKeyword) {
return (List) getCompassTemplate().execute(new CompassCallback() {
public Object doInCompass(CompassSession session)
throws CompassException {
CompassHits hits = session.find(sKeyword);
List users = new ArrayList();
for (int i = 0; i < hits.length(); i++) {
CompassHit hit = hits.hit(i);
if (hit.getAlias().equals("user")) {
users.add(hit.getData());
}
}
return users;
}
});


}


}
/************** UserCompassDao.java **************/



/************** AddUserController.java **************/
package test.joeyta;


import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;


public class AddUserController extends SimpleFormController {
public AddUserController() {
setCommandClass(User.class);
}


protected ModelAndView onSubmit(Object command) throws Exception {
User user = (User) command;


try {
IUserCompassDao dao = (IUserCompassDao)getApplicationContext().getBean("userCompassDao");
dao.save(user);
return new ModelAndView(getSuccessView());
} catch (Exception e) {


}
return new ModelAndView(getFormView());
}
}
/************** AddUserController.java **************/



/************** SearchUserController.java **************/
package test.joeyta;


import java.util.List;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;


public class SearchUserController extends AbstractController {


String previousView;


String nextView;


protected ModelAndView handleRequestInternal(HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (request.getParameter("keyword") != null && !request.getParameter("keyword").equals("")) {
String sKeyword = request.getParameter("keyword");
IUserCompassDao dao = (IUserCompassDao) getApplicationContext().getBean("userCompassDao");
List users = dao.find(sKeyword);
return new ModelAndView(getNextView(), "users", users);
}
return new ModelAndView(getPreviousView());
}


public String getNextView() {
return nextView;
}


public void setNextView(String nextView) {
this.nextView = nextView;
}


public String getPreviousView() {
return previousView;
}


public void setPreviousView(String previousView) {
this.previousView = previousView;
}


}
/************** SearchUserController.java **************/



<!---------------- index.jsp -------------->
<%@ page session="false"%>


<%
request.getRequestDispatcher("/WEB-INF/jsp/addUser.jsp").forward(request,response);
%>
<!---------------- index.jsp -------------->



<!---------------- addUser.jsp -------------->
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Add User</title>
</head>
<body>


<form name="addUserForm" action="<%=request.getContextPath()%>/AddUser.do" method="POST">
Add user to compass <br/>
<table border="0">
<tr>
<td>ID</td><td><input type="text" name="id"/></td>
</tr>
<tr>
<td>NAME</td><td><input type="text" name="name"/></td>
</tr>
<tr>
<td>TEL</td><td><input type="text" name="tel"/></td>
</tr>
<tr>
<td>ADDRESS</td><td><input type="text" name="address"/></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="Add"/></td>
</tr>
</table>
</form>


</body>
</html>
<!---------------- addUser.jsp -------------->



<!---------------- searchUser.jsp -------------->
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Add User</title>
</head>
<body>


<form name="searchUserForm" action="<%=request.getContextPath()%>/SearchUser.do" method="POST">
Search user from compass <br/>
<table border="0">
<tr>
<td><input type="text" name="keyword"/></td>
</tr>
<tr>
<td><input type="submit" value="Search"/></td>
</tr>
</table>
</form>


</body>
</html>
<!---------------- searchUser.jsp -------------->



<!---------------- listUser.jsp -------------->
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="
http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Add User</title>
</head>
<body>
<a href="<%=request.getContextPath()%>/index.jsp">back to index</a> <br/>
<table border="1">
<tr><td>ID</td><td>NAME</td><td>TEL</td><td>ADDRESS</td></tr>
<c:forEach var="user" items="${users}">
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.tel}</td>
<td>${user.address}</td>
</tr>
</c:forEach>
</table>


</body>
</html>
<!---------------- listUser.jsp -------------->



Eclipse project 下載位置:
http://blog.matrix.org.cn/joeyta/resource/FirstCompass.zip
由於 spring.jar 及 compass.jar 太大了, 故不包含於 project 中, 請自行加入.


項目結構如下所示:


測試頁面如下圖所示:





參考資料:
http://www.opensymphony.com/compass/
http://www.opensymphony.com/compass/content/documentation.html





OpenCms 備忘記

前陣子由於需要選擇一個好用的開源 CMS, 於是便測試了多種 CMS.
在開源的 CMS 裡, 我覺得 OpenCMS 功能最強, 最少能滿足我的要求.
尤其是在繁體搜尋功能, 可以簡單的 implement,
不像其他的 CMS 不支援繁體, OpenCMS 是使用 Lucene 作為搜尋功能,
加入繁體搜尋非常簡單, 只要細心研究一下就可以了.

由於這編備忘記是半年前寫下的, 很多安裝後的測試過程沒有記錄下來.
例如如何轉成繁體版, 我當初是把所有 properties 檔案從簡體轉成繁體,
然後更改一些設定, 就可以順利的完成繁體版,
以下是如何簡單的安裝 OpenCMS 作備忘.

開始備忘記:
[1] 安裝 jdk 5
[2] 下載 Tomcat
[3] 安裝 mysql
[4] 下載 OpenCMS
[5] 啟動 Tomcat 及 進行安裝

[1] 安裝 jdk 5:
下載 jdk-1_5_0_07-nb-5_0-win-ml.exe
http://java.sun.com/j2se/1.5.0/download-netbeans.html
安裝至 D:\jdk1.5.0_07
新增環境變數 JAVA_HOME=D:\jdk1.5.0_07
D:\jdk1.5.0_07\bin 加入至 PATH 中
D:\jdk1.5.0_07\lib\dt.jar 及 D:\jdk1.5.0_07\lib\tools.jar 加入至 CLASSPATH 中
執行 D:\>java -version
輸出 java version "1.5.0_07" 即安裝成功.


[2] 下載 Tomcat:
下載 apache-tomcat-5.5.20.zip
http://www.eng.lsu.edu/mirrors/apache/tomcat/tomcat-5/v5.5.20/bin/apache-tomcat-5.5.20.zip
解壓縮至 C:\apache-tomcat-5.5.20


[3] 安裝 mysql:
下載 mysql-noinstall-4.1.21-win32.zip
http://dev.mysql.com/get/Downloads/MySQL-4.1/mysql-noinstall-4.1.21-win32.zip/from/http://mysql.he.net/
解壓至 c:\mysql
執行 c:\mysql\bin\mysqld-nt.exe


[4] 下載 OpenCMS
英文版安裝:

下載 opencms_6.2.2.zip
http://www.opencms.org/export/download/opencms/opencms_6.2.2.zip
解壓縮後將 opencms.war 放進 C:\apache-tomcat-5.5.20\webapps


簡體版安裝:
下載簡體版 opencms-6.2.1-zh.war
http://www.langhua.cn/langhua-info/xinwen/opencms-6.2.1-zh.html
http://sourceforge.net/project/showfiles.php?group_id=168679
將 opencms-6.2.1-zh.war 改名為 opencms_zh.war
將 opencms_zh.war 放進 C:\apache-tomcat-5.5.20\webapps

[5] 啟動 Tomcat 及 進行安裝:
執行 C:\apache-tomcat-5.5.20\bin\startup.bat


英文版:
進入 http://localhost:8080/opencms/setup/ 進行安裝.
一直按 Continue 直至安裝完成.
安裝後刪除 C:\apache-tomcat-5.5.20\webapps\opencms\setup 目錄.


http://localhost:8080/opencms/opencms/
http://localhost:8080/opencms/opencms/system/login/
登入名稱: Admin
登入密碼: admin


簡體版:
進入 http://localhost:8080/opencms_zh/setup_zh/
http://localhost:8080/opencms_zh/setup/ 進行安裝.
一直按 Continue 直至安裝完成.
安裝後刪除 C:\apache-tomcat-5.5.20\webapps\opencms_zh\setup_zh 及
C:\apache-tomcat-5.5.20\webapps\opencms_zh\setup 目錄.
登入頁面 : http://192.168.70.112/opencms_zh/cms/
登入名稱: Admin
登入密碼: admin


參考資料:
http://www.opencms.org/

GnuWin32(wget & sed) 備忘記

很多時候程式員在 windows 裡, 卻想使用 linux 的一些 command.
如 wget 或 sed 等, 那 GnuWin32 是一個好選擇,
雖然可以安裝 Cygwin 等, 但還是要啟動 console.
不能直接使用 batch file 執行.
今天的備忘記主要是介紹在 windows 裡如何執行 linux 裡的一些 command.


開始備忘記:
[1] 安裝 GnuWin32 wget 及 sed
[2] 測試 wget
[3] 測試 sed command


[1] 安裝 GnuWin32 wget 及 sed:
這裡只簡單地對 wget 及 sed 進行測試.
下載 wget-1.10.1.exe , sed-4.1.4
http://gnuwin32.sourceforge.net/packages.html


下載後雙擊它們安裝, 安裝目錄為 C:\GnuWin32
然後進入 C:\GnuWin32\bin 輸入 dir 如下圖所示.

現在可以在 windows 裡使用 wget 及 seq 這些 linux 裡常用的 command.


[2] 測試 wget:
輸入 wget http://www.google.com/index.html 下載 index.html
就會把 index.html 下載至當前的目錄. 如下圖所示.


[3] 測試 sed:
輸入 sed -n "1,2p" index.html 將 index.html 的第1,2行打印出來.
如下圖所示:


參考資料:
http://gnuwin32.sourceforge.net/install.html
http://www.cygwin.com/

KeePass 備忘記

KeePass Password Safe 為近期 sourceforge 社區熱門下載項目, 累計超過 200 萬,
現今的電腦年代, 需要記住的密碼尤其多, 想一個安全的密碼亦不容易,


包括 email, forum, blog 等等, 雖然可以使用單一密碼, 但不是一種好習慣,
而 網上銀行, 公司的伺服器 等等的密碼, 就必需小心處理,
這套軟件主要是讓使用者只需記住一個密碼,
然後其他的密碼自動產生並加密起來.


開始備忘記:
[1] 安裝 KeePass
[2] 第一次使用 KeePass
[3] 新增密碼


[1] 安裝 KeePass:
下載 KeePass-1.07-Setup.exe
http://downloads.sourceforge.net/keepass/KeePass-1.07-Setup.exe
http://sourceforge.net/project/showfiles.php?group_id=95013


雙擊 KeePass-1.07-Setup.exe 安裝完成,


[2] 第一次使用 KeePass:
點擊 KeePass 打開軟件,
按 File -> New ->> Key
Enter password 輸入 abc123 [這個密碼必須記住]
然後按 OK, 按 OK 後會要求重覆確認密碼.


[3] 新增密碼:
按 Edit -> Add Entry
Group 選 Windows
User Name: joeyta
然後按 OK


如下圖所示:


下圖點擊 joeyta, 然後選擇 Edit -> Copy Password To Clipboard
然後可以將密碼貼到適當的地方.


參考資料:
http://keepass.info/


這裡制圖使用了 GIMP:
http://www.gimp.org/

Hibernate oracle blob 備忘記

這次備忘記主要是記載處理 Hibernate blob 在 Oracle 出現的問題.

以及在 mapping file 裡使用 date 做 mapping 時, 進入 database 後, 時分秒會不見了的問題,
故此時要使用 timestamp 取代做 mapping , 而在 java 中仍使用 java.util.Date.
另一個問題是當 entity 在 detached 狀態時, java.sql.Timestamp 在經過 XMLEncoder 轉換時,
由於 XMLEncoder 並不支持 java.sql.Timestamp 的問題.


開始備忘記:
[1]
處理 Hibernate oracle blob
[2] 處理 XMLEncoder java.sql.Timestamp

[1] 處理 Hibernate oracle blob:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"
http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">


<hibernate-mapping package="test.joeyta">
<class name="Image" table="image_table">
<id name="id">
<generator class="seqhilo"/>
<param name="sequence">my_sequence</param>
<generator
</id>
<property name="photoBlob" column="photo" type="blob" />
</class>
</hibernate-mapping>


public class Image {
private Integer id;


private byte[] phto;


public Blob getPhotoBlob(){
return Hibernate.createBlob(this.photo);
}

public void setPhotoBlob(Blob photoBlob){
this.photo = OracleBlobUtil.toByteArray(photoBlob);
}


public Integer getId() {
return id;
}


public void setId(Integer id) {
this.id = id;
}


public byte[] getPhoto() {
return photo;
}


public void setPhoto(byte[] photo) {
this.photo = photo;
}


}

public class OracleBlobUtil {
public static byte[] toByteArray(Blob fromBlob){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
if(fromBlob != null){
byte[] buf = new byte[1000];
InputStream is = fromBlob.getBinaryStream();
try {
int dataSize;
while ((dataSize = is.read(buf)) != -1) {
baos.write(buf, 0, dataSize);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
}
}
}
return baos.toByteArray();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
try {
if (baos != null) {
baos.close();
}
} catch (IOException e) {
}
}
}
}

[2] 處理 XMLEncoder java.sql.Timestamp:
只要更改 Entity bean 的 相關 getter 將 Timestamp 轉成 Date
public Date getLastUpdate() {
if(lastUpdate != null && lastUpdate instanceof Timestamp){
lastUpdate = new Date(lastUpdate.getTime());
}
return lastUpdate;
}


以下是 XMLEncoder 的實作:
public class BeanXMLConverter {

public String beanToXml(Object object) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
XMLEncoder xe = new XMLEncoder(new BufferedOutputStream(baos));
xe.writeObject(object);
xe.close();
try{
return baos.toString("UTF-8");
} catch (UnsupportedEncodingException uee){
System.out.println("["+this.getClass().getName()+"]"+uee);
}
return baos.toString();
}


public Object xmlToBean(String xmldata) {
ByteArrayInputStream bais;
try{
bais = new ByteArrayInputStream(xmldata.getBytes("UTF-8"));
} catch (UnsupportedEncodingException uee){
bais = new ByteArrayInputStream(xmldata.getBytes());
System.out.println("["+this.getClass().getName()+"]"+uee);
}
XMLDecoder d = new XMLDecoder(new BufferedInputStream(bais));
Object result = d.readObject();
d.close();
return result;
}


}



參考資料:
http://beanlib.sourceforge.net/
http://hansonchar.blogspot.com/2005/06/oracle-blob-mapped-to-byte-in.html








JNDI AD 備忘記

這編主要是描述 Java JNDI 連 Windows Active Directory 的實作.
包括認證, 新增用戶, 修改密碼 及 取得用戶資料.

開始備忘記:
1. 建立 IIS SSL
2. 將 CA Certificate 加入至 jre keystore 裡
3. JNDI 連 AD

1. 建立 IIS SSL:
Install Windows 2003 Server:

Install AD:
Start -> Run -> dcpromote
domain name : joeyta.local
NT domain name : joeyserver
即 Fully Qualified Domain Name (FQDN) 為 joeyserver.joeyta.local

先安裝 IIS , 再安裝 CA.

Install IIS:
Start -> Programs -> Administrative Tools -> Configure Your Server Wizard
->> Next -> Next -> Application server (IIS, ASP.NET) -> Next

進入 http://joeyserver.joeyta.local/postinfo.html 表示安裝成功.

Install CA:
Start -> Settings -> Control Panel -> Add or Remove Programs
->> Add/Remove Windows Components
選擇 Certificate Services -> Next
選擇 Enterprise root CA -> Next
Common name for this CA: testca -> Next

進入 http://joeyserver.joeyta.local/CertSrv 表示安裝成功.


Generating a Certificate Signing Request:
Start -> Programs -> Administrative Tools -> Internet Information Services (IIS) Manager
->> Internet Information Services -> (local computer) -> Web Sites
-> > 右鍵點選 Default Web Site -> Properties
選擇 "Directory Security" -> Server Certificate
->> Create a new certificate -> Prepare the request now, but send it later
一直按 Next , 需要注意的是 Common name 必須為 joeyserver.joeyta.local, 這是給使用者連 ssl 的 website.
最後產生 certificate request file , 預設為 c:\certreq.txt


Request a certificate on CA:
進入 http://joeyserver.joeyta.local/CertSrv
按 Request a certificate -> advanced certificate request
-> Submit a certificate request by using a base-64-encoded CMC or PKCS#10 file, or submit a renewal request by using a base-64-encoded PKCS#7 file
使用 notepad 打開 c:\certreq.txt , copy c:\certreq.txt 內容貼至 Saved Request:
Certificate Template 選擇 Web Server, 按 Submit
然後點選 Download certificate , 將 certnew.cer 儲存至 c:\certnew.cer


Installing a Certificate:
Start -> Programs -> Administrative Tools -> Internet Information Services (IIS) Manager
->> Internet Information Services -> (local computer) -> Web Sites
-> > 右鍵點選 Default Web Site -> Properties
選擇 "Directory Security" -> Server Certificate
->> Process the pending request and install the certificate -> Next
Path and file name: c:\certnew.cer -> Next
SSL port this web site should use: 443 -> Next -> Next -> Finish


2. 將 CA Certificate 加入至 jre keystore 裡:
進入 http://joeyserver.joeyta.local/CertSrv
點選 Download a CA certificate, certificate chain, or CRL
點選 Download CA certificate , 然後下載並改名為 c:\testca_cert.cer

然後執行 command:
c:\temp>keytool -import -alias testca_cert -file "/testca_cert.cer" -keystore "/jdk1.5.0_09/jre/lib/security/cacerts" -storepass "changeit"

出現 Trusted this certificate? 按 "y" 即新增成功.


3. JNDI 連 AD:

/***************************** LDAPFastBind.java *****************/
package test.ldap;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Hashtable;

import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.StartTlsRequest;
import javax.naming.ldap.StartTlsResponse;

class FastBindConnectionControl implements Control {
public byte[] getEncodedValue() {
return null;
}

public String getID() {
return "1.2.840.113556.1.4.1781";
}

public boolean isCritical() {
return true;
}
}

public class LDAPFastBind {
public Hashtable env = null;

public LdapContext ctx = null;

public Control[] connCtls = null;

public LDAPFastBind(String ldapurl) {
env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.PROVIDER_URL, ldapurl);

env.put(Context.SECURITY_PROTOCOL,"ssl");

String keystore = "/jdk1.5.0_09/jre/lib/security/cacerts";
System.setProperty("javax.net.ssl.trustStore",keystore);

connCtls = new Control[] { new FastBindConnectionControl() };

// first time we initialize the context, no credentials are supplied
// therefore it is an anonymous bind.

try {
ctx = new InitialLdapContext(env, connCtls);

} catch (NamingException e) {
System.out.println("Naming exception " + e);
}
}

public boolean Authenticate(String username, String password) {
try {
ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, username);
ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, password);
ctx.reconnect(connCtls);
System.out.println(username + " is authenticated");
return true;
}

catch (AuthenticationException e) {
System.out.println(username + " is not authenticated");
System.out.println(e);
return false;
} catch (NamingException e) {
System.out.println(username + " is not authenticated");
System.out.println(e);
return false;
}
}

public void finito() {
try {
ctx.close();
System.out.println("Context is closed");
} catch (NamingException e) {
System.out.println("Context close failure " + e);
}
}

public void printUserAccountControl() {
try {

// Create the search controls
SearchControls searchCtls = new SearchControls();

// Specify the search scope
searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);

// specify the LDAP search filter
//String searchFilter = "(&(objectClass=user)(CN=test))";
//String searchFilter = "(&(objectClass=group))";
String searchFilter = "(&(objectClass=user)(CN=peter lee))";

// Specify the Base for the search
String searchBase = "DC=joeyta,DC=local";

// initialize counter to total the group members
int totalResults = 0;

// Specify the attributes to return
String returnedAtts[] = { "givenName", "mail" };
searchCtls.setReturningAttributes(returnedAtts);

// Search for objects using the filter
NamingEnumeration answer = ctx.search(searchBase, searchFilter,
searchCtls);

// Loop through the search results
while (answer.hasMoreElements()) {
SearchResult sr = (SearchResult) answer.next();

System.out.println(">>>" + sr.getName());

// Print out the groups

Attributes attrs = sr.getAttributes();
if (attrs != null) {

try {
for (NamingEnumeration ae = attrs.getAll(); ae
.hasMore();) {
Attribute attr = (Attribute) ae.next();
System.out.println("Attribute: " + attr.getID());
for (NamingEnumeration e = attr.getAll(); e
.hasMore(); totalResults++) {

System.out.println(" " + totalResults + ". "
+ e.next());
}

}

} catch (NamingException e) {
System.err.println("Problem listing membership: " + e);
}

}
}

System.out.println("Total attrs: " + totalResults);

}

catch (NamingException e) {
System.err.println("Problem searching directory: " + e);
}

}

public boolean adminChangePassword(String sUserName, String sNewPassword){
try {

//set password is a ldap modfy operation
ModificationItem[] mods = new ModificationItem[1];

//Replace the "unicdodePwd" attribute with a new value
//Password must be both Unicode and a quoted string
String newQuotedPassword = "\"" + sNewPassword + "\"";
byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE");

mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("unicodePwd", newUnicodePassword));

// Perform the update
ctx.modifyAttributes(sUserName, mods);

System.out.println("Reset Password for: " + sUserName);

return true;
}
catch (NamingException e) {
System.out.println("Problem resetting password: " + e);
}
catch (UnsupportedEncodingException e) {
System.out.println("Problem encoding password: " + e);
}
return false;
}

public boolean userChangePassword(String sUserName, String sOldPassword, String sNewPassword){
try {
//StartTlsResponse tls = (StartTlsResponse)ctx.extendedOperation(new StartTlsRequest());
//tls.negotiate();

//change password is a single ldap modify operation
//that deletes the old password and adds the new password
ModificationItem[] mods = new ModificationItem[2];

//Firstly delete the "unicdodePwd" attribute, using the old password
//Then add the new password,Passwords must be both Unicode and a quoted string
String oldQuotedPassword = "\"" + sOldPassword + "\"";
byte[] oldUnicodePassword = oldQuotedPassword.getBytes("UTF-16LE");
String newQuotedPassword = "\"" + sNewPassword + "\"";
byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE");

mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, new BasicAttribute("unicodePwd", oldUnicodePassword));
mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute("unicodePwd", newUnicodePassword));

// Perform the update
ctx.modifyAttributes(sUserName, mods);

System.out.println("Changed Password for: " + sUserName);
//tls.close();
return true;

}
catch (NamingException e) {
System.err.println("Problem changing password: " + e);
}
catch (UnsupportedEncodingException e) {
System.err.println("Problem encoding password: " + e);
} catch ( Exception e){
System.err.println("Problem: " + e);
}
return false;
}

public boolean createNewUser(String sGroupName, String sUserName){
try {
// Create attributes to be associated with the new user
Attributes attrs = new BasicAttributes(true);

//These are the mandatory attributes for a user object
//Note that Win2K3 will automagically create a random
//samAccountName if it is not present. (Win2K does not)
attrs.put("objectClass","user");
attrs.put("sAMAccountName","AlanT");
attrs.put("cn","Alan Tang");

//These are some optional (but useful) attributes
attrs.put("givenName","Alan");
attrs.put("sn","Tang");
attrs.put("displayName","Alan Tang");
attrs.put("description","Engineer");
attrs.put("userPrincipalName","alan@joeyta.local");
attrs.put("mail","alang@mail.joeyta.local");
attrs.put("telephoneNumber","123 456 789");

//some useful constants from lmaccess.h
int UF_ACCOUNTDISABLE = 0x0002;
int UF_PASSWD_NOTREQD = 0x0020;
int UF_PASSWD_CANT_CHANGE = 0x0040;
int UF_NORMAL_ACCOUNT = 0x0200;
int UF_DONT_EXPIRE_PASSWD = 0x10000;
int UF_PASSWORD_EXPIRED = 0x800000;

//Note that you need to create the user object before you can
//set the password. Therefore as the user is created with no
//password, user AccountControl must be set to the following
//otherwise the Win2K3 password filter will return error 53
//unwilling to perform.

attrs.put("userAccountControl",Integer.toString(UF_NORMAL_ACCOUNT + UF_PASSWD_NOTREQD + UF_PASSWORD_EXPIRED+ UF_ACCOUNTDISABLE));

// Create the context
Context result = ctx.createSubcontext(sUserName, attrs);
System.out.println("Created disabled account for: " + sUserName);

//now that we've created the user object, we can set the
//password and change the userAccountControl
//and because password can only be set using SSL/TLS
//lets use StartTLS

//StartTlsResponse tls = (StartTlsResponse)ctx.extendedOperation(new StartTlsRequest());
//tls.negotiate();

//set password is a ldap modfy operation
//and we'll update the userAccountControl
//enabling the acount and force the user to update ther password
//the first time they login
ModificationItem[] mods = new ModificationItem[2];

//Replace the "unicdodePwd" attribute with a new value
//Password must be both Unicode and a quoted string
String newQuotedPassword = "\"P@ssw0rd\"";
byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE");

mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("unicodePwd", newUnicodePassword));
mods[1] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("userAccountControl",Integer.toString(UF_NORMAL_ACCOUNT + UF_PASSWORD_EXPIRED)));

// Perform the update
ctx.modifyAttributes(sUserName, mods);
System.out.println("Set password & updated userccountControl");


//now add the user to a group.

try {
ModificationItem member[] = new ModificationItem[1];
member[0]= new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute("member", sUserName));

ctx.modifyAttributes(sGroupName,member);
System.out.println("Added user to group: " + sGroupName);

}
catch (NamingException e) {
System.err.println("Problem adding user to group: " + e);
}
//Could have put tls.close() prior to the group modification
//but it seems to screw up the connection or context ?
//tls.close();

System.out.println("Successfully created User: " + sUserName);
return true;

}
catch (NamingException e) {
System.err.println("Problem creating object: " + e);
}

catch (IOException e) {
System.err.println("Problem creating object: " + e);
}
return false;
}

public boolean addUserToGroup(LdapContext ctx, String userDN, String groupDN) {
try{
ModificationItem[] mods = new ModificationItem[1];
mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute("member", userDN));
ctx.modifyAttributes(groupDN, mods);
System.out.println("Added user " + userDN + " to group " + groupDN);
return true;
} catch (NamingException ne){
System.err.println("Problem add user to group: " + ne);
}
return false;
}

public boolean removeUserFromGroup(LdapContext ctx, String userDN, String groupDN) {
try{
ModificationItem[] mods = new ModificationItem[1];
mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, new BasicAttribute("member", userDN));
ctx.modifyAttributes(groupDN, mods);
System.out.println("Remove user " + userDN + " from group " + groupDN);
return true;
} catch (NamingException ne){
System.err.println("Problem remove user from group: " + ne);
}
return false;
}

}
/***************************** LDAPFastBind.java *****************/


/***************************** LDAPClient.java *****************/
package test.ldap;

class LDAPClient {
public static void main(String[] args) {
// Could also use ldaps over port 636 to protect the communication to
// the
// Active Directory domain controller. Would also need to add
// env.put(Context.SECURITY_PROTOCOL,"ssl") to the "server" code
//String ldapurl = "ldap://joeyserver.joeyta.local:389";
String ldapurl = "ldap://joeyserver.joeyta.local:636";

LDAPFastBind ctx = new LDAPFastBind(ldapurl);

String sAdminUserName = "CN=Administrator,CN=Users,DC=joeyta,DC=local";
String sAdminPassword = "I@mRoot";

// String sUserName = "CN=peter lee,CN=Users,DC=joeyta,DC=local";
String sUserName = "joeyta\\peter";
// String sUserName = "peter@joeyta.local";

String sOldPassword = "P@ssw0rd";
String sNewPassword = "P@$$w0rd";

String sNewUserName = "CN=Alan Tang,CN=Users,DC=joyeta,DC=local";
String sNewGroupName = "CN=test,CN=Users,DC=joeyta,DC=local";

boolean IsAuthenticated = ctx.Authenticate(sAdminUserName, sAdminPassword);
// boolean IsAuthenticated = ctx.Authenticate(sUserName, sOldPassword);

ctx.printUserAccountControl();

ctx.createNewUser(sNewGroupName, sNewUserName);

//boolean IsAdminSuccessChangePWD = ctx.adminChangePassword(sUserName,sNewPassword);
//boolean IsUserSuccessChangePWD = ctx.userChangePassword(sUserName,sOldPassword,sNewPassword);

ctx.finito();

}
}
/***************************** LDAPClient.java *****************/


參考資料:
http://www.javaworld.com.tw/jute/post/view?bid=7&id=164710&sty=1&amp;tpg=1&age=0
http://forum.java.sun.com/forum.jspa?forumID=51&start=0

shell script備忘記

# 依最後更改時間 刪除檔案
find ./yourpath -type -f -mtime -beforeDays -exec rm -f {} \;

# ftp script
#!/usr/bin/sh
fm_year="`date +%Y'`"
fm_monday="`date +'%m%d'`"
fm_dir_prefix="/db/db_backup"
fm_dir="${fm_dir_prefix}/${fm_year}/${fm_monday}"
ftp -i -n 192.168.1.1 <<FTPIT
user dbuser dbpass
bin
cd $fm_dir
lcd dump
mget *.*
quit
FTPIT

sort 指令
將讀入的檔案作排序.
-f 把英文字母當成大寫來看
-n 將各行前頭的數字來做排序
-r 將排序順序逆轉
-b 將欄位前的空格省略
-u 去除重複行
-o 將輸出導入至檔案
[-t seperator] seperator為分隔符 ex. sort -t, -k2, 3 filename
-k pos1[,pos2] pos1為以第一個position作排序
sort +1n filename (跳過每行第一段落作排序)



uniq 指令
-d 將重複行顯示出來
-u 將不重複行顯示出來
-c 統計出現的次數


cut 指令
[-d seperator] seperator為分隔符
-f field field為指定輸出的欄位 ex. cut -d : -f2,4-6


awk -F: '{ print $5 }' (顯示seperator為":"的第5行)
awk -F: '{ print $1 ":" $5 }' (顯示seperator為":"的第1及5行)


paste 指令
[-d seperator] seperator為分隔符 ex. paste -d = file1 file2 file3


diff 指令
-y 將兩個檔案 side-by-side 輸出
[-W cols] cols為指定顯示的寬度 ex. diff -y -W90 file1 file2


wc 指令
-l 統計行數
-c 統計單詞數
-w 統計字數


expand -4 將縮排轉換成4空格


unexpand 指令 將行頭的空格轉換成縮排
unexpand -a filename 將所有空格轉換成縮排


read -r filename 逐行讀入檔案
IFS=$'\n' # IFS 系統預設的區隔符 這裡為換行
ln=0
while read -r line; do
ln=`expr $ln + 1`
printf '%3d %s\n' "$ln" "$line"
done < "$1"



split -b 1m infile outfile
將輸入檔案分割成指定大小


tr e x < filename (將檔案裡的 e 字符轉換成 x 字符)
tr '[a-z]' '[A-Z]' < filename (將小寫轉成大寫)
tr -s ' ' ' ' < filename (-s為將連結一樣的轉成一個)
tr -d ' ' < filename (-d為刪除所有空格)


sed 's/from/to/G' filename (將檔案裡的 from 轉換成 to)
sed -n '1,2p' filename (將檔案的1至2行輸出)
sed -n '/seachKey/p' filename (將檔案含有 seachKey 的行輸出)
sed '1,2d' filename (刪除第1,2行)
sed '/seachKey/d' filename (刪除含有 seachKey 的行)


Array:
message=(hi there how are you today)
message[0]=hi
message[1]=there
message[2]=how
message[3]=are
message[4]=you
message[5]=today


names=([2]=alice [0]=hatter [1]=duchess)


names=(hatter duchess alice)


names=(hatter [5]=duchess alice)


for i in "${names[@]}"; do
echo $i
done


for i in $(cut -f 1,3 -d: /etc/passwd) ; do
array[${i#*:}]=${i%:*}
done


values=(39 5 36 12 9 3 2 30 4 18 22 1 28 25)
numvalues=${#values[@]}
for (( i=0; i < numvalues; i++ )); do
echo -ne "${values[$i]}\t"
done


if/else:
String comparison operators:
str1 = str2[4]
str1 matches str2

str1 != str2
str1 does not match str2

str1 < str2
str1 is less than str2

str1 > str2
str1 is greater than str2

-n str1
str1 is not null (has length greater than 0)

-z str1
str1 is null (has length 0)



if [ -z $filename ]; then
echo "File not found"
exit 1
fi


File attribute operators:
-a file
file exists

-d file
file exists and is a directory

-e file
file exists; same as -a

-f file
file exists and is a regular file (i.e., not a directory or other special type of file)

-r file
You have read permission on file

-s file
file exists and is not empty

-w file
You have write permission on file

-x file
You have execute permission on file, or directory search permission if it is a directory

-N file
file was modified since it was last read

-O file
You own file

-G file
file's group ID matches yours (or one of yours, if you are in multiple groups)

file1 -nt file2
file1 is newer than file2 [6]

file1 -ot file2
file1 is older than file2

Arithmetic test operators:
-lt
Less than

-le
Less than or equal

-eq
Equal

-ge
Greater than or equal

-gt
Greater than

-ne
Not equal

for:

for x := 1 to 10 do
begin
statements...
end


IFS=:
for dir in $PATH
do
ls -ld $dir
done


case:
for filename in "$@"; do
case $filename in
*.jpg ) exit 0 ;;
*.gif ) exit 0 ;;
* ) exit 1 ;;
esac
done


while/until:
while [ $path ]; do
ls -ld ${path%%:*}
path=${path#*:}
done


until cp $1 $2; do
echo 'Attempt to copy failed. waiting...'
sleep 5
done


while test $# -gt 0
do
case $1 in
done


Function:
process( ) {
cat "$1"
}


for f in "$@"
do
process "$f"
done



: (colon) Do nothing (just do expansions of arguments).


. (dot) Read file and execute its contents in current shell.


alias Set up shorthand for command or command line (interactive use).


bg Put job in background (interactive use).


break Exit from surrounding for, while, or until loop.


cat Copy input to output


cd Change working directory.


command Locate built-in and external commands; find a built-in command instead of an


identically named function.

continue Skip to next iteration of for, while, or until loop.


cut Extract columns from input

eval Process arguments as a command line.

exec Replace shell with given program or change I/O for shell.

exit Exit from shell.

export Create environment variables.

false Do nothing, unsuccessfully.

fc Work with command history (interactive use).

fg Put background job in foreground (interactive use).

getopts Process command-line options.

grep Search for strings in the input


jobs List background jobs (interactive use).

kill Send signals.

newgrp Start new shell with new group ID (obsolete).

pwd Print working directory.

read Read a line from standard input.

readonly Make variables read-only (unassignable).

return Return from surrounding function.


sed Perform editing operations on input

set Set options or positional parameters.

shift Shift command-line arguments.

sort Sort lines in the input


times Print accumulated user and system CPU times for the shell and its children.


tr Translate characters in the input to other characters

trap Set up signal-catching routine.

true Do nothing, successfully.

umask Set/show file permission mask.

unalias Remove alias definitions (interactive use).

unset Remove definitions of variables or functions.

wait Wait for background job(s) to finish.



$# Number of command-line arguments.


$- Options currently in effect (arguments supplied on command line or to set). The shell


sets some options automatically.


$? Exit value of last executed command.


$$ Process number of current process.


$! Process number of last background command.


$0 First word; that is, the command name. This will have the full pathname if it was found


via a PATH search.


$n Individual arguments on command line (positional parameters). The Bourne shell allows


only nine parameters to be referenced directly (n = 1–9); Bash allows n to be greater than


9 if specified as ${n}.


$*, $@ All arguments on command line ($1 $2 ...).


"$*" All arguments on command line as one string ("$1 $2..."). The values are separated by


the first character in IFS.


"$@" All arguments on command line, individually quoted ("$1" "$2" ...).


VI command:
h Move left one character

l Move right one character

w Move right one word

b Move left one word

W Move to beginning of next non-blank word

B Move to beginning of preceding non-blank word

e Move to end of current word

E Move to end of current non-blank word

0 Move to beginning of line

^ Move to first non-blank character in line

$ Move to end of line

i Text inserted before current character (insert)

a Text inserted after current character (append)

I Text inserted at beginning of line

A Text inserted at end of line

R Text overwrites existing text

dh Delete one character backwards

dl Delete one character forwards

db Delete one word backwards

dw Delete one word forwards

dB Delete one non-blank word backwards

dW Delete one non-blank word forwards

d$ Delete to end of line

d0 Delete to beginning of line

D Equivalent to d$ (delete to end of line)

dd Equivalent to 0d$ (delete entire line)

C Equivalent to c$ (delete to end of line, enter input mode)

cc Equivalent to 0c$ (delete entire line, enter input mode)

X Equivalent to dl (delete character backwards)

x Equivalent to dh (delete character forwards)

k or - Move backward one line

j or + Move forward one line

G Move to line given by repeat count

/string Search backward for string

?string Search forward for string

n Repeat search in same direction as previous

N Repeat search in opposite direction of previous

fx Move right to next occurrence of x

Fx Move left to previous occurrence of x

tx Move right to next occurrence of x, then back one space

Tx Move left to previous occurrence of x, then forward one space

; Redo last character-finding command

, Redo last character-finding command in opposite direction

~ Invert (twiddle) case of current character(s)

- Append last word of previous command, enter input mode

CTRL-L Clear the screen and redraw the current line on it; good for when your screen


becomes garbled

# Prepend # (comment character) to the line and send it to the history list; useful


for saving a command to be executed later without having to retype it[8]


set nu Display number of lines


History Expansion:
! Start a history substitution

!! Refers to the last command

!n Refers to command line n

!-n Refers to the current command line minus n

!string Refers to the most recent command starting with string

!?string? Refers to the most recent command containing string; the ending ? is optional

^string1^string2 Repeat the last command, replacing string1 with string2

Time stamp formats:
%a
The locale's abbreviated weekday name

%A
The locale's full weekday name

%b
The locale's abbreviated month name

%B
The locale's full month name

%c
The locale's appropriate date and time representation

%C
The century number (the year divided by 100 and truncated to an integer) as a decimal


number [00-99]

%d
The day of the month as a decimal number [01-31]

%D
The date in American format; the same value as %m/%d/%y.

%e
The day of the month as a decimal number [1-31]; a single digit is preceded by a space

%h
The same as %b

%H
The hour (24-hour clock) as a decimal number [00-23]

%I
The hour (12-hour clock) as a decimal number [01-12]

%j
The day of the year as a decimal number [001-366]

%m
The month as a decimal number [01-12]

%M
The minute as a decimal number [00-59]

%n
A newline character

%p
The locale's equivalent of either a.m. or p.m

%r
The time in a.m. and p.m. notation; in the POSIX locale this is equivalent to %I:%M:%S %p

%R
The time in 24-hour notation (%H:%M)

%S
The second as a decimal number [00-61]

%t
A tab character

%T
The time (%H:%M:%S)

%u
The weekday as a decimal number [1-7], with 1 representing Monday

%U
The week number of the year (Sunday as the first day of the week) as a decimal number [00-


53]

%V
The week number of the year (Monday as the first day of the week) as a decimal number [01-


53]; if the week containing 1 January has four or more days in the new year, then it is


considered week 1—otherwise, it is the last week of the previous year, and the next week is


week 1

%w
The weekday as a decimal number [0-6], with 0 representing Sunday

%W
The week number of the year (Monday as the first day of the week) as a decimal number [00-


53]; all days in a new year preceding the first Monday are considered to be in week 0

%x
The locale's appropriate date representation

%X
The locale's appropriate time representation

%y
The year without century as a decimal number [00-99]

%Y
The year with century as a decimal number

%Z
The timezone name or abbreviation, or by nothing if no timezone information exists

%%
%


I/O redirectors: ("$@" > logfile 2>&1 &)
cmd1 cmd2
Pipe; take standard output of cmd1 as standard input to cmd2.

> file
Direct standard output to file.

< file
Take standard input from file.

>> file
Direct standard output to file; append to file if it already exists.

> file
Force standard output to file even if noclobber is set.

n> file
Force output to file from file descriptor n even if noclobber is set.

<> file
Use file as both standard input and standard output.

n<> file
Use file as both input and output for file descriptor n.

<< label
Here-document; see text.

n> file
Direct file descriptor n to file.

n< file
Take file descriptor n from file.

n>> file
Direct file descriptor n to file; append to file if it already exists.

n>&
Duplicate standard output to file descriptor n.

n<&
Duplicate standard input from file descriptor n.

n>&m
File descriptor n is made to be a copy of the output file descriptor.

n<&m
File descriptor n is made to be a copy of the input file descriptor.

&>file
Directs standard output and standard error to file.

<&-
Close the standard input.

>&-
Close the standard output.

n>&-
Close the output from file descriptor n.

n<&-
Close the input from file descriptor n.

n>&word
If n is not specified, the standard output (file descriptor 1) is used. If the digits in


word do not specify a file descriptor open for output, a redirection error occurs. As a


special case, if n is omitted, and word does not expand to one or more digits, the standard


output and standard error are redirected as described previously.

n<&word
If word expands to one or more digits, the file descriptor denoted by n is made to be a


copy of that file descriptor. If the digits in word do not specify a file descriptor open


for input, a redirection error occurs. If word evaluates to -, file descriptor n is closed.


If n is not specified, the standard input (file descriptor 0) is used.

n>&digit-
Moves the file descriptor digit to file descriptor n, or the standard output (file


descriptor 1) if n is not specified.

n<&digit-
Moves the file descriptor digit to file descriptor n, or the standard input (file


descriptor 0) if n is not specified. digit is closed after being duplicated to n.

echo escape sequences:
\a
ALERT or CTRL-G (bell)

\b
BACKSPACE or CTRL-H

\c
Omit final NEWLINE

\e
Escape character (same as \E)

\E
Escape character[3]

\f
FORMFEED or CTRL-L

\n
NEWLINE (not at end of command) or CTRL-J

\r
RETURN (ENTER) or CTRL-M

\t
TAB or CTRL-I

\v
VERTICAL TAB or CTRL-K

\n
ASCII character with octal (base-8) value n, where n is 1 to 3 digits

\0nnn
The eight-bit character whose value is the octal (base-8) value nnn where nnn is 1 to 3


digits

\xHH
The eight-bit character whose value is the hexadecimal (base-16) value HH (one or two


digits)

\\
Single backslash

printf format specifiers: (printf "hello %s\n" world)
%c
ASCII character (prints first character of corresponding argument)

%d
Decimal integer

%i
Same as %d

%e
Floating-point format ([-]d.precisione[+-]dd) (see following text for meaning of precision)

%E
Floating-point format ([-]d.precisionE[+-]dd)

%f
Floating-point format ([-]ddd.precision)

%g
%e or %f conversion, whichever is shorter, with trailing zeros removed

%G
%E or %f conversion, whichever is shortest, with trailing zeros removed

%o
Unsigned octal value

%s
String

%u
Unsigned decimal value

%x
Unsigned hexadecimal number; uses a-f for 10 to 15

%X
Unsigned hexadecimal number; uses A-F for 10 to 15

%%
Literal %

Meaning of precision:
%d, %I, %o, %u, %x, %X
The minimum number of digits to print. When the value has fewer digits, it is padded with


leading zeros. The default precision is 1.

%e, %E
The minimum number of digits to print. When the value has fewer digits, it is padded with


zeros after the decimal point. The default precision is 10. A precision of 0 inhibits


printing of the decimal point.

%f
The number of digits to the right of the decimal point.

%g, %G
The maximum number of significant digits.

%s
The maximum number of characters to print.

Flags for printf:
-
Left-justify the formatted value within the field.

space
Prefix positive values with a space and negative values with a minus.

+
Always prefix numeric values with a sign, even if the value is positive.

#
Use an alternate form: %o has a preceding 0; %x and %X are prefixed with 0x and 0X,


respectively; %e, %E and %f always have a decimal point in the result; and %g and %G do not


have trailing zeros removed.

0
Pad output with zeros, not spaces. This only happens when the field width is wider than the


converted result. In the C language, this flag applies to all output formats, even non-


numeric ones. For bash, it only applies to the numeric formats.



basename
The basename utility, missing from some systems


dirfuncs
Directory manipulation facilities


dirname
The dirname utility, missing from some systems


whatis
An implementation of the Tenth Edition Bourne shell whatis builtin


whence
An almost exact clone of the Korn shell whence builtin