31 Ekim 2013 Perşembe

Primefaces Growl Not Rendered After Update

I continue forwarding my experiences on primefaces. Today I've had another issue and finally got the solution.

If you are using primefaces growl component to show messages after a process, you have to add a FacesMessage to FacesContext and update the component or set globalOnly attribute true. Ordinarily, growl must show up and show your test you put in FacesMessage. In my project, I push a primefaces command button that has an actionListener, I do things in server side, put my FacesMessage to the context and update my components in UI. But the growl does not show up. I've searched for the problem and finally say my mistake got the solution.

Here is my problematic code:
<h:form id="formNewF">

  <p:dialog widgetVar="dlgNewF" dynamic="true">
    <p:ajax event="close" listener="#{myController.closeF()}" update="formNewF"/>
    
    <p:panelGrid columns="3" id="newFPanel">
      <h:outputLabel for="fName" value="FName" /> 
      <p:inputText id="fName" value="#{myController.newF.name}" /> 
      <p:message id="fNameMessage" for="fName" display="text" />  
      <p:column/>
      <p:commandButton value="Save" update="newFPanel, dataTable" action="#{myController.saveF()}" oncomplete="if(!args.validationFailed){dlgNewF.hide();}"/> 
      <p:column/>
    </p:panelGrid>
  </p:dialog>

</h:form>

As you can see, I execute my saveF method. In that method, I put my FacesMessage to context. Here the problem comes. When I return to xhtml the code updates dataTable but the code in my oncomplete event triggers an ajax event which means that we have to go to the server side again. Remember, these are all happening before rendering the xhtml. So my second request to server side overrides the previous context or creates a new one and deletes my FacesMessage. This is the reason why growl does not render.

As a summary, more than one request to server side before rendering the view causes disappearing your FacesMessage and the growl does not render. You can solve this problem by removig the second request or doing the job in one server side request.

( primefaces, jsf, growl, facesmessage, facescontext, rendering, twice, not shown )

28 Ekim 2013 Pazartesi

Cehennem (Inferno) - Dan Brown


Kitabı çıktığı gün almıştım fakat okumak ve inceleme yazmak bu zamana kadar kaldı ne yazık ki. Şimdi düşünüyorum da keşke hemen okusaymışım. Uzun bir kitap okumama döneminden sonra bu kitap ilaç gibi geldi diyebilirim.

Kitap, daha ilk 10 sayfada kendine bağımlı yapmaya başlıyor insanı. Dan Brown'un tarzı, insanı çok fazla boğmayan, kısmen basit olan betimlemeleri ile oldukça kolay anlaşılır oluyor. Beğendiğim bir diğer husus ise roman içerisindeki hikaye sayısını sınırlı tutması. Yani 10 tane ayrı hikaye ile başlayıp kitabın sonunu bağlayamama gibi bir durum olmuyor. Akış ve takip edilebilme durumu, önceki kitaplarındakinde de daha iyi olmuş diyebilirim.

Gelelim konuya. Belki konuların çok ilgimi çekmesinden dolayı, ya da çok tutulan konuları seçmesinden dolayı Dan Brown'un kitaplarını merak içerisinde okuyorsunuz. Bu kitapta, insanı merakta bırakan en önemli unsur kuşkusuz ki Dante olmuş. Dante ile ilgili, fazlaca paralar saçılarak yapılacak bir reklam kampanyası bile bu kadar tutmazdı eminim. Kitabın çeşitli yerlerinde bir çok kez kendisine, yaşamına, siyasi görüşüne, eserlerine değinilmiş ve bir çok detay verilmiş. Bu kitap sonrası, hiç alakası olmayan insanların bile merak edip İlahi Komedya'yı okuyacağından eminim. Kitapta bahsi geçen eserler, belli ki çok büyük bir bilgi birikimi, çalışma ve ekip çalışması ile incelenmiş. Dan Brown'ın ve etrafındaki yardımcı insanların gerçekten de inanılmaz bir bilgi birikimine sahip olduğu söylenebilir. Mekanları ve eserleri öylesine güzel işlemişler ki, kitap okurken bir kaç kez durup Google'a araştırma gereği duydum. Müzelerde gezdiğinizde yanından geçip "güzel çizilmiş" dediğiniz eserlerin altındaki anlam, kimin için, ne amaçla yapıldığı gibi bilgileri öğrendikçe yeniden yeniden incelemek istiyorsunuz eserleri.

Kitabın geçtiği mekanlara bakacak olursak, Dan Brown'ın uzmanlık alanı olan Floransa, Venedik ve az da olsa İstanbul işlenmiş. İtalya'yı ve Floransa'yı daha önce gezmiş bir insan olarak keşke bu kitap bir kaç sene önce çıksaydı da, o eserlere daha fazla bilgi ve merak sahibi olarak baksaydım diyorum. Hikayenin geçtiği mekanlardaki gizli geçitler, çoğu kişinin dikkat etmeyeceği ufak detaylar, bundan sonra özellikle Floransa'yı gezecek insanlar için incelenecek yerler oldu artık. Gelelim İstanbul'a. Diğer şehirlerden biraz daha az değinilmiş olsa da özellikle Tarihi Yarımada ile ilgili güzel tespitler mevcut. Yabancı birileri tarafından her işlendiğindeki o fesli, sarıklı, Arabistan'a benzetilen İstanbul bu sefer daha doğru tespitlerle, ön yargılardan uzak, gerçekten de olduğu gibi işlenmiş. Yalnız bildiğiniz tanıdığınız yerlerin kitapta işlenmesinin şöyle bir dezavantajı var; konudan sapıp mekanların nasıl tanıtılmış onu takip ediyorsunuz bu sefer. Bir de neresi olduğunun merak edildiği yerler var ki biz bildiğimiz için bunları hemen tahmin edebiliyoruz ve heyecanı kalmıyor :) Bir diğer nokta ise, herkesin bir çok gezip gördüğü tarihi eserlerimiz ile ilgili bilmediğimiz detay bilgileri edinebiliyoruz kitaptan.

