001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU General
007: * Public License Version 2 only ("GPL") or the Common Development and Distribution
008: * License("CDDL") (collectively, the "License"). You may not use this file except in
009: * compliance with the License. You can obtain a copy of the License at
010: * http://www.netbeans.org/cddl-gplv2.html or nbbuild/licenses/CDDL-GPL-2-CP. See the
011: * License for the specific language governing permissions and limitations under the
012: * License. When distributing the software, include this License Header Notice in
013: * each file and include the License file at nbbuild/licenses/CDDL-GPL-2-CP. Sun
014: * designates this particular file as subject to the "Classpath" exception as
015: * provided by Sun in the GPL Version 2 section of the License file that
016: * accompanied this code. If applicable, add the following below the License Header,
017: * with the fields enclosed by brackets [] replaced by your own identifying
018: * information: "Portions Copyrighted [year] [name of copyright owner]"
019: *
020: * Contributor(s):
021: *
022: * The Original Software is NetBeans. The Initial Developer of the Original Software
023: * is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun Microsystems, Inc. All
024: * Rights Reserved.
025: *
026: * If you wish your version of this file to be governed by only the CDDL or only the
027: * GPL Version 2, indicate your decision by adding "[Contributor] elects to include
028: * this software in this distribution under the [CDDL or GPL Version 2] license." If
029: * you do not indicate a single choice of license, a recipient has the option to
030: * distribute your version of this file under either the CDDL, the GPL Version 2 or
031: * to extend the choice of license to its licensees as provided above. However, if
032: * you add GPL Version 2 code and therefore, elected the GPL Version 2 license, then
033: * the option applies only if the new code is made subject to such option by the
034: * copyright holder.
035: */
036:
037: package org.netbeans.installer.infra.server.admin.servlets;
038:
039: import java.io.ByteArrayOutputStream;
040: import java.io.File;
041: import java.io.FileOutputStream;
042: import java.io.IOException;
043: import java.io.OutputStream;
044: import java.net.MalformedURLException;
045: import java.net.URL;
046: import java.net.URLEncoder;
047: import java.util.HashMap;
048: import java.util.Map;
049: import java.util.regex.Matcher;
050: import java.util.regex.Pattern;
051: import javax.ejb.EJB;
052: import javax.servlet.ServletInputStream;
053: import javax.servlet.http.HttpServlet;
054: import javax.servlet.http.HttpServletRequest;
055: import javax.servlet.http.HttpServletResponse;
056: import org.netbeans.installer.infra.server.ejb.Manager;
057: import org.netbeans.installer.infra.server.ejb.ManagerException;
058: import org.netbeans.installer.utils.FileUtils;
059:
060: /**
061: *
062: * @author Kirill Sorokin
063: * @version
064: */
065: public class RunCommand extends HttpServlet {
066: /////////////////////////////////////////////////////////////////////////////////
067: // Constants
068: private static final String UTF = "UTF-8";
069:
070: /////////////////////////////////////////////////////////////////////////////////
071: // Instance
072: @EJB
073: private Manager manager;
074:
075: protected void doPost(HttpServletRequest request,
076: HttpServletResponse response) throws IOException {
077: String command = (String) request.getAttribute("command");
078:
079: String registry = null;
080: String uid = null;
081: String version = null;
082: String platforms = null;
083: File archive = null;
084:
085: String codebase = null;
086:
087: String fallback = null;
088:
089: try {
090: if (isMultiPartFormData(request)) {
091: Map<String, Object> parameters = getParameters(request);
092:
093: registry = (String) parameters.get("registry");
094:
095: uid = (String) parameters.get("uid");
096: version = (String) parameters.get("version");
097: platforms = (String) parameters.get("platforms");
098:
099: archive = (File) parameters.get("archive");
100:
101: codebase = (String) parameters.get("codebase");
102:
103: fallback = (String) parameters.get("fallback");
104: } else {
105: registry = request.getParameter("registry");
106:
107: uid = request.getParameter("uid");
108: version = request.getParameter("version");
109: platforms = request.getParameter("platforms");
110:
111: codebase = request.getParameter("codebase");
112:
113: fallback = request.getParameter("fallback");
114: }
115:
116: String getFilePrefix = null;
117: if (registry != null) {
118: getFilePrefix = getHostUrl(request)
119: + "/nbi/dev/get-file?registry="
120: + URLEncoder.encode(registry, "UTF-8")
121: + "&file=";
122: }
123:
124: if (command.equals("add-registry")) {
125: manager.addRegistry(registry);
126: }
127:
128: if (command.equals("remove-registry")) {
129: manager.removeRegistry(registry);
130: }
131:
132: if (command.equals("update-engine")) {
133: manager.updateEngine(archive);
134: }
135:
136: if (command.equals("add-package")) {
137: manager.addPackage(registry, archive, uid, version,
138: platforms, getFilePrefix);
139: }
140:
141: if (command.equals("remove-product")) {
142: manager
143: .removeProduct(registry, uid, version,
144: platforms);
145: }
146:
147: if (command.equals("remove-group")) {
148: manager.removeGroup(registry, uid);
149: }
150:
151: if (command.equals("generate-bundles")) {
152: manager.generateBundles(new String[] { registry });
153: }
154:
155: if (command.equals("delete-bundles")) {
156: manager.deleteBundles();
157: }
158:
159: if (command.equals("export-registry")) {
160: manager.exportRegistries(new String[] { registry },
161: codebase);
162: }
163:
164: response.getWriter().write(
165: "The \"" + command
166: + "\" command was successfully executed.");
167:
168: if (archive != null) {
169: archive.delete();
170: }
171:
172: if (fallback != null) {
173: response.setStatus(HttpServletResponse.SC_SEE_OTHER);
174: response.setHeader("Location", fallback);
175: }
176: } catch (ManagerException e) {
177: e.printStackTrace(response.getWriter());
178:
179: response
180: .setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
181: }
182:
183: response.getWriter().close();
184: }
185:
186: /**
187: * Reads the servlet parameters passed in via multipart/form-data http request.
188: * The resulting map would contain values of two types: String and File,
189: * depending on the type of parameter.
190: *
191: * @param request
192: * The request for which to read the parameters.
193: * @throws java.io.IOException
194: * If an I/O error happens.
195: * @return
196: * The parameters map.
197: */
198: private Map<String, Object> getParameters(HttpServletRequest request)
199: throws IOException {
200: final Map<String, Object> parameters = new HashMap<String, Object>();
201: final ServletInputStream input = request.getInputStream();
202: final String boundary = getBoundary(request);
203: final String endBoundary = boundary + "--";
204:
205: byte[] buffer = new byte[boundary.length() + 102400];
206: byte[] remainder = null;
207: OutputStream output = null;
208: String name = null;
209: String filename = null;
210:
211: // read the servlet input stream line by line and react accordingly
212: while (true) {
213: int length = read(input, buffer);
214: String line = new String(buffer, 0, length, UTF);
215:
216: // if we've reached a boundary this means that the current parameter's
217: // data stream finished, we should start parsing the next one
218: if (line.startsWith(boundary)) {
219: // first we finish with the previous parameter, if there was one. if
220: // it was a string, we need to put it to the map, if it was a file,
221: // we just need to close the stream
222: if (output != null) {
223: output.close();
224:
225: if (parameters.get(name) == null) {
226: parameters.put(name,
227: ((ByteArrayOutputStream) output)
228: .toString(UTF));
229: }
230: }
231:
232: // if this is the end - break the loop and return the parameters
233: if (line.startsWith(endBoundary)) {
234: break;
235: }
236:
237: // parse the parameter descriptor - we need to find out whether it is
238: // a string or a file and the name of this parameter. descriptor may
239: // be longer than our buffer is, thus we need to make sure a
240: // complete line is read before proceeding any further
241: String descriptor = new String(buffer, 0, read(input,
242: buffer), UTF);
243: while (descriptor.trim().equals(descriptor)) {
244: descriptor += new String(buffer, 0, read(input,
245: buffer), UTF);
246: }
247:
248: name = getName(descriptor);
249: filename = getFileName(descriptor);
250:
251: // initialize the target output stream for parameter's data and read
252: // the remaining lines before parameter data (one for strings, two
253: // for files)
254: read(input, buffer);
255: if (filename == null) {
256: output = new ByteArrayOutputStream();
257: } else {
258: read(input, buffer);
259:
260: Manager.UPLOADS.mkdirs();
261: File file = FileUtils
262: .createTempFile(Manager.UPLOADS);
263:
264: parameters.put(name, file);
265: output = new FileOutputStream(file);
266: }
267:
268: remainder = null;
269:
270: // once we've finished with the boundary - we should proceed
271: // directly to the next line, since it won't make any sense to store
272: // this data anywhere
273: continue;
274: }
275:
276: // we need to watch very carefully for EOLs, since the last one does not
277: // really belong to the parameter's data, hence the mind-bending logic
278: if (remainder != null) {
279: output.write(remainder);
280: }
281:
282: if (buffer[length - 1] == 10) {
283: if ((length >= 2) && (buffer[length - 2] == 13)) {
284: remainder = new byte[] { 13, 10 };
285: } else {
286: remainder = new byte[] { 10 };
287: }
288: } else if (buffer[length - 1] == 13) {
289: if ((length >= 2) && (buffer[length - 2] == 10)) {
290: remainder = new byte[] { 10, 13 };
291: } else {
292: remainder = new byte[] { 13 };
293: }
294: } else {
295: remainder = new byte[0];
296: }
297:
298: // write the parameter's data to the target output stream (would be
299: // bytearrayoutputstream for strings and fileoutputstream for files)
300: output.write(buffer, 0, length - remainder.length);
301: }
302:
303: return parameters;
304: }
305:
306: // entry desriptor parsing //////////////////////////////////////////////////////
307: /**
308: * Gets the value of "name" attribute from a multipart/form-data entry. A
309: * shorthand for getAttribute(descriptor, "name").
310: *
311: * @param descriptor
312: * The descriptor string to parse.
313: * @return
314: * The value of the "name" attribute of null, if this attribute is
315: * not present.
316: */
317: private String getName(String descriptor) {
318: return getAttribute(descriptor, "name");
319: }
320:
321: /**
322: * Gets the value of "filename" attribute from a multipart/form-data entry. A
323: * shorthand for getAttribute(descriptor, "filename").
324: *
325: * @param descriptor
326: * The descriptor string to parse.
327: * @return
328: * The value of the "filename" attribute of null, if this attribute is
329: * not present.
330: */
331: private String getFileName(String descriptor) {
332: return getAttribute(descriptor, "filename");
333: }
334:
335: /**
336: * Gets attrbutes' values for a multipart/form-data entry descriptor. The
337: * descriptor is expected to have attributes in the form <name>="<value>".
338: *
339: * @param descriptor
340: * The descriptor string to parse.
341: * @param name
342: * The name of the attribute.
343: * @return
344: * The value of the attribute or null if this attribute is not present.
345: */
346: private String getAttribute(String descriptor, String name) {
347: Matcher matcher = Pattern.compile(name + "=\"(.*?)\"").matcher(
348: descriptor);
349: if (matcher.find()) {
350: return matcher.group(1);
351: } else {
352: return null;
353: }
354: }
355:
356: // request properties accessors /////////////////////////////////////////////////
357: /**
358: * Checks whether the request is multipart/form-data.
359: *
360: * @param request
361: * An HttpServletReequest object for which the check should be done.
362: * @return
363: * True is the request is multipart/form-data, false otherwise.
364: */
365: private boolean isMultiPartFormData(HttpServletRequest request) {
366: return request.getContentType().startsWith(
367: "multipart/form-data");
368: }
369:
370: /**
371: * Gets the boundary value of a multipart/form-data request.
372: *
373: * @param request
374: * An HttpServletRequest object for which the boundary value should be
375: * extracted.
376: * @return
377: * The boundary value or null, if it cannot be obtained.
378: */
379: private String getBoundary(HttpServletRequest request) {
380: Matcher matcher = Pattern.compile("boundary=(.*)$").matcher(
381: request.getContentType());
382: if (matcher.find()) {
383: return "--" + matcher.group(1);
384: } else {
385: return null;
386: }
387: }
388:
389: // utility methods //////////////////////////////////////////////////////////////
390: /**
391: * Reads a line into the specified byte array. The data is read until a newline
392: * character is encountered (all of "\n", "\r", "\n\r", "\r\n" are considered a
393: * newline character) or a maximum number of bytes is read. Thus the clients
394: * should not expect the result to be always a complete line. The amximum number
395: * of bytes is dictated by the length of the byte array.
396: *
397: * This wrapper is introduced in order not to return -1, when a line cannot be
398: * read, but to throw an IOException instead, since this situation is erroneous
399: * anyway.
400: *
401: * @param input
402: * A ServletInputStream from which to read data.
403: * @param buffer
404: * A byte array where to put the data
405: * @throws java.io.IOException
406: * If an I/O error happens.
407: * @return
408: * The number of bytes read.
409: */
410: private int read(ServletInputStream input, byte[] buffer)
411: throws IOException {
412: int length = input.readLine(buffer, 0, buffer.length);
413:
414: if (length == -1) {
415: throw new IOException("Could not read a complete "
416: + "buffer without reaching an EOL or reached "
417: + "the end of stream prematurely");
418: }
419:
420: return length;
421: }
422:
423: private String getHostUrl(HttpServletRequest request)
424: throws MalformedURLException {
425: URL url = new URL(request.getRequestURL().toString());
426: String string = url.toString();
427:
428: return string.substring(0, string.indexOf(url.getFile()));
429: }
430: }
|