Monday, July 28, 2014

Accessing Alfresco's Spring datasource

In past articles I've discussed having the need to store custom application data in addition to Alfresco document and metadata storage in Alfresco's Two-Phase commit limitation, or a backup and restore strategy for Alfresco and a custom application.  For another recent project of mine, the custom application data that needed to be stored was so small that we decided to go ahead and store it inside the Alfresco database itself as an additional status table.

Note: A similar version of the example below were tested on:
  • Alfresco 4.1.5 with Windows default Tomcat install with PostGreSQL 
  • Alfresco 4.1.5 with JBoss 7.0.0 install with Oracle
High Level Steps:
  1. Created a maven database module using mybatis for persistence.  This article helped immensely when setting this up.
    • Included the CherryShoeStatusDao class, mybatis domain classes, mapper classes, and mapper xml files
    • The key to accessing Alfresco's spring datasource is to reference it as "defaultDataSource" in the alfresco-custom-database spring configuration file's datasource bean.  This is because the alfresco datasource is defined in core-services-context.xml with that spring bean id.
      <!-- Alfresco's DataSource is obtained via a bean called "defaultDataSource" which is a org.apache.commons.dbcp.BasicDataSource, it's defined in WEB-INF/classes/alfresco/core-services-context.xml, simulating it here -->
      
      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      
      <property name="dataSource" ref="defaultDataSource" />
      </bean>
    • Necessary dependencies included (versions were controlled by the parent pom so not shown here):
      <dependency>
       <groupId>org.mybatis</groupId>
       <artifactId>mybatis-spring</artifactId>
      </dependency>
      <dependency>
       <groupId>log4j</groupId>
       <artifactId>log4j</artifactId>
      </dependency>
      <dependency>
       <groupId>cglib</groupId>
       <artifactId>cglib</artifactId>
      </dependency>
      <dependency>
       <groupId>com.google.guava</groupId>
       <artifactId>guava</artifactId>
      </dependency>
        
  2. Add the database module as a module dependency to the alfresco amp module pom:
        <dependency>
             <groupId>com.cherryshoe</groupId>
            <artifactId>alfresco-custom-database</artifactId>
        </dependency>
    
  3. Import the alfresco-custom-database.xml spring file to module-context.xml so it will be recognized by alfresco. The classpath should be from the root of the jar file created (/spring/spring-alfresco-custom-database.xml). Import it before any other imports that depend on it.
    <!--  alfresco-custom-database spring config file -->
    <import resource="classpath:/spring/spring-alfresco-custom-database.xml" />
    
    ... other imports
  4. cherryShoeStatusDao will already be available as a spring bean after step 3 is done because of component scanning in the custom database module spring configuration file.  It can be referenced from other beans in alfresco's service-context.xml custom spring config file.  i.e. a CustomAlfrescoService can now access CherryShoeStatusDao to insert and update Status table values.
           
    <bean id="CustomAlfrescoService" class="com.cherryshoe.services.impl.CustomAlfrescoServiceImpl" >
        <property name="cherryShoeStatusDao" ref="cherryShoeStatusDao" />
    </bean>