Senaryoya gelirsek. Akıcı ve insanı sürükleyen bir konu var yine ortada. Önceki kitaplardaki gibi belli bir amaç doğrultusunda tarih içerisinde yolculuk ediyor ve bir takım gizemler çözmeye çalışıyoruz. Hikaye ilerlerken tarihi mekanlar ve detayları ile birlikte, günümüze ait teknolojik gelişmelerden bahsedilmesi, kitapta biraz da bizden birşeyler bulmayı sağlıyor. Özellikte polisiye seven insanlar için kitapta Dan Brown'ın, kim olduğunu merak ettiğimiz karakterler arasında bir ters köşe yapması var ki dillere destan:) Fakat ne yazık ki -spoiler- Robert Langdon'ın hafızasını kaybetmesi sonrası gerçeklerin anlatıldığı esnadaki kurgu çok yavan ve basit kalmış. Truman Show tarzındaki 'Her şey bir senaryoydu' yaklaşımı bende, bir film izleyip sonrasında herşeyin rüya olmasının bıraktığı etkiyi bıraktı. Burası için daha iyi bir kurgu yapılabilirmiş bence -spoiler sonu-

İşten güçten ötürü kitabı tek seferde okuyamadım ne yazık ki fakat bir pazar günü sabahtan başlanıp bir solukta okunacak bir kitap olmuş diyebilirim. Dan Brown'ın yeni kitaplarını dört gözle bekliyor olacağım.

( dan brown, inferno, cehennem, dante, ilahi komedya, cennet, araf, istanbul, venedik, floaransa )

12 Eylül 2013 Perşembe

Primefaces Datatable Contextmenu Disappear Problem Fix

Updatable context menu is a big problem if you are using tree or datatable component of Primefaces. For example in my case, I have a datatable that has row which has different boolean flags. Depending of that flags, I have to change my context menu items. Basically, I have to render different context menus for each row in my datatable. First I tried to update context menu in element but there was a problem which I was finally able to solve...

ContextMenu component has two phases. One is generate or update phase and the other is show phase. If you try to update your context menu on right click, show phase comes first and then it updates itself. After this process, you will see that you context menu appears and disappears. While I was searching for a workaround, I found a post which helped me a lot in Primefaces forum. A guy found a solution which is overriding show function of context menu and then calling this method right after update process is done. This is the best solution I think. That guy applied this workaround for tree component. Below, you can find this approach that is applied to datatable.

<script type="text/javascript">
//patch to fix a problem that the context menu disappears after update
//delay the show to occure after the update 
var siteFunctions = {
    patchContextMenuShow: function() {
        var protShow = PrimeFaces.widget.ContextMenu.prototype.show;
        siteFunctions.patchContextMenuShow.lastEvent = null;
        PrimeFaces.widget.ContextMenu.prototype.show = function(e) {
            var ret;
            if (e) {
//saving last event
                siteFunctions.patchContextMenuShow.lastEvent = e;
                siteFunctions.patchContextMenuShow.lastEventArg = arguments;
                siteFunctions.patchContextMenuShow.lastEventContext = this;
            } else if (siteFunctions.patchContextMenuShow.lastEvent) {
//executing last event
                ret = protShow.apply(siteFunctions.patchContextMenuShow.lastEventContext, siteFunctions.patchContextMenuShow.lastEventArg);
//clearing last event
                siteFunctions.patchContextMenuShow.lastEvent = null;
            }
            return ret;
        };
    }
};

$(document).ready(function() {
    try {
        siteFunctions.patchContextMenuShow();
    } catch (e) {
        console.error(e);
    }
});
</script>

<p:contextmenu beforeshow="return true;" 
               for="dataTable" 
               id="contextMenuID" 
               widgetvar="tableContextMenuWV">
   <p:menuitem value="Menu 1" />
   <p:menuitem value="Menu 2" />
</p:contextmenu>
                
<p:datatable id="dataTable" widgetvar="dataTableW">
   <p:ajax event="contextMenu" 
           oncomplete="tableContextMenuWV.show();" 
           update=":formList:contextMenuID" />
</p:datatable>
( primefaces, jsf, datatable, row, context menu, update, show, disappear )
Kaynak : http://forum.primefaces.org/viewtopic.php?f=3&t=26813

31 Temmuz 2013 Çarşamba

Primefaces Row Toggle on Row Click

Due to the requests of people, I am going to write in English right now (for software topics).

Primefaces, which is a popular tag library for jsf has lots of different components which are pretty useful but these kid of libraries have some problems when you want to do something more detailed. I am going to help these people who are using Primefaces datatable component.

This component has a feature called rowToggler which let us toggle the row by clicking a generated triangle button. But what if you want to toggle the row by clicking anywhere on the row instead of just clicking rowTogglee button? The answer is below. I coded a little javascript stuff to make this feature work.

From one release to another, the css style names and html hierarchy may change in Primefaces. I make this code work in Primefaces v3.5.10. Putting code below to your xhtml page will make datatable rows expandable by clicking on the row:

<script type="text/javascript">
    $(document).on("click", ".ui-datatable-tablewrapper .ui-datatable-data tr.ui-widget-content", function() {
        /*1 - for expanded area*/
        if ($(this).hasClass('ui-expanded-row-content')) {
            return;
        }
        /*2 - to collapse open ones*/
        expandedRow = $('.ui-expanded-row');
        if (expandedRow.length !== 0 && !$(this).hasClass('ui-expanded-row')) {
            $('.ui-expanded-row-content').css('display', 'none');
            var untoggler = expandedRow.find('.ui-row-toggler');
            dataTableWidgetVarName.toggleExpansion(untoggler);
        }
        /*3 - for main expand feature*/
        var toggler = $(this).find('.ui-row-toggler');
        dataTableWidgetVarName.toggleExpansion(toggler);
    });

    $(document).on("click", ".ui-icon-circle-triangle-e", function() {
        if ($(this).hasClass('ui-icon-circle-triangle-s')) {
            return;
        }
        expandedRow = $('.ui-expanded-row');
        if (expandedRow.length !== 0) {
            $('.ui-expanded-row-content').css('display', 'none');
            var untoggler = expandedRow.find('.ui-row-toggler');
            dataTableWidgetVarName.toggleExpansion(untoggler);
        }
    });
</script>

First function is for the row and the second one is for the rowToggler button itself. Because of a bug in Primefaces, you cannot fire click event of the row when you click on images in the row. This is why I had to implement another click function for rowToggler button itself. You can find other descriptions below:

1 - This code block is for expanded area of the row. With this code, onthing happens when user clicks on expanded are of the row.

2 - This code brings a feature to close other expanded rows in datatable when user clicks another row.

3 - This is the main expand feature. When user clicks on the row, I am just calling toggleExpansion() function of datatable element.

Hope this post will help and guide someone who needs similar feature for datatable.

( primefaces, jsf, mojarra, datatable, row, rowToggle, toggleExpansion )

29 Haziran 2013 Cumartesi

Man Of Steel

Çok fazla çizgi roman okumuşluğum falan yoktur aslında fakat Superman'i sanırım herkes bir şekilde bilir küçüklüğünden beri. Benim için çizgi filmleri, 78 yapımı filmi ve 'Superman Returns' ibaretti aslında. Uzun zamandır sinemaya gitmediğim için ve gerçekten bu tarz bir film izlemeyi özlediğim için büyük bir heyecanla gittim aslında filme.


Öncelikle belirteyim bu 3D film furyası başladığından beri, üç boyutsuz izlemek istediğim filmleri o şekilde izleyememek, güzel senaryosu vs. olan filmlerin de üç boyutlu yapılmak uğruna efektlere falan boğulması beni en sinir eden şeydi fakat bu film o konuda çok başarılı diyebilirim. Süper kahraman filmlerindeki klasik olan dünyayı kurtarma kısımları başlamadan önceki kahramanın geçmişinin anlatıldığı kısımlar genelde çok kısa olup hemen aksiyona geçilmek istenir. Man of Steel'de bu durum çok güzel aşılmış bence. Filmin yaklaşık %20lik bir bölümünde nam-ı değer Superman'in doğuşu, gezegeninin geçmişi, dünyamıza gelişi gibi konular işlenmiş. Bu kısımlara hatta apayrı bir film gözüyle bile bakılabilir bence ve çok başarılı olmuş. Kısa kesilmemiş, insanı sıkacak kadar uzatılmamış, konu hemen geçilmemiş, efektler, aksiyon ve senaryo ile izleyiciyi ekranda tutmuş bu bölümler. Superman'in dünyaya gelişi sonrası çocukluğu, büyümesi, özelliklerini insanlardan gizleme çabası da ilk bölümden apayrı bir şekilde işlenmiş, daha sakin geçen, insanları kurtardığı bölümlerle filmin ana düşüncesinden insanları koparmayan bir bölüm olmuş. Filmin son bölümleri ise malum zaten, aksiyon patlaması. Kal-El'in zamanla dünya şartlarına adapte olup kendi gezegeninin atmosferine, materyallerine karşı zayıf düşmesi, ileride muhtemelen kendisini zor durumda bırakacak olan kriptonitin de neden buna sebep olacağını izleyiciye göstermiş ve güzel olmuş. Bunu yaparken insanın gözüne gözüne yeşil bir madde sokmayıp bu şekilde bir senaryodan gidilmesi de oldukça mantıklı olmuş. Ayrıca artık bu tarz filmlerde oldukça sık rastlanılan ufak espriler ve komiklikler de güzel bir renk katmış diyebilirim. Dünya'yı yok etme senaryosuna gelirsek, bence oldukça orijinal olmuş iki taraftan yer çekimini kullanmak. Fakat dünyayı ele geçirme sebebi için aynısını söylemeyeceğim. Arkadaşım koskoca uzayda başka gezegen mi yok da illaki dünya ile uğraşıyorsun. Al gezegeninin çocuk tarlasına sahip gemiyi ve git :) Şaka bir yana o noktada ufak bir zayıflık var ama film Superman'i biraz tanıtan bir film olduğundan ötürü her şeyden çok konulmamış; aynı kriptonitin çok kullanılmaması gibi dünyayı ele geçirme senaryosunun da üzerinde, detay olarak çok düşünülmemiş. Bu durum filmi kötü bir film yapıyor mu? Bence kesinlikle hayır. Benim gözümde hatta eksik denilebilecek tek ufak nokta burası olmuş. Bu senaryonun daha derinleşmesi ve Superman'in dünyadaki halkın sevgisini kazanıp onlarla kader birliği yapması durumunda zaten film bir Dark Knight seviyesine gelir.

Oyunculara geleyim önce. Fragmanı izlediğim zaman kim bu adam demiştim açıkçası. Henry Cavill rolüne tam gitmiş, adam net bir Superman yahu (alnında saçının kıvrılmamış olmasını saymazsak :). Bence oyunculukla aynı seviyede olan şey varsa, o kıyafeti vücuduyla doldurmaktır kesinlikle. Adamda çok başarılı bir vücut var. İleri derecede oyunculuk gerektirecek filmler için belki erken fakat vücut isteyen ne bileyim süper kahraman, kötü adam rollerinde kendisini artık görebileceğiz gibi geliyor. Lois Lane'imiz ise, rolüme çok gitmiş olan Amy Adams. Zaten filmin devamının geleceği bariz. Detayları okumadım, bir kaç filmlik anlaşıldı mı falan ama bu ikilinin devam etmesi bence oldukça güzel olur.

Herkesin çok tartıştığı kıyafet kısmına gelirsek. Ben genel olarak süper kahramanların tayt giymesine karşıyım o ayrı :) ama bence kırmızı slipsiz daha bir iyi olmuş gibi kıyafet. Eski çocuksu kırmızı-mavi ikilisinden biraz daha ürkütücü, daha heybetli, daha cool duran bir kıyafet olmuş. Pelerindeki logunun olmayışını filmin sonlarına doğru fark ettim ki bu da kötü durmamış demek ki. Benim her şeyden çok logo hoşuma gitti. Yani tam bir Superman Ssi değil, biraz daha farklı formatta, daha güzel bir logo olmuş. Özetlersek kıyafetin bu hale gelmesi bence genel olarak güzel olmuş. Yani Batman serilerine de bakarsak, bugün çekilecek bir filmin, siyah üzerine sırıtan sarılarla dolu retro bir kıyafet olmasını artık kimse istemez sanırım, bu da öyle bir şey işte.

Üç boyutlu filmlerdeki, 'Aman üç boyutlu film yaptık, herşeyi derin yapalım, show yapalım' anlayışı sanırım biraz biraz yıkılıyor. Zira bu filmde de benzer bir durum vardır. Efektlerde üç boyut kararında kullanılmış. Zaten karanlık ve hızlı sahnelere sahip filmlerde tam bir işkenceye dönüşüyor bence üç boyut ki bu filmde aynı hataya düşülmemiş. IMAX ise zaten ayrı bir keyif. Binalar arasındaki dövüş sahneleri, sesler, her bir yere çarpınca kırılan, çöken betonlarda kriptondan gelenlerin gücünün yansıtılışı, gezegende filmin başında geçen sahneler, kripton gezegenini parçalanışı... Uzun bir aradan sonra, geç bir saatte gitmeme rağmen bir filmi, saatime hiç bakmadan bitirdim diyebilirim.

After Earth, World War Z ve bu film arasında kalmıştım. Biraz yorum okudum ve sanırım akıllı bir tercih yapmışım. Filmi çok beğendim. Dark Knight seviyesinde tabi ki olmasa da, çok da uzak demeyeceğim ama. Diğer filmini dört gözle bekliyor olacağım. Süper kahraman filmlerini sevenler için kaçırılmaması gereken bir film ki bazıları özellikle Superman hayranıdır, o zaman kesinlikle kaçırmayın derim. Paraya kıyıp IMAX'de izlenmesi gerekiyor. Blueray'ini de çıktığı gibi edinip bir parti de evdeki büyük ekran TV'de izlemeyi planlıyorum. Kaçırmayın derim...

( man of steel, superman, henry cavill, amy adams, kriptonit, imax )

14 Mayıs 2013 Salı

JSF Ajax Requestlerdeki Tarayıcı Geri Butonu Sorunu ve Dojo İle Çözümü

Yine JSF kodlama ve yeni bir sorun ile karşı karşıyayız :) Aslında problem tam olarak JSF ile ilgili değil. Bir web uygulaması içerisinde AJAX request gönderildiğinde, URL üzerinde herhangi bir değişiklik olmuyor ise, tarayıcılar, hash değeri değişmediği için, içinde bulunduğu sayfayı tarayıcının History stack'ine eklemiyor. Dolayısıyla siz aynı sayfanın üzerinde bir çok işlem yapsanız bile, tarayıcıdaki sayfa adresi değişmediği için geri ve ileri butonlarını kullanamıyoruz.

Bu probleme çözümler mevcut aslında. Çoğu kişi tarayıcı geri-ileri butonlarını kullanıcıya kullandırtmak yerine uygulama içerisinde kendisinin oluşturduğu geri-ileri butonlarını javascript metodları vasıtasıyla kullandırtıyor. Fakat yine de tarayıcının butonlarına bir çözüm bulunmalı ve mevcut sürece entegre edilmeli. Bu noktada, araştırıp, biraz modifiye ederek uygulamamda kullandığım bir yöntemden bahsedeceğim.

Primefaces 3.5 ve JSF 2.1 kullanarak geliştirdiğim bir uygulamada, Primefaces Datatable elemanı içerisinde yüklü bir listem var ve bir file system dizini gibi, her bir satıra tıklandığında, o satırdaki klasörün içerisine girilmesi gerekiyor. Geri ve ileri butonlarının da çalışması gerekiyor.

Bu noktada çözüm olarak dojo'nun back sınıfını kullandım. Bu kütüphane, her bir click eventi esnasında, history stack'ine bir veri kaydetmemizi sağlıyor. Bu veriyi de, geri ve ileri butonlarına tıklandığı zaman yapacağı işlemi tanımlayarak kaydediyoruz. Böylece kullanıcı geri butonuna bastığı zaman, kaydettiği parametrelerle işlem yapabiliyor.

Kodlarla özetlemek gerekirse:

myfiles.xhtml
<script>
     function HistoryState(fileid)
     {
           this.fileid = fileid;
           this.back = function() { 
                browserBck([{name: 'fileid', value: fileid}]);
           };
           this.forward = function() { 
               browserFrwrd([{name: 'fileid', value: fileid}]);
           };
           this.changeUrl = false;
       }
</script>

<script type="text/javascript"  src="js/dojo.js" djConfig="preventBackButtonFix: false"></script>
<script type="text/javascript">
     dojo.require("dojo.back");
     dojo.back.init();
     dojo.back.setInitialState(new HistoryState(null));
</script>

...<p:dataTable>... 
        <p:ajax event="rowSelect" listener="#{fileController.list()}"/> 
...</p:dataTable>

   <p:remoteCommand name="browserBck" action="{fileController.listBack()}"/> 

Yukarıdaki örnekte HistoryState adında bir nesne oluşturdum. Bu nesnede, dojo için gerekli olan back, forward ve changeUrl değişkenlerini set ettim. back değişkeni, geri butonuna tıklanınca yapılacakları, forward değişkeni ileri butonu için aynı işlemleri tanımlamamızı sağlıyor. changeUrl ise, her bir state kaydolurken dojo tarafından oluşturulan random rakamın adres satırına yazılıp yazılmamasını kontrol ediyor. HTML5 desteği olmayan tarayıcılar için bu değer true olmalı. Sayfa ilk defa render olurken ise setInitialState'i çalıştırıp işlemi başlatıyoruz. Yine aynı dosyada bulunan p:dataTable elemanının bir satırına tıklanınca server tarafında liste yenilemek için <p:ajax> kullandım:

FileController.java
public void list() {
        RequestContext rc = RequestContext.getCurrentInstance();
        rc.execute("dojo.back.addToHistory(new HistoryState('" + selectedFile.getId() + "'));");
        listByParentID(selectedFile.getId());
}

public void listBack() {
        ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext(); 
        String fileID = ec.getRequestParameterMap().get("fileid");
        listByParentID(fileID);
} 

Yukarıdaki örnekte görüldüğü gibi, her bir listeleme işleminde, client tarafındaki addToHistory metoduna, HistoryState nesnemi ,içerisinde mevcut parent id bulunacak şekilde set ediyorum.

Bu örnek çok daha basitleştirilbilirdi fakat JSF ve server side'da detaylı bir örnek bulunsun istedim.

Süreci Özetlersek:
1 - xhtml dosyasında dojo'yu initialize ettik. History'ye kaydedeceğimiz nesneyi ve back-forwad metodlarımızı belirledik
2 - Bir linke tıklayıp server tarafına bir ajax request geçtik. Server tarafındaki metodda "RequestContext.getCurrentInstance().execute()" ile client tarafındaki dojo.back.addHistory metodunu çağırdık.
3 - Tarayıcının geri butonuna bastık. Bu işlem, 1. maddede belirlediğimiz back metodunu çağırdı (client-side).
4 - <p:remoteCommand> ile, 3. maddede çağrılan metodun, server tarafındaki başka bir metodu çağırmasını sağladık.
5 - Server tarafındaki metod ise istediğimiz listelemeyi yaptı ve geri butonumuz çalışmış oldu.

İleri butonu için de, forward nesnesine gerekli fonksiyonu tanımlayarak aynı işlemleri tekrarlamamız lazım.

Not: Bu şekilde çalışabilmesi için 'js' klasörünün içerisinde;
* dojo.js
* back.js
* resources/iframe_history.html

dosyalarının bulunması gerekiyor.

( jsf, primefaces, ajax, browser back button, forward, dojo, dojo.back, dojo.hash, request, xhtml, p:remoteCommand, p:dataTable )
Kaynak: http://blog.andreaskahler.com/2009/09/managing-browser-history-for-ajax-apps.html

7 Mayıs 2013 Salı

JSF ve HTML Escape Problemi, Çözümü

JSF teknolojisi, web uygulamaları geliştirirken standart olarak yazılımcının uygulaması gereken bir çok güvenlik açığını kendisi gidermektedir. Bunların en önemlilerinden birisi de XSS'i engellemek için render edilen HTML'de bulunan ve atak yapmaya müsait karakterleri escape etmesidir. Örneğin ">" şeklinde yazılan bir yazı, kaynak kodunda "&gt;" şeklinde gözükecektir. Bu özellik güvenlik açısından kolaylık sağlasa da bir takım kısıtlamalar da getirmektedir. Örneğin, yazılımcının kendisinin, xhtml içerisine yazacağı javascript ya da benzeri kod blokları, JSF'in bu özelliği sebebi ile ya compile olmaz, ya da ekranda sorunlu bir şekilde render edilir.

Bu problemin çözümü ise <h:outputText> kullanmaktır. Bu tag ile, JSF'in default davranışını bypass edebiliriz. Örneğin basit bir Javascript tagi kullanalım:
<script>alert('Test');</script>
Bu kod bloğunu direkt olarak xhtml içerisine yazarsak &lt;script&gt;alert('Test');&lt;/script&gt; gibi bir görüntü ile karşılaşırız ya da IDE'miz bu kodu derlemez, hata verir. Bu kodu önyüze taşımak için:
<h:outputText value="&lt;script&gt;alert('Test');&lt;/script&gt;" escape="false" />
şeklinde bir kullanımda bulunmalıyız. Burada, h:outputText içerisindeki değerin escape="false" ile escape edilmemesini sağlıyoruz. Burada value'yu bu şekilde vermek yerine normal bir şekilde yazarak bir String halinde tutup: <h:outputText value="#{fooController.myScript}" escape="false" /> şeklinde de kullanabilirsiniz.

Görüldüğü üzere yöntem oldukça basit. Fakat benim başıma gelen bir problem, bu şekilde bir çözüme gitmeyi engelledi ne yazık ki. Sorun şu şekilde; Internet Explorer versiyonlarına göre farklı CSS dosyaları kullanmak için html taginin başına:
"<!--[if lt IE 7]> <html lang=\"en-us\" class=\"no-js ie6\" xmlns:h="http://java.sun.com/jsf/html"> <![endif]-->..." 
tarzı kodlar yazarız. Bu kodları, yukarıda bahsettiğim şekilde escape etmek ne yazık ki mümkün değil çünkü kullandığımız <h:outputText>'in dahil olduğu "http://java.sun.com/jsf/html" namespace'ini, henüz tanımlamadan bu tagi kullanmak istiyoruz ki bu da mümkün değil. Burada da imdadımıza <f:view> tagi geliyor. <f:view> taginin içerisine namespace'lerimizi tanımladıktan sonra bütün içeriğimizi (<html> tagi vs. de dahil), <f:view> içerisine koyuyoruz. Böylece içeride <h:outputText>'i kullanabilir duruma geliyoruz ve sorunumuz çözülmüş oluyor. Daha anlaşılır olması için:

Sorun: Aşağıdaki kod bloğunu escape etmeden ön tarafa taşımak
<!--[if lt IE 7]>          <html lang="en-us" class="no-js ie6"> <![endif]-->
<!--[if IE 7]>             <html lang="en-us" class="no-js ie7"> <![endif]-->
<!--[if IE 8]>             <html lang="en-us" class="no-js ie8"> <![endif]-->
<!--[if gt IE 8]><!--> <html lang="en-us" class="no-js" xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"> <!--<![endif]-->
Çözüm: Kod bloğunu <f:view> içerisine alıp namespace'leri buraya taşımak
@Controller("fooController")
FooController{
   public String myUnescapedString =
 
                    "<!--[if lt IE 7]><html lang=\"en-us\" class=\"no-js ie6\"> <![endif]-->\n" +
                    "<!--[if IE 7]><html lang=\"en-us\" class=\"no-js ie7\"> <![endif]-->\n" +
                    "<!--[if IE 8]><html lang=\"en-us\" class=\"no-js ie8\"> <![endif]-->\n" +
                    "<!--[if gt IE 8]><!--> <html lang=\"en-us\" class=\"no-js\"> <!--<![endif]-->";
}
<f:view contentType="text/html"
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:f="http://java.sun.com/jsf/core">
<h:outputText value="#{fooController.myUnescapedString}" escape="false"/>
</f:view>
( jsf, html, escape, h:outputText, f:view, unescape, xss )
Kaynak: http://stackoverflow.com/questions/10616944/jsf-2-1-ie-conditional-comments

7 Mart 2013 Perşembe

Ubuntu 12.10'a Oracle SQL Developer Kurulumu

Bu kurulum için ihtiyacımız olan bir kaç kurulum daha var. Öncelikle sistemde jre'nin kurulu olması gerekiyor. debhelper ve sqldeveloper-package paketlerine de ihtiyacımız var. Tabiki bir de ziplenmiş olarak indirdiğiniz SQL Developer'a ihtiyaç var.  Bu yazıda ben 'sqldeveloper64-3.2.20.09.87-no-jre.zip' kullanıyor olacağım.

sudo apt-get install sqldeveloper-package debhelper

Bu kodu çalıştırıp gerekli paketleri kurduktan sonra aşağıdaki komutla .deb dosyasını oluşturuyoruz:

make-sqldeveloper-package sqldeveloper64-3.2.20.09.87-no-jre.zip

Bu esnada make-sqldeveloper-package paketinde bulunan bir bugdan ötürü chmod: missing operand after `755' hatası alabilirsiniz. Bu hatayı çözmek için bu paketin kodunda ufak bir değişiklik yapmamız gerekiyor. Paketin versiyonuna göer değişiyor fakat 370 ile 385. satırlar arasında bir yerlerde bulunan aşağıdaki kodu, bir alttakiyle değiştirmeniz gerekiyor:

Silinecek Satır
${FIND} "${OPTDIR}" ! \( -type d -o -name "*.jar" \) |${XARGS} ${XARGS_OPTS} ${FILE} ${FILE_OPTS} |${GREP} ${GREP_OPTS} "shell script text executable" |${CUT} ${CUT_OPTS_FUNC_CLEAN} |${XARGS} ${XARGS_OPTS} ${CHMOD} ${CHMOD_OPTS}

Yerine Eklenecek Satır
${FIND} "${OPTDIR}" ! \( -type d -o -name "*.jar" \) |${XARGS} ${XARGS_OPTS} ${FILE} ${FILE_OPTS} |${GREP} ${GREP_OPTS} "shell script" | ${GREP} ${GREP_OPTS} "text executable" |${CUT} ${CUT_OPTS_FUNC_CLEAN} |${XARGS} ${XARGS_OPTS} ${CHMOD} ${CHMOD_OPTS}

Bu işlemden sonra hatadan kurtulmuş oluyoruz ve kodu yeniden çalıştırdığımızda .deb uzantılı dosyamız oluşmuş oluyor. Aşağıdaki şekilde .deb paketini açıyoruz:

sudo dpkg -i sqldeveloper.......deb

Böylece kurulum tamamlanmış oluyor. Dash Home'a "sqldeveloper" yazdıktan sonra uygulamamız açılacak. Yalnız yapmamız gereken son bir işlem daha var. Uygulama ilk kez açılırken size yüklü olan JRE'nin lokasyonunu soracak. Buraya da yüklediğimiz JRE'nin lokasyonunu yazacağız. Örneğin benimki şu şekildeydi:

/usr/lib/jvm/java-7-oracle

Artık IDE'miz kullanıma hazır durumdadır.

( ubuntu 12.04, oracle, sql developer, chmod, 755 )
Kaynak : https://bugs.launchpad.net/ubuntu/+source/sqldeveloper-package/+bug/985810

1 Mart 2013 Cuma

Spring MVC @Autowired NullPointerException

Spring MVC'de, nesnelerin yönetimini framework'e bırakma olayı sıkça kullanılan bir durumdur ve Spring'in ana kullanım amaçlarından birisidir. Bu kullanımlar esnasında, @Autowired annotation'unu kullanarak ya da getBean() metodu ile nesnelere ulaşmak istediğimizde alınan NullPointerException da oldukça sık karşılaşılan bir durumdur:) Ben yazının geri kalanında Annotation kullanımı ile devam edeceğim.

Burada, hataya sebebiyet verecek milyonlarca ihtimal vardır. Internette araştırdığınızda da bir dolu yazı karşınıza çıkacaktır. Bu sebeplerden bir kaçı:

  • @Autowired kulllandığınız nesneye sahip sınıfın Spring tarafından inject edilmemiş olması. Yani bu nesneye ait sınıfın başında @Component, @Controller gibi Spring'e özgü ve o sınıfın Spring tarafından yönetildiğini belirten annotationlar kullanılmalıdır.
  • Annotation kullanılan durumlarda, springContext.xml içerisinde '<tx:annotation-driven />' ve '<context:component-scan base-package />' taglerinin kullanılması gerekir.
  • Başına @Autowired eklediğiniz bir nesneyi 'new MyClass()' şeklinde kendiniz oluşturmamalısınız; bu nesnenin yönetimi artık Spring'dedir.

Yukarıdaki maddelere eklenecek başka maddeler de vardır. Bu hatalardan bir ya da birkaçına düştüyseniz kullanmak istediğiniz nesnenin null olması ihtimal dahilindedir. Peki herşeyi denediniz, bütün kodlama doğru ve hala bu hatayı alıyorsanız? O zaman benim karşılaştığım durumla karşılaşmış olma ihtimaliniz var. Spring MVC, kontrolüne verdiğiniz sınıfın instance'ını oluştururken, constructor'ı çalıştırma esnasında autowired nesneyi henüz inject etmiyor. Dolayısıyla bu nesneyi consturctor içerisinde kullanıyorsanız NullPointerException alıyorsunuz. Bu sorunun çözümü ise, eğer sınıfın instance'ını oluştururken yapmanız gereken bir iş yok ise, constructor içerisindeki bütün kod bloğunu @PostConstruct annotation'una sahip bir metoda vermeniz. Böylece ilgili nesne Spring tarafından oluşturulmuş ve kullanıma hazır hale gelmiş olacaktır. Aşağıda örnek kod parçalarını bulabilirsiniz.

Yanlış Kullanım

@Controller("userController")
public class UserController implements Serializable{
    @Autowired
    transient private SessionController sessionController;
    
    public UserController() {
        System.out.println(sessionController.getActiveUser().getUserName());
    }
}

Doğru Kullanım

@Controller("userController")
public class UserController implements Serializable{
    @Autowired
    transient private SessionController sessionController;
    
    public UserController() {}
    
    @PostConstruct
    public onLoad() {
        System.out.println(sessionController.getActiveUser().getUserName());
    }
}

spring mvc, injection, autowiring, @autowired, nullpointerexception, constructor, postconstruct )

20 Şubat 2013 Çarşamba

Spring Security Invalid Session Ajax Redirect Sorunu

Merhaba,

Bu aralar sıkça uğraştığım Spring Security hakkında yazdığım yazılara devam ediyorum. Bu seferki problem invalid hale gelmiş bir sessionda ajax request gönderdiğimizde view kısmında herhangi bir aksiyon olmaması. Önce problemin kaynağını belirteyim. Giden standart bir request'in response'ı ekrada düzgün bir şekilde gösterilirken giden bir ajax requestin response'ı partial-response olmalıdır. Uygulamada herhangi bir sayfadayken session timeout gerçekleştiğinde ve biz sonrasında ajax request gönderen bir işlem yaptığımızda spring security'nin standart jsf redirection metodu ajax response'ı redirect edemiyor. Bu noktada yapmamız gereken custom bir RedirectStrategy yazmak. Bu sınıf InvalidSessionStrategy sınıfını implement etmeli ve onInvalidSessionDetected metodunda ajax requesti yakalayıp ona göre farklı bir işlem yapmalıyız. İsterseniz burada http error gönderebilirsiniz. Ben bunun yerine bir partial response yazdım ve redirect işlemi gerçekleştirdim.

İhtiyacınız olacak örnek sınıf ve konfigürasyonları aşağıda bulabilirsiniz.

spring-security.xml
...
<http>
   <custom-filter ref="sessionManagementFilter" before="SESSION_MANAGEMENT_FILTER" />
</http>
<beans:bean id="sessionManagementFilter" class="org.springframework.security.web.session.SessionManagementFilter">
   <beans:constructor-arg name="securityContextRepository" ref="httpSessionSecurityContextRepository" />
   <beans:property name="invalidSessionStrategy" ref="customRedirectStrategy" />
</beans:bean>
<beans:bean id="customRedirectStrategy" class="com.test.JsfRedirectStrategy"/>
<beans:bean id="httpSessionSecurityContextRepository" class="org.springframework.security.web.context.HttpSessionSecurityContextRepository"/>
...

JsfRedirectStrategy.java
...
implements InvalidSessionStrategy
...
    @Override
    public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response){
        String redirectUrl = "invalid-session.xhtml";
        boolean ajaxRedirect = isAjaxRequest(request);
        if (ajaxRedirect) {
            if (request.getSession() != null) {
                HttpSessionRequestCache httpSessionRequestCache = new HttpSessionRequestCache();
                SavedRequest savedRequest = httpSessionRequestCache.getRequest(request, response);
                if (savedRequest != null) {
                    httpSessionRequestCache.removeRequest(request, response);
                }
            }
            String ajaxRedirectXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><partial-response><redirect url=\"" + redirectUrl + "\"></redirect></partial-response>";
            response.setContentType("text/xml");
            PrintWriter out = response.getWriter();
           out.write(ajaxRedirectXml);
           out.flush();
           out.close();
        } else {
            response.sendRedirect(redirectUrl);
        }
    }

Yukarıda mavi renkli kod bloğunu sebebi ise AJAX targetURL'i silmek. Yoksa bir sonraki tıklamada ekrana kaydedilmiş AJAX requesti basılıyor. Eski Spring Security versiyonlarında direkt olarak URL'i de silebiliyorduk fakat 3.1.3 versiyonunda ben SavedRequest'i silerek bu işlemi gerçekleştirdim.

Bir diğer önemli konu ise, yukarıdaki işlemi ben yalnızca session invalidate olurkenki süreçte gerçekleştirdim. Bir de daha önce invalide olmuş bir session varken gönderilen bir AJAX request olması durumu olabilir (Örneğin yeni bir sekmede uygulamaya devam ediyorsunuz, logout yapıp sessionu öldürdünüz fakat bir önceki sekmede bulunan açık olan sayfada ajax request gönderen bir linke tıkladınız). Bu tarz bir durumda ise AuthenticationEntryPoint sınıfını implement edip commence metodunu aynı şekilde oluşturmalısınız. Bu yöntemle, hiç bir requesti kaçırmayıp, kullanıcıyı istediğiniz adrese yönlendirebilirsiniz.

Bir üstteki paragrafa örnek sınıf:

CustomAuthenticationEntryPoint.java
...
implements AuthenticationEntryPoint
...
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {
        //Bir önceki sınıftaki kodun aynısı
    }

spring mvc, spring security, session management, invalid session, ajax redirect, jsf, redirect strategy, invalid session strategy )
Kaynak: http://code.google.com/a/apache-extras.org/p/ajax4click/wiki/ConfiguringSpringSecuritySessionTimeout
Kaynak: http://www.icesoft.org/wiki/display/ICE/Spring+Security

18 Şubat 2013 Pazartesi

Spring Security Session Management Concurrency Control Problemi

Springde session yönetimini Spring Security ile yapmak isteyenler, eş zamanlı session yönetimini de basit bir şekilde spring-security.xml dosyasını değiştirerek yapabilirler. Bu noktada, gerekli düzenlemeleri yaptıktan sonra bile aynı bilgiler ile login olan kullanıcı, session oluşturamaması gerekirken bu işlemi yapabiliyorsa, çok büyük ihtimalle UserDetails sınıfınızdaki bir problem ile karşı karşıyasınız.

Buradaki sıkıntı, Spring Security'yi kullanabilmek için gerekli olan default kullanıcı arayüzü olan UserDetails'in implement edilmesi esnasında yaşanıyor. Bu arayüzü implement ederken oluşturduğumuz  kendi kullanıcı sınıfımızda equals metodunu override etmemiz lazım. Çünkü bu metod içerisinde, sessionu oluşturan kullanıcıların concurrent olup olmadığını anlamamız için belirleyeceğimiz bir ya da birden fazla alanın karşılaştırılıp aynı olup olmadığının kontrol edilmesi gerekiyor. Bu metodu override ettikten sonra belirlediğimiz aynı alana sahip başka bir kullanıcı session oluşturmaya çalışınca Authentication hatası alacaktır.

Örnek vermek gerekirse;

Bir adet User sınıfımız olsun. Uygulamamızda bir kullanıcı session oluşturmuşken aynı kullanıcı adına sahip başka bir kullanıcının session oluşturmamasını istiyoruz. Örnek dosyalar aşağıdaki gibi olmalıdır:

web.xml
...
<listener>
  <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
...

spring-security.xml
...
<session-management invalid-session-url="/no-sess.xhtml">
  <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" expired-url="/no-sess.xhtml" />
</session-management>
...

MyUser.java
public class MyUser implements UserDetails
...
   @Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MyUser other = (MyUser) obj;
if (userName == null) {
if (other.userName != null)
return false;
} else if (!userName.equals(other.userName))
return false;
return true;
  }
...

Bu konfigürasyonlar sonrasında bir kullanıcı session oluşturmuşken aynı kullanıcı adı ile başka bir session oluşturmaya çalışırsanız 'Maximum sessions of 1 for this principal exceeded' gibi bir hata mesajı alacaksınız.

spring mvc, spring security, session management, concurrency control, max-sessions, UserDetails )

7 Ocak 2013 Pazartesi

Zaman Çarkı (Elise) - Ken Grimwood


Evet, oldukça spoiler içerir şekilde:) 'Zaman Çarkı', orijial adıyla 'Elise' adlı kitaptan bahsedeceğim biraz.

Koridor Yayıncılık çıkışlı ve Ender Nail çevirili kitap Ken Grimwood tarafından yazılmış. Kitabın arka kapağındaki hikaye oldukça ilgiç gelmişti. Alma sebeplerimden birisi bu, diğeri de sanıyorum 'Olasılıksız' tarzı siyah beyaz, çekici kapağı oldu:) Kitapta 1600'lü yıllarda doğan ve sonrasında hiç yaşlanmayan bir kişnin hikayesi anlatılıyor. Kitap 2 ayrı senaryoda gidiyor. Bir tanesi 1600'lü yıllarda başlayıp devam eden, diğeri ise 1900'ün sonlarında devam eden iki hikaye olarak. Sonrasında tahmin edeceğiniz gibi bu hikayeler kesişiyor ve birleşiyor. Kem Grimwood'un bir kitabını ilk kez okudum. Açıkçası özellikle sürükleyici hikayelerde kahramanın ağzından olan anlatımı sevmediğim için bu kitabın anlatım kısmını oldukça iyi buldum. Yazar dışarıdan bir kişi olarak hikayeyi aktarıyor.

1600'lü yıllarda başlayan bölüm o yılların Fransa'sını kafanızda canlandırmanıza yetiyor ve sizi o tarihlere götürüyor. Romanlardaki en büyük sıkıntım olan farklı dillerdeki insan isimleri ve bu kişilerin saysının fazla olması bu noktada beni biraz zorladı açıkçası ama hikayeyi kaybetmeden devam edebildim. Ana karakterimiz bu yıllarda doğuyor ve ölümsüz yaşantısına başlıyor. Sevgilileri, eşleri oluyor. Onlar yaşlanırken ve ölürken kendisi genç olarak kalmaya devam ediyor. Bu hikaye, 1900'lü yılların sonralarında karakterin ismini Elise olarak değiştirmesi ve kendisini bu durumdan kurtarmasını umduğu bir profesör ile tanışması, durumunu anlatması ve çözüm araması kısmına geliyor. Çok da fazla detay vermek istemiyorum okumak isteyenler için. Genel hikaye bu şekilde.

Kitap, konunun ilginçliği ile birlikte sürükleyici başladı. Ana karakterin, Fransa'da o yıllarda yaşadıkları, kölecilik zamanları ve o zamanlardaki ayaklanmalar oldukça iyi aktarılmış. Özellikle karakterin, yaşlanmadığını keşfedip sevdiklerini birer birer kaybetmeye başlayınca hissettikleri ve büründüğü ruh hali, sizi de kolaylıkla etkiliyor. Fakat sonlara yaklaştığımda hikaye hızlanmaya başladı. Eskiden bölümler arası 1-2 yıl ilerlerken bir anda 10 yıl 20 yıl ilerlemeye başladık. Elise'in bir ülkedeki yaşantısı anlatılırken sonraki bölümde bir anda hiç alakası olmayan başka bir ülke ve yaşama geçti. Sanki yazar kitabın sonralına doğru sıkılmış da bir an önce bitirmek istemiş gibi. Tarih sayfalarında duyduğumuz isimlerle geçen o kadar güzel işlenmiş hikayeler, çok basit bir sonla bitti açıkçası. Kitabın ortalarına kadar oldukça heyacanlı okurken son bölümlerinde ise sıkıldım ve hayal kırıklığına uğradım. Fakat 3-4 gün içinde normal bir tempoda bitirilebilecek, fazla beklenti olmadan zaman geçirmek için fena olmayan bir kitap. Eğer 'The Man From Earth' filmini izlediyseniz ve hoşunuza gittiyse, bu kitabı da muhtemelen seveceksiniz.

zaman çarkı, elise, ken grimwood, Ender Nail, Koridor Yayıncılık, kitap, roman, inceleme )