วันจันทร์ที่ 9 ธันวาคม พ.ศ. 2556

How to send mail with Golang

ท้าวความนิดส์นึงครับว่า งานปีใหม่ของ Osdev จะมีการจัดฉลากของขวัญกัน แต่ปัญหาหลักๆ ของการจับฉลากนั้นคือ ไม่รู้จะซื้ออะไร นั่นเอง
ดังนั้นเราจึงแก้ปัญหาโดยการ จับฉลากก่อนซะเลย แต่การจับฉลากก่อนนั้น จะเป็นไปโดยที่ไม่มีใครรู้ว่าใครจับได้ใคร
ดังนั้นเราต้องมา Coding กันซึ่งก็ต้องคิดก่อนว่าจะ Code ภาษาอะไรดี ซึ่งสรุปได้ว่าใช้ Golang เพราะอยากลองเขียนอยู่พอดี
โจทย์คือ
  1. ห้าม Random ได้ตัวเอง
  2. กรณีที่ผู้เล่นเป็นเลขคี่ ห้ามเหลือเศษ (กรณีที่ทุกคนมีคู่จนเหลือคนเดียว)
Solution ที่คิดออกมาคือเอาทุกคนเข้าไปอยู่ใน Array จากนั้น Shuffle Array ซะแล้วก็ไล่ส่งเมลแบบปกติเลย ได้ Code ออกมาแบบนี้

package main

import "math/rand"
import "time"
import "net/smtp"
import "net/mail"
import "strings"

func encodeRFC2047(String string) string{
 // use mail's rfc2047 to encode any string
 addr := mail.Address{String, ""}
 return strings.Trim(addr.String(), " <>")
}

func main() {
 var members = shuffle([]string {
  "example@osdev.co.th", 
  "example@osdev.co.th", 
  "example@osdev.co.th", 
  "example@osdev.co.th", 
  "example@osdev.co.th", 
  "example@osdev.co.th", 
  "example@osdev.co.th", 
  "example@osdev.co.th", 
  "example@osdev.co.th", 
  "example@osdev.co.th", 
  "example@osdev.co.th", 
  "example@osdev.co.th", 
 })
 auth := smtp.PlainAuth("", "example@osdev.co.th", "password", "smtp.gmail.com")
 subject := "Subject: Osdev Gift Random for New Year Cerebration\r\n\r\n"

 for i, _ := range members {
  if i != len(members) - 1 {
   message := "นี่คือเมลแจ้งการจับคุ่ขาของคุณ\r\nคู่ขาของคุณคือ " + members[i] + "\r\n\r\nกรุณาซื้อของขวัญราคาไม่ต่ำกว่า xxxx บาทมาให้คู่ขาของคุณด้วยนะจ๊ะ\r\nและอย่าลืมเก็บเรื่องนี้เป็นความลับล่ะ อย่าให้คู่ขาของคุณรู้เป็นอันขาด!!!"
   smtp.SendMail("smtp.gmail.com:587", auth, "example@osdev.co.th", []string{members[i+1]}, []byte(subject + message))
  }
 }
 message := "นี่คือเมลแจ้งการจับคุ๋ขาของคุณ\r\nคู่ขาของคุณคือ " + members[len(members) - 1] + "\r\n\r\nกรุณาซื้อของขวัญราคาไม่ต่ำกว่า xxxx บาทมาให้คู่ขาของคุณด้วยนะจ๊ะ\r\nและอย่าลืมเก็บเรื่องนี้เป็นความลับล่ะ อย่าให้คู่ขาของคุณรู้เป็นอันขาด!!!"
 smtp.SendMail("smtp.gmail.com:587", auth, "example@osdev.co.th", []string{members[0]}, []byte(subject + message))
}

func shuffle(members []string) []string {
 rand.Seed(time.Now().UTC().Unix())
 var newMembers = make([]string, len(members))
 var rands = rand.Perm(len(members))
 for i, v := range rands {
  newMembers[i] = members[v]
 }
 return newMembers
}

