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
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.web.core.jsploader;
043:
044: import java.beans.PropertyChangeListener;
045: import java.beans.PropertyChangeEvent;
046: import java.io.IOException;
047: import java.lang.ref.WeakReference;
048: import java.nio.ByteBuffer;
049: import java.nio.CharBuffer;
050: import java.nio.charset.Charset;
051: import java.nio.charset.CharsetDecoder;
052: import java.nio.charset.CharsetEncoder;
053: import java.nio.charset.CoderResult;
054: import java.nio.charset.CodingErrorAction;
055: import java.nio.charset.IllegalCharsetNameException;
056: import java.nio.charset.UnsupportedCharsetException;
057: import java.util.Collections;
058: import java.util.Date;
059: import java.util.EventListener;
060: import java.util.concurrent.atomic.AtomicBoolean;
061: import java.util.logging.Level;
062: import java.util.logging.Logger;
063:
064: import org.openide.*;
065: import org.openide.cookies.EditorCookie;
066: import org.openide.cookies.SaveCookie;
067: import org.openide.filesystems.*;
068: import org.openide.loaders.*;
069: import org.openide.util.*;
070: import org.openide.nodes.Node;
071: import org.openide.nodes.CookieSet;
072:
073: import org.netbeans.modules.web.core.QueryStringCookie;
074: import org.netbeans.modules.web.core.WebExecSupport;
075: import org.openide.util.lookup.Lookups;
076: import org.openide.util.lookup.ProxyLookup;
077:
078: /** Object that provides main functionality for internet data loader.
079: *
080: * @author Petr Jiricka
081: */
082: public class JspDataObject extends MultiDataObject implements
083: QueryStringCookie {
084:
085: public static final String EA_JSP_ERRORPAGE = "jsp_errorpage"; // NOI18N
086: // property for the servlet dataobject corresponding to this page
087: public static final String PROP_SERVLET_DATAOBJECT = "servlet_do"; // NOI18N
088: public static final String PROP_CONTENT_LANGUAGE = "contentLanguage"; // NOI18N
089: public static final String PROP_SCRIPTING_LANGUAGE = "scriptingLanguage"; // NOI18N
090: public static final String PROP_SERVER_CHANGE = "PROP_SERVER_CHANGE";// NOI18N
091: public static final String PROP_REQUEST_PARAMS = "PROP_REQUEST_PARAMS"; //NOI18N
092:
093: static final String ATTR_FILE_ENCODING = "Content-Encoding"; // NOI18N
094:
095: private static final String DEFAULT_ENCODING = "ISO-8559-1"; // NOI18N
096:
097: private static final Logger LOGGER = Logger
098: .getLogger(JspDataObject.class.getName());
099:
100: transient private EditorCookie servletEdit;
101: transient protected JspServletDataObject servletDataObject;
102: // it is guaranteed that if servletDataObject != null, then this is its
103: // last modified date at the time of last refresh
104: transient private Date servletDataObjectDate;
105: transient private CompileData compileData;
106: transient private boolean firstStart;
107: transient private Listener listener;
108: transient private BaseJspEditorSupport editorSupport;
109: transient final private static boolean debug = false;
110:
111: transient volatile private Lookup currentLookup;
112: transient volatile private boolean useEditorForEncoding = false,
113: reparseEncoding = false;
114: transient final private AtomicBoolean resolvingEncoding = new AtomicBoolean(
115: false);
116:
117: public JspDataObject(FileObject pf, final UniFileLoader l)
118: throws DataObjectExistsException {
119: super (pf, l);
120: CookieSet cookies = getCookieSet();
121: initialize();
122: }
123:
124: @Override
125: public Lookup getLookup() {
126: return currentLookup;
127: }
128:
129: // Public accessibility for e.g. JakartaServerPlugin.
130: // [PENDING] Handle this more nicely.
131: public org.openide.nodes.CookieSet getCookieSet0() {
132: return super .getCookieSet();
133: }
134:
135: public Node.Cookie getCookie(Class type) {
136: if (type.isAssignableFrom(BaseJspEditorSupport.class)) {
137: return getJspEditorSupport();
138: }
139: return super .getCookie(type);
140: }
141:
142: protected org.openide.nodes.Node createNodeDelegate() {
143: return new JspNode(this );
144: }
145:
146: private synchronized BaseJspEditorSupport getJspEditorSupport() {
147: if (editorSupport == null) {
148: editorSupport = new BaseJspEditorSupport(this );
149: }
150: return editorSupport;
151: }
152:
153: protected EditorCookie createServletEditor() {
154: return new ServletEditor(this );
155: }
156:
157: public synchronized CompileData getPlugin() {
158: if (compileData == null) {
159: if (firstStart) {
160: firstStart = false;
161: }
162: compileData = new CompileData(this );
163: checkRefreshServlet();
164: }
165: return compileData;
166: }
167:
168: /** Invalidates the current copy of server plugin for this JSP.
169: * @param reload true if the new version of the plugin should be loaded.
170: */
171: public synchronized void refreshPlugin(boolean reload) {
172: //System.out.println("REFRESHING PLUGIN " + reload);
173: compileData = null;
174: if (reload)
175: getPlugin();
176: }
177:
178: public void refreshPlugin() {
179: refreshPlugin(true);
180: }
181:
182: public JspServletDataObject getServletDataObject() {
183: // force registering the servlet
184: getPlugin();
185: return servletDataObject;
186: }
187:
188: /** Returns the MIME type of the content language for this page set in this file's attributes.
189: * If nothing is set, defaults to 'text/html'.
190: */
191: public String getContentLanguage() {
192: return "text/html"; // NOI18N
193: }
194:
195: /** Returns the MIME type of the scripting language for this page set in this file's attributes.
196: * If nothing is set, defaults to 'text/x-java'.
197: */
198: public String getScriptingLanguage() {
199: return "text/x-java"; // NOI18N
200: }
201:
202: // this is just a flag for obtaining encoding at first time.
203: private boolean isEncodingRetrieved = false;
204:
205: public String getFileEncoding() {
206: if (!isEncodingRetrieved) {
207: updateFileEncoding(false);
208: isEncodingRetrieved = true;
209: }
210: String retrievedEncoding = (String) getPrimaryFile()
211: .getAttribute(ATTR_FILE_ENCODING);
212: retrievedEncoding = retrievedEncoding != null ? retrievedEncoding
213: : DEFAULT_ENCODING;
214:
215: if (LOGGER.isLoggable(Level.FINER)) {
216: LOGGER.log(Level.FINER, "Retrieved encoding for "
217: + getPrimaryFile().getNameExt() + " is "
218: + retrievedEncoding);
219: }
220: return retrievedEncoding;
221: }
222:
223: void updateFileEncoding(boolean fromEditor) {
224: TagLibParseSupport tlps = (TagLibParseSupport) getCookie(TagLibParseSupport.class);
225: if (tlps != null) {
226: String encoding = tlps.getCachedOpenInfo(true, fromEditor)
227: .getEncoding();
228: try {
229: getPrimaryFile().setAttribute(ATTR_FILE_ENCODING,
230: encoding);
231: } catch (IOException e) {
232: LOGGER.log(Level.WARNING, null, e);
233: }
234: }
235:
236: }
237:
238: private static final String CORRECT_WINDOWS_31J = "windows-31j";
239: private static final String CORRECT_EUC_JP = "EUC-JP";
240: private static final String CORRECT_GB2312 = "GB2312";
241: private static final String CORRECT_BIG5 = "BIG5";
242:
243: private static String canonizeEncoding(String encodingAlias) {
244:
245: // canonic name first
246: if (Charset.isSupported(encodingAlias)) {
247: Charset cs = Charset.forName(encodingAlias);
248: encodingAlias = cs.name();
249: }
250:
251: // this is not supported on JDK 1.4.1
252: if (encodingAlias.equalsIgnoreCase("MS932")) {
253: return CORRECT_WINDOWS_31J;
254: }
255: // this is not a correct charset by http://www.iana.org/assignments/character-sets
256: if (encodingAlias.equalsIgnoreCase("euc-jp-linux")) {
257: return CORRECT_EUC_JP;
258: }
259: // chinese encodings that must be adjusted
260: if (encodingAlias.equalsIgnoreCase("EUC-CN")) {
261: return CORRECT_GB2312;
262: }
263: if (encodingAlias.equalsIgnoreCase("GBK")) {
264: return CORRECT_GB2312;
265: }
266: if (encodingAlias.equalsIgnoreCase("GB18030")) {
267: return CORRECT_GB2312;
268: }
269: if (encodingAlias.equalsIgnoreCase("EUC-TW")) {
270: return CORRECT_BIG5;
271: }
272:
273: return encodingAlias;
274: }
275:
276: private void initialize() {
277: firstStart = true;
278: listener = new Listener();
279: listener.register(getPrimaryFile());
280: refreshPlugin(false);
281: createLookup();
282: assert currentLookup != null;
283: }
284:
285: /** Updates classFileData, servletDataObject, servletEdit
286: * This does not need to be synchronized, because the calling method
287: * getPlugin() is synchronized.
288: */
289: private void checkRefreshServlet() {
290:
291: final DataObject oldServlet = servletDataObject;
292: if (debug)
293: System.out.println("refreshing servlet, old = "
294: + oldServlet); // NOI18N
295:
296: // dataobject
297: try {
298: FileObject servletFileObject = updateServletFileObject();
299: if (debug)
300: System.out
301: .println("refreshing servlet, new servletFile = "
302: + servletFileObject); // NOI18N
303: if (servletFileObject != null) {
304: // if the file has not changed, just return
305: if ((oldServlet != null)
306: && (oldServlet.getPrimaryFile() == servletFileObject)
307: && (servletFileObject.lastModified()
308: .equals(servletDataObjectDate)))
309: return; // performance
310:
311: // set the origin JSP page
312: JspServletDataObject.setSourceJspPage(
313: servletFileObject, this );
314:
315: //set the preferred DataLoader
316: DataLoaderPool.setPreferredLoader(servletFileObject,
317: DataLoader
318: .getLoader(JspServletDataLoader.class));
319:
320: // now the loader should recognize that this servlet was generated from a JSP
321: DataObject dObj = DataObject.find(servletFileObject);
322: if (debug) {
323: System.out.println("checkRefr::servletDObj=" + // NOI18N
324: ((dObj == null) ? "null" : dObj.getClass()
325: .getName()) + // NOI18N
326: "/" + dObj); // NOI18N
327: }
328: /*if (!(dObj instanceof JspServletDataObject)) {
329: // need to re-recognize
330: dObj = rerecognize(dObj);
331: }*/
332: if (dObj instanceof JspServletDataObject) {
333: servletDataObject = (JspServletDataObject) dObj;
334: servletDataObjectDate = dObj.getPrimaryFile()
335: .lastModified();
336: }
337: // set the encoding of the generated servlet
338: String encoding = compileData.getServletEncoding();
339: if (encoding != null) {
340: if (!"".equals(encoding)) {
341: try {
342: Charset.forName(encoding);
343: } catch (IllegalArgumentException ex) {
344: IOException t = new IOException(NbBundle
345: .getMessage(JspDataObject.class,
346: "FMT_UnsupportedEncoding",
347: encoding));
348: t.initCause(ex);
349: Logger.getLogger("global").log(Level.INFO,
350: null, t);
351: }
352: } else
353: encoding = null;
354: }
355: try {
356: // actually set the encoding
357: servletFileObject.setAttribute(ATTR_FILE_ENCODING,
358: encoding); //NOI18N
359: } catch (IOException ex) {
360: Logger.getLogger("global")
361: .log(Level.INFO, null, ex);
362: }
363: } else
364: servletDataObject = null;
365: } catch (IOException e) {
366: Logger.getLogger("global").log(Level.INFO, null, e);
367: servletDataObject = null;
368: }
369:
370: // editor
371: if ((oldServlet == null)/*&&(servletDataObject != null)*/) {
372: } else {
373: RequestProcessor.postRequest(new Runnable() {
374: public void run() {
375: updateServletEditor();
376: // Bugfix 31143: oldValue must be null, since if oldValue == newValue, no change will be fired
377: JspDataObject.this .firePropertyChange0(
378: PROP_SERVLET_DATAOBJECT, null,
379: getServletDataObject());
380: // the state of some CookieActions may need to be updated
381: JspDataObject.this .firePropertyChange0(PROP_COOKIE,
382: null, null);
383: }
384: });
385: }
386: }
387:
388: /** This method causes a DataObject to be re-recognized by the loader system.
389: * This is a poor practice and should not be normally used, as it uses reflection
390: * to call a protected method DataObject.dispose().
391: */
392: /* private DataObject rerecognize(DataObject dObj) {
393: // invalidate the object so it can be rerecognized
394: FileObject prim = dObj.getPrimaryFile();
395: try {
396: dObj.setValid(false);
397: return DataObject.find(prim);
398: }
399: catch (java.beans.PropertyVetoException e) {
400: ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
401: }
402: catch (DataObjectNotFoundException e) {
403: ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
404: }
405: return dObj;
406: }*/
407:
408: /** JDK 1.2 compiler hack. */
409: public void firePropertyChange0(String propertyName,
410: Object oldValue, Object newValue) {
411: super .firePropertyChange(propertyName, oldValue, newValue);
412: }
413:
414: /** Returns an editor for the servlet. Architecturally, a better solution would be to attach a cookie for
415: * editing the servlet, but we choose this approach for performance reasons - this allows lazy initialization of
416: * the editor (unlike the cookie). */
417: public EditorCookie getServletEditor() {
418: DataObject obj = getServletDataObject();
419: if ((obj == null) != (servletEdit == null))
420: updateServletEditor();
421: return servletEdit;
422: }
423:
424: private void updateServletEditor() {
425: if (servletDataObject == null) {
426: if (servletEdit != null) {
427: servletEdit.close();
428: servletEdit = null;
429: }
430: } else {
431: if (servletEdit == null) {
432: servletEdit = createServletEditor();
433: }
434: }
435: }
436:
437: /** Gets the current fileobject of the servlet corresponding to this JSP or null if may not exist.
438: * Note that the file still doesn't need to exist, even if it's not null.
439: * This does not need to be synchronized, because the calling method
440: * getPlugin() is synchronized.
441: */
442: private FileObject updateServletFileObject() throws IOException {
443: return compileData.getServletFileObject();
444: }
445:
446: /////// -------- FIELDS AND METHODS FOR MANIPULATING THE PARSED INFORMATION -------- ////////
447:
448: /** Updates the information about statically included pages for these pages.
449: * E.g. tells the included pages that they are included in this page. */
450: /* private void updateIncludedPagesInfo(JspCompilationInfo compInfo) throws IOException {
451: FileObject included[] = compInfo.getIncludedFileObjects();
452: for (int i = 0; i < included.length; i++) {
453: IncludedPagesSupport.setIncludedIn(getPrimaryFile(), included[i]);
454: }
455: }*/
456:
457: public void setQueryString(String params)
458: throws java.io.IOException {
459: WebExecSupport.setQueryString(getPrimaryEntry().getFile(),
460: params);
461: firePropertyChange(PROP_REQUEST_PARAMS, null, null);
462: }
463:
464: protected org.openide.filesystems.FileObject handleRename(String str)
465: throws java.io.IOException {
466: if ("".equals(str)) // NOI18N
467: throw new IOException(NbBundle.getMessage(
468: JspDataObject.class, "FMT_Not_Valid_FileName"));
469:
470: org.openide.filesystems.FileObject retValue;
471:
472: retValue = super .handleRename(str);
473: return retValue;
474: }
475:
476: public void addSaveCookie(SaveCookie cookie) {
477: getCookieSet().add(cookie);
478: }
479:
480: public void removeSaveCookie() {
481: Node.Cookie cookie = getCookie(SaveCookie.class);
482: if (cookie != null)
483: getCookieSet().remove(cookie);
484: }
485:
486: protected FileObject handleMove(DataFolder df) throws IOException {
487:
488: FileObject retValue;
489:
490: retValue = super .handleMove(df);
491:
492: // fix for issue #55961 - remove old TagLibParseSupport and add new one.
493: TagLibParseSupport tlps = null;
494: tlps = (TagLibParseSupport) getCookie(TagLibParseSupport.class);
495: if (tlps != null) {
496: getCookieSet().remove(tlps);
497: tlps = new TagLibParseSupport(retValue);
498: getCookieSet().add(tlps);
499: }
500: return retValue;
501: }
502:
503: private void createLookup() {
504: Lookup noEncodingLookup = super .getLookup();
505:
506: org.netbeans.spi.queries.FileEncodingQueryImplementation feq = new org.netbeans.spi.queries.FileEncodingQueryImplementation() {
507:
508: public Charset getEncoding(FileObject file) {
509: assert file != null;
510: assert file.equals(getPrimaryFile());
511:
512: String charsetName = getFileEncoding();
513: try {
514: return Charset.forName(charsetName);
515: } catch (IllegalCharsetNameException ichse) {
516: //the jsp templates contains the ${encoding} property
517: //so the ICHNE is always thrown for them, just ignore
518: Boolean template = (Boolean) file
519: .getAttribute("template");//NOI18N
520: if (template == null || !template.booleanValue()) {
521: Logger.getLogger("global").log(
522: Level.INFO,
523: "Detected illegal charset name in file "
524: + file.getNameExt() + " ("
525: + ichse.getMessage() + ")");
526: }
527: } catch (UnsupportedCharsetException uchse) {
528: Logger.getLogger("global").log(
529: Level.INFO,
530: "Detected unsupported charset name in file "
531: + file.getNameExt() + " ("
532: + uchse.getMessage() + ")");
533: }
534:
535: return null;
536: }
537: };
538:
539: currentLookup = new ProxyLookup(noEncodingLookup, Lookups
540: .singleton(feq));
541: }
542:
543: ////// -------- INNER CLASSES ---------
544:
545: private class Listener extends FileChangeAdapter implements
546: PropertyChangeListener/*, ServerRegistryImpl.ServerRegistryListener */{
547: WeakReference weakListener;
548:
549: Listener() {
550: }
551:
552: private void register(FileObject fo) {
553: EventListener el = WeakListeners.create(
554: FileChangeListener.class, this , fo);
555: fo.addFileChangeListener((FileChangeListener) el);
556: weakListener = new WeakReference(el);
557: }
558:
559: private void unregister(FileObject fo) {
560: FileChangeListener listener = (FileChangeListener) weakListener
561: .get();
562: if (listener != null) {
563: fo.removeFileChangeListener(listener);
564: }
565: }
566:
567: public void propertyChange(PropertyChangeEvent evt) {
568: // listening on properties which could affect the server plugin
569: // saving the file
570: if (PROP_MODIFIED.equals(evt.getPropertyName())) {
571: if ((Boolean.FALSE).equals(evt.getNewValue())) {
572: refreshPlugin(false);
573: }
574: }
575: // primary file changed or files changed
576: if (PROP_PRIMARY_FILE.equals(evt.getPropertyName())
577: || PROP_FILES.equals(evt.getPropertyName())) {
578: if (evt.getOldValue() instanceof FileObject)
579: unregister((FileObject) evt.getOldValue());
580: if (evt.getNewValue() instanceof FileObject)
581: register((FileObject) evt.getNewValue());
582: ;
583: refreshPlugin(true);
584: }
585: // the context object has changed
586: if (DataObject.PROP_VALID.equals(evt.getPropertyName())) {
587: if (evt.getSource() instanceof DataObject) {
588: DataObject dobj = (DataObject) evt.getSource();
589: if (dobj.getPrimaryFile().getPackageNameExt('/',
590: '.').equals("")) { // NOI18N
591: dobj.removePropertyChangeListener(this );
592: // PENDING
593: //ServerRegistryImpl.getRegistry().removeServerRegistryListener(this);
594: //JspDataObject.this.addWebContextListener();
595: }
596: }
597: }
598:
599: }
600:
601: public void fileRenamed(FileRenameEvent fe) {
602: refreshPlugin(true);
603: }
604:
605: // implementation of ServerRegistryImpl.ServerRegistryListener
606: /*
607: PENDING
608: public void added(ServerRegistryImpl.ServerEvent added) {
609: serverChange();
610: }
611:
612: public void setAppDefault(ServerRegistryImpl.InstanceEvent inst) {
613: serverChange();
614: }
615:
616: public void setWebDefault(ServerRegistryImpl.InstanceEvent inst) {
617: serverChange();
618: }
619:
620: public void removed(ServerRegistryImpl.ServerEvent removed) {
621: serverChange();
622: }
623: */
624:
625: private void serverChange() {
626: refreshPlugin(true);
627: firePropertyChange0(PROP_SERVER_CHANGE, null, null);
628: }
629: }
630: }
|