001: /* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
002: * This code is licensed under the GPL 2.0 license, availible at the root
003: * application directory.
004: */
005: package org.vfny.geoserver.action.data;
006:
007: import org.apache.struts.action.ActionError;
008: import org.apache.struts.action.ActionErrors;
009: import org.apache.struts.action.ActionForm;
010: import org.apache.struts.action.ActionForward;
011: import org.apache.struts.action.ActionMapping;
012: import org.apache.struts.upload.FormFile;
013: import org.apache.struts.util.MessageResources;
014: import org.geotools.styling.SLDParser;
015: import org.geotools.styling.Style;
016: import org.geotools.styling.StyleFactory;
017: import org.geotools.styling.StyleFactoryFinder;
018: import org.geotools.styling.StyledLayerDescriptor;
019: import org.vfny.geoserver.action.ConfigAction;
020: import org.vfny.geoserver.action.HTMLEncoder;
021: import org.vfny.geoserver.config.DataConfig;
022: import org.vfny.geoserver.config.StyleConfig;
023: import org.vfny.geoserver.form.data.StylesEditorForm;
024: import org.vfny.geoserver.global.ConfigurationException;
025: import org.vfny.geoserver.global.GeoserverDataDirectory;
026: import org.vfny.geoserver.global.UserContainer;
027: import org.vfny.geoserver.util.SLDValidator;
028: import org.xml.sax.SAXParseException;
029: import java.io.BufferedReader;
030: import java.io.ByteArrayInputStream;
031: import java.io.File;
032: import java.io.FileReader;
033: import java.io.FileWriter;
034: import java.io.IOException;
035: import java.io.InputStreamReader;
036: import java.io.StringReader;
037: import java.util.ArrayList;
038: import java.util.List;
039: import java.util.Locale;
040: import javax.servlet.ServletContext;
041: import javax.servlet.ServletException;
042: import javax.servlet.http.HttpServletRequest;
043: import javax.servlet.http.HttpServletResponse;
044:
045: /**
046: * This class takes care of processing new sld files. It makes use of a nice
047: * upload button, checks to make sure the file isn't already in the system,
048: * does a bit of validation, and then adds it to data config.
049: *
050: * @author rgould
051: * @author Chris Holmes, Fulbright
052: *
053: * @task REVISIT: Still need to do the nice text box to edit the sld file
054: * directly. This will probably involve some trickiness - the work flow
055: * I am thinking is that an upload would just put it into the big style
056: * text box. On a submit the text box would then be validated and
057: * written out to the file location.
058: * @task TODO: write to a temp file before validation. Right now we delete the
059: * file in the style directory.
060: */
061: public class StylesEditorAction extends ConfigAction {
062: public ActionForward execute(ActionMapping mapping,
063: ActionForm form, UserContainer user,
064: HttpServletRequest request, HttpServletResponse response)
065: throws IOException, ServletException {
066: DataConfig config = (DataConfig) getDataConfig();
067: StylesEditorForm stylesForm = (StylesEditorForm) form;
068: FormFile file = stylesForm.getSldFile();
069: final String styleID = stylesForm.getStyleID();
070: final String originalStyleID = stylesForm.getOriginalStyleId();
071: StyleConfig style = user.getStyle();
072: boolean doFullValidation = stylesForm.getFullyValidate();
073: String action = stylesForm.getAction();
074: String sldContents = stylesForm.getSldContents();
075:
076: // decide what has been pressed
077: Locale locale = (Locale) request.getLocale();
078: MessageResources messages = getResources(request);
079:
080: // final String SUBMIT = HTMLEncoder.decode(messages.getMessage(locale, "label.submit"));
081: final String UPLOAD = HTMLEncoder.decode(messages.getMessage(
082: locale, "label.upload"));
083:
084: if (UPLOAD.equals(action)) {
085: stylesForm.setSldContents(readSldContents(file));
086:
087: return mapping.findForward("config.data.style.editor");
088: } else {
089: if (stylesForm.getFullyValidateChecked() == false) {
090: doFullValidation = false;
091: }
092:
093: if (doFullValidation) {
094: List l = getSchemaExceptions(sldContents, request);
095:
096: if (l.size() != 0) {
097: handleValidationErrors(l, sldContents, stylesForm);
098:
099: return mapping.findForward("schemaErrors");
100: }
101: }
102:
103: if (style == null) {
104: // Must of bookmarked? Redirect so they can select
105: return mapping.findForward("config.data.style");
106: }
107:
108: // ServletContext sc = getServlet().getServletContext();
109:
110: //DJB: changed for geoserver_data_dir
111: //File rootDir = new File(getServlet().getServletContext().getRealPath("/"));
112: File rootDir = GeoserverDataDirectory
113: .getGeoserverDataDirectory();
114:
115: File styleDir;
116:
117: try {
118: styleDir = GeoserverDataDirectory.findConfigDir(
119: rootDir, "styles");
120: } catch (ConfigurationException cfe) {
121: LOGGER.warning("no style dir found, creating new one");
122: //if for some bizarre reason we don't fine the dir, make a new one.
123: styleDir = new File(rootDir, "styles");
124: }
125:
126: // send content of FormFile to /styles :
127: // there nothing to keep the styles in memory for XMLConfigWriter.store()
128: StyleConfig styleForID = config.getStyle(originalStyleID);
129: File newSldFile = null;
130:
131: if (styleForID != null) {
132: // for backward compatibility, use the old style file
133: File oldFile = styleForID.getFilename();
134: newSldFile = oldFile;
135: } else {
136: newSldFile = new File(styleDir, styleID + ".sld");
137:
138: if (newSldFile.exists()) {
139: doFileExistsError(newSldFile, request);
140:
141: return mapping
142: .findForward("config.data.style.editor");
143: }
144: }
145:
146: //here we do a check to see if the file we are trying to upload is
147: //overwriting another style file.
148: LOGGER.fine("new sld file is: " + newSldFile + ", exists: "
149: + newSldFile.exists());
150:
151: //When we have time we should put this in a temp file, to be safe, before
152: //we do the validation, and only write to the real style directory when we
153: //have things set. If only java had a nice file copy utility.
154: FileWriter fw = new FileWriter(newSldFile);
155: fw.write(sldContents);
156: fw.flush();
157: fw.close();
158: style.setFilename(newSldFile);
159:
160: style.setId(styleID);
161:
162: StyleFactory factory = StyleFactoryFinder
163: .createStyleFactory();
164: SLDParser styleReader = new SLDParser(factory, newSldFile
165: .toURL());
166: Style[] readStyles = null;
167: Style newStyle;
168:
169: try {
170: readStyles = styleReader.readXML();
171:
172: if (readStyles.length == 0) {
173: //I think our style parser does pretty much no error reporting.
174: //This is one of the many reasons we need a new SLD parser.
175: //We could add new exceptions to it, but it's really just
176: //patching a sinking ship. One option that we could do
177: //here is do a xerces validating parse, to make sure the
178: //sld matches the schema before we try to pass it to our
179: //crappy parser.
180: String message = "The xml was valid, but couldn't get a Style"
181: + " from it. Make sure your style validates against "
182: + " the SLD schema";
183: doStyleParseError(message, newSldFile, request);
184:
185: return mapping
186: .findForward("config.data.style.editor");
187: }
188:
189: newStyle = readStyles[0];
190: LOGGER.fine("sld is " + newStyle);
191: } catch (Exception e) {
192: e.printStackTrace();
193:
194: String message = (e.getCause() == null) ? e
195: .getLocalizedMessage() : e.getCause()
196: .getLocalizedMessage();
197: doStyleParseError(message, newSldFile, request);
198:
199: return mapping.findForward("config.data.style.editor");
200: }
201:
202: if (newStyle == null) {
203: throw new RuntimeException("new style equals null"); //I don't
204:
205: //think this will ever happen, our SLD parser won't return a null.
206: }
207:
208: // Do configuration parameters here
209: config.removeStyle(originalStyleID);
210: config.addStyle(style.getId(), style);
211: getApplicationState().notifyConfigChanged();
212:
213: return mapping.findForward("config.data.style");
214: }
215: }
216:
217: private String readSldContents(FormFile file) throws IOException {
218: StringBuffer sb = new StringBuffer();
219: BufferedReader reader = null;
220:
221: try {
222: reader = new BufferedReader(new InputStreamReader(file
223: .getInputStream()));
224:
225: String line = null;
226:
227: while ((line = reader.readLine()) != null) {
228: sb.append(line).append("\n");
229: }
230: } finally {
231: reader.close();
232: }
233:
234: return sb.toString();
235: }
236:
237: /**
238: * make the validation report for the bean
239: * its a listing of the original file (prefixed by line #)
240: * and any validation errors
241: *
242: * @param l
243: * @param file
244: * @param stylesForm
245: */
246: private void handleValidationErrors(List errors,
247: String sldContents, StylesEditorForm stylesForm) {
248: ArrayList lines = new ArrayList();
249: BufferedReader reader = null;
250:
251: try {
252: reader = new BufferedReader(new StringReader(sldContents));
253:
254: String line = reader.readLine();
255: int linenumber = 1;
256: int exceptionNum = 0;
257:
258: //check for lineNumber -1 errors --> invalid XML
259: if (errors.size() > 0) {
260: SAXParseException sax = (SAXParseException) errors
261: .get(0);
262:
263: if (sax.getLineNumber() < 0) {
264: lines.add(" INVALID XML: "
265: + sax.getLocalizedMessage());
266: lines.add(" ");
267: exceptionNum = 1; // skip ahead (you only ever get one error in this case)
268: }
269: }
270:
271: while (line != null) {
272: line.replace('\n', ' ');
273: line.replace('\r', ' ');
274:
275: String header = linenumber + ": ";
276: lines.add(header + line); // record the current line
277:
278: boolean keep_going = true;
279:
280: while (keep_going) {
281: if ((exceptionNum < errors.size())) {
282: SAXParseException sax = (SAXParseException) errors
283: .get(exceptionNum);
284:
285: if (sax.getLineNumber() <= linenumber) {
286: String head = "---------------------"
287: .substring(0, header.length() - 1);
288: String body = "------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------";
289: int colNum = sax.getColumnNumber(); //protect against col 0 problems
290:
291: if (colNum < 1) {
292: colNum = 1;
293: }
294:
295: lines.add(head
296: + body.substring(0, sax
297: .getColumnNumber() - 1)
298: + "^");
299: lines.add(" "
300: + sax.getLocalizedMessage());
301: exceptionNum++;
302: } else {
303: keep_going = false; //report later (sax.getLineNumber() > linenumber)
304: }
305: } else {
306: keep_going = false; // no more errors to report
307: }
308: }
309:
310: line = reader.readLine(); //will be null at eof
311: linenumber++;
312: }
313:
314: for (int t = exceptionNum; t < errors.size(); t++) {
315: SAXParseException sax = (SAXParseException) errors
316: .get(t);
317: lines.add(" " + sax.getLocalizedMessage());
318: }
319: } catch (Exception e) {
320: e.printStackTrace();
321: } finally {
322: try {
323: if (reader != null) {
324: reader.close();
325: }
326: } catch (Exception e) {
327: e.printStackTrace();
328: }
329: }
330:
331: stylesForm.setValidationReport((String[]) lines
332: .toArray(new String[1]));
333: }
334:
335: /**
336: * Check the .sld file and check to see if it passes the validation test!
337: *
338: * @param file
339: * @return
340: */
341: private List getSchemaExceptions(String sldContents,
342: HttpServletRequest request) {
343: SLDValidator validator = new SLDValidator();
344:
345: ServletContext sc = request.getSession().getServletContext();
346:
347: try {
348: List l = validator.validateSLD(new ByteArrayInputStream(
349: sldContents.getBytes("UTF-8")), sc);
350:
351: return l;
352: } catch (Exception e) {
353: ArrayList al = new ArrayList();
354: al
355: .add(new SAXParseException(e.getLocalizedMessage(),
356: null));
357:
358: return al;
359: }
360: }
361:
362: /*
363: * Called when there is trouble parsing the file. Note that we
364: * also delete the file here, so it doesn't stick on the system.
365: * Would be a bit better to write to a temp file before putting
366: * it in the style directory, but so it goes.
367: */
368: private void doStyleParseError(String message, File newSldFile,
369: HttpServletRequest request) {
370: LOGGER.fine("parse error message is: " + message);
371:
372: ActionErrors errors = new ActionErrors();
373: errors.add(ActionErrors.GLOBAL_ERROR, new ActionError(
374: "error.style.noParse", message));
375: saveErrors(request, errors);
376: newSldFile.delete();
377: }
378:
379: /*
380: * reports an error for an attempt to upload an sld file that is already
381: * in the system.*/
382: private void doFileExistsError(File file, HttpServletRequest request) {
383: ActionErrors errors = new ActionErrors();
384: errors.add(ActionErrors.GLOBAL_ERROR, new ActionError(
385: "error.style.sldFileExists", file.getName()));
386: saveErrors(request, errors);
387: }
388: }
|