ประเด็นอยู่ที่ว่า ไอ้ function SendMail http://golang.org/pkg/net/smtp/#SendMail นั้นมันไม่มีที่ให้ใส่ Subject น่ะสิ ซึ่งค้นไปค้นมาไปเจอ Post นึงบอกว่าให้ใช้ "Subject: xxxx" ใน body message ได้เลย https://groups.google.com/d/msg/golang-nuts/b_zNHgr5cXU/2HLlGfwkTp4J ก็เลยจัดไป ได้ผลตามต้องการ
Continue Reading...

วันจันทร์ที่ 21 ตุลาคม พ.ศ. 2556

How to add user to Alfresco Sites without invite

โดยปกติแล้วเวลาเราจะ Add Users เข้าไปใน Sites ของ Alfresco นั้นเราจะใช้วิธีการ Invite บุคคลนั้นๆ ผ่านเมนู Invite จากนั้นบุคคลนั้นต้อง Accept Invitation ถึงจะเข้าไปอยู่ใน Site ได้ 

ปัญหาคือบางกรณีเราต้องการทำ Pre Invite Users ให้เรียบร้อยก่อนที่จะเริ่มใช้งานจริง ซึ่งสามารถทำได้ผ่าน REST API ของ Alfresco ครับโดย API ที่เราจะใช้งานคือ Membership API http://wiki.alfresco.com/wiki/Repository_RESTful_API_Reference#Membership

ขั้นแรกเราต้องมี REST Client ก่อนครับ ในส่วนนี้จะใช้ตัวไหนก็ได้นะครับ แต่ผมแนะนำให้เป็น Postman ครับ (ความชอบส่วนตัว) http://www.getpostman.com/

การ Add member สามารถทำได้ผ่าน POST Method ครับโดยรายระเอียดของ Membership POST Method มีดังนี้

Adds a new membership to the site.
POST /alfresco/service/api/sites/{shortname}/memberships
Requirements:
  • Default Format: json
  • Authentication: user
  • Transaction: required
  • Format Style: any
Definition:
  • Id: org/alfresco/repository/site/membership/memberships.post
  • Description: classpath:alfresco/templates/webscripts/org/alfresco/repository/site/membership/memberships.post.desc.xml

ซึ่งรายละเอียดได้ได้บอก JSON Format เรามา ดังนั้นผมจึงเข้าไปดู Code ว่า Format ที่จะใช้งานเป็นอย่างไร  ซึ่งสรุปได้ดังนี้
  1. role : กำหนดเป็น SiteConsumer, SiteContributor, .....
  2. member ที่จะ Add เข้าไปใน Site (person หรือ group)
    1. person : กำหนดเป็นชื่อ userName ของบุคคลที่จะ Add เข้าไปใน Site
    2. group : กำหนดเป็น groupId โดยต้องมี GROUP Prefix ด้วย
Example


จากตัวอย่างเป็นการ Add User tantai เข้าไปที่ site other โดยให้สิทธิ์เป็น SiteContributor สังเกตว่าผมใส่ Basic Authentication และ Content-Type ไว้เรียบร้อยแล้ว โดยเมื่อกด Send Request แล้วสำเร็จ จะได้ผลลัพธ์คือ


เท่านี้เราก็สามารถ Add Member เข้าไปใน Site ได้แล้วครับ แต่ว่าจะทำได้ทีละคน ดังนั้นเราอาจใช้ Python Script หรืออะไรก็ตามทำ loop เพื่อ Add Member ทีละเยอะๆ ได้ครับ
Continue Reading...

วันพุธที่ 16 ตุลาคม พ.ศ. 2556

How to list all user in Alfresco

