/* The contents of this file are subject to the license and copyright terms
* detailed in the license directory at the root of the source tree (also
* available online at http://fedora-commons.org/license/).
*/
package org.fcrepo.server.search;
import static org.mockito.AdditionalMatchers.aryEq;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import junit.framework.JUnit4TestAdapter;
import org.fcrepo.server.errors.ServerException;
import org.fcrepo.server.storage.ConnectionPool;
import org.fcrepo.server.storage.MockDOReader;
import org.fcrepo.server.storage.MockRepositoryReader;
import org.fcrepo.server.storage.types.BasicDigitalObject;
import org.fcrepo.server.storage.types.DatastreamXMLMetadata;
import org.fcrepo.server.utilities.SQLUtility;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"org.slf4j.*", "org.apache.xerces.*", "javax.xml.*",
"org.xml.sax.*", "javax.management.*"})
@PrepareForTest({SQLUtility.class})
public class TestFieldSearchSQLImpl {
private static final String[] SHORT_FIELDS = FieldSearchSQLImpl.DB_COLUMN_NAMES_NODC;
private static final String[] LONG_FIELDS = FieldSearchSQLImpl.DB_COLUMN_NAMES;
private static final String DC_PAYLOAD_NO_DATES = "<oai_dc:dc "
+ " xmlns:oai_dc=\"http://www.openarchives.org/OAI/2.0/oai_dc/\" "
+ " xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n"
+ " <dc:title>Sandy's Reference Object</dc:title>\n"
+ " <dc:creator>Sandy Payette</dc:creator>\n"
+ " <dc:subject>FOXML Testing</dc:subject>\n"
+ " <dc:description>Object depicts all types of datastreams</dc:description>\n"
+ " <dc:publisher>Cornell CIS</dc:publisher>\n"
+ " <dc:identifier>test:100</dc:identifier>\n" + "</oai_dc:dc>\n";
private static final String DC_PAYLOAD_WITH_DATES = "<oai_dc:dc "
+ " xmlns:oai_dc=\"http://www.openarchives.org/OAI/2.0/oai_dc/\" "
+ " xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n"
+ " <dc:title>Sandy's Reference Object</dc:title>\n"
+ " <dc:creator>Sandy Payette</dc:creator>\n"
+ " <dc:subject>FOXML Testing</dc:subject>\n"
+ " <dc:description>Object depicts all types of datastreams</dc:description>\n"
+ " <dc:publisher>Cornell CIS</dc:publisher>\n"
+ " <dc:identifier>test:100</dc:identifier>\n"
+ " <dc:date>2006-10-15</dc:date>\n" + "</oai_dc:dc>\n";
public static junit.framework.Test suite() {
return new JUnit4TestAdapter(TestFieldSearchSQLImpl.class);
}
private static final ObjectData OBJECT_WITH_NO_DC = new ObjectData(
"somePid", "myLabel", "A", "theOwner", new Date(
12345), new Date(67890), new Date(0), null);
private static final ObjectData OBJECT_WITH_DC = new ObjectData("somePid",
"myLabel", "A", "theOwner", new Date(12345),
new Date(67890), new Date(10000), DC_PAYLOAD_NO_DATES);
private static final ObjectData OBJECT_WITH_DC_AND_DATES = new ObjectData(
"somePid", "myLabel", "A", "theOwner", new Date(
12345), new Date(67890), new Date(10000),
DC_PAYLOAD_WITH_DATES);
@Mock
private MockRepositoryReader mockRepositoryReader;
@Mock
private ConnectionPool connectionPool;
@Mock
private Connection mockConnection;
@Mock
private PreparedStatement mockStmt;
@Before
public void setUp() throws Exception {
mockStatic(SQLUtility.class);
when(connectionPool.getReadWriteConnection()).thenReturn(mockConnection);
when(mockConnection.prepareStatement(any(String.class))).thenReturn(mockStmt);
//PowerMockito.doNothing().when(SQLUtility.class, "createNonExistingTables",any(ConnectionPool.class), any(InputStream.class));
}
@Test
public void noDC() throws ServerException, SQLException {
updateRecord(OBJECT_WITH_NO_DC, false);
verify(mockConnection,times(0)).prepareStatement("DELETE FROM dcDates WHERE pid=?");
verify(mockConnection, times(0)).prepareStatement("INSERT INTO dcDates (pid, dcDate) values (?, ?)");
verifyStatic(times(1));
SQLUtility.replaceInto(
any(Connection.class), eq("doFields"), aryEq(SHORT_FIELDS), aryEq(OBJECT_WITH_NO_DC.getShortFieldValueList().toArray(new String[]{})), eq("pid"), any(boolean[].class));
}
@Test
public void dcNoDatesShortFields() throws ServerException, SQLException {
updateRecord(OBJECT_WITH_DC, false);
verify(mockConnection,times(0)).prepareStatement("DELETE FROM dcDates WHERE pid=?");
verify(mockConnection, times(0)).prepareStatement("INSERT INTO dcDates (pid, dcDate) values (?, ?)");
verifyStatic(times(1));
SQLUtility.replaceInto(
any(Connection.class), eq("doFields"), aryEq(SHORT_FIELDS), aryEq(OBJECT_WITH_DC.getShortFieldValueList().toArray(new String[]{})), eq("pid"), any(boolean[].class));
}
@Test
public void dcNoDatesLongFields() throws ServerException, SQLException {
updateRecord(OBJECT_WITH_DC, true);
verify(mockConnection,times(1)).prepareStatement("DELETE FROM dcDates WHERE pid=?");
verify(mockConnection, times(0)).prepareStatement("INSERT INTO dcDates (pid, dcDate) values (?, ?)");
verifyStatic(times(1));
SQLUtility.replaceInto(
any(Connection.class), eq("doFields"), aryEq(LONG_FIELDS), aryEq(OBJECT_WITH_DC.getLongFieldValueList().toArray(new String[]{})), eq("pid"), any(boolean[].class));
}
@Test
public void dcDatesShortFields() throws ServerException, SQLException {
updateRecord(OBJECT_WITH_DC_AND_DATES, false);
verify(mockConnection,times(0)).prepareStatement("DELETE FROM dcDates WHERE pid=?");
verify(mockConnection, times(0)).prepareStatement("INSERT INTO dcDates (pid, dcDate) values (?, ?)");
verifyStatic(times(1));
SQLUtility.replaceInto(
any(Connection.class), eq("doFields"), aryEq(SHORT_FIELDS), aryEq(OBJECT_WITH_DC.getShortFieldValueList().toArray(new String[]{})), eq("pid"), any(boolean[].class));
}
@Test
public void dcDatesLongFields() throws ServerException, SQLException {
updateRecord(OBJECT_WITH_DC_AND_DATES, true);
verify(mockConnection,times(1)).prepareStatement("DELETE FROM dcDates WHERE pid=?");
verify(mockConnection, times(1)).prepareStatement("INSERT INTO dcDates (pid, dcDate) values (?, ?)");
verifyStatic(times(1));
SQLUtility.replaceInto(
any(Connection.class), eq("doFields"), aryEq(LONG_FIELDS), aryEq(OBJECT_WITH_DC_AND_DATES.getLongFieldValueList().toArray(new String[]{})), eq("pid"), any(boolean[].class));
}
private void updateRecord(ObjectData objectData, boolean longFields)
throws ServerException {
// Create a DC datastream if appropriate.
DatastreamXMLMetadata dcmd = null;
if (objectData.getDcPayload() != null) {
dcmd = new DatastreamXMLMetadata();
dcmd.DatastreamID = "DC";
dcmd.DSCreateDT = objectData.getDcModifiedDate();
dcmd.xmlContent = objectData.getDcPayload().getBytes();
}
// Create the object and populate it.
BasicDigitalObject theObject = new BasicDigitalObject();
theObject.setPid(objectData.getPid());
theObject.setLabel(objectData.getLabel());
theObject.setState(objectData.getState());
theObject.setOwnerId(objectData.getOwnerId());
theObject.setCreateDate(objectData.getCreateDate());
theObject.setLastModDate(objectData.getLastModDate());
if (dcmd != null) {
theObject.addDatastreamVersion(dcmd, false);
}
// Create the test instance.
FieldSearchSQLImpl fssi = new FieldSearchSQLImpl(this.connectionPool,
this.mockRepositoryReader, 50, 50, longFields);
// And do the update.
fssi.update(new MockDOReader(theObject));
}
private static class ObjectData {
private final String pid;
private final String label;
private final String state;
private final String ownerId;
private final Date createDate;
private final Date lastModDate;
private final Date dcModifiedDate;
private final String dcPayload;
public ObjectData(String pid, String label,
String state, String ownerId, Date createDate,
Date lastModDate, Date dcModifiedDate, String dcPayload) {
this.pid = pid;
this.label = label;
this.state = state;
this.ownerId = ownerId;
this.createDate = createDate;
this.lastModDate = lastModDate;
this.dcModifiedDate = dcModifiedDate;
this.dcPayload = dcPayload;
}
public List<String> getShortFieldValueList() {
List<String> result = new ArrayList<String>();
result.add(pid);
result.add(lowerCase(label));
result.add(lowerCase(state));
result.add(lowerCase(ownerId));
result.add(dateStamp(createDate));
result.add(dateStamp(lastModDate));
result.add(dateStamp(dcModifiedDate));
return result;
}
public List<String> getLongFieldValueList() {
List<String> result = new ArrayList<String>();
result.addAll(getShortFieldValueList());
result.add(lowerCase(getDcFields("dc:title")));
result.add(lowerCase(getDcFields("dc:creator")));
result.add(lowerCase(getDcFields("dc:subject")));
result.add(lowerCase(getDcFields("dc:description")));
result.add(lowerCase(getDcFields("dc:publisher")));
result.add(lowerCase(getDcFields("dc:contributor")));
result.add(lowerCase(getDcFields("dc:date")));
result.add(lowerCase(getDcFields("dc:type")));
result.add(lowerCase(getDcFields("dc:format")));
result.add(lowerCase(getDcFields("dc:identifier")));
result.add(lowerCase(getDcFields("dc:source")));
result.add(lowerCase(getDcFields("dc:language")));
result.add(lowerCase(getDcFields("dc:relation")));
result.add(lowerCase(getDcFields("dc:coverage")));
result.add(lowerCase(getDcFields("dc:rights")));
return result;
}
public String getPid() {
return pid;
}
public String getLabel() {
return label;
}
public String getState() {
return state;
}
public String getOwnerId() {
return ownerId;
}
public Date getCreateDate() {
return createDate;
}
public Date getLastModDate() {
return lastModDate;
}
public Date getDcModifiedDate() {
return dcModifiedDate;
}
public String getDcPayload() {
return dcPayload;
}
private String lowerCase(String raw) {
return (raw == null) ? null : raw.toLowerCase();
}
private String dateStamp(Date date) {
return (date == null) ? null : String.valueOf(date.getTime());
}
private String getDcFields(String fieldName) {
String pString = String.format("<%1$s>\\s*([^<]*)\\s*</%1$s>",
fieldName);
Pattern p = Pattern.compile(pString);
Matcher m = p.matcher(dcPayload);
List<String> values = new ArrayList<String>();
int start = 0;
while (m.find(start)) {
values.add(m.group(1));
start = m.end();
}
return joinStrings(values);
}
private String joinStrings(Collection<String> strings) {
if ((strings == null) || (strings.isEmpty())) {
return null;
}
StringBuffer result = new StringBuffer();
for (String string : strings) {
result.append(" ").append(string).append(" .");
}
return result.toString();
}
}
}