Reference Only
Detailed Files:

  1. The alfresco-custom-database follows the typical maven project structure

  2. /alfresco-custom-database     <-- Maven pom.xml
      /src
        /main/
          /java                   <-- Java code
            /com/
              /cherryshoe
                /database
                  /dao            <-- Dao logic
                  /domain         <-- Business domain objects
                  /persistence    <-- Mapper interfaces
          /resources              <-- Non java files
            /com
              /cherryshoe
                /database         
                  /persistence    <-- Mapper XML files
            /spring               <-- Spring files
          /scripts                <-- Sql files (Oracle)
        /test/
          /java                   <-- Java code
            /com/
              /cherryshoe
                /database
                  /dao            <-- Dao logic tests
          /resources              <-- Non java files
            /database             <-- Sql in memory files (H2)
            /spring               <-- Spring test files
    

  3. java files
    • database/dao/CherryShoeStatusDao.java
      package com.cherryshoe.database.dao;
      
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      import org.springframework.transaction.annotation.Transactional;
      
      import com.cherryshoe.database.domain.Status;
      import com.cherryshoe.database.persistence.StatusMapper;
      import com.google.common.base.Preconditions;
      
      @Service
      public class CherryShoeStatusDao {
      
        @Autowired
        private StatusMapper statusMapper;
      
        public Status getStatus(String statusId) {
          return statusMapper.getStatusById(statusId);
        }
      
        @Transactional
        public void insertStatus(Status status) {
          // check preconditions with google guava
          Preconditions.checkNotNull(status.getStatusCreateDate(), "statusCreateDate is required");
          Preconditions.checkNotNull(status.getStatusModifiedDate(), "statusModifiedDate is required");
          Preconditions.checkNotNull(status.getStatusId(), "statusId is required");
          Preconditions.checkNotNull(status.getStatusRequest(), "statusRequest is required");
          Preconditions.checkNotNull(status.getStatusStatus(), "statusStatus is required");
          
          statusMapper.insertStatus(status);
        }
      
        @Transactional
        public void updateStatus(Status status) {
          // check preconditions with google guava    
          
          statusMapper.updateStatus(status);
        }
      
      }
       
    • database/domain/Status.java
      package com.cherryshoe.database.domain;
      
      import java.io.Serializable;
      import java.sql.Timestamp;
      
      public class Status implements Serializable {
      
          private static final long serialVersionUID = 8751282105532159742L;
      
          private Timestamp statusCreatedDate;
          private Timestamp statusModifiedDate;
          private String statusId;
          private Integer statusStatus;
          private String statusRequest;
      
          public Timestamp getStatusCreateDate() {
              return statusCreatedDate;
          }
      
          public void setStatusCreateDate(Timestamp statusCreatedDate) {
              this.statusCreatedDate = statusCreatedDate;
          }
      
          public Timestamp getStatusModifiedDate() {
              return statusModifiedDate;
          }
      
          public void setStatusModifiedDate(Timestamp statusModifiedDate) {
              this.statusModifiedDate = statusModifiedDate;
          }
      
          public String getStatusId() {
              return statusId;
          }
      
          public void setStatusId(String statusId) {
              this.statusId = statusId;
          }
      
          public Integer getStatusStatus() {
              return statusStatus;
          }
      
          public void setStatusStatus(Integer statusStatus) {
              this.statusStatus = statusStatus;
          }
      
          public String getStatusRequest() {
              return statusRequest;
          }
      
          public void setStatusRequest(String statusRequest) {
              this.statusRequest = statusRequest;
          }
      
          @Override
          public int hashCode() {
              final int prime = 31;
              int result = 1;
              result = prime * result
                      + ((statusRequest == null) ? 0 : statusRequest.hashCode());
              result = prime * result
                      + ((statusStatus == null) ? 0 : statusStatus.hashCode());
              result = prime * result
                      + ((statusId == null) ? 0 : statusId.hashCode());
              return result;
          }
      
          @Override
          public boolean equals(Object obj) {
              if (this == obj)
                  return true;
              if (obj == null)
                  return false;
              if (getClass() != obj.getClass())
                  return false;
              Status other = (Status) obj;
              if (statusRequest == null) {
                  if (other.statusRequest != null)
                      return false;
              } else if (!statusRequest.equals(other.statusRequest))
                  return false;
              if (statusStatus == null) {
                  if (other.statusStatus != null)
                      return false;
              } else if (!statusStatus.equals(other.statusStatus))
                  return false;
              if (statusId == null) {
                  if (other.statusId != null)
                      return false;
              } else if (!statusId.equals(other.statusId))
                  return false;
              
              // do not put date in equals, they will never be equal
              
              return true;
          }
      
          @Override
          public String toString() {
              return "Status [statusCreatedDate=" + statusCreatedDate + ", statusModifiedDate="
                      + statusModifiedDate + ", statusId=" + statusId
                      + ", statusStatus=" + statusStatus + ", statusRequest=" + statusRequest + "]";
          }
      
      
      
      
      }
       
    • database/domain/StatusEnum.java
      package com.cherryshoe.database.domain;
      
      public enum StatusEnum {
        NOT_PROCESSED(-1),
        IN_PROCESS(1),
        PROCESSED(2);
        
        private int status;
      
        private StatusEnum(int status) {
            this.status = status;
        }
      
          public Integer getStatus() {
              return status;
          }
      }
       
    • database/persistence/StatusMapper.java
      package com.cherryshoe.database.persistence;
      
      import com.cherryshoe.database.domain.Status;
      
      public interface StatusMapper {
      
        Status getStatusById(String statusId);
      
        void insertStatus(Status status);
      
        void updateStatus(Status status);
      
      }
      
      
  4. resources files
    • com/cherryshoe/database/persistance/StatusMapper.xml
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
      "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      
      <mapper namespace="com.cherryshoe.database.persistence.StatusMapper">
      
        <cache />
        
        <resultMap id="baseResultMap" type="com.cherryshoe.database.domain.Status">
              <id column="STATUS_ID" property="statusId" />
              <result column="STATUS_STATUS" property="statusStatus" />
              <result column="STATUS_REQUEST" property="statusRequest" />
              <result column="STATUS_CREATED_DATE" property="statusCreatedDate" />
              <result column="STATUS_MODIFIED_DATE" prstatuserty="statusModifiedDate" />
        </resultMap>
        
        <sql id="base_column_list">
          STATUS_ID, STATUS_STATUS, STATUS_REQUEST, STATUS_CREATED_DATE, STATUS_MODIFIED_DATE
        </sql>
      
        <select id="getStatusById" parameterType="Long" resultMap="baseResultMap">
          SELECT
                <include refid="base_column_list" />
          FROM STATUS
          WHERE STATUS_ID = #{statusId}
        </select>
      
        <!-- Mapper does not allow the create date to be modified -->
        <update id="updateStatus" parameterType="com.cherryshoe.database.domain.Status">
          UPDATE STATUS SET
            <if test="statusStatus != null">
                STATUS_STATUS = #{statusStatus},
            </if>
            <if test="statusRequest != null">
                STATUS_REQUEST = #{statusRequest},
            </if>
            <if test="statusModifiedDate != null">
                STATUS_MODIFIED_DATE = #{statusModifiedDate}
            </if>
          WHERE STATUS_ID = #{statusId}
        </update>
      
        <insert id="insertStatus" parameterType="com.cherryshoe.database.domain.Status">
          INSERT INTO STATUS
            ( <include refid="base_column_list" />)
          VALUES
            (#{statusId}, #{statusStatus}, #{statusRequest}, #{statusCreatedDate}, #{statusModifiedDate})
        </insert>
      
        <!--  
        TODO MyBatis 3 does not map booleans to integers
        -->
      
      </mapper>
      
    • spring/spring-alfresco-custom-database.xml
      <?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:aop="http://www.springframework.org/schema/aop"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:jdbc="http://www.springframework.org/schema/jdbc"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
      
       
          <!-- Alfresco's DataSource is obtained via a bean called "defaultDataSource" which is a org.apache.commons.dbcp.BasicDataSource, it's defined in
          WEB-INF/alfresco/core-services-context.xml, simulating it here -->
       
         
          <!-- transaction manager, use JtaTransactionManager for global tx -->
          <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
              <property name="dataSource" ref="defaultDataSource" />
          </bean>
      
          <!-- enable component scanning (beware that this does not enable mapper scanning!) -->    
          <context:component-scan base-package="com.cherryshoe.database.dao" />
      
          <!-- enable autowire -->
          <context:annotation-config />
      
          <!-- enable transaction demarcation with annotations -->
          <tx:annotation-driven />
      
          <!-- define the SqlSessionFactory -->
          <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
              <property name="dataSource" ref="defaultDataSource" />
              <property name="typeAliasesPackage" value="com.cherryshoe.database.domain" />
          </bean>
      
         <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
              <constructor-arg index="0" ref="sqlSessionFactory" />
          </bean>
      
          <!-- scan for mappers and let them be autowired -->
          <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
              <property name="basePackage" value="com.cherryshoe.database.persistence" />
          </bean>
      </beans>
      
      
Detailed Test Files:
  1. Necessary test dependencies to use in-memory H2 database included:
    <dependency>
     <groupId>org.hsqldb</groupId>
     <artifactId>hsqldb</artifactId>
    </dependency>
    
    <dependency>
     <groupId>commons-dbcp</groupId>
     <artifactId>commons-dbcp</artifactId>
    </dependency>
    
    <!-- Unfortunately due the binary license there is no public repository with the Oracle Driver JAR. 
    i.e. mvn install:install-file -Dfile=C:\ojdbc.jar -DgroupId=ojdbc -DartifactId=ojdbc -Dversion=6 -Dpackaging=jar
    -->
    <dependency>
     <groupId>ojdbc</groupId>
     <artifactId>ojdbc</artifactId>
    </dependency>        
    
    <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
    </dependency>
    
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
    </dependency>         
      
  2. test java files
    • database/dao/CherryShoeStatusDaoTest.java
      package com.cherryshoe.database.dao;
      
      import static org.junit.Assert.assertEquals;
      
      import java.sql.Timestamp;
      import java.util.Calendar;
      import java.util.Date;
      import java.util.UUID;
      
      
      import org.junit.Test;
      import org.junit.runner.RunWith;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.test.context.ContextConfiguration;
      import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
      
      import com.cherryshoe.database.BaseTestCase;
      import com.cherryshoe.database.dao.CherryShoeStatusDao;
      import com.cherryshoe.database.domain.Status;
      import com.cherryshoe.database.domain.StatusEnum;
      import com.cherryshoe.utils.Utils;
      
      @RunWith(SpringJUnit4ClassRunner.class)
      @ContextConfiguration(locations=
      {
          "/spring/spring-alfresco-custom-database-test.xml"
      })
      
      public class CherryShoeStatusDaoTest extends BaseTestCase {
      
          @Autowired
          CherryShoeStatusDao service;
          
          @Test
          public void createStatusnotProcessed() {
              String id = UUID.randomUUID().toString();
              Integer statusStatus = StatusEnum.NOT_PROCESSED.getStatus();
              String statusRequest = "statusRequestTestCreateStatus";
              
              Status status = createStatus(id, statusStatus, statusRequest);
              String statusId = status.getStatusId();
      
              // test that it was created in DB
              Status retStatus = service.getStatus(statusId);
              System.out.println(retStatus.toString());
              
              assertEquals(status, retStatus);
          }
          
          @Test
          public void updateStatus_inProcess() {
              String statusId = UUID.randomUUID().toString();
              Integer statusStatus = StatusEnum.NOT_PROCESSED.getStatus();
              String statusRequest = "statusRequestTestUpdateStatus";
              
              Status status = createStatus(statusId, statusStatus, statusRequest);
              String retStatusId = status.getStatusId();
      
              Integer updateStatusStatus = StatusEnum.IN_PROCESS.getStatus();
              String updateStatusRequest = "statusRequestTestUpdateStatus2";
              status.setStatusStatus(updateStatusStatus);
              status.setStatusRequest(updateStatusRequest);
              
              // new timestamp
              Date now = Calendar.getInstance().getTime();
              Timestamp modifyTimestamp = new Timestamp(now.getTime());
              status.setStatusModifiedDate(modifyTimestamp);
              
              // test that it was created in DB
              service.updateStatus(status);
              
              Status retStatus = service.getStatus(retStatusId);
              System.out.println(retStatus.toString());
              
              assertEquals(retStatus.getStatusStatus(), updateStatusStatus);
              assertEquals(retStatus.getStatusRequest(), updateStatusRequest);
          }
          
          @Test
          public void createStatus_andupdate_processed() {
              String statusId = UUID.randomUUID().toString();
              Integer statusStatus = StatusEnum.IN_PROCESS.getStatus();
              String statusRequest = "statusRequestTestCreateStatus";
              
              Status status = createStatus(statusId, statusStatus, statusRequest);
              String retStatusId = status.getStatusId();
      
              // test that it was created in DB
              Status retStatus = service.getStatus(statusId);
              assertEquals(status, retStatus);
              
              // update
              status.setStatusStatus(StatusEnum.PROCESSED.getStatus());
              // new timestamp
              Date now = Calendar.getInstance().getTime();
              Timestamp modifyTimestamp = new Timestamp(now.getTime());
              status.setStatusModifiedDate(modifyTimestamp);
              
              service.updateStatus(status);
              retStatus = service.getStatus(retStatusId);
              System.out.println(retStatus.toString());
              
              assertEquals(status, retStatus);
              
          }
          
          protected Status createStatus(String statusId, Integer statusStatus, String statusRequest) {    
              Status status = new Status();
              status.setStatusId(statusId);
              status.setStatusRequest(statusRequest);
              status.setStatusStatus(statusStatus);
              
              Date now = Calendar.getInstance().getTime();
              Timestamp timestamp = new Timestamp(now.getTime());
              status.setStatusCreateDate(timestamp);
              status.setStatusModifiedDate(timestamp);
              
              service.insertStatus(status);
              return status;
          }
          
          
      }
      
      
    • utils/Utils.java
      package com.cherryshoe.utils;
      
      import java.util.Random;
      
      public class Utils {
          /*
          * Generate a 'unique' 8 digit id
          */
          public static String get8DigitUniqueId()
          {
              Random r = new Random();
              long l = 10000000  + r.nextInt(20000000);
              
              return Long.toString(l);
          }
      
      }
      
      
  3. test resources files
    • database/alfresco-hsqldb-status-schema.sql
      -- drop table STATUS;
      drop table status if exists
      
      create table status (
          status_id varchar(64) not null,
          status_status int not null,
          status_request clob not null,
          status_created_date timestamp not null,
          status_modified_date timestamp not null,
          CONSTRAINT pk_status_id PRIMARY KEY (status_id)
      );
        
      
    • spring/spring-alfresco-custom-database-test.xml
      <?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:aop="http://www.springframework.org/schema/aop"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:jdbc="http://www.springframework.org/schema/jdbc"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
      
      
          <!-- For unit integration testing, you can switch between the hsqldb and the oracle one. -->
          <!-- in-memory database and a datasource -->
          <jdbc:embedded-database id="defaultDataSource">
              <jdbc:script location="classpath:database/alfresco-hsqldb-status-schema.sql"/>
          </jdbc:embedded-database>
          
          <!-- Alfresco's DataSource is obtained via a bean called "defaultDataSource" which is a org.apache.commons.dbcp.BasicDataSource, it's defined in
          WEB-INF/alfresco/core-services-context.xml, simulating it here -->
          <!-- For testing real oracle instance -->
      <!--     <bean id="defaultDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> -->
      <!--         <property name="driverClassName"> -->
      <!--             <value>oracle.jdbc.OracleDriver</value> -->
      <!--         </property> -->
      <!--         <property name="url"> -->
      <!--             <value>jdbc:oracle:thin:@localhost:1521:psrdb</value> -->
      <!--         </property> -->
      <!--         <property name="username"> -->
      <!--             <value>cherryshoe</value> -->
      <!--         </property> -->
      <!--         <property name="password"> -->
      <!--             <value>cherryshoe</value> -->
      <!--         </property> -->
      <!--      </bean> -->
       
         
          <!-- **********************************************************************************************
          Basically, everything starting below has to go in the real spring-alfresco-custom-database.xml 
          ********************************************************************************************** -->
      
          <!-- transaction manager, use JtaTransactionManager for global tx -->
          <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
              <property name="dataSource" ref="defaultDataSource" />
          </bean>
      
          <!-- enable component scanning (beware that this does not enable mapper scanning!) -->    
          <context:component-scan base-package="com.cherryshoe.database.dao" />
      
          <!-- enable autowire -->
          <context:annotation-config />
      
          <!-- enable transaction demarcation with annotations -->
          <tx:annotation-driven />
      
          <!-- define the SqlSessionFactory -->
          <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
              <property name="dataSource" ref="defaultDataSource" />
              <property name="typeAliasesPackage" value="com.cherryshoe.database.domain" />
          </bean>
          
         <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
              <constructor-arg index="0" ref="sqlSessionFactory" />
          </bean>
      
          <!-- scan for mappers and let them be autowired -->
          <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
              <property name="basePackage" value="com.cherryshoe.database.persistence" />
          </bean>
      </beans>
      

For more examples and ideas, I encourage you explore the links provided throughout this blog. What questions do you have about this post? Let me know in the comments section below, and I will answer each one.

5 comments:

  1. Hi Judy,

    Very nice article. First time using ibatis and mybatis-spring for me, but using this article and the very neat mybatis documentation as a basis, it turned to be very simple to understand. Thanks !

    I just keep asking myself one question (which is more a spring question actually) : although it seems to work just fine, how comes the transactionManager bean declaration does not override the native Alfresco bean declared in the hibernate-context.xml ??

    ReplyDelete
    Replies
    1. Hi Tony, It's great to hear that this article helped you, you are welcome.

      One of the spring beans, whether it be the one defined in the mybatis-spring config xml, or in alfresco's hibernate-context.xml must be taking precedence. Since I didn't get any errors when using bean id "transactionManager" in the mybatis-spring configuration when running the module by itself, or integrated with Alfresco; I did not realize I was using the same bean id!

      When running unit and integration tests on the mybatis-module by itself, it definitely needed to have the transactionManager defined. BUT when running inside the alfresco web application, one must be overriding the other, not sure which one.

      This article explained nicely why we didn't see an error when running alfresco with two duplicate bean id's:
      http://stackoverflow.com/questions/5849192/springs-overriding-bean: Two different XML files, one is overriding the other depending on which bean is loaded last.
      http://stackoverflow.com/questions/19034273/how-to-stop-overiding-a-bean-in-spring: You can explicitly disable the feature to diallow bean overriding.

      Happy coding,
      Judy

      Delete
  2. Hi Judy,
    firstly thank you for this great tuto and secondly I can't figure out why I'm getting a javaNullPointerException.

    Details:
    Class CherryShoeStatusDao -> statusMapper.insertStatus(status);

    ReplyDelete
    Replies
    1. Sorry, the problem was Alfresco doesn't read the spring-alfresco-custom-database.xml file by default
      Thanks

      Delete
  3. Hi Izzedine,

    I clarified the need to import the spring-alfresco-custom-database.xml file in steps 3 and 4 of "High Level Steps".

    Thanks,
    Judy

    ReplyDelete

I appreciate your time in leaving a comment!