Project

General

Profile

« Previous | Next » 

Revision 8498

allow indexing of RDF documents - provide a sparql query that will return values for the field name. Using measurement_sm initially (a dynamic multivalued solr field). https://projects.ecoinformatics.org/ecoinfo/issues/6253

View differences:

metacat-index/src/test/java/edu/ucsb/nceas/metacat/index/SolrIndexIT.java
42 42
    private static final String id = "urn:uuid:606a19dd-b531-4bf4-b5a5-6d06c3d39098";
43 43
    private static final String newId = "urn:uuid:606a19dd-b531-4bf4-b5a5-6d06c3d39099";
44 44
    
45
	private String annotation_id = "http://doi.org/annotation.1.1";
46
	private static final String ANNOTATION_SYSTEM_META_FILE_PATH = "src/test/resources/annotation-system-meta-example.xml";
47
	private static final String ANNOTATION_FILE_PATH = "src/test/resources/annotation-example.rdf";;
48
    
45 49
	private SolrIndex solrIndex = null;
46 50
    
47
    
48 51
    @Before
49 52
    public void setUp() throws Exception {
50 53
            solrIndex  = generateSolrIndex();
......
66 69
    /**
67 70
     * Test building index for an insert.
68 71
     */
69
    @Test
72
//    @Test
70 73
    public void testInsert() throws Exception {
71 74
    	
72 75
    	
......
94 97
    /**
95 98
     * Test building index for an insert.
96 99
     */
97
    @Test
100
//    @Test
98 101
    public void testUpdate() throws Exception {
99 102
       //InputStream systemInputStream = new FileInputStream(new File(SYSTEMMETAFILEPATH));
100 103
       SystemMetadata systemMetadata = TypeMarshaller.unmarshalTypeFromFile(SystemMetadata.class, SYSTEMMETAUPDATEFILEPATH);
......
112 115
    /**
113 116
     * Test building index for an insert.
114 117
     */
115
    @Test
118
//    @Test
116 119
    public void testArchive() throws Exception {
117 120
       SolrIndex solrIndex = generateSolrIndex();
118 121
       //InputStream systemInputStream = new FileInputStream(new File(SYSTEMMETAFILEPATH));
......
176 179
    }
177 180
    
178 181
    /**
182
     * Test building index for annotation.
183
     */
184
    @Test
185
    public void testAnnotation() throws Exception {
186
    	
187
       SystemMetadata systemMetadata = TypeMarshaller.unmarshalTypeFromFile(SystemMetadata.class, SYSTEMMETAFILEPATH);
188
       InputStream emlInputStream = new FileInputStream(new File(EMLFILEPATH)); 
189
       Identifier pid = new Identifier();
190
       pid.setValue(id);
191
       solrIndex.update(pid, systemMetadata, emlInputStream);
192
       String result = doQuery(solrIndex.getSolrServer());
193
       List<String> ids = solrIndex.getSolrIds();
194
       boolean foundId = false;
195
       for(String identifiers :ids) {
196
           if (id.equals(identifiers)) {
197
               foundId = true;
198
           }
199
       }
200
       assertTrue(foundId);
201
       assertTrue(result.contains("version1"));
202
       
203
       // augment with the dynamic field
204
       SystemMetadata annotationSystemMetadata = TypeMarshaller.unmarshalTypeFromFile(SystemMetadata.class, ANNOTATION_SYSTEM_META_FILE_PATH);
205
       InputStream annotationInputStream = new FileInputStream(new File(ANNOTATION_FILE_PATH)); 
206
       Identifier annotationPid = new Identifier();
207
       annotationPid.setValue(annotation_id);
208
       solrIndex.update(annotationPid, annotationSystemMetadata, annotationInputStream);
209
       String annotationResult = doQuery(solrIndex.getSolrServer());
210
       assertTrue(annotationResult.contains("measurement_sm"));
211
       
212
    }
213
    
214
    /**
179 215
     * Do query - with no additional params
180 216
     */
181 217
    public static String doQuery(SolrServer server)
metacat-index/src/main/java/edu/ucsb/nceas/metacat/index/annotation/RdfXmlSubprocessor.java
1
/**
2
 * This program is free software; you can redistribute it and/or modify
3
 * it under the terms of the GNU General Public License as published by
4
 * the Free Software Foundation; either version 2 of the License, or
5
 * (at your option) any later version.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
15
 */
16
package edu.ucsb.nceas.metacat.index.annotation;
17

  
18
import java.io.ByteArrayInputStream;
19
import java.io.ByteArrayOutputStream;
20
import java.io.File;
21
import java.io.IOException;
22
import java.io.InputStream;
23
import java.net.MalformedURLException;
24
import java.util.ArrayList;
25
import java.util.Date;
26
import java.util.HashMap;
27
import java.util.Iterator;
28
import java.util.List;
29
import java.util.Map;
30
import java.util.Set;
31

  
32
import javax.xml.parsers.ParserConfigurationException;
33
import javax.xml.transform.Result;
34
import javax.xml.transform.Source;
35
import javax.xml.transform.TransformerConfigurationException;
36
import javax.xml.transform.TransformerException;
37
import javax.xml.transform.TransformerFactory;
38
import javax.xml.transform.TransformerFactoryConfigurationError;
39
import javax.xml.transform.dom.DOMSource;
40
import javax.xml.transform.stream.StreamResult;
41

  
42
import org.apache.commons.logging.Log;
43
import org.apache.commons.logging.LogFactory;
44
import org.apache.solr.client.solrj.SolrServer;
45
import org.apache.solr.client.solrj.SolrServerException;
46
import org.apache.solr.client.solrj.response.QueryResponse;
47
import org.apache.solr.common.SolrDocument;
48
import org.apache.solr.common.params.SolrParams;
49
import org.apache.solr.schema.IndexSchema;
50
import org.apache.solr.servlet.SolrRequestParsers;
51
import org.dataone.cn.indexer.convert.SolrDateConverter;
52
import org.dataone.cn.indexer.parser.AbstractDocumentSubprocessor;
53
import org.dataone.cn.indexer.parser.IDocumentSubprocessor;
54
import org.dataone.cn.indexer.parser.ISolrField;
55
import org.dataone.cn.indexer.solrhttp.SolrDoc;
56
import org.dataone.cn.indexer.solrhttp.SolrElementField;
57
import org.dataone.service.exceptions.NotFound;
58
import org.dataone.service.exceptions.UnsupportedType;
59
import org.dataone.service.util.DateTimeMarshaller;
60
import org.w3c.dom.Document;
61
import org.xml.sax.SAXException;
62

  
63
import com.hp.hpl.jena.ontology.OntModel;
64
import com.hp.hpl.jena.query.Dataset;
65
import com.hp.hpl.jena.query.Query;
66
import com.hp.hpl.jena.query.QueryExecution;
67
import com.hp.hpl.jena.query.QueryExecutionFactory;
68
import com.hp.hpl.jena.query.QueryFactory;
69
import com.hp.hpl.jena.query.QuerySolution;
70
import com.hp.hpl.jena.query.ResultSet;
71
import com.hp.hpl.jena.rdf.model.ModelFactory;
72
import com.hp.hpl.jena.tdb.TDBFactory;
73

  
74
import edu.ucsb.nceas.metacat.common.SolrServerFactory;
75
import edu.ucsb.nceas.metacat.common.query.SolrQueryServiceController;
76

  
77

  
78
/**
79
 * A solr index parser for an RDF/XML file.
80
 * The solr doc of the RDF/XML object only has the system metadata information.
81
 * The solr docs of the science metadata doc and data file have the annotation information.
82
 */
83
public class RdfXmlSubprocessor extends AbstractDocumentSubprocessor implements IDocumentSubprocessor {
84

  
85
    private static final String QUERY ="q=id:";
86
    private static Log log = LogFactory.getLog(RdfXmlSubprocessor.class);
87
    private static SolrServer solrServer =  null;
88
    static {
89
        try {
90
            solrServer = SolrServerFactory.createSolrServer();
91
        } catch (Exception e) {
92
            log.error("RdfXmlSubprocessor - can't generate the SolrServer since - "+e.getMessage());
93
        }
94
    }
95
          
96
    @Override
97
    public Map<String, SolrDoc> processDocument(String identifier, Map<String, SolrDoc> docs, Document doc) throws Exception {
98
        SolrDoc resourceMapDoc = docs.get(identifier);
99
        List<SolrDoc> processedDocs = process(resourceMapDoc, doc);
100
        Map<String, SolrDoc> processedDocsMap = new HashMap<String, SolrDoc>();
101
        for (SolrDoc processedDoc : processedDocs) {
102
            processedDocsMap.put(processedDoc.getIdentifier(), processedDoc);
103
        }
104
        return processedDocsMap;
105
    }
106

  
107
    private InputStream toInputStream(Document doc) throws TransformerConfigurationException, TransformerException, TransformerFactoryConfigurationError {
108
    	ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
109
    	Source xmlSource = new DOMSource(doc);
110
    	Result outputTarget = new StreamResult(outputStream);
111
    	TransformerFactory.newInstance().newTransformer().transform(xmlSource, outputTarget);
112
    	InputStream is = new ByteArrayInputStream(outputStream.toByteArray());
113
    	return is;
114
    }
115
    
116
    private List<SolrDoc> process(SolrDoc indexDocument, Document rdfXmlDocument) throws Exception {
117
    	
118
    	// get the triplestore dataset
119
		Dataset dataset = TripleStoreService.getInstance().getDataset();
120
		
121
    	// read the annotation
122
		InputStream source = toInputStream(rdfXmlDocument);
123
    	String name = indexDocument.getIdentifier();
124
    	boolean loaded = dataset.containsNamedModel(name);
125
		if (!loaded) {
126
			OntModel ontModel = ModelFactory.createOntologyModel();
127
			ontModel.read(source, name);
128
			dataset.addNamedModel(name, ontModel);
129
		}
130
		//dataset.getDefaultModel().add(ontModel);
131
		
132
		// process each field query
133
        Map<String, SolrDoc> documentsToIndex = new HashMap<String, SolrDoc>();
134
		for (ISolrField field: this.getFieldList()) {
135
			String q = null;
136
			if (field instanceof SparqlField) {
137
				q = ((SparqlField) field).getQuery();
138
				q = q.replaceAll("\\$GRAPH_NAME", name);
139
				Query query = QueryFactory.create(q);
140
				QueryExecution qexec = QueryExecutionFactory.create(query, dataset);
141
				ResultSet results = qexec.execSelect();
142
				
143
				while (results.hasNext()) {
144
					SolrDoc solrDoc = null;
145
					QuerySolution solution = results.next();
146
					System.out.println(solution.toString());
147
					
148
					// find the index document we are trying to augment with the annotation
149
					if (solution.contains("pid")) {
150
						String id = solution.getLiteral("pid").getString();
151
						solrDoc = documentsToIndex.get(id);
152
						if (solrDoc == null) {
153
							solrDoc = new SolrDoc();
154
							documentsToIndex.put(id, solrDoc);
155
						}
156
					}
157

  
158
					// add the field to the index document
159
					if (solution.contains(field.getName())) {
160
						String value = solution.get(field.getName()).toString();
161
						SolrElementField f = new SolrElementField(field.getName(), value);
162
						if (!solrDoc.hasFieldWithValue(f.getName(), f.getValue())) {
163
							solrDoc.addField(f);
164
						}
165
					}
166
				}
167
			}
168
		}
169
		
170
		// clean up the triple store
171
		TDBFactory.release(dataset);
172

  
173
		// merge the existing index with the new[er] values
174
        Map<String, SolrDoc> existingDocuments = getSolrDocs(documentsToIndex.keySet());
175
        Map<String, SolrDoc> mergedDocuments = mergeDocs(documentsToIndex, existingDocuments);
176
        mergedDocuments.put(indexDocument.getIdentifier(), indexDocument);
177
        
178
        return new ArrayList<SolrDoc>(mergedDocuments.values());
179
    }
180
    
181
    private Map<String, SolrDoc> getSolrDocs(Set<String> ids) throws Exception {
182
        Map<String, SolrDoc> list = new HashMap<String, SolrDoc>();
183
        if (ids != null) {
184
            for (String id : ids) {
185
            	SolrDoc doc = getSolrDoc(id);
186
                if (doc != null) {
187
                    list.put(id, doc);
188
                }
189
            }
190
        }
191
        return list;
192
    }
193
    
194
    private Map<String, SolrDoc> mergeDocs(Map<String, SolrDoc> pending, Map<String, SolrDoc> existing) {
195
    	Map<String, SolrDoc> merged = new HashMap<String, SolrDoc>();
196
    	Iterator<String> pendingIter = pending.keySet().iterator();
197
    	while (pendingIter.hasNext()) {
198
    		String id = pendingIter.next();
199
    		SolrDoc pendingDoc = pending.get(id);
200
    		SolrDoc existingDoc = existing.get(id);
201
    		SolrDoc mergedDoc = new SolrDoc();
202
    		if (existingDoc != null) {
203
    			// merge the existing fields
204
    			for (SolrElementField field: existingDoc.getFieldList()) {
205
    				mergedDoc.addField(field);
206
    				
207
    			}
208
    		}
209
    		// add the pending
210
    		for (SolrElementField field: pendingDoc.getFieldList()) {
211
				mergedDoc.addField(field);
212
				
213
			}
214
    		
215
    		// include in results
216
			merged.put(id, mergedDoc);
217
    	}
218
    	return merged;
219
    }
220
	/*
221
	 * Get the SolrDoc for the specified id
222
	 */
223
	public static SolrDoc getSolrDoc(String id) throws SolrServerException, MalformedURLException, UnsupportedType, NotFound, ParserConfigurationException, IOException, SAXException {
224
		SolrDoc doc = new SolrDoc();
225

  
226
		if (solrServer != null) {
227
			String query = QUERY + "\"" + id + "\"";
228
			SolrParams solrParams = SolrRequestParsers.parseQueryString(query);
229
			QueryResponse qr = solrServer.query(solrParams);
230
			SolrDocument orig = qr.getResults().get(0);
231
			IndexSchema indexSchema = SolrQueryServiceController.getInstance().getSchema();
232
			for (String fieldName : orig.getFieldNames()) {
233
				// don't transfer the copyTo fields, otherwise there are errors
234
				if (indexSchema.isCopyFieldTarget(indexSchema.getField(fieldName))) {
235
					continue;
236
				}
237
				for (Object value : orig.getFieldValues(fieldName)) {
238
					String stringValue = value.toString();
239
					// special handling for dates in ISO 8601
240
					if (value instanceof Date) {
241
						stringValue = DateTimeMarshaller.serializeDateToUTC((Date) value);
242
						SolrDateConverter converter = new SolrDateConverter();
243
						stringValue = converter.convert(stringValue);
244
					}
245
					SolrElementField field = new SolrElementField(fieldName, stringValue);
246
					log.debug("Adding field: " + fieldName);
247
					doc.addField(field);
248
				}
249
			}
250

  
251
		}
252
		return doc;
253
	}
254

  
255

  
256
}
0 257

  
metacat-index/src/main/java/edu/ucsb/nceas/metacat/index/annotation/SparqlField.java
1
package edu.ucsb.nceas.metacat.index.annotation;
2

  
3
import java.util.List;
4

  
5
import javax.xml.xpath.XPath;
6

  
7
import org.dataone.cn.indexer.parser.ISolrField;
8
import org.dataone.cn.indexer.solrhttp.SolrElementField;
9
import org.w3c.dom.Document;
10

  
11
public class SparqlField implements ISolrField {
12
	
13
	private String name;
14
	
15
	private String query;
16
	
17
	public SparqlField(String name, String query) {
18
		this.name = name;
19
		this.query = query;
20
	}
21

  
22
	public String getName() {
23
		return name;
24
	}
25

  
26
	public void setName(String name) {
27
		this.name = name;
28
	}
29

  
30
	public String getQuery() {
31
		return query;
32
	}
33

  
34
	public void setQuery(String query) {
35
		this.query = query;
36
	}
37

  
38
	@Override
39
	public List<SolrElementField> getFields(Document arg0, String arg1)
40
			throws Exception {
41
		// TODO Auto-generated method stub
42
		return null;
43
	}
44

  
45
	@Override
46
	public void initExpression(XPath arg0) {
47
		// TODO Auto-generated method stub
48
		
49
	}
50

  
51
}
0 52

  
metacat-index/src/main/java/edu/ucsb/nceas/metacat/index/annotation/TripleStoreService.java
1
package edu.ucsb.nceas.metacat.index.annotation;
2

  
3
import java.io.File;
4

  
5
import com.hp.hpl.jena.query.Dataset;
6
import com.hp.hpl.jena.tdb.TDBFactory;
7

  
8
public class TripleStoreService {
9

  
10
	private static TripleStoreService instance;
11
	
12
	private TripleStoreService() {}
13
	
14
	public static TripleStoreService getInstance() {
15
		if (instance == null) {
16
			instance = new TripleStoreService();
17
		}
18
		return instance;
19
	}
20
	
21
	public Dataset getDataset() {
22
		String directory = "./tdb";
23

  
24
    	// for testing, delete the triplestore each time
25
    	File dir = new File(directory);
26
//    	if (dir.exists()) {
27
//    		dir.delete();
28
//    	}
29
		Dataset dataset = TDBFactory.createDataset(directory);
30
		return dataset;
31
	}
32
}
0 33

  
metacat-index/src/main/resources/application-context-resource-map.xml
5 5

  
6 6
	<bean id="resourceMapSubprocessor" class="edu.ucsb.nceas.metacat.index.resourcemap.ResourceMapSubprocessor">
7 7
		<property name="matchDocument"
8
			value="/d100:systemMetadata/formatId[text()='http://www.openarchives.org/ore/terms'] | /d100:systemMetadata/formatId[text()='http://www.w3.org/TR/rdf-syntax-grammar']" />
8
			value="/d100:systemMetadata/formatId[text()='http://www.openarchives.org/ore/terms']" />
9 9
		<!-- <property name="httpService" ref="httpService"></property>
10 10
		<property name="solrQueryUri" value="${solr.query.uri}"></property> -->
11 11
	</bean>
metacat-index/src/main/resources/index-processor-context.xml
26 26
    
27 27
    <import resource="application-context-resource-map.xml" />
28 28
    <import resource="application-context-systemmeta100.xml" />
29
    
30
    <import resource="application-context-annotation.xml" />
29 31

  
30 32
 <bean id="dateConverter" class="org.dataone.cn.indexer.convert.SolrDateConverter" />
31 33
 <bean id="fgdcDateConverter" class="org.dataone.cn.indexer.convert.FgdcDateConverter"/>
......
57 59
       <ref bean="fgdcEsri80Subprocessor" />
58 60
       <ref bean="dryad30Subprocessor" />
59 61
       <ref bean="dryad31Subprocessor" />
62
       <ref bean="rdfXmlSubprocessor" />
60 63
      </list>
61 64
     </property>
62 65
    </bean>
metacat-index/src/main/resources/application-context-annotation.xml
1
<beans xmlns="http://www.springframework.org/schema/beans"
2
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
	xmlns:p="http://www.springframework.org/schema/p"
4
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
5

  
6
	<bean id="rdfXmlSubprocessor" class="edu.ucsb.nceas.metacat.index.annotation.RdfXmlSubprocessor">
7
		<property name="matchDocument"
8
			value="/d100:systemMetadata/formatId[text()='http://www.w3.org/TR/rdf-syntax-grammar']" />
9
		<property name="fieldList">
10
			<list>
11
				<ref bean="annotation.measurement" />
12
			</list>
13
		</property>	
14
	</bean>
15
	
16
	<bean id="annotation.measurement" class="edu.ucsb.nceas.metacat.index.annotation.SparqlField">
17
		<constructor-arg name="name" value="measurement_sm" />
18
		<constructor-arg name="query">
19
			<value>
20
				<![CDATA[
21
				PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> 
22
				PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> 
23
				PREFIX owl: <http://www.w3.org/2002/07/owl#> 
24
				PREFIX oboe-core: <http://ecoinformatics.org/oboe/oboe.1.0/oboe-core.owl#> 
25
				PREFIX ao: <http://purl.org/ao/>
26
				PREFIX dcterms: <http://purl.org/dc/terms/>
27
				PREFIX pav: <http://purl.org/pav/>
28
				PREFIX foaf: <http://xmlns.com/foaf/0.1/>
29

  
30
				SELECT ?measurement_sm ?pid
31
				FROM <$GRAPH_NAME>
32
				WHERE { 
33

  
34
						?measurement_sm rdf:type oboe-core:Measurement .
35
						?measurement_sm rdf:type ?restriction .
36
						?restriction owl:onProperty oboe-core:ofCharacteristic .
37
						?restriction owl:allValuesFrom ?characteristic .
38
						?characteristic rdfs:subClassOf+ ?allChar .
39
						?allChar rdfs:subClassOf oboe-core:Characteristic .
40
						
41
						OPTIONAL { 
42
							?measurement_sm rdf:type ?restriction2 .
43
							?restriction2 owl:onProperty oboe-core:usesStandard .
44
							?restriction2 owl:allValuesFrom ?standard .
45
						}
46
						
47
						?annotation ao:context ?measurement_sm .
48
						?annotation ao:annotatesResource ?metadata .
49
						?metadata dcterms:identifier ?pid . 
50
						
51
						?annotation pav:createdBy ?person .
52
						?person foaf:name ?name .
53
						?annotation pav:createdOn ?date .
54
				 	} 
55
				 ]]>
56
			</value>
57
		</constructor-arg>
58
		<!--property name="multivalue" value="false" /-->
59
	</bean>
60

  
61
</beans>
0 62

  
metacat-index/pom.xml
34 34
			<artifactId>metacat-common</artifactId>
35 35
			<version>2.4.0-SNAPSHOT</version>
36 36
			<type>jar</type>
37
			<exclusions>
38
				<exclusion>
39
					<groupId>org.apache.httpcomponents</groupId>
40
					<artifactId>httpclient</artifactId>
41
				</exclusion>
42
				<exclusion>
43
					<groupId>org.apache.httpcomponents</groupId>
44
					<artifactId>httpmime</artifactId>
45
				</exclusion>
46
			</exclusions>
37 47
		</dependency>
38 48
		<dependency>
39 49
			<groupId>javax.servlet</groupId>
......
52 62
                    <groupId>org.slf4j</groupId>
53 63
                    <artifactId>slf4j-log4j12</artifactId>
54 64
                </exclusion>
65
				<exclusion>
66
					<groupId>com.hp.hpl.jena</groupId>
67
					<artifactId>jena</artifactId>
68
				</exclusion>
69
				<exclusion>
70
					<groupId>org.apache.httpcomponents</groupId>
71
					<artifactId>httpclient</artifactId>
72
				</exclusion>
73
				
74
				<exclusion>
75
					<groupId>xml-apis</groupId>
76
					<artifactId>xml-apis</artifactId>
77
				</exclusion>
55 78
        </exclusions>
56 79
        </dependency>
57 80
        <dependency>
......
59 82
            <artifactId>commons-io</artifactId>
60 83
            <version>2.4</version>
61 84
        </dependency>
85
        
86
        <dependency>
87
			<groupId>org.apache.jena</groupId>
88
			<artifactId>jena-tdb</artifactId>
89
			<version>1.0.0</version>
90
		</dependency>
62 91
		
63 92
	</dependencies>
64 93

  
metacat-common/pom.xml
111 111
            </resource>
112 112
        </resources>
113 113
	   <plugins> 
114
	   	<!--  
114 115
	       <plugin>
115 116
            <groupId>org.apache.maven.plugins</groupId> 
116 117
            <artifactId>maven-scm-plugin</artifactId> 
......
128 129
                </execution>
129 130
            </executions>
130 131
          </plugin> 
132
          -->
133
          <!--  
131 134
          <plugin>
132 135
                <groupId>org.apache.maven.plugins</groupId>
133 136
                <artifactId>maven-antrun-plugin</artifactId>
......
147 150
                    </execution>
148 151
                </executions>
149 152
        </plugin>
153
        -->
150 154
        <plugin>
151 155
            <artifactId>maven-resources-plugin</artifactId>
152 156
            <version>2.6</version>

Also available in: Unified diff