001/** 002 * Copyright 2005-2015 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.xml.ingest; 017 018import java.util.Collection; 019import java.util.Collections; 020import java.util.List; 021 022import org.apache.commons.collections.CollectionUtils; 023import org.apache.commons.lang3.StringUtils; 024import org.kuali.common.util.FormatUtils; 025import org.kuali.common.util.LocationUtils; 026import org.kuali.common.util.execute.Executable; 027import org.kuali.common.util.log.LoggerUtils; 028import org.kuali.rice.core.api.CoreApiServiceLocator; 029import org.kuali.rice.core.api.impex.xml.LocationXmlDocCollection; 030import org.kuali.rice.core.api.impex.xml.XmlDocCollection; 031import org.kuali.rice.core.api.impex.xml.XmlIngesterService; 032import org.slf4j.Logger; 033 034import com.google.common.base.Optional; 035import com.google.common.base.Preconditions; 036import com.google.common.collect.Lists; 037 038/** 039 * Locates workflow XML documents available on the classpath and ingests them. 040 * 041 * <p> 042 * No file system access is required. The XML documents are ingested using Spring's {@code classpath:} notation to 043 * locate them, open an {@link java.io.InputStream}, and feed them to the ingester service. Any workflow document 044 * failing to be ingested correctly results in an exception being thrown. 045 * </p> 046 * 047 * <p> 048 * If an explicit {@link XmlIngesterService} instance is not provided, 049 * {@code CoreApiServiceLocator.getXmlIngesterService()} must be able to correctly locate {@link XmlIngesterService}. 050 * </p> 051 * 052 * @author Kuali Rice Team (rice.collab@kuali.org) 053 */ 054public final class IngestXmlExecutable implements Executable { 055 056 private static final Logger logger = LoggerUtils.make(); 057 058 private static final String XML_SUFFIX = ".xml"; 059 060 private final List<String> xmlDocumentLocations; 061 private final boolean skip; 062 063 private final Optional<XmlIngesterService> xmlIngesterService; 064 065 /** 066 * {@inheritDoc} 067 */ 068 @Override 069 public void execute() { 070 if (skip) { 071 logger.info("Skipping XML ingestion"); 072 return; 073 } 074 075 long start = System.currentTimeMillis(); 076 logger.info("Starting XML Ingester."); 077 078 for (String xmlDocumentLocation : xmlDocumentLocations) { 079 logger.info("Ingesting XML documents listed in [{}]", xmlDocumentLocation); 080 } 081 082 List<XmlDocCollection> xmlDocumentCollections = getXmlDocCollectionList(xmlDocumentLocations); 083 logger.info("Found {} files to ingest.", Integer.valueOf(xmlDocumentCollections.size())); 084 085 Collection<XmlDocCollection> failedXmlDocumentCollections = ingest(xmlDocumentCollections); 086 validateNoFailures(failedXmlDocumentCollections); 087 logger.info("There were zero failures ingesting {} XML documents", Integer.valueOf(xmlDocumentCollections.size())); 088 089 long end = System.currentTimeMillis() - start; 090 logger.info("Finished ingesting bootstrap XML - {}", FormatUtils.getTime(end)); 091 } 092 093 /** 094 * Gets the list of XML documents to ingest. 095 * 096 * @param locationListings the locations to search for XML documents to ingest 097 * 098 * @return the list of XML documents to ingest 099 */ 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}