001 package gate.util.ant.packager;
002
003 import gate.util.Files;
004 import gate.util.GateRuntimeException;
005 import gate.util.LuckyException;
006 import gate.util.persistence.PersistenceManager;
007
008 import java.io.BufferedOutputStream;
009 import java.io.File;
010 import java.io.FileOutputStream;
011 import java.io.IOException;
012 import java.net.MalformedURLException;
013 import java.net.URL;
014 import java.util.ArrayList;
015 import java.util.HashMap;
016 import java.util.List;
017 import java.util.Map;
018 import java.util.Set;
019
020 import org.jdom.Document;
021 import org.jdom.Element;
022 import org.jdom.JDOMException;
023 import org.jdom.input.SAXBuilder;
024 import org.jdom.output.Format;
025 import org.jdom.output.XMLOutputter;
026 import org.jdom.xpath.XPath;
027
028 public class GappModel {
029 private Document gappDocument;
030
031 /**
032 * The URL at which this GAPP file is saved.
033 */
034 private URL gappFileURL;
035
036 /**
037 * The URL against which to resolve $gatehome$ relative paths.
038 */
039 private URL gateHomeURL;
040
041 /**
042 * Map whose keys are the resolved URLs of plugins referred to by relative
043 * paths in the GAPP file and whose values are the JDOM Elements of the
044 * <urlString> elements concerned.
045 */
046 private Map<URL, List<Element>> pluginRelpathsMap =
047 new HashMap<URL, List<Element>>();
048
049 /**
050 * Map whose keys are the resolved URLs of resource files other than plugin
051 * directories referred to by relative paths in the GAPP file and whose
052 * values are the JDOM Elements of the <urlString> elements concerned.
053 */
054 private Map<URL, List<Element>> resourceRelpathsMap =
055 new HashMap<URL, List<Element>>();
056
057 /**
058 * XPath selecting all urlStrings that contain $relpath$ or $gatehome$ in the
059 * <application> section of the file.
060 */
061 private static XPath relativeResourcePathElementsXPath;
062
063 /**
064 * XPath selecting all urlStrings that contain $relpath$ or $gatehome$ in the
065 * <urlList> section of the file.
066 */
067 private static XPath relativePluginPathElementsXPath;
068
069 /**
070 * @see #GappModel(URL,URL)
071 */
072 public GappModel(URL gappFileURL) {
073 this(gappFileURL, null);
074 }
075
076 /**
077 * Create a GappModel for a GAPP file.
078 *
079 * @param gappFileURL the URL of the GAPP file to model.
080 * @param gateHomeURL the URL against which $gatehome$ relative paths should
081 * be resolved. This may be null if you are sure that the GAPP you are
082 * packaging does not contain any $gatehome$ paths. If no gateHomeURL is
083 * provided but the application does contain a $gatehome$ path, a
084 * GateRuntimeException will be thrown.
085 */
086 @SuppressWarnings("unchecked")
087 public GappModel(URL gappFileURL, URL gateHomeURL) {
088 if(!"file".equals(gappFileURL.getProtocol())) {
089 throw new GateRuntimeException("GAPP URL must be a file: URL");
090 }
091 if(gateHomeURL != null && !"file".equals(gateHomeURL.getProtocol())) {
092 throw new GateRuntimeException("GATE home URL must be a file: URL");
093 }
094 this.gappFileURL = gappFileURL;
095 this.gateHomeURL = gateHomeURL;
096
097 try {
098 SAXBuilder builder = new SAXBuilder();
099 this.gappDocument = builder.build(gappFileURL);
100 }
101 catch(Exception ex) {
102 throw new GateRuntimeException("Error parsing GAPP file", ex);
103 }
104
105 // compile the XPath expression
106 if(relativeResourcePathElementsXPath == null) {
107 try {
108 relativeResourcePathElementsXPath =
109 XPath
110 .newInstance("/gate.util.persistence.GateApplication/application"
111 + "//gate.util.persistence.PersistenceManager-URLHolder"
112 + "/urlString[starts-with(., '$relpath$') "
113 + "or starts-with(., '$gatehome$')]");
114 relativePluginPathElementsXPath =
115 XPath
116 .newInstance("/gate.util.persistence.GateApplication/urlList"
117 + "//gate.util.persistence.PersistenceManager-URLHolder"
118 + "/urlString[starts-with(., '$relpath$') "
119 + "or starts-with(., '$gatehome$')]");
120 }
121 catch(JDOMException jdx) {
122 throw new GateRuntimeException("Error creating XPath expression", jdx);
123 }
124 }
125
126 List<Element> resourceRelpaths = null;
127 List<Element> pluginRelpaths = null;
128 try {
129 // the compiler thinks this is unsafe, but we know that the XPath
130 // expression will only select Elements so it's OK really
131 resourceRelpaths =
132 relativeResourcePathElementsXPath.selectNodes(gappDocument);
133
134 pluginRelpaths =
135 relativePluginPathElementsXPath.selectNodes(gappDocument);
136 }
137 catch(JDOMException e) {
138 throw new GateRuntimeException(
139 "Error extracting 'relpath' URLs from GAPP file", e);
140 }
141
142 try {
143 buildRelpathsMap(resourceRelpaths, resourceRelpathsMap);
144 buildRelpathsMap(pluginRelpaths, pluginRelpathsMap);
145 }
146 catch(MalformedURLException mue) {
147 throw new GateRuntimeException(
148 "Error parsing relative paths in GAPP file", mue);
149 }
150 }
151
152 private void buildRelpathsMap(List<Element> relpathElements,
153 Map<URL, List<Element>> relpathsMap) throws MalformedURLException {
154 for(Element el : relpathElements) {
155 String elementText = el.getText();
156 URL targetURL = null;
157 if(elementText.startsWith("$gatehome$")) {
158 // complain if gateHomeURL not set
159 if(gateHomeURL == null) {
160 throw new GateRuntimeException("Found a $gatehome$ relative path in "
161 + "GAPP file, but no GATE home URL provided to resolve against");
162 }
163 String relativePath = el.getText().substring("$gatehome$".length());
164 targetURL = new URL(gateHomeURL, relativePath);
165 }
166 else if(elementText.startsWith("$relpath$")) {
167 String relativePath = el.getText().substring("$relpath$".length());
168 targetURL = new URL(gappFileURL, relativePath);
169 }
170 List<Element> eltsForURL = relpathsMap.get(targetURL);
171 if(eltsForURL == null) {
172 eltsForURL = new ArrayList<Element>();
173 relpathsMap.put(targetURL, eltsForURL);
174 }
175 eltsForURL.add(el);
176 }
177 }
178
179 /**
180 * Get the URL at which the GAPP file resides.
181 *
182 * @return the gappFileURL
183 */
184 public URL getGappFileURL() {
185 return gappFileURL;
186 }
187
188 /**
189 * Set the URL at which the GAPP file resides. When this GappModel is
190 * constructed this will be the URL from which the file is loaded, but
191 * this should be changed if you wish to write the updated GAPP to
192 * another location.
193 *
194 * @param gappFileURL the gappFileURL to set
195 */
196 public void setGappFileURL(URL gappFileURL) {
197 this.gappFileURL = gappFileURL;
198 }
199
200 /**
201 * Get the JDOM Document representing this GAPP file.
202 *
203 * @return the document
204 */
205 public Document getGappDocument() {
206 return gappDocument;
207 }
208
209 /**
210 * Get the plugin URLs that are referenced by relative paths in this
211 * GAPP file.
212 *
213 * @return the set of URLs.
214 */
215 public Set<URL> getPluginURLs() {
216 return pluginRelpathsMap.keySet();
217 }
218
219 /**
220 * Get the resource URLs that are referenced by relative paths in this
221 * GAPP file.
222 *
223 * @return the set of URLs.
224 */
225 public Set<URL> getResourceURLs() {
226 return resourceRelpathsMap.keySet();
227 }
228
229 /**
230 * Update the modelled content of the GAPP file to replace any
231 * relative paths referring to <code>originalURL</code> with those
232 * pointing to <code>newURL</code>. If makeRelative is
233 * <code>true</code>, the new path will be relativized against the
234 * <b>current</b> {@link #gappFileURL}, so you should call
235 * {@link #setGappFileURL} with the URL at which the file will
236 * ultimately be saved before calling this method. If
237 * <code>makeRelative</code> is <code>false</code> the new URL
238 * will be used directly as an absolute URL (so to replace a relative
239 * path with the absolute URL to the same file you can call
240 * <code>updatePathForURL(u, u, false)</code>).
241 *
242 * @param originalURL The original URL whose references are to be
243 * replaced.
244 * @param newURL the replacement URL.
245 * @param makeRelative should we relativize the newURL before use?
246 */
247 public void updatePathForURL(URL originalURL, URL newURL, boolean makeRelative) {
248 List<Element> resourceEltsToUpdate = resourceRelpathsMap.get(originalURL);
249 List<Element> pluginEltsToUpdate = pluginRelpathsMap.get(originalURL);
250 if(resourceEltsToUpdate == null && pluginEltsToUpdate == null) {
251 return;
252 }
253
254 String newPath;
255 if(makeRelative) {
256 newPath =
257 "$relpath$"
258 + PersistenceManager.getRelativePath(gappFileURL, newURL);
259 }
260 else {
261 newPath = newURL.toExternalForm();
262 }
263
264 if(resourceEltsToUpdate != null) {
265 for(Element e : resourceEltsToUpdate) {
266 e.setText(newPath);
267 }
268 }
269 if(pluginEltsToUpdate != null) {
270 for(Element e : pluginEltsToUpdate) {
271 e.setText(newPath);
272 }
273 }
274 }
275
276 /**
277 * Finish up processing of the gapp file ready for writing.
278 */
279 @SuppressWarnings("unchecked")
280 public void finish() {
281 // remove duplicate plugin entries
282 try {
283 // this XPath selects all URLHolders out of the URL list that have
284 // the same URL string as one of their following siblings, i.e. if
285 // there are N URLs in the list with the same value then this
286 // XPath
287 // will select all but the last one of them.
288 XPath duplicatePluginXPath =
289 XPath
290 .newInstance("/gate.util.persistence.GateApplication/urlList"
291 + "/localList/gate.util.persistence.PersistenceManager-URLHolder"
292 + "[urlString = following-sibling::gate.util.persistence.PersistenceManager-URLHolder/urlString]");
293 List<Element> duplicatePlugins =
294 duplicatePluginXPath.selectNodes(gappDocument);
295 for(Element e : duplicatePlugins) {
296 e.getParentElement().removeContent(e);
297 }
298 }
299 catch(JDOMException e) {
300 throw new LuckyException(
301 "Error applying XPath expression to remove duplicate plugins", e);
302 }
303 }
304
305 /**
306 * Write out the (possibly modified) GAPP file to its new location.
307 *
308 * @throws IOException if an I/O error occurs.
309 */
310 public void write() throws IOException {
311 finish();
312 File newGappFile = Files.fileFromURL(gappFileURL);
313 FileOutputStream fos = new FileOutputStream(newGappFile);
314 BufferedOutputStream out = new BufferedOutputStream(fos);
315
316 XMLOutputter outputter = new XMLOutputter(Format.getRawFormat());
317 outputter.output(gappDocument, out);
318 }
319 }
|