View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.xml.ingest;
17  
18  import java.util.Collection;
19  import java.util.Collections;
20  import java.util.List;
21  
22  import org.apache.commons.collections.CollectionUtils;
23  import org.apache.commons.lang3.StringUtils;
24  import org.kuali.common.util.FormatUtils;
25  import org.kuali.common.util.LocationUtils;
26  import org.kuali.common.util.execute.Executable;
27  import org.kuali.common.util.log.LoggerUtils;
28  import org.kuali.rice.core.api.CoreApiServiceLocator;
29  import org.kuali.rice.core.api.impex.xml.LocationXmlDocCollection;
30  import org.kuali.rice.core.api.impex.xml.XmlDocCollection;
31  import org.kuali.rice.core.api.impex.xml.XmlIngesterService;
32  import org.slf4j.Logger;
33  
34  import com.google.common.base.Optional;
35  import com.google.common.base.Preconditions;
36  import com.google.common.collect.Lists;
37  
38  /**
39   * Locates workflow XML documents available on the classpath and ingests them.
40   * 
41   * <p>
42   * No file system access is required. The XML documents are ingested using Spring's {@code classpath:} notation to
43   * locate them, open an {@link java.io.InputStream}, and feed them to the ingester service. Any workflow document
44   * failing to be ingested correctly results in an exception being thrown.
45   * </p>
46   * 
47   * <p>
48   * If an explicit {@link XmlIngesterService} instance is not provided,
49   * {@code CoreApiServiceLocator.getXmlIngesterService()} must be able to correctly locate {@link XmlIngesterService}.
50   * </p>
51   * 
52   * @author Kuali Rice Team (rice.collab@kuali.org)
53   */
54  public final class IngestXmlExecutable implements Executable {
55  
56  	private static final Logger logger = LoggerUtils.make();
57  
58  	private static final String XML_SUFFIX = ".xml";
59  
60  	private final List<String> xmlDocumentLocations;
61  	private final boolean skip;
62  
63  	private final Optional<XmlIngesterService> xmlIngesterService;
64  
65      /**
66       * {@inheritDoc}
67       */
68  	@Override
69  	public void execute() {
70  		if (skip) {
71  			logger.info("Skipping XML ingestion");
72  			return;
73  		}
74  
75  		long start = System.currentTimeMillis();
76  		logger.info("Starting XML Ingester.");
77  
78          for (String xmlDocumentLocation : xmlDocumentLocations) {
79  		    logger.info("Ingesting XML documents listed in [{}]", xmlDocumentLocation);
80          }
81  
82  		List<XmlDocCollection> xmlDocumentCollections = getXmlDocCollectionList(xmlDocumentLocations);
83  		logger.info("Found {} files to ingest.", Integer.valueOf(xmlDocumentCollections.size()));
84  
85  		Collection<XmlDocCollection> failedXmlDocumentCollections = ingest(xmlDocumentCollections);
86  		validateNoFailures(failedXmlDocumentCollections);
87  		logger.info("There were zero failures ingesting {} XML documents", Integer.valueOf(xmlDocumentCollections.size()));
88  
89          long end = System.currentTimeMillis() - start;
90  		logger.info("Finished ingesting bootstrap XML - {}", FormatUtils.getTime(end));
91  	}
92  
93      /**
94       * Gets the list of XML documents to ingest.
95       *
96       * @param locationListings the locations to search for XML documents to ingest
97       *
98       * @return the list of XML documents to ingest
99       */
100 	private List<XmlDocCollection> getXmlDocCollectionList(List<String> locationListings) {
101 		List<XmlDocCollection> xmlDocCollectionList = Lists.newArrayList();
102 		List<String> locations = LocationUtils.getLocations(locationListings);
103 
104 		for (String location : locations) {
105 			Preconditions.checkState(StringUtils.endsWith(location.toLowerCase(), XML_SUFFIX), "[%s] is not an XML document", location);
106 			Preconditions.checkState(LocationUtils.exists(location), "[%s] does not exist", location);
107 
108             logger.info("[{}]", location);
109 
110             xmlDocCollectionList.add(new LocationXmlDocCollection(location));
111 		}
112 
113 		return xmlDocCollectionList;
114 	}
115 
116     /**
117      * Ingests the documents in {@code collections}.
118      *
119      * @param collections the list of XML documents to ingest
120      *
121      * @return the list of XML documents that failed to ingest
122      */
123     private Collection<XmlDocCollection> ingest(List<XmlDocCollection> collections) {
124         try {
125             return getXmlIngesterService().ingest(collections);
126         } catch (Exception e) {
127             throw new IllegalStateException("Unexpected error ingesting XML documents", e);
128         }
129     }
130 
131     /**
132      * Verifies whether there are any failures in {@code failedXmlDocumentCollections} and lists them if there are.
133      *
134      * @param failedXmlDocumentCollections the list of failures from the ingestion process
135      */
136 	private void validateNoFailures(Collection<XmlDocCollection> failedXmlDocumentCollections) {
137 		if (failedXmlDocumentCollections.isEmpty()) {
138 			return;
139 		}
140 
141         List<String> failureNamesList = Lists.newArrayList();
142 		for (XmlDocCollection failedXmlDocumentCollection : failedXmlDocumentCollections) {
143             failureNamesList.add(failedXmlDocumentCollection.getFile().getName());
144 		}
145 
146         String failureNames = StringUtils.join(failureNamesList, ", ");
147 		Preconditions.checkState(false, "%s XML documents failed to ingest -> [%s]", Integer.valueOf(failedXmlDocumentCollections.size()), failureNames);
148 	}
149 
150     /**
151      * Returns the {@link XmlIngesterService}.
152      *
153      * @return the {@link XmlIngesterService}
154      */
155     public XmlIngesterService getXmlIngesterService() {
156         return xmlIngesterService.isPresent() ? xmlIngesterService.get() : CoreApiServiceLocator.getXmlIngesterService();
157     }
158 
159     private IngestXmlExecutable(Builder builder) {
160         this.xmlDocumentLocations = builder.xmlDocumentLocations;
161         this.skip = builder.skip;
162         this.xmlIngesterService = builder.xmlIngesterService;
163     }
164 
165     /**
166      * Returns the builder for this {@code IngestXmlExecutable}.
167      *
168      * @param xmlDocumentLocations the list of locations with XML documents to ingest
169      *
170      * @return the builder for this {@code IngestXmlExecutable}
171      */
172     public static Builder builder(List<String> xmlDocumentLocations) {
173         return new Builder(xmlDocumentLocations);
174     }
175 
176     /**
177      * Builds this {@link IngestXmlExecutable}.
178      */
179 	public static class Builder {
180 
181 		// Required
182 		private final List<String> xmlDocumentLocations;
183 
184 		// Optional
185 		private Optional<XmlIngesterService> xmlIngesterService = Optional.absent();
186 		private boolean skip;
187 
188         /**
189          * Builds the {@link IngestXmlExecutable} with a single {@code xmlDocumentLocation}.
190          *
191          * @param xmlDocumentLocation the location with an XML document to ingest
192          */
193 		public Builder(String xmlDocumentLocation) {
194 			this.xmlDocumentLocations = Collections.singletonList(xmlDocumentLocation);
195         }
196 
197         /**
198          * Builds the {@link IngestXmlExecutable} with multiple {@code xmlDocumentLocations}.
199          *
200          * @param xmlDocumentLocations the list of locations with XML documents to ingest
201          */
202         public Builder(List<String> xmlDocumentLocations) {
203             this.xmlDocumentLocations = xmlDocumentLocations;
204         }
205 
206         /**
207          * Sets the {@link XmlIngesterService}.
208          *
209          * @param service the {@link XmlIngesterService} to set
210          *
211          * @return this {@code Builder}
212          */
213 		public Builder service(XmlIngesterService service) {
214 			this.xmlIngesterService = Optional.of(service);
215 			return this;
216 		}
217 
218         /**
219          * Sets whether to skip this executable or not.
220          *
221          * @param skip whether to skip this executable or not
222          *
223          * @return this {@code Builder}
224          */
225 		public Builder skip(boolean skip) {
226 			this.skip = skip;
227 			return this;
228 		}
229 
230         /**
231          * Builds the {@link IngestXmlExecutable}.
232          *
233          * @return the built {@link IngestXmlExecutable}
234          */
235 		public IngestXmlExecutable build() {
236 			IngestXmlExecutable instance = new IngestXmlExecutable(this);
237 
238             validate(instance);
239 
240             return instance;
241 		}
242 
243 		private static void validate(IngestXmlExecutable instance) {
244 			Preconditions.checkNotNull(instance.xmlIngesterService, "service cannot be null");
245 			Preconditions.checkArgument(!CollectionUtils.isEmpty(instance.xmlDocumentLocations), "locationListings cannot be empty");
246 
247             for (String locationListing : instance.xmlDocumentLocations) {
248                 Preconditions.checkArgument(!StringUtils.isBlank(locationListing), "locationListings cannot have blank entries");
249 			    Preconditions.checkArgument(LocationUtils.exists(locationListing), "[%s] does not exist", locationListing);
250             }
251 		}
252 
253 	}
254 
255 }