บน Alfresco หากเราต้องการที่จะ list user ทั้งหมดออกมาดูจะสามารถหลายวิธี แต่ผมจะแนะนำวิธีง่ายๆ 2 วิธีคือ
  1. แก้ไข share-config-custom.xml ให้ default search length มีค่าเป็น 0 จากนั้นพิมพ์ * ที่ช่อง User Search ได้ทันที
  2. เขียน Script ลง JavaScript Console (http://share-extras.github.io/addons/js-console/) โดยใช้ Person Service ไปดึงค่าออกมา แล้ว log result เพื่อไปทำเป็น Report ต่อไป
โดยตัว Script  สามารถเขียนได้ดังนี้

var ctx = Packages.org.springframework. web.context.ContextLoader.getCurrentWebApplicationContext();
var personService = ctx.getBean("personService");
var all = personService.getAllPeople().toArray();
for (var i = 0 ; i < all.length ; i++) {
  var node = utils.getNodeFromString(all[i]);
  logger.log(node.properties.email + "," + node.properties.firstName + "," + node.properties.lastName + "," + node.properties.userName);
}
Continue Reading...

วันอังคารที่ 8 ตุลาคม พ.ศ. 2556

How to comment to Document with JavaScript in Alfresco

ในบางกรณี เราอาจจำเป็นต้องใช้ JavaScript เพื่อไป Comment เอกสารแต่การที่จะ Comment เอกสารนั้นด้วย JavaScript API ของ Alfresco นั้นไม่ง่ายเลย ผมจึงทำ Function สำหรับ Comment ไปยัง node ของเอกสารไว้เพื่อง่ายต่อการใช้งานภายหลัง
function commentNode(nodeRef, comment) {
   var node = search.findNode(nodeRef);

   if(!node.hasAspect("fm:discussable")) {
      node.addAspect("fm:discussable");
   }
   if(!node.hasAspect("fm:commentsRollup")) {
      node.addAspect("fm:commentsRollup");
   }

   var forums = node.childAssocs["fm:discussion"];
   var forum;
   if(forums.length === 0) {
     forum = node.createNode(node.properties.name + " discussion", "fm:forum", "fm:discussion");
   } else {
     forum = forums[0];
   }

   var topics = forum.childAssocs["cm:contains"];
   var topic;
   if(topics == null || topics.length === 0) {
     topic = forum.createNode("Comments", "fm:topic", [], "cm:contains", "cm:Comments");
   } else {
     topic = topics[0];
   }

   var now = new Date();
   var name = "comment-" + now.getTime();
   var mypost = topic.createNode(name, "fm:post", [], "cm:contains", name);

   mypost.content = comment;
}
ตัว Function จะรับ NodeRef และ CommentText ไปจากนั้นนำ NodeRef ไปหา Node จริง จากนั้น check ว่า Node นั้นมี Aspect discussable กับ commentRollup หรือไม่ โดยหากไม่มีจะใส่ Aspect ให้

จากนั้นตรวจสอบว่ามีการทำ Child Association กับตัว discussion และตัว contains ไว้หรือยัง โดยหากยังก็ให้ Create Child Association ด้วย จากนั้นก็ add comment ลงไปครับ
Continue Reading...

วันจันทร์ที่ 16 กันยายน พ.ศ. 2556

Alfresco Workflow with Activiti

Overview

การสร้าง Custom Workflow บน Alfresco นั้นแบ่งออกเป็น 3 ส่วนใหญ่ๆ คือ
  1. Workflow Definition
  2. Alfresco Model
  3. Share UI

Preparation

ก่อนที่เราจะเริ่มสร้าง Workflow กันเราก็ต้องมาเตรียมความพร้อมกันก่อนโดยสิ่งที่เราต้องเตรียมมีดังนี้
  1. Assume Alfresco Installed in your machine
  2. Eclipse (can be STS) with Activiti Designer http://www.activiti.org/userguide/index.html#eclipseDesignerInstallation

Creating Workflow Definition

หลังจากติดตั้ง Tools กันเรียบร้อยแล้ว ขั้นตอนต่อไปเราจะมาสร้าง (Simple) Custom Workflow  กันโดยมี Step การทำงานซัก 1-2 step โดยมีขั้นตอนดังนี้
  1. Start the Eclipse (or STS)
  2. Create Activiti Diagram ใน project ใดก็ได้ (เนื่องจาก Alfresco ใช้งานเฉพาะ xml definition file ไม่ได้ต้องการ Activiti Project ดังนั้นการสร้างแค่ Diagram จึงทำได้ง่ายกว่า) โดยผมตั้งชื่อเป็น sample.bpmn20.xml
  3. วาด Workflow ตามรูปด้านล่าง
  4. Edit the candidate user or group in text editor

    จากรูปจะเห็นได้ว่ามีการกำหนด formKey ลงไปที่ startEvent และ userTask หมายความว่าเมื่อเรา Deploy Definition นี้ลงไปที่ Alfresco แล้ว เราต้องการให้ใช้ form ใดในการแสดงผลนั่นเอง ซึ่งผมกำหนดไว้ว่า startEvent ให้ใช้ form bpm:startTask และ userTask ต่างๆ ให้ใช้ form ของตัวเองที่ชื่อว่า sample:task1 และ sample:task2

    ในส่วนของ acvititi:assignee นั้นผมกำหนดให้ task วิ่งไปยัง user tantai ซึ่งเมื่อมีการ Start Workflow แล้ว Task ก็จะวิ่งไปยัง user tantai ทันที รวมทั้งเมื่อจบ task1 ก็จะวิ่งไปยัง user tantai เช่นกันที่ task2

    ซึ่งต่อไปเราจะมาสร้าง Workflow Model กันบนฝั่ง Alfresco

    Note :  Recommended to edit workflow definition in the text editor (not in Activiti Designer) because Activiti Designer have a lot of bug in current version (My version is 5.12.0)

Creating Alfresco Model

การสร้าง Model บน Alfresco นั้นประกอบไปด้วย 3 ส่วนคือ
  1. Spring Context files
  2. Model files
  3. Message
Note: แต่ในการสร้าง Workflow Model นั้น Message ใน Model ไม่จำเป็นครับ ผมขอตัดออกไปก่อนเพื่อ Focus ในส่วนของ Workflow จริงๆ

แนะนำให้อ่าน Data Dictionary ของ Alfresco ก่อนครับ http://wiki.alfresco.com/wiki/Data_Dictionary_Guide

เนื่องจาก Alfresco ได้เขียน Spring Config ไว้ให้ import ไฟล์ที่ชื่อตามด้วย -context.xml ทุกไฟล์ใน folder TOMCAT_HOME/shared/classes/alfresco/extension/ ไว้ ขั้นตอนแรกให้เราสร้าง Spring Context file ขึ้นมาโดยต้องตั้งชื่อเป็น sample-context.xml และวางไว้ที่ folder extension config bean ดังนี้



<beans>

   <!-- Registration of new models -->
   <bean depends-on="dictionaryBootstrap" id="sample.model" parent="dictionaryModelBootstrap">
      <property name="models">
         <list>
            <value>alfresco/extension/sample-model.xml</value>
         </list>
      </property>
   </bean>
</beans>
จาก code ด้านบนเมื่อ Start Alfresco แล้ว Alfresco จะ load model จากไฟล์ที่ชื่อว่า sample-model.xml ดังนั้นให้เราสร้างไฟล์ sample-model.xml วางไว้ที่เดียวกันและ config bean ดังนี้




<?xml version="1.0" encoding="UTF-8"?>
<model name="sample:sampleModel" xmlns="http://www.alfresco.org/model/dictionary/1.0">

   <!-- Optional meta-data about the model -->
   <description>Sample Model</description>
   <author>tantai@osdev.co.th</author>
   <version>1.0</version>

   <imports>
      <!-- Import Alfresco Dictionary Definitions -->
      <import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d"/>
      <!-- Import Alfresco Content Domain Model Definitions -->
      <import uri="http://www.alfresco.org/model/content/1.0" prefix="cm"/>
      <!-- Import Alfresco BPM Model Definitions -->
      <import uri="http://www.alfresco.org/model/bpm/1.0" prefix="bpm" />
      <!-- Import Alfresco Workflow Model Definitions -->
      <import uri="http://www.alfresco.org/model/workflow/1.0" prefix="wf"/>
   </imports>

   <!-- Introduction of new namespaces defined by this model -->
   <namespaces>
      <namespace uri="http://www.osdev.co.th/sample/workflow" prefix="sample"/>
   </namespaces>

   <types>
      <type name="sample:task1">
         <title>Task1</title>
         <parent>wf:reviewTask</parent>
      </type> 
      <type name="sample:task2">
         <title>Task2</title>
         <parent>wf:reviewTask</parent>
      </type>
   </types> 
</model>

จาก code ด้านบน ผมกำหนด type ขึ้นมา 2 type คือ sample:task1 และ sample:task2  เพื่อให้ Workflow ใช้ type ดังกล่าวในการแสดงผล สังเกตุได้ว่า ผมไม่ได้สร้าง bpm:startTask เนื่องจาก bpm:startTask นั้นมีอยู่แล้วบน Alfresco (สามารถดูได้จากไฟล์ bpmModel.xml) และทั้ง 2 type ที่ผมสร้างขึ้นมาใหม่นั้นใช้ parent เป็น wf:reviewTask ซึ่งมีอยู่แล้วใน Alfresco เช่นกัน โดยเมื่อสร้าง Model เรียบร้อยแล้ว ให้สั่ง Restart Service Alfresco เพื่อให้ Alfresco load model ไปใช้งาน

Deploy Workflow

การ Deploy Workflow บน Alfresco นั้นไม่ยากครับเพราะว่า Alfresco มีหน้า workflow-console เพื่อใช้จัดการพวก Workflow อยู่แล้ว โดยขั้นตอนการ Deploy เราแค่ copy file sample.bpmn20.xml ไปไว้ที่ folder extension จากนั้นไปยัง http://{host}:{port}/alfresco/faces/jsp/admin/workflow-console.jsp เพื่อสั่ง deploy workflow ด้วยคำสั่ง
 
deploy activiti alfresco/extension/sample.bpmn20.xml 
Note: หากเข้า url ดังกล่าวไม่ได้ ให้เช้าไปที่ url http://{host}:{port}/alfresco เพื่อทำการ login ก่อนแล้วจึงเข้าไปยัง url ดังกล่าวอีกครั้ง  

โดยเมื่อสั่ง Deploy แล้ว ที่ Alfresco Share เราจะสามารถสั่ง Start Workflow Sample ที่เราทำได้
 
 แต่เมื่อเลือก Sample Workflow แล้วจะสังเกตุได้ว่า form UI ต่างๆ นั้นจะแสดง properties ทั้งหมดออกมา
ซึ่งตามความเป็นจริงแล้ว เราต้องเลือกที่จะแสดงผล properties อะไรบ้าง ซึ่งจะอธิบายใน Step ต่อไปว่าเราจะกำหนด Share UI กันอย่างไร

Configure Share UI 

 การ Config Share UI นั้นเราสามารถทำได้ที่ไฟล์ share-config-custom.xml ซึ่งอยู่ที่ TOMCAT_HOME/shared/classes/alfresco/web-extension/share-config-custom.xml โดยเราสามารถกำหนดการแสดงผลของแต่ละ form ได้ดังนี้
   
<config evaluator="string-compare" condition="activiti$sample">
   <forms>
      <form>
         <field-visibility>
            <show id="packageItems" />
            <show id="transitions" />
         </field-visibility>
         <appearance>
            <set id="" appearance="title" label-id="workflow.set.general" />
            <set id="items" appearance="title" label-id="workflow.set.items" />
            <set id="response" appearance="title" label-id="workflow.set.response" />

            <field id="packageItems" set="items" />
         </appearance>
      </form>
   </forms>
</config>

<config evaluator="task-type" condition="sample:task1">
   <forms>
      <form>
         <field-visibility>
            <show id="packageItems" />
            <show id="transitions" />
         </field-visibility>
         <appearance>
            <set id="" appearance="title" label-id="workflow.set.general" />
            <set id="items" appearance="title" label-id="workflow.set.items" />
            <set id="response" appearance="title" label-id="workflow.set.response" />

            <field id="packageItems" set="items" />
         </appearance>
      </form>
   </forms>
</config>

<config evaluator="task-type" condition="sample:task2">
   <forms>
      <form>
         <field-visibility>
            <show id="packageItems" />
            <show id="transitions" />
         </field-visibility>
         <appearance>
            <set id="" appearance="title" label-id="workflow.set.general" />
            <set id="items" appearance="title" label-id="workflow.set.items" />
            <set id="response" appearance="title" label-id="workflow.set.response" />

            <field id="packageItems" set="items" />
         </appearance>
      </form>
   </forms>
</config>

จาก config ด้านบนผมกำหนดให้ตอน startEvent, task1, task2 นั้นแสดงผล properties แค่ packageItem เท่านั้นหมายถึงผู้ Start Workflow จะ add file เข้ามาได้เพียงอย่างเดียว
โดยเมื่อ Start Workflow แล้ว Task จะวิ่งไปหา tantai ซึ่งถูกกำหนดไว้ที่ definition แล้ว โดยเมื่อ tantai login เข้ามาในระบบ จะเห็น task ดังนี้
โดยเมื่อคลิกเข้าไปก็จะเห็นหน้าตาดังที่เรา Config ไว้
โดยเมื่อกดปุ่ม Task Done, Task ก็จะวิ่งไปยัง tantai อีกครั้ง (หน้าตาเหมือนกันเพราะ Config ไว้เหมือนกัน)
โดยเมื่อกด Task Done อีกครั้งก็จะจบ Flow

Summary

โดยปกติแล้วการสร้าง Workflow จะมีขั้นตอนพื้นฐานเพียงเท่านี้ แต่หากจะต้องการให้ Workflow ของเราทำงานอื่นๆ ด้วยเช่น ต้องการส่งเมลเตือน, Generate PDF files เราจำเป็นต้องทำเองโดยกำหนด Listener ของแต่ละ userTask เช่น เมื่อจบ task1 ให้สร้าง PDF files และ mail ไปยังบุคคลที่กำหนดไว้ที่ task2 สามารถดูรายละเอียดได้จาก link นี้เลยครับ http://www.activiti.org/userguide/index.html#taskListeners ซึ่งผมแนะนำให้เขียน Listener เป็น ScriptTaskListener พอครับ ไม่จำเป็นต้องเป็น Java Class เนื่องจาก Alfresco ใช้ Rhino เป็น JavaScript Engine ทำให้เราสามารถเรียกใช้งาน Java Class ได้ผ่าน Js ทันที ในส่วนการ Customize form หากต้องการเพิ่ม properties ใดๆ ก็สามารถทำได้ที่ Model จากนั้นก็มา Config การแสดงผลที่ share-config-cuxtom.xml ได้ทันที
Continue Reading...

วันอังคารที่ 14 พฤษภาคม พ.ศ. 2556

Fix bug in numbertext for thai in libreoffice

ใน numbertext extension สำหรับ libreoffice นั้นมี bug อยู่คือตัวเลขที่มากกว่าหลักล้านจะแปลผิด (สามารถลองใส่ตัวเลขได้ผ่านหน้า web http://numbertext.org ได้) ซึ่งผมได้ post bug + patch ไว้ที่ launchpad แล้วแต่เจ้าของเขายังไม่ patch ให้ซักที ผมเลย patch เองซะเลย คนอื่นจะได้เอาไปใช้กันได้ โดยสามารถ download ไปใช้งานกันได้เลยครับ

link download
https://docs.google.com/file/d/0B74Y1jkVlWmhZVJOQTg1MUtQRFE/edit?usp=sharing

issue
https://bugs.launchpad.net/numbertext/+bug/1074639
Continue Reading...

Blogroll

About