/******************************************************************************* * Copyright (c) 2004, 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jst.jsp.core.internal.modelhandler; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.util.ArrayList; import java.util.List; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.content.IContentDescription; import org.eclipse.core.runtime.content.IContentType; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentPartitioner; import org.eclipse.jst.jsp.core.internal.Logger; import org.eclipse.jst.jsp.core.internal.contentproperties.JSPFContentProperties; import org.eclipse.jst.jsp.core.internal.document.PageDirectiveAdapter; import org.eclipse.jst.jsp.core.internal.document.PageDirectiveAdapterFactory; import org.eclipse.jst.jsp.core.internal.document.PageDirectiveWatcherFactory; import org.eclipse.jst.jsp.core.internal.domdocument.DOMModelForJSP; import org.eclipse.jst.jsp.core.internal.encoding.IJSPHeadContentDetector; import org.eclipse.jst.jsp.core.internal.encoding.JSPDocumentHeadContentDetector; import org.eclipse.jst.jsp.core.internal.encoding.JSPDocumentLoader; import org.eclipse.jst.jsp.core.internal.modelquery.JSPModelQueryAdapterImpl; import org.eclipse.jst.jsp.core.internal.modelquery.ModelQueryAdapterFactoryForJSP; import org.eclipse.jst.jsp.core.internal.parser.JSPReParser; import org.eclipse.jst.jsp.core.internal.parser.JSPSourceParser; import org.eclipse.jst.jsp.core.internal.provisional.contenttype.ContentTypeIdForJSP; import org.eclipse.jst.jsp.core.internal.provisional.contenttype.IContentDescriptionForJSP; import org.eclipse.jst.jsp.core.internal.text.StructuredTextPartitionerForJSP; import org.eclipse.wst.html.core.internal.provisional.contenttype.ContentTypeFamilyForHTML; import org.eclipse.wst.html.core.internal.provisional.contenttype.ContentTypeIdForHTML; import org.eclipse.wst.html.core.internal.text.StructuredTextPartitionerForHTML; import org.eclipse.wst.sse.core.internal.PropagatingAdapter; import org.eclipse.wst.sse.core.internal.document.DocumentReader; import org.eclipse.wst.sse.core.internal.document.IDocumentLoader; import org.eclipse.wst.sse.core.internal.document.StructuredDocumentFactory; import org.eclipse.wst.sse.core.internal.ltk.modelhandler.EmbeddedTypeHandler; import org.eclipse.wst.sse.core.internal.ltk.parser.RegionParser; import org.eclipse.wst.sse.core.internal.model.AbstractModelLoader; import org.eclipse.wst.sse.core.internal.modelhandler.EmbeddedTypeRegistry; import org.eclipse.wst.sse.core.internal.modelhandler.EmbeddedTypeRegistryImpl; import org.eclipse.wst.sse.core.internal.provisional.IModelLoader; import org.eclipse.wst.sse.core.internal.provisional.INodeAdapterFactory; import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier; import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; import org.eclipse.wst.sse.core.internal.text.BasicStructuredDocument; import org.eclipse.wst.sse.core.internal.util.Assert; import org.eclipse.wst.sse.core.internal.util.Debug; import org.eclipse.wst.xml.core.internal.DebugAdapterFactory; import org.eclipse.wst.xml.core.internal.document.DOMModelImpl; import org.eclipse.wst.xml.core.internal.propagate.PropagatingAdapterFactoryImpl; import org.eclipse.wst.xml.core.internal.provisional.contenttype.ContentTypeIdForXML; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; import org.eclipse.wst.xml.core.internal.ssemodelquery.ModelQueryAdapter; import org.eclipse.wst.xml.core.internal.text.rules.StructuredTextPartitionerForXML; import org.w3c.dom.Document; public class JSPModelLoader extends AbstractModelLoader { protected final int MAX_BUFFERED_SIZE_FOR_RESET_MARK = 200000; /** * DMW - Note: I think the embeddedTypeRegistry in IModelManager can be * removed */ private EmbeddedTypeRegistry embeddedContentTypeRegistry; private final static String DEFAULT_MIME_TYPE = "text/html"; //$NON-NLS-1$ private final static String DEFAULT_LANGUAGE = "java"; //$NON-NLS-1$ public JSPModelLoader() { super(); } /** * Gets the embeddedContentTypeRegistry. * * @return Returns a EmbeddedContentTypeRegistry */ private EmbeddedTypeRegistry getEmbeddedContentTypeRegistry() { if (embeddedContentTypeRegistry == null) { embeddedContentTypeRegistry = EmbeddedTypeRegistryImpl.getInstance(); } return embeddedContentTypeRegistry; } public IStructuredModel newModel() { DOMModelForJSP model = new DOMModelForJSP(); return model; } /** * For JSP files, text/html is the default content type. This may want * this different for types like jsv (jsp for voice xml) For now, hard * code to new instance. In future, should get instance from registry. * * Specification cites HTML as the default contentType. */ private EmbeddedTypeHandler getJSPDefaultEmbeddedType(IStructuredModel model) { EmbeddedTypeRegistry reg = getEmbeddedContentTypeRegistry(); String mimeType = null; // default embedded type for fragments if (model != null) { IFile file = getFile(model); if (file != null) { mimeType = JSPFContentProperties.getProperty(JSPFContentProperties.JSPCONTENTTYPE, file, true); } } mimeType = mimeType == null ? getDefaultMimeType() : mimeType; return reg.getTypeFor(mimeType); } /** * Method getDefaultMimeType. * * @return String */ private String getDefaultMimeType() { return DEFAULT_MIME_TYPE; } /** * This method must return a new instance of IStructuredDocument, that has * been initialized with appropriate parser. For many loaders, the * (default) parser used is known for any input. For others, the correct * parser (and its initialization) is normall dependent on the content of * the file. This no-argument method should assume "empty input" and would * therefore return the default parser for the default contentType. * * If the parser is to handle tag libraries, it must have a TaglibSupport * object with a valid URIResolver and this IStructuredDocument attached * to it before the contents are set on the IStructuredDocument. */ public IStructuredDocument newStructuredDocument() { IStructuredDocument structuredDocument = StructuredDocumentFactory.getNewStructuredDocumentInstance(getParser()); ((BasicStructuredDocument) structuredDocument).setReParser(new JSPReParser()); // structuredDocument.setDocumentPartitioner(new // JSPJavaDocumentPartioner()); // even though this is an "empty model" ... we want it to have at // least the // default embeddeded content type handler EmbeddedTypeHandler embeddedType = getJSPDefaultEmbeddedType(null); embeddedType.initializeParser(structuredDocument.getParser()); return structuredDocument; } public RegionParser getParser() { // remember, the Loader // will need to finish initialization of parser // based on "embedded content" return new JSPSourceParser(); } protected void preLoadAdapt(IStructuredModel structuredModel) { super.preLoadAdapt(structuredModel); IDOMModel domModel = (IDOMModel) structuredModel; // // document must have already been set for this to // work. Document document = domModel.getDocument(); Assert.isNotNull(document); // if there is a model in the adapter, this will adapt it to // first node. After that the PropagatingAdater spreads over the // children being // created. Each time that happends, a side effect is to // also "spread" sprecific registered adapters, // they two can propigate is needed. // This 'get' causes first to be be attached. PropagatingAdapter propagatingAdapter = (PropagatingAdapter) ((INodeNotifier) document).getAdapterFor(PropagatingAdapter.class); // may make this easier to use in futue propagatingAdapter.addAdaptOnCreateFactory(new PageDirectiveWatcherFactory()); if (Debug.debugNotificationAndEvents) { propagatingAdapter.addAdaptOnCreateFactory(new DebugAdapterFactory()); } // For JSPs, the ModelQueryAdapter must be "attached" to the document // before content is set in the model, so taglib initization can // take place. ((INodeNotifier) document).getAdapterFor(ModelQueryAdapter.class); // } /** * This method must return those factories which must be attached to the * structuredModel before content is applied. */ public List getAdapterFactories() { List result = new ArrayList(); INodeAdapterFactory factory = null; // factory = new ModelQueryAdapterFactoryForJSP(); result.add(factory); factory = new PropagatingAdapterFactoryImpl(); result.add(factory); factory = new PageDirectiveAdapterFactory(); result.add(factory); return result; } public IJSPHeadContentDetector getHeadParser() { return new JSPDocumentHeadContentDetector(); } private IContentDescription getContentDescription(IDocument doc) { if (doc == null) return null; DocumentReader reader = new DocumentReader(doc); return getContentDescription(reader); } /** * Returns content description for an input stream Assumes it's JSP * content. Closes the input stream when finished. * * @param reader * @return the IContentDescription for in, or null if in is null */ private IContentDescription getContentDescription(Reader reader) { if (reader == null) return null; IContentDescription desc = null; try { IContentType contentTypeJSP = Platform.getContentTypeManager().getContentType(ContentTypeIdForJSP.ContentTypeID_JSP); desc = contentTypeJSP.getDescriptionFor(reader, IContentDescription.ALL); } catch (IOException e) { Logger.logException(e); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { Logger.logException(e); } } } return desc; } private IFile getFile(IStructuredModel model) { if (model != null) { String location = model.getBaseLocation(); if (location != null) { IPath path = new Path(location); if (!path.toFile().exists() && path.segmentCount() > 1) { return ResourcesPlugin.getWorkspace().getRoot().getFile(path); } } } return null; } /** * Method getLanguage. * * @param model * @return String */ private String getLanguage(IStructuredModel model) { String result = null; // first check the model (document itself) to see if contains result = getLanguageFromStructuredDocument(model.getStructuredDocument()); // Note: if model contains an unsupported // language, we'll even return it, // since who knows what future holds. // get default language specified in properties page IFile file = getFile(model); result = JSPFContentProperties.getProperty(JSPFContentProperties.JSPLANGUAGE, file, true); // always return something if (result == null) { result = DEFAULT_LANGUAGE; } return result; } /** * Method getLanguageFromStructuredDocument. * * @param structuredDocument * @return String */ private String getLanguageFromStructuredDocument(IStructuredDocument structuredDocument) { if (structuredDocument == null) return null; String result = null; // bascially same algorithm as get encoding or // get content type from structuredDocument. IJSPHeadContentDetector localHeadParser = getHeadParser(); // we can be assured that its already been // parsed. If not call parseHeaderForPageDirective() // before calling getLanguage; localHeadParser.set(structuredDocument); try { result = localHeadParser.getLanguage(); } catch (IOException e) { // impossible // TODO need to reconsider design to avoid throw new Error(e); } return result; } /** * This is "reinitialize" since there should always be at least the * default one assigned, before we start checking the stream */ private void reInitializeEmbeddedType(IStructuredModel model, EmbeddedTypeHandler oldEmbeddedContentType, EmbeddedTypeHandler newEmbeddedContentType) { // check program logic Assert.isNotNull(oldEmbeddedContentType, "Program error: invalid call during model initialization"); //$NON-NLS-1$ // once we know the embedded content type, we need to set it in the // PageDirectiveAdapter ... the order of initialization is // critical here, the doc must have been created, but its contents not // set yet, // and all factories must have been set up also. IDOMModel domModel = (IDOMModel) model; IStructuredDocument structuredDocument = model.getStructuredDocument(); IDOMDocument document = domModel.getDocument(); PageDirectiveAdapter pageDirectiveAdapter = (PageDirectiveAdapter) document.getExistingAdapter(PageDirectiveAdapter.class); // ==> // PropagatingAdapter propagatingAdapter = (PropagatingAdapter) // ((INodeNotifier) // document).getExistingAdapter(PropagatingAdapter.class); // ==> // ModelQueryAdapter modelQueryAdapter = (ModelQueryAdapter) // ((INodeNotifier) // document).getExistingAdapter(ModelQueryAdapter.class); oldEmbeddedContentType.uninitializeFactoryRegistry(model.getFactoryRegistry()); oldEmbeddedContentType.uninitializeParser(structuredDocument.getParser()); // since 'document' is not recreated in this // reinit path, we need to remove all adapters, // except for the propagated adapters (including page // directive adapter, and model query adapter). // to accomplish this, we'll just remove all, then // add back with a call to pre-load adapt. // let clients decide to unload adapters from document // Collection oldAdapters = document.getAdapters(); // Iterator oldAdaptersIterator = oldAdapters.iterator(); // while (oldAdaptersIterator.hasNext()) { // INodeAdapter oldAdapter = (INodeAdapter) // oldAdaptersIterator.next(); // if (oldAdapter != pageDirectiveAdapter && oldAdapter != // propagatingAdapter && oldAdapter != modelQueryAdapter) { // // DO NOT remove directly! // // can change contents while in notifity loop! // //oldAdaptersIterator.remove(); // document.removeAdapter(oldAdapter); // } // } // DMW: I believe something like the following is needed, // since releases cached adapters // if (document instanceof DocumentImpl) { // ((DocumentImpl) document).releaseDocumentType(); // ((DocumentImpl) document).releaseStyleSheets(); // } // remember, embedded type factories are automatically cleared when // embededType changed pageDirectiveAdapter.setEmbeddedType(newEmbeddedContentType); // // but still need to clear the page directive watchers, and let // them be rediscovered (with new, accurate node as target) // pageDirectiveAdapter.clearPageWatchers(); if (newEmbeddedContentType != null) { // need to null out or else ModelParserAdapter // won't get reinitialized ((DOMModelImpl) model).setModelParser(null); newEmbeddedContentType.initializeFactoryRegistry(model.getFactoryRegistry()); newEmbeddedContentType.initializeParser(structuredDocument.getParser()); // partitioner setup is the responsibility of this loader IDocumentPartitioner documentPartitioner = structuredDocument.getDocumentPartitioner(); // ISSUE: this logic is flawed, not sure of original intent, but // added null/type checks for safety. if (documentPartitioner != null && documentPartitioner instanceof StructuredTextPartitionerForJSP) { if (newEmbeddedContentType.getFamilyId().equals(ContentTypeIdForXML.ContentTypeID_XML)) { ((StructuredTextPartitionerForJSP) documentPartitioner).setEmbeddedPartitioner(new StructuredTextPartitionerForXML()); } else if (newEmbeddedContentType.getFamilyId().equals(ContentTypeIdForHTML.ContentTypeID_HTML)) { ((StructuredTextPartitionerForJSP) documentPartitioner).setEmbeddedPartitioner(new StructuredTextPartitionerForHTML()); } } } // adding language here, in this convienent central // location, but some obvious renaming or refactoring // wouldn't hurt, in future. // I needed to add this language setting for JSP Fragment support // Note: this is the one that counts, since at this point, // the model has an ID, so we can look up IFile, etc. String language = getLanguage(model); if (language != null && language.length() > 0) { pageDirectiveAdapter.setLanguage(language); } } /** * This is "reinitialize" since there should always be at least the * default one assigned, before we start checking the stream */ private void initCloneOfEmbeddedType(IStructuredModel model, EmbeddedTypeHandler oldEmbeddedContentType, EmbeddedTypeHandler newEmbeddedContentType) { // check program logic Assert.isNotNull(oldEmbeddedContentType, "Program error: invalid call during model initialization"); //$NON-NLS-1$ // once we know the embedded content type, we need to set it in the // PageDirectiveAdapter ... the order of initialization is // critical here, the doc must have been created, but its contents not // set yet, // and all factories must have been set up also. IDOMModel domModel = (IDOMModel) model; IStructuredDocument structuredDocument = model.getStructuredDocument(); IDOMDocument document = domModel.getDocument(); PageDirectiveAdapter pageDirectiveAdapter = (PageDirectiveAdapter) document.getAdapterFor(PageDirectiveAdapter.class); // ==> // PropagatingAdapter propagatingAdapter = (PropagatingAdapter) // ((INodeNotifier) document).getAdapterFor(PropagatingAdapter.class); // ==> // ModelQueryAdapter modelQueryAdapter = (ModelQueryAdapter) // ((INodeNotifier) document).getAdapterFor(ModelQueryAdapter.class); // because, even in the clone case, the model has been paritally // intialized with // the old embedded type (during createModel), we need to unitialize // parts of it, based on the old (or default) ones oldEmbeddedContentType.uninitializeFactoryRegistry(model.getFactoryRegistry()); oldEmbeddedContentType.uninitializeParser(structuredDocument.getParser()); // remember, embedded type factories are automatically cleared when // embededType changed pageDirectiveAdapter.setEmbeddedType(newEmbeddedContentType); if (newEmbeddedContentType != null) { newEmbeddedContentType.initializeFactoryRegistry(model.getFactoryRegistry()); newEmbeddedContentType.initializeParser(structuredDocument.getParser()); } // adding language here, in this convienent central // location, but some obvious renaming or refactoring // wouldn't hurt, in future. // I needed to add this language setting for JSP Fragment support // Note: this is the one that counts, since at this point, // the model has an ID, so we can look up IFile, etc. String language = getLanguage(model); if (language != null && language.length() > 0) { pageDirectiveAdapter.setLanguage(language); } } private EmbeddedTypeHandler getEmbeddedType(IStructuredModel model) { Document doc = ((IDOMModel) model).getDocument(); PageDirectiveAdapter pageDirectiveAdapter = (PageDirectiveAdapter) ((INodeNotifier) doc).getAdapterFor(PageDirectiveAdapter.class); EmbeddedTypeHandler embeddedHandler = pageDirectiveAdapter.getEmbeddedType(); return embeddedHandler; } /* (non-Javadoc) * @see org.eclipse.wst.sse.core.internal.model.AbstractModelLoader#initEmbeddedTypePre(org.eclipse.wst.sse.core.internal.provisional.IStructuredModel) */ protected void initEmbeddedTypePre(IStructuredModel model) { JSPModelLoader.this.initEmbeddedTypePre(model, model.getStructuredDocument()); } protected void initEmbeddedTypePre(IStructuredModel model, IStructuredDocument structuredDocument) { // note: this will currently only work for models backed by files EmbeddedTypeHandler embeddedContentType = null; IDOMModel domModel = (IDOMModel) model; if (embeddedContentType == null) { IContentDescription desc = getContentDescription(structuredDocument); if (desc != null) { Object prop = null; prop = desc.getProperty(IContentDescriptionForJSP.CONTENT_FAMILY_ATTRIBUTE); if (prop != null) { if (ContentTypeFamilyForHTML.HTML_FAMILY.equals(prop)) { embeddedContentType = EmbeddedTypeRegistryImpl.getInstance().getTypeFor("text/html"); } } if (embeddedContentType == null) { prop = desc.getProperty(IContentDescriptionForJSP.CONTENT_TYPE_ATTRIBUTE); if (prop != null) { embeddedContentType = EmbeddedTypeRegistryImpl.getInstance().getTypeFor((String) prop); } } } } IDOMDocument document = domModel.getDocument(); PageDirectiveAdapter pageDirectiveAdapter = (PageDirectiveAdapter) document.getAdapterFor(PageDirectiveAdapter.class); if (embeddedContentType != null) { pageDirectiveAdapter.setEmbeddedType(embeddedContentType); embeddedContentType.initializeFactoryRegistry(model.getFactoryRegistry()); } else { // use default embeddedType if it couldn't determine one embeddedContentType = getJSPDefaultEmbeddedType(model); pageDirectiveAdapter.setEmbeddedType(embeddedContentType); embeddedContentType.initializeFactoryRegistry(model.getFactoryRegistry()); } } protected void initEmbeddedTypePost(IStructuredModel model) { // should already be initialized (from initEmbeddedTypePre) // via IContentDescription setLanguageInPageDirective(model); } /** * As part of the model cloning process, ensure that the new model has the * same embedded content type handler as the old model, and that it is * properly initialized */ protected void initEmbeddedType(IStructuredModel oldModel, IStructuredModel newModel) { EmbeddedTypeHandler existingEmbeddedType = getEmbeddedType(oldModel); if (existingEmbeddedType == null) { initEmbeddedTypePre(newModel, newModel.getStructuredDocument()); initEmbeddedTypePost(newModel); } else { EmbeddedTypeHandler newEmbeddedContentType = existingEmbeddedType.newInstance(); // initEmbeddedType(newModel); initCloneOfEmbeddedType(newModel, existingEmbeddedType, newEmbeddedContentType); setLanguageInPageDirective(newModel); } } protected void setLanguageInPageDirective(IStructuredModel newModel) { if (newModel instanceof IDOMModel) { IDOMDocument document = ((IDOMModel) newModel).getDocument(); PageDirectiveAdapter pageDirectiveAdapter = (PageDirectiveAdapter) document.getAdapterFor(PageDirectiveAdapter.class); String language = getLanguage(newModel); pageDirectiveAdapter.setLanguage(language); } } public IStructuredModel reinitialize(IStructuredModel model) { EmbeddedTypeHandler oldHandler = null; EmbeddedTypeHandler newHandler = null; Object reinitStateData = model.getReinitializeStateData(); if (reinitStateData instanceof EmbeddedTypeStateData) { EmbeddedTypeStateData oldStateData = (EmbeddedTypeStateData) reinitStateData; oldHandler = oldStateData.getOldHandler(); newHandler = oldStateData.getNewHandler(); // note. We should already have the new handler in the model's // (documents) adapters, // so need need to use the old one to undo the old state data reInitializeEmbeddedType(model, oldHandler, newHandler); } else { // for language ... we someday MIGHT have to do something // here, but for now, we don't have any model-side language // sensitive adapters. } return super.reinitialize(model); } public IModelLoader newInstance() { return new JSPModelLoader(); } public IDocumentLoader getDocumentLoader() { if (documentLoaderInstance == null) { documentLoaderInstance = new JSPDocumentLoader(); } return documentLoaderInstance; } /** * Ensures that an InputStream has mark/reset support. */ public static InputStream getMarkSupportedStream(InputStream original) { if (original == null) return null; if (original.markSupported()) return original; return new BufferedInputStream(original); } protected byte[] getBytes(InputStream inputStream, int max) throws IOException { byte[] smallBuffer = new byte[max]; byte[] returnBuffer = null; int nRead = inputStream.read(smallBuffer, 0, max); if (nRead < max) { // empty file will return -1; if (nRead < 0) nRead = 0; byte[] smallerBuffer = new byte[nRead]; System.arraycopy(smallBuffer, 0, smallerBuffer, 0, nRead); returnBuffer = smallerBuffer; } else { returnBuffer = smallBuffer; } return returnBuffer; } public IStructuredModel createModel(IStructuredModel oldModel) { IStructuredModel model = super.createModel(oldModel); // For JSPs, the ModelQueryAdapter must be "attached" to the document // before content is set in the model, so taglib initialization can // take place. // In this "clone model" case, we create a ModelQuery adapter // create a new instance from the old data. Note: I think this // "forced fit" only works here since the implementation of // ModelQueryAdapter does not // have to be released. ModelQueryAdapter modelQueryAdapter = getModelQueryAdapter(model); if (modelQueryAdapter == null) { modelQueryAdapter = getModelQueryAdapter(oldModel); IDOMDocument document = ((IDOMModel) model).getDocument(); document.addAdapter(new JSPModelQueryAdapterImpl(modelQueryAdapter.getCMDocumentCache(), modelQueryAdapter.getModelQuery(), modelQueryAdapter.getIdResolver())); } return model; } private ModelQueryAdapter getModelQueryAdapter(IStructuredModel model) { IDOMDocument document = ((IDOMModel) model).getDocument(); ModelQueryAdapter modelQueryAdapter = (ModelQueryAdapter) ((INodeNotifier) document).getAdapterFor(ModelQueryAdapter.class); return modelQueryAdapter; } }