вторник, 18 ноября 2014 г.

Spring Tests + Jpa + DbUnitTest + JUnit


Подтягиваем зависимости
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${version.spring}</version>
</dependency>

<dependency>
    <groupId>com.github.springtestdbunit</groupId>
    <artifactId>spring-test-dbunit</artifactId>
    <version>1.1.0</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.dbunit</groupId>
    <artifactId>dbunit</artifactId>
    <version>2.4.9</version>
    <scope>test</scope>
<exclusions>
        <exclusion>
            <artifactId>junit</artifactId>
            <groupId>junit</groupId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
</dependency>
Настраиваем контекст:
    <bean id="dbUnitDatabaseConnection"
          class="com.github.springtestdbunit.bean.DatabaseDataSourceConnectionFactoryBean">
        <property name="databaseConfig" ref="dbUnitDatabaseConfig"/>
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="dbUnitDatabaseConfig" 
class="com.github.springtestdbunit.bean.DatabaseConfigBean">
        <property name="datatypeFactory">
            <bean class="org.dbunit.ext.postgresql.PostgresqlDataTypeFactory"/>
        </property>
        <property name="qualifiedTableNames" value="true"/>
    </bean>
    <bean id="dataSource" 
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.postgresql.Driver"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
Вносим тестовые данные в xml-файл:
<dataset>
    <test.users id="1" email="test222@test.ru" username="Test222"/>
</dataset>
Пишем сам класс для тестирования:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/spring/application-context-test.xml")
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class})
@DatabaseSetup(value = "/dbunit/userData.xml", type = DatabaseOperation.INSERT)
@DatabaseTearDown(value="/dbunit/userData.xml", type=DatabaseOperation.DELETE_ALL)
public class UserEntityRepositoryIT {

    @Autowired
    private co.brandly.repository.UserEntityRepository userEntityRepository;

    @Test
    public void shouldHaveNoObjectsAtStart() {

        List<UserEntity> results = (List<UserEntity>) userEntityRepository.findAll();
        assert (results.size() == 1);
    }
}
Можно добавить класс для замены произвольных значений в dataSet'e, например попробуем заменить "[now]" на сегодняшнюю дату. Наши данные в userData.xml:
<dataset>
    <test.users id="1"  username="Test222" created_at="[now]"/>
</dataset>
Класс, реализующий интерфейс DataSetLoader:
public class DateReplacementDataSetLoader implements DataSetLoader {

    private DataSetLoader delegate;

    public DateReplacementDataSetLoader() {
        this(new FlatXmlDataSetLoader());
    }

    public DateReplacementDataSetLoader(DataSetLoader delegate) {
        this.delegate = delegate;
    }

    public IDataSet loadDataSet(Class<?> testClass, String location) throws Exception {
        ReplacementDataSet replacementDataSet = new ReplacementDataSet(delegate.loadDataSet(testClass, location));
        replacementDataSet.addReplacementObject("[now]", DateTime.now());
        return replacementDataSet;
    }
}
Класс теста (не забываем аннотацию DbUnitConfiguration, с указанием выше созданного класса):
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/spring/application-context-test.xml")
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class})
@DatabaseSetup(value = "/dbunit/userData.xml", type = DatabaseOperation.CLEAN_INSERT)
@DatabaseTearDown(value = "/dbunit/userData.xml", type = DatabaseOperation.DELETE_ALL)
@DbUnitConfiguration(dataSetLoader = DateReplacementDataSetLoader.class)
public class UserEntityRepositoryIT {

    @Autowired
    private co.brandly.repository.UserEntityRepository userEntityRepository;

    @Test
    public void shouldHaveNoObjectsAtStart() throws Exception {

        List<UserEntity> results = (List<UserEntity>) userEntityRepository.findAll();
        assert (results.size() == 3);
        assert (results.get(0).getCreatedAt()!=null);
    }
}

среда, 3 сентября 2014 г.

Spring Data MongoDB

Если при попытке вставить документ в коллекцию ничего не происходит или возникает OptimisticalLockException (при том, что очевидных поводов для этого исключения не наблюдается) советую проверить индексы коллекции. Особенно если индексы используются для геолокации.

К примеру есть у нас такой репозиторий:
@Component
public class TestRepository extends BaseRepository<TestEntity> {
 @PostConstruct
    public void init() {
        ensureGeoIndex("testField");
    }
}
наследуется от такого класса:
@Component
public class BaseRepository<T extends BaseEntity> {
@Autowired
protected MongoOperations op;
protected void ensureGeoIndex(String fieldName) {
        GeospatialIndex index = new GeospatialIndex(fieldName);
        IndexOperations indexOperations = op.indexOps(domainClass);
        indexOperations.ensureIndex(index);
    }
}
Если попытаться вставить запись вот так:
TestEntity testEntity=new TestEntity();testEntity.setTestField("testValue");testRepository.save(testEntity);
в базу ничего не запишется, т.к. монга ожидает в качестве значения примерно такую структуру:
{ type: "Point", coordinates: [ -1, 1 ] }

MongoDB

Удалить по дате создания
function objectIdWithTimestamp(timestamp)
{
    // Convert string date to Date object (otherwise assume timestamp is a date)
    if (typeof(timestamp) == 'string') {
        timestamp = new Date(timestamp);
    }

    // Convert date object to hex seconds since Unix epoch
    var hexSeconds = Math.floor(timestamp/1000).toString(16);

    // Create an ObjectId with that hex timestamp
    var constructedObjectId = ObjectId(hexSeconds + "0000000000000000");

    return constructedObjectId
}
db.users.remove({ _id: { $gt: objectIdWithTimestamp('2014/07/15') } })

http://stackoverflow.com/questions/8749971/can-i-query-mongodb-objectid-by-date


Spring mvc jackson json serialization: null as empty string or ignore null

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import java.io.IOException;

@Configuration
public class JacksonSerializationConfig {

    @Bean
    public ObjectMapper jacksonObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        DefaultSerializerProvider sp = new DefaultSerializerProvider.Impl();
        sp.setNullValueSerializer(new NullSerializer());
        objectMapper.setSerializerProvider(sp);
        // objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); //uncomment if need to ignore null strings
        return objectMapper;
    }

    @Bean
    public MappingJackson2HttpMessageConverter jackson2Converter() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.setObjectMapper(jacksonObjectMapper());
        return converter;
    }

    @Bean
    public SerializationConfig serializationConfig() {
        return jacksonObjectMapper().getSerializationConfig();
    }

    // and NullSerializer can be something as simple as:
    public class NullSerializer extends JsonSerializer<Object> {
        public void serialize(Object value, JsonGenerator jgen,
                              SerializerProvider provider)
                throws IOException, JsonProcessingException {
            // any JSON value you want...
            jgen.writeString("");
        }
    }
}

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/mvc
      http://www.springframework.org/schema/mvc/spring-mvc.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <mvc:default-servlet-handler/>
    <mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="objectMapper" ref="jacksonObjectMapper"/>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
</beans>

четверг, 23 января 2014 г.

Преобразование JSON и XML в объекты c#

Преобразование строки Json в объект c#

К примеру имеем такой Json :
var jsonStr=@"{
    "one": "value",
    "two": [
        [
            "123",
            "325",
            "string",
            "0.10"
        ],
        [
            "k",
            "1.0",
            "20.04.2004",
            ""
        ]
    ],
    "three": [
        "29",
        "1.0",
        "13.10.2000",
        ""
    ]
}";

Чтобы преобразовать его в объект c# подключаем пространство имен System.Web.Script.Serialization;

Затем создаем класс, описывающий наш объект. В Visual Studio 2012 есть возможность вставить JSON - код как класс, достаточно лишь поместить нужный экземпляр в буфер обмена (скопировать) и в студии нажать Edit - Paste special - Paste JSON as Classes (Правка - Специальная вставка - Вставить JSON как классы):
public class Test
        {
            public string one {get;set;}
            public List<List<string>> two{ get; set; }
            public List<string> three{ get; set; }
        }
И получаем результат двумя строчками:
 var jss = new JavaScriptSerializer();
 var result = jss.Deserialize<Test>(jsonStr));
Кстати, JSONLint - отличный способ проверить и отформатировать ваш Json.


Преобразование строки XML в объект c# 

Имеем такой XML:
var xmlStr=@"<?xml version="1.0" encoding="UTF-8"?>
<Root>
   <One>value</One>
   <Three>
      <element>29</element>
      <element>1.0</element>
      <element>13.10.2000</element>
      <element />
   </Three>
   <Two>
      <element>
         <element>123</element>
         <element>325</element>
         <element>string</element>
         <element>0.10</element>
      </element>
      <element>
         <element>k</element>
         <element>1.0</element>
         <element>20.04.2004</element>
         <element />
      </element>
   </Two>
</Root>";

В Visual Studio 2012 есть возможность вставить XML код как класс, достаточно лишь поместить нужный экземпляр в буфер обмена (скопировать) и в студии нажать Edit - Paste special - Paste XML as Classes (Правка - Специальная вставка - Вставить XML как классы). Проверить свой XML-код можно тут.

Получили следующий класс:
    [XmlTypeAttribute(AnonymousType = true)]
    [XmlRootAttribute(Namespace = "", IsNullable = false)]
    public class Root
    {
        public string One { get; set; }
        [XmlArrayItemAttribute("element", IsNullable = false)]
        public string[] Three { get; set; }
        [XmlArrayItemAttribute("element", IsNullable = false),
XmlArrayItemAttribute("element", IsNullable = false, NestingLevel = 1)]
        public string[][] Two { get; set; }
    }
Преобразовать эту строку XML в объект c# теперь можно, используя следующую функцию:
 public static Root DeserializeFromXML(string xmlStr)
        {
            private var result = "";
            var deserializer = new XmlSerializer(typeof(Root));
            using (var reader = new StringReader(xmlStr))
                result = (Root)deserializer.Deserialize(reader);
         
            return result;
        }
      

среда, 22 января 2014 г.

Visual studio проблема при публикации веб-приложения, пустая папка

Не первый раз натыкаюсь на проблему - при попытке публикации веб-приложения на диск (по-умолчанию в C:\inetpub\wwwroot) в выходных данных пишется что-то вроде:
...
...
...
Публикация каталога /...
Публикация: успешно.

А на деле - создается пустая папка. И вроде бы запустили студию от администратора и права на запись в папку есть. Решить проблему можно, удалив текущий профиль публикации и создав хотя бы точно такой же.

вторник, 21 января 2014 г.

JQuery DataTables баги и фичи

Недавно использовал в ASP.NET приложении библиотеку DataTables для вывода большой таблицы (данные получаются с сервера) и заметил, что информации на русском языке по теме не много, так что решил написать о некоторых нюансах, надеюсь кому-то это поможет сэкономить время. Про основы использования рассказывать не буду, на сайте полно примеров. Поехали.

Проблема №1

Ширина ячеек в заголовке таблицы (в thead) отличается от ширины ячеек в теле таблицы (в tbody) на 1px.

Мне помогло использование вот такого стиля для таблицы.
#myTable {
table-layout: fixed;
margin: 0 auto;
clear: both;
width:100%
}
(ссылка на ответ разработчика библиотеки)

Проблема №2 

При использовании плагина ColVis (предназначен для сокрытия столбцов) в таблице с данными, получаемыми с сервера (server-side processing) - таблица перезагружается полностью, даже если необходимо просто скрыть один столбец.

Решить можно таким способом: находим в ColVis.js следующие строчки и комментируем выделенную.
// Optimisation for server-side processing when scrolling - don't do a full redraw

if ( dt.oFeatures.bServerSide && (dt.oScroll.sX !== "" || dt.oScroll.sY !== "" ) )

{

that.s.dt.oInstance.fnSetColumnVis(i, showHide, false);

that.s.dt.oInstance.fnAdjustColumnSizing(false);

    //that.s.dt.oInstance.oApi._fnScrollDraw(that.s.dt);    that._fnDrawCallback();

}
(ссылка на ответ разработчика библиотеки)

Задача №1

Добавить собственный класс к тегу элемента таблицы

Решение:
var extensions = {
"sPaging": "yourClass",
"sPageButton": "yourClass",
"sPageButtonActive": "yourClass",
"sWrapper": "table_container "
};
$.extend($.fn.dataTableExt.oStdClasses, extensions);
(ссылка на источник)

Задача №2

Переместить элемент таблицы в другой тег ( к примеру информацию о количестве записей вынести в заголовок)

Решение:
oTable = $("#myTable").dataTable({
fnInitComplete: function (oSettings, json) {
$("#table_info").detach().prependTo("#dtInfo");
}});
"table_info" - идентификатор целевого элемента, который нужно переместить, "dtInfo" - тега, куда произойдет перемещение после загрузки таблицы.

(ссылка на источник)

Задача №3

Воздействовать на ячейки таблицы после её загрузки (удалить содержимое, применить стиль и тд).

Решение: Например сделаем шрифт жирным в шестом столбце таблицы.
oTable = $("#myTable").dataTable({
"fnRowCallback": function (nRow, aData, iDisplayIndex, iDisplayIndexFull) {
$('td:eq(5)', nRow).css('font-weight', 'bold');
return nRow;
}});

Задача №4 

Сделать ручной ввод отображаемого количества записей на странице.

Изначально в DataTables количество отображаемых записей контролируется выпадающим списком, но мне понадобилось использовать обычное текстовое поле, чтобы в любой момент задать произвольное число. 

Решение: 
function redrawWithNewCount(t, row_count) {
//Lifted more or less right out of the DataTables source
var oSettings = t.fnSettings();
oSettings._iDisplayLength = parseInt(row_count, 10);
t._fnCalculateEnd(oSettings);
/* If we have space to show extra rows (backing up from the end point - then do so */
if (oSettings.fnDisplayEnd() == oSettings.fnRecordsDisplay()) {
oSettings._iDisplayStart = oSettings.fnDisplayEnd() - oSettings._iDisplayLength; if (oSettings._iDisplayStart < 0) {
    oSettings._iDisplayStart = 0;
; }
}
if (oSettings._iDisplayLength == -1) {
    oSettings._iDisplayStart = 0;
} t.fnDraw(oSettings);
return t;
} oTable = $("#myTable").dataTable({
fnInitComplete: function (oSettings, json) {
$("#myTable_length").html("<label>Записей на странице:<input name=\"myTable_length\" aria-controls=\"myTable\" type=\"number\"/></label>");
$("#myTable_length>label>input").val(oTable.fnSettings()._iDisplayLength);
$('#dtLength>label>input').change(function () {
redrawWithNewCount(oTable, this.value);
});
}
});
(ссылка на ответ разработчика, откуда я взял функцию "redrawWithNewCount")