열이 아빠님의 글을 보고 http://koko8829.tistory.com/575 삽질을 시작했습니다.
사실 Spring Bean을 BlazeDS에서 사용하는 것은 이미 다른 사람들이 많이 만들었죠^^ 근데 스프링소스에서 공식적으로 지원을 해주다니 대단합니다^^ 제가 한번 해봤습니다-_-; 스프링과 BlazeDS의 기본만 알고 있어서 하는데에는 무리가 없었습니다-_-;

환경 : JDK 6 U 10 + Tomcat 6.0.18 + BlazeDS 3.2.0.3978 + Spring BlazeDS Integration 1.0.0.M1 + Flex SDK 3.2 + Flex Builder 3.0.2 + SpringFramework 2.5.6

SpringFramework 2.5.6 Download
BlazeDS 3.2.3978 Download
Spring BlazeDS Integration Download

쉬운 개발환경을 위해 플렉스빌더에서.....
New Flex Project -> Project name은 SpringBlazeDS, Web application을 선택하고, Application server type은 J2EE로 합니다 ^^ Next를 하시면 Target runtime은 Tomcat 6.0, Flex WAR파일은 BlazeDS를 다운로드해서 blazeds.war파일을 선택합니다. Finish를 때려줍니다-_-;

프로젝트의 Properties에서 Flex Server에 보면 Context root부분이 /WebContent로 되어있는데, /SpringBlazeDS로 바꿔줍니다.

라이브러리는 Spring에서 spring.jar, spring-webmvc.jar, Spring BlazeDS Integration에서 org.springframework.flex-1.0.0.M1.jar를 WEB-INF/lib폴더에 복사하면 됩니다.

자바쪽 셋팅을 해봅시다.
Webcontent/WEB-INF/web.xml파일을 열어서 수정합니다.
기존에는 MessageBrokerServlet을 사용해서 하는데, Spring BlazeDS Integration에서는 Spring Servlet을 사용합니다. MessageBroker Servlet을 servlet-mapping과 함께 지워주고, Spring Servlet을 선언합니다.
web.xml
[code]<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" 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">
    <display-name>SpringBlazeDS</display-name>

    <context-param>
        <param-name>flex.class.path</param-name>
        <param-value>/WEB-INF/flex/hotfixes,/WEB-INF/flex/jars</param-value>
    </context-param>

    <!-- Http Flex Session attribute and binding listener support -->
    <listener>
        <listener-class>flex.messaging.HttpFlexSession</listener-class>
    </listener>

    <!--  Spring Dispatcher Servlet -->
    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/messagebroker/*</url-pattern>
    </servlet-mapping>
   
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>
[/code]
설정파일을 작성하기 전에 초간단 스프링 빈을 하나 만들어봅시다.
service패키지를 하나 만들고, HelloService라는 클래스를 만듭시다.
[code]package service;

public class HelloService {
    public String sayHello(String name) {
        return name + "! 메리추석!";
    }
}
[/code]
이제 /WebContent/WEB-INF/applicationContext.xml파일을 생성합니다.
applicationContext.xml
[code]<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
   
    <bean id="mySpringManagedMessageBroker"
        class="org.springframework.flex.messaging.MessageBrokerFactoryBean" />
       
    <!-- Maps request paths at /messagebroker to the BlazeDS MessageBroker -->
    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"
        p:mappings="/*=mySpringManagedMessageBroker" />
   
    <!-- Dispatches requests mapped to a MessageBroker -->
    <bean class="org.springframework.flex.messaging.servlet.MessageBrokerHandlerAdapter"/>
   
    <bean id="helloService" class="service.HelloService" />
   
    <bean id="hello"
        class="org.springframework.flex.messaging.remoting.FlexRemotingServiceExporter"
        p:messageBroker-ref="mySpringManagedMessageBroker"
        p:service-ref="helloService"/>
       
</beans>
[/code]
기존 MessageBroker가 Spring에 의해 관리된 MesssageBroker로 들어있는 것 같습니다. 그래서 Remote요청이 들어오면 Spring MessageBroker가 해당 destination을 찾아서 해주는 것 같습니다. 그리고, Spring Bean인 helloService를 불러오는 방법은 FlexRemotingServiceExporter를 이용해서 하는 것 같습니다. 요청하고 싶은 Bean을 FlexRemotingServiceExporter에 DI를 해서 사용하는 것이군요.
이곳에서 FlexRemotingServiceExporter의 id가 destination입니다^^ 저기서 hello로 정의했으니 Flex에서는 destination을 hello로 맞춰주면 되겠죠? ^^
나중에 destination을 추가하는 것은 service-config.xml에서 하는 것이 아니라 이곳에서 해야겠죠.
제가 잘못 이해하고 있는 것이 좀 많은 것 같아서...원문을 참조하세요~ ^^

이제 service-config.xml에 추가해야할 부분이 있습니다.
[code]<services>
    <service-include file-path="remoting-config.xml" />
    <service-include file-path="proxy-config.xml" />
    <service-include file-path="messaging-config.xml" />   
    <default-channels>
       <channel ref="my-amf"/>
    </default-channels>   
</services>
[/code]
아....중요합니다. default-channels를 추가해야합니다!

이제 클라이언트로 가봅시다.
SpringBlazeDS.xml
[code]<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
    <mx:RemoteObject id="srv" destination="hello" />
    <mx:TextInput id="inputName" />
    <mx:Button label="전송" id="btnConfirm" click="srv.sayHello(inputName.text)" />
    <mx:Label id="labelResult" text="{srv.sayHello.lastResult}" />
</mx:Application>
[/code]
RemoteObject로 sayHello함수를 input에 입력해서 결과를 Label에 쓰는 간단한 프로그램입니다.
서버를 실행시키고 실행해봅시다.
사용자 삽입 이미지
로그도 자세히 남는군요.
2008. 12. 27 오전 2:31:42 org.springframework.flex.messaging.servlet.MessageBrokerHandlerAdapter handle
정보: Channel endpoint my-amf received request.

젠장......어느카테고리에 넣어야 하지-_-; Spring에 넣자-_-;
 
Posted by 머드초보
,
 
ActionScript3가 제공하는 Sound클래스에서 구하는 재생시간은 구하는데 너무 오래걸립니다.
Sound객체를 생성에 Complete이벤트가 발생한 다음에야 재생시간을 구할 수 있습니다.
재생목록에 추가를 해서 그냥 간단히 재생시간을 보여줘야하는데, 100곡을 재생목록에 추가를 해버리면 AIR애플리케이션이 미친듯이 메모리를 잡아먹는 것을 볼 수 있습니다-_-;

그래서 찾아보니, mp3 Header정보를 이용해서 재생시간을 구할 수 있습니다.

재생시간 = 파일크기 * 8 / 비트레이트 로 구할 수 있습니다.
그러면 비트레이트만 구하면 되는데, 이건 MP3Header에서 찾을 수 있습니다.

MP3 BITRATE는 http://www.datavoyage.com/mpgscript/mpeghdr.htm에 의하면 MPEG버전, LAYER, Bitrate Index로 구할 수 있습니다. MP3는 각각 Frame별로 Header가 존재하는데, 거기서 위에 정보를 구할 수 있는 것 같습니다.

ID3v2태그가 있는 경우는 ID3v2태그 다음에 MP3Header가 나옵니다. 그렇다면 ID3v2태그의 길이를 구해서 그 다음부터 MP3Header를 찾아야 합니다. ID3v2태그는 길이가 가변적입니다.그래서 총길이를 알아야하는데, 총길이는 ID3v2태그 맨 앞에 나오는 10byte Header부분에서 구할 수 있습니다.

이 Header부분의 6byte~10byte까지가 ID3v2의 총 길이인데요. 여기의 값이 00 00 1F 76(00011111 01110110)이라면 각각 MSB를 제거하여 붙인 값이 총 길이가 된다더군요. 00111111110110 -> 4086byte.

우선 C#으로 구현해놓은 소스가 있습니다. 그것을 Actionscript3로 변환했습니다(구현하려니 힘들어서 ㅠ)
C#소스 -> http://www.devhood.com/tutorials/tutorial_details.aspx?tutorial_id=79

제가 변환한 AS3용 MP3Header클래스입니다. 제가 가지고 있는 MP3 대부분 테스트해봤는데 잘 되더라구요.
invalid-file

MP3Header클래스


사용법은 이렇게 쓰시면 됩니다.
[code]
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
    creationComplete="init()">
    <mx:Script>
        <![CDATA[
            import util.MP3Header;
            private function init():void
            {
                var mp3Header:MP3Header = new MP3Header();
                mp3Header.readMP3Information("D:/눈물이 글썽 - 서진영.MP3");
                trace("BITRATE = " + mp3Header.intBitRate);
                trace("Frequency = " + mp3Header.intFrequency);
                trace("Mode = " + mp3Header.strMode);
                trace("LengthFormatted = " + mp3Header.strLengthFormatted);
                trace("Length = " + mp3Header.intLength);
            }
        ]]>
    </mx:Script>   
</mx:WindowedApplication>
[/code]
[code]
BITRATE = 64
Frequency = 44100
Mode = Stereo
LengthFormatted = 04:00
Length = 240
[/code]

 
Posted by 머드초보
,
 
다...다른 방법을 검색해서 구했습니다-_-;
하지만, 이건 한글은 이제 잘 읽는 것 같은데......한자를 못 읽습니다-_-; 원래 안되는건가....-_-;

우선 ID3Parser 링크입니다. 매우 빠른 속도로 ID3데이터를 가져옵니다.
http://blog.ashier.com/2007/11/08/id3-parser/

여기서 한글을 읽을 수 있게 수정하는 부분이......
보면 ID3데이터를 추출해오는 부분이 있습니다.
거기서 한글로 추출할 수 있게 변경합니다.

[code]
private function parseFrames():void {
    var id:String = "";
    var size:uint = 0;
    if(fs.position <length) {
        try {
            id = fs.readUTFBytes(frameIdSize);
            size = fs.readUnsignedInt();
            if (version>= 3) {
                fs.readByte();
                fs.readByte();
            }
            if(id.match(regEx)) {
                var obj:Object = new Object();
                obj.encoding = fs.readByte();
                obj.text = fs.readUTFBytes(size - 1);
                frames[id] = obj;
            }
            parseFrames();
        }catch(e:Error) {}
    }
}
[/code]
이 부분이 있는데요. 한글을 읽어오는 readUTFBytes 부분을 바꿔줍니다.
[code]
//obj.text = fs.readUTFBytes(size - 1);
if (obj.encoding == "1")
{
    obj.text = StringUtil.trim(fs.readMultiByte(size - 1, "unicode"));
}
else
{
    obj.text = StringUtil.trim(fs.readMultiByte(size - 1, "EUC-KR"));
}
[/code]
보니까 obj.encoding이 1인 값은 unicode로 인코딩 된 것 같아요. obj.encoding이 0인 값은 EUC-KR로...
이렇게 하니까 잘 되네요....가끔 공백을 추출해오고 그래서 trim처리했습니다.

아래글은 ID3v1방식 추출 방법 및 ID3v2의 다른 추출 방식 입니다^^ 참고하세요~
http://mudchobo.tomeii.com/tt/356
 
Posted by 머드초보
,
 
그......sound객체를 이용해서 하는 것이라니라 직접 바이트로 읽어서-_-; 하는 법이 있더라구요.
우선 ID3v1태그는 음악내용 맨 뒤에 있습니다. 그래서 추출하기는 쉽습니다.

http://cafe.naver.com/flexcomponent.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=10087
flexcomponent에서 혀니님께서 작성하신 글입니다.

그 다음 ID3v2태그는 맨 앞에 있습니다. 또한 이것을 추출하는 방법이 만만치 않습니다-_-;
왜냐하면 이건 뭐 길이도 가변적이고, 태그가 복잡합니다-_-;
그래서 태그를 공부하는 것보다 누가 만들어 놓은 것을 찾는 게 더 빠를 듯하여.....-_-;
구글신님께 부탁드리니 잘 찾아주셨습니다 ^^

http://blog.benstucki.net/?id=3

이것을 그대로 사용하시면 한글이 깨집니다.
사용자 삽입 이미지

저 위에 소스를 다운 받아서 첫번째 ID3v1에서 사용한 방법을 사용합니다.
ID3Reader.as파일에 readTextFrame이라는 함수가 있는데요.
이게 텍스트를 읽어올 때 사용하는 함수인 것 같습니다.
[code]
//obj.text = bytes.readUTFBytes(size-1);
obj.text = bytes.readMultiByte(size-1, "EUC-KR");
[/code]
요렇게 바꿔줍니다-_-;
그런 다음에 실행하면 한글이 잘 나옵니다.
사용자 삽입 이미지
하지만........-_-;
기존에 Actionscript에서 제공하는 Sound클래스에서 추출하는 ID3에서 잘나오는 한글(즉 UTF-8로 인코딩 된 것)은 여기서 읽으면 잘 안나옵니다-_-; 이거 추출하는 프로그램 만드신 분이 잘못 만든건지 잘 모르겠는데요. 확인해봐야할 것 같네요 ㅠ

사용자 삽입 이미지
헉...-_-; 인코딩을 unicode로 바꾸니까 잘 나오네요. 기존에 Actionscript에서 잘 나오는 것은 unicode로 되어있어서 그런거였나요? ㅠ 더 확인해봐야겠습니다 ㅠ
이거 말고, 요아래 것으로 하면 잘 되네요.

ID3v2추출하는 다른 방법입니다.
http://mudchobo.tomeii.com/tt/357
 
Posted by 머드초보
,
 
Microsoft Outlook 2007에서 최소화버튼을 누르면, 창이 사라지면서 작업표시줄에서도 같이 사라지고, 트레이아이콘으로만 남게 됩니다.
그것을 AIR에서도 구현할 수 있습니다.

AIR에서는 윈도우의 DisplayState를 캐치할 수 있는데요. 즉, 최소화, 최대화 이런 이벤트가 발생하는 것을 잡을 수 있습니다. 그래서 만약 위와 같은 기능을 구현하고자 한다면 이렇게 하면 됩니다.

최소화이벤트를 잡은 뒤, 창의 visible을 false로 바꾸고, trayicon의 icon이미지를 넣어주면 됩니다.
또, 창을 다시 원래 대로 돌리려면, trayicon에 이벤트를 걸어서 클릭 시, 창의 visible을 true로 바꿔주고, trayicon의 icon이미지를 삭제하면 됩니다.

http://help.adobe.com/en_US/AIR/1.1/devappsflex/WS5b3ccc516d4fbf351e63e3d118666ade46-7dcb.html

음....저는 찾는데 하루를 소비했습니다만-_-;(머리가 딸려서-_-) 찾은 문서는 Adobe AIR에 있는 "Developing Adobe AIR 1.1 Applications with Flex"문서군요 ㅠ

아래는 제가 구현해봤습니다.
[code]
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
    creationComplete="creationCompleteHandler()">
    <mx:Script>
        <![CDATA[

            private var icons:Loader = new Loader();
                   
            private function creationCompleteHandler():void
            {
                makeTrayIcon();
                icons.contentLoaderInfo.addEventListener(Event.COMPLETE, iconLoadComplete);
               
                nativeWindow.addEventListener(
                        NativeWindowDisplayStateEvent.DISPLAY_STATE_CHANGING, onChange);
            }
           
            private function onChange(event:NativeWindowDisplayStateEvent):void
            {
                if (event.afterDisplayState == NativeWindowDisplayState.MINIMIZED)
                {
                    event.preventDefault();
                    nativeWindow.visible = false;
                   
                    icons.load(new URLRequest("icons/icon_16.png"));
                }
            }
           
            private function makeTrayIcon():void
            {
                var iconMenu:NativeMenu = new NativeMenu();
                var visibleCommand:NativeMenuItem = iconMenu.addItem(new NativeMenuItem("Visible"));
                visibleCommand.addEventListener(Event.SELECT, function(event:Event):void {
                    nativeWindow.visible = true;
                    NativeApplication.nativeApplication.icon.bitmaps = [];
                });
               
                var exitCommand:NativeMenuItem = iconMenu.addItem(new NativeMenuItem("Exit"));
                exitCommand.addEventListener(Event.SELECT, function(event:Event):void {
                    NativeApplication.nativeApplication.icon.bitmaps = [];
                    NativeApplication.nativeApplication.exit();
                });
               
                var systray:SystemTrayIcon =
                NativeApplication.nativeApplication.icon as SystemTrayIcon;
                systray.menu = iconMenu;
            }
        
            private function iconLoadComplete(event:Event):void
            {
                NativeApplication.nativeApplication.icon.bitmaps =
                    [event.target.content.bitmapData];
            }

        ]]>
    </mx:Script>
</mx:WindowedApplication>
[/code]
핵심은
[code]
nativeWindow.addEventListener(
                        NativeWindowDisplayStateEvent.DISPLAY_STATE_CHANGING, onChange);

private function onChange(event:NativeWindowDisplayStateEvent):void{
    if(event.afterDisplayState == NativeWindowDisplayState.MINIMIZED){
        event.preventDefault();
        event.target.visible = false;
    }
}
[/code]
우선 윈도우에 NativeWindowDisplayStateEvent.DISPLAY_STATE_CHANGING 이벤트를 겁니다. 이것은 윈도우의 상태가 변경되었을 때 발생합니다.
최소화버튼을 누르거나 작업표시줄에서 버튼을 클릭하여 최소화 할 때 이벤트가 발생합니다.
event.preventDefault()를 호출하게 되면 디폴트동작을 취소하게 됩니다. 그리고 창만 숨기는거죠. 창을 숨기면 작업표시줄에도 사라집니다. 그리고, trayicon을 만들면 됩니다.

후.....달이차오르니 가야겠습니다.

 
Posted by 머드초보
,