Java Developers Day ‘08 – Call For Papers Grails – koniec poszukiwań?
Jun 20

JackRabbit

Inne biblioteki, Spring | autor: Piotr Święciak Add comments


Jackrabbit jest najpopularniejszą darmową implementacją JCR dla technologii Java. Ale zacznijmy od początku, co to jest JCR? Java Content Repository jest repozytorium treści o strukturze drzewiastej, które dzięki zdolności definiowania typów węzłów i liści, bardzo rozszerza możliwości tego narzędzia. Mamy kilka wbudowanych typów (np. katalog, plik, link ) ale możemy również sami definiować nowe typy. Kolejnymi możliwościami są: tekstowe przeszukiwanie czy zarządzanie wersjami. JCR jest wiec idealnym narzędziem do budowania systemów zarządzania treścią. W artykule jako przykład stworzymy aplikację, która będzie korzystać z możliwości wersjonowania.


Konfiguracja JCR

Zaczynamy od konfiguracji Jackrabbita na serwerze (w naszym przykładzie Tomcat6). Proces składa się z trzech etapów:

  1. Kopiujemy do katalogu lib na serwerze potrzebne biblioteki (w pliku pom.xml są wszystkie dependency, także można pobrać źródła aplikacji i maven pobierze nam potrzebne biblioteki):
    • jcr-1.0.jar
    • jackrabbit-api-1.4.jar
    • jackrabbit-core-1.4.jar
    • jackrabbit-jcr-commons-1.4.jar
    • jackrabbit-spi-1.4.jar
    • jackrabbit-spi-commons-1.4.jar
    • jackrabbit-text-extractors-1.4.jar
    • slf4j-api-1.3.0.jar
    • slf4j-simple-1.3.0.jar
    • commons-collections-3.1.jar
    • lucene-core-2.2.0.jar
    • derby-10.2.1.6.jar
    • concurrent-1.3.4.jar
  2. Konfiguracja pliku conf/server.xml. W GlobalNamingResources definiujemy Resource.
    <Resource name="jcr/globalRepository" auth="Container" type="javax.jcr.Repository" factory="org.apache.jackrabbit.core.jndi.BindableRepositoryFactory" configFilePath="webapps/repo/repository.xml" repHomeDir="webapps/repo"/>

    Parametr configFilePath definiuje ścieżkę dostępu do pliku konfiguracyjnego repozytorium – repository.xml. W repHomeDir podajemy ścieżkę do katalogu domowego repozytorium. W naszym przypadku widzimy ze repozytoriom znajduje się w katalogu webapps naszego serwera.

  3. Konfiguracja pliku conf/context.xml
    <ResourceLink name="jcr/repository" global="jcr/globalRepository" type="javax.jcr.Repository"/>

Mamy już skonfigurowany serwer oraz podane ścieżki gdzie znajdować się będzie nasze repozytorium. Teraz musimy skonfigurować plik repository.xml, a następnie skopiować go w miejsce które podaliśmy w configFilePath.

<?xml version="1.0"?>
<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.2//EN"
                            "http://jackrabbit.apache.org/dtd/repository-1.2.dtd">
<Repository>

    <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
        <param name="path" value="${rep.home}/repository"/>
    </FileSystem>

    <Security appName="Jackrabbit">

        <AccessManager class="org.apache.jackrabbit.core.security.SimpleAccessManager">
            <!-- <param name="config" value="${rep.home}/access.xml"/> -->
        </AccessManager>

        <LoginModule class="org.apache.jackrabbit.core.security.SimpleLoginModule">
           <!-- anonymous user name ('anonymous' is the default value) -->
           <param name="anonymousId" value="anonymous"/>
           <!--
              default user name to be used instead of the anonymous user
              when no login credentials are provided (unset by default)
           -->
           <!-- <param name="defaultUserId" value="superuser"/> -->
        </LoginModule>
    </Security>

    <Workspaces rootPath="${rep.home}/workspaces" defaultWorkspace="default"/>

	<Workspace name="Jackrabbit Core">

        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
            <param name="path" value="${wsp.home}"/>
        </FileSystem>

        <PersistenceManager class="org.apache.jackrabbit.core.persistence.db.DerbyPersistenceManager">
          <param name="url" value="jdbc:derby:${wsp.home}/db;create=true"/>
          <param name="schemaObjectPrefix" value="Jackrabbit Core_"/>
        </PersistenceManager>

        <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
            <param name="path" value="${wsp.home}/index"/>
        </SearchIndex>
    </Workspace>

    <Versioning rootPath="${rep.home}/version">

        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
            <param name="path" value="${rep.home}/version" />
        </FileSystem>

        <PersistenceManager class="org.apache.jackrabbit.core.persistence.db.DerbyPersistenceManager">
          <param name="url" value="jdbc:derby:${rep.home}/version/db;create=true"/>
          <param name="schemaObjectPrefix" value="version_"/>
        </PersistenceManager>

    </Versioning>

    <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
        <param name="path" value="${rep.home}/repository/index"/>
    </SearchIndex>
</Repository>

Mamy już skonfigurowany JCR. Teraz zajmiemy się nasza przykładową aplikacją.

Aplikacja
Jako przykład stworzymy prostą aplikacje webową w oparciu o szkielet springa. Na stronie będzie przykładowy formularz, odzwierciedlający dokument, który będzie zapisywany a zarazem wersjonowany w naszym repozytorium. Oczywiście zaczynamy od stworzenia szkieletu projektu:

mvn archetype:create -DarchetypeArtifactId=maven-archetype-webapp -DgroupId=pl.j2ee.jcr_example -DartifactId=jcr-example

Kolejnym krokiem jest konfiguracja pliku pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>pl.j2ee</groupId>
	<artifactId>jcr-example</artifactId>
	<packaging>war</packaging>
	<version>1</version>
	<name>jcr-example</name>
	<url>http://maven.apache.org</url>
	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<encoding>utf8</encoding>
					<source>1.6</source>
					<target>1.6</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring</artifactId>
			<version>2.5.4</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>2.5.4</version>
		</dependency>
		<dependency>
			<groupId>taglibs</groupId>
			<artifactId>standard</artifactId>
			<version>1.1.2</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>2.5</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.jackrabbit</groupId>
			<artifactId>jackrabbit-api</artifactId>
			<version>1.4</version>
		</dependency>
		<dependency>
			<groupId>org.apache.jackrabbit</groupId>
			<artifactId>jackrabbit-core</artifactId>
			<version>1.4.4</version>
		</dependency>
		<dependency>
			<groupId>javax.jcr</groupId>
			<artifactId>jcr</artifactId>
			<version>1.0</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.0</version>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.8</version>
		</dependency>
	</dependencies>
</project>

Teraz tworzymy prosty model naszego przykładowego dokumentu.

package pl.j2ee.jcr_example;
import java.io.Serializable;
import java.util.Date;

public class DocumentJcr implements Serializable {

    private static final long serialVersionUID = 1L;
    private String name;
    private String title;
    private Date date;
    private String text;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public Date getDate() {
        return date;
    }
    public void setDate(Date date) {
        this.date = date;
    }
    public String getText() {
        return text;
    }
    public void setText(String text) {
        this.text = text;
    }
}

Service do obsługi repozytorium

package pl.j2ee.jcr_example.service;

import ...

public class DocumentService implements IDocumentService {

    public final Session sessionLogin() {

        InitialContext ctx;
        try {
            ctx = new InitialContext();

            Context environment = (Context) ctx.lookup("java:comp/env");
            Repository repository = (Repository) environment.lookup("jcr/repository");

            return repository.login(new SimpleCredentials("username", "password".toCharArray()));

        } catch (NamingException e) {
            e.printStackTrace();

        } catch (RepositoryException e) {
            e.printStackTrace();
        }

        return null;
    }

    public final void sessionLogout(Session session) {
        if (session != null) {
            session.logout();
        }
    }

    public void addDocument(DocumentJcr documentJcr) {

        Session session = sessionLogin();
        try {

            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream obj = new ObjectOutputStream(out);
            obj.writeObject(documentJcr);
            obj.flush();
            InputStream ins = new ByteArrayInputStream(out.toByteArray());

            Node root = session.getRootNode();

            if (!root.hasNode("repozytorium/dokument")) {
                Node repozytorium = root.addNode("repozytorium");
                Node document = repozytorium.addNode("dokument", "nt:unstructured");

                document.addMixin("mix:versionable");
                document.setProperty("object", ins);

                session.save();

            } else {

                Node child = root.getNode("repozytorium/dokument");
                child.checkout();
                child.setProperty("object", ins);
                session.save();
                child.checkin();

            }

        } catch (Exception e) {
            // TODO: handle exception

        } finally {
            sessionLogout(session);
        }
    }

    public List getDocumentJcr() {
        Session session = sessionLogin();

        List listDocument = new ArrayList();
        try {

            Node root = session.getRootNode();
            Node node = root.getNode("repozytorium/dokument");

            VersionHistory versionHistory = node.getVersionHistory();
            VersionIterator versionIterator = versionHistory.getAllVersions();

            while (versionIterator.hasNext()) {
                Version version = versionIterator.nextVersion();
                NodeIterator nodeIterator = version.getNodes();

                while (nodeIterator.hasNext()) {

                    Node versionedNode = nodeIterator.nextNode();

                    if (versionedNode.hasProperty("object")) {

                        InputStream in = versionedNode.getProperty("object").getStream();
                        ObjectInputStream ois = new ObjectInputStream(in);

                        DocumentJcr document = (DocumentJcr) ois.readObject();
                        listDocument.add(document);
                    }
                }
            }

        } catch (Exception e) {
        } finally {
            sessionLogout(session);
        }
        return listDocument;
    }
}

Jak widzimy w przykladzie dodajemy wezeł repozytorium do głównego wezła a następnie do niego kolejny “document” w ktorym będziemy zapisywać nasz prosty dokument. Ma on typ “nt:unstructured”, który cechuje się brakiem jakichkolwiek wymagań odnośnie swojej wewnętrznej budowy. Możemy więc tworzyć dowolną strukturę takiego węzła. Następnie dodajemy do węzła typ “mix:versionable”,co daje możliwość wersjonowania tego węzła (repozytorium przechowuje jego historie zmian). Podczas odczytu z tego węzła, dostaniemy ostatnią wersje dokumentu. Jednak możemy przeglądać wszystkie wersje naszego dukumentu iterując po VersionHistory dla konkretnego węzła. W naszym przykładzie cały czas odwołujemy się do jednego dokumentu (węzła). Wprowadzamy w nim zmiany, a następnie wszystkie wersje dokumentu wyświetlamy na stronie. Każdy węzeł posiada pola “property” w których przechowujemy informacje w naszym przypadku obiekt DocumentJCR.

W pliku web.xml dodajemy wpis:

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>

     ...
	<resource-env-ref>
		<description>Content Repository</description>
		<resource-env-ref-name>jcr/repository</resource-env-ref-name>
		<resource-env-ref-type>
			javax.jcr.Repository
		</resource-env-ref-type>
	</resource-env-ref>
     ...

</web-app>

Jeszcze kontroler i apliakcja gotowa

package pl.j2ee.jcr_example.controller;

public class MainController extends SimpleFormController {

    private IDocumentService documentService;

    public MainController() {
        setCommandClass(DocumentJcr.class);
    }

    @Override
    protected Map referenceData(HttpServletRequest request, Object command, Errors errors)
            throws Exception {

        HashMap map = new HashMap();

        map.put("documentList", documentService.getDocumentJcr());

        return map;
    }

    protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object command,
            BindException errors) throws Exception {

        DocumentJcr doc = (DocumentJcr) command;
        doc.setDate(new Date());
        documentService.addDocument(doc);

        return new ModelAndView("redirect:/main.action");
    }

    @Autowired
    public void setDocumentService(IDocumentService documentService) {
        this.documentService = documentService;
    }
}

Oczywiscie to jest tylko mala czastka możliwosci jakie posiada Jackrabbit. Przedstawiłem jedynie jedną z możliwości czyli wersjonowanie. Warte zaznaczenia są jeszcze możliwość blokowania dostępu do danego węzła czy możliwość śledzenia zmian w repozytorium.
Nasuwa się wniosek, że jeżeli mamy do czynienia z aplikacją do zarządzania treścią, napewno warto rozważyc mozliwość wykorzystania do tego biblioteki Jackrabbit.

Kod źródłowy aplikacji do pobrania tutaj.

Podziel się z innymi:
  • Wykop
  • Digg
  • del.icio.us
  • StumbleUpon
  • Slashdot

4 Responses to “JackRabbit”

  1. Mariusz Sieraczkiewicz Says:

    Ciekawy artykuł na start :-) A sama specyfikacja naprawdę warta uwagi, nie dość, że się rozwija, to jest powszechnie stosowana, tyle że jako backend nie zawsze ją widać. Na pewno warto śledzić i rozważyć użycie, np. jako baza danych następnej generacji.

    Myślę, że przydałyby się kolejne artykuły zgłębiające technologię.

  2. Adam K. Says:

    Jestem w trakcie robienia CMS’a i szukalem rozwiazania do przechowywania danych. podpatrzylem , ze pewien CMS szwajcarskiej firmy chodzi na JackRabbit wiec zainteresowalem sie tym. przejrzalem specyfikacje , czesc jej przeczytalem dokladnie i musze stwierdzic , ze nijak ma sie to do bazy danych i z pewnoscia nie zastapi bazy danych , choc samo w sobie JackRabbit wlasnie , korzysta z bazy dancyh do wersjonowania. Biorac pod uwage, ze przechowywanie w postaci drzew danych samo w sobie jest dziwacznym , a raczej bezposrednie modlowanie tych danych w postaci drzew to :

    1. po pierwsze wyszukiwanie danych, SQL co prawda mozna zastosowac ale jest wiele ograniczen, na ktore wezly bedzie dzialac na ktore nie , poza tym trzeba sie zapoznac dobrze co i na co jest mapowane przy zapytaniach sql
    2. same zapytania z jakimis sciezkami itd – nastepna rzecz, ktorej trzeba sie nauczyc
    3. wyniki w jakiejs dziwacznej formie (tej formy akurat nie przegladalem w specyfikacji ale to zrobie :))
    4. biorac pod uwage , ze zeby z tego skorzystac trzeba sie sporo natrudzic zeby sie tego nauczyc, jesli zdazylem sie zorientowac literatury to chyba jeszcze nie za bardzo sie uswiadczy w tym temacie
    5. mapowania w wielu kierunkach raczej nie tchna wysoka wydajnoscia
    6. uczenie sie nowej technologii nie wnoszacej generalnie niczego nowego spowalnia proces tworzenia danej aplikacji
    7. moge nie miec racji ze wzgledu na slaba znajomosc specyfikacj :)
    8. jedyna zaleta , ktora widze to wersjonowanie i sledzenie zmian ale przy tradycyjnym modelu relacyjnej bazy dancyh niewiele trzeba zeby zaimplementowac to pierwsze

    Podsumowujac nie wiem czy warto zainwestowac w swieza technologie nie sprawdzana za bardzo w pozadnych bojach.byc moze moj sceptycyzm opiera sie na slabiutkiej znajomosci tematu wiec jeszcze go zaatakuje. tak czy inaczej tak to mniej wiecej widze.

  3. Sebek Says:

    Ale to nie jest relacyjna baza danych – tylko repozytorium treści (dokumentów) i jako takie ma wspierać operacje na treści w strukturze drzewiastej (indeksowanie zawartości, wyszukiwanie, wersjonowanie, itp.)
    pozdr s.

  4. Krzysztof Says:

    Zajmuję się obsługą dużego portalu zbudowanego na Liferay’u z repozytorium JackRabbit właśnie (serwer Linux) i od pewnego czasu nie można dodać kolejnych dokumentów do repozytorium…
    Proszę o pilną odpowiedź czy to prawda, że JackRabbit ma ograniczenie do 64000 plików lub katalogów i jeżeli tak to jak to zmienić?

Leave a Reply

Security Code: