| /******************************************************************************* |
| * Copyright (c) 2008, 2011 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.validation; |
| |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.ProjectScope; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.preferences.DefaultScope; |
| import org.eclipse.core.runtime.preferences.IPreferencesService; |
| import org.eclipse.core.runtime.preferences.IScopeContext; |
| import org.eclipse.core.runtime.preferences.InstanceScope; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.Region; |
| import org.eclipse.jst.jsp.core.internal.JSPCoreMessages; |
| import org.eclipse.jst.jsp.core.internal.JSPCorePlugin; |
| import org.eclipse.jst.jsp.core.internal.Logger; |
| import org.eclipse.jst.jsp.core.internal.contentmodel.TaglibController; |
| import org.eclipse.jst.jsp.core.internal.contentmodel.tld.TLDCMDocumentManager; |
| import org.eclipse.jst.jsp.core.internal.contentmodel.tld.TaglibTracker; |
| import org.eclipse.jst.jsp.core.internal.contentmodel.tld.provisional.JSP11TLDNames; |
| import org.eclipse.jst.jsp.core.internal.contentmodel.tld.provisional.TLDAttributeDeclaration; |
| import org.eclipse.jst.jsp.core.internal.contentmodel.tld.provisional.TLDElementDeclaration; |
| import org.eclipse.jst.jsp.core.internal.contenttype.DeploymentDescriptorPropertyCache; |
| import org.eclipse.jst.jsp.core.internal.contenttype.DeploymentDescriptorPropertyCache.PropertyGroup; |
| 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.preferences.JSPCorePreferenceNames; |
| import org.eclipse.jst.jsp.core.internal.regions.DOMJSPRegionContexts; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.wst.sse.core.StructuredModelManager; |
| import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; |
| import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; |
| import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; |
| import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; |
| import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; |
| import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionContainer; |
| import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList; |
| import org.eclipse.wst.sse.core.internal.validate.ValidationMessage; |
| import org.eclipse.wst.validation.internal.provisional.core.IMessage; |
| import org.eclipse.wst.validation.internal.provisional.core.IReporter; |
| import org.eclipse.wst.validation.internal.provisional.core.IValidator; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMAttributeDeclaration; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMNamedNodeMap; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMNode; |
| import org.eclipse.wst.xml.core.internal.contentmodel.basic.CMNamedNodeMapImpl; |
| import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.ModelQuery; |
| import org.eclipse.wst.xml.core.internal.modelquery.ModelQueryUtil; |
| import org.eclipse.wst.xml.core.internal.provisional.contentmodel.CMNodeWrapper; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMAttr; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode; |
| import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext; |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.NodeList; |
| |
| /** |
| * Checks for: missing required attributes & undefined attributes in jsp |
| * action tags such as jsp directives and jsp custom tags as well as non-empty |
| * inline jsp action tags |
| */ |
| public class JSPActionValidator extends JSPValidator { |
| /** |
| * |
| */ |
| private static final String PREFERENCE_NODE_QUALIFIER = JSPCorePlugin.getDefault().getBundle().getSymbolicName(); |
| private IValidator fMessageOriginator; |
| private IPreferencesService fPreferencesService = null; |
| private IScopeContext[] fScopes = null; |
| private int fSeverityMissingRequiredAttribute = IMessage.HIGH_SEVERITY; |
| private int fSeverityNonEmptyInlineTag = IMessage.NORMAL_SEVERITY; |
| private int fSeverityUnknownAttribute = IMessage.NORMAL_SEVERITY; |
| private int fSeverityUnexpectedRuntimeExpression = IMessage.NORMAL_SEVERITY; |
| |
| private HashSet fTaglibPrefixes = new HashSet(); |
| private boolean fIsELIgnored = false; |
| |
| public JSPActionValidator() { |
| this.fMessageOriginator = this; |
| } |
| |
| public JSPActionValidator(IValidator validator) { |
| this.fMessageOriginator = validator; |
| } |
| |
| private void checkNonEmptyInlineTag(IDOMElement element, CMElementDeclaration cmElementDecl, IReporter reporter, IFile file, IStructuredDocument document) { |
| if (cmElementDecl.getContentType() == CMElementDeclaration.EMPTY && element.getChildNodes().getLength() > 0) { |
| String msgText = NLS.bind(JSPCoreMessages.JSPActionValidator_0, element.getNodeName()); |
| LocalizedMessage message = new LocalizedMessage(fSeverityNonEmptyInlineTag, msgText, file); |
| int start = element.getStartOffset(); |
| int length = element.getStartEndOffset() - start; |
| int lineNo = document.getLineOfOffset(start); |
| message.setLineNo(lineNo); |
| message.setOffset(start); |
| message.setLength(length); |
| |
| reporter.addMessage(fMessageOriginator, message); |
| } |
| } |
| |
| /** |
| * Checks an attribute for runtime expressions |
| * @param a The attribute to check for runtime expressions |
| * @return true if the attribute contains a runtime expression, false otherwise |
| */ |
| private boolean checkRuntimeValue(IDOMAttr a) { |
| ITextRegion value = a.getValueRegion(); |
| if (value instanceof ITextRegionContainer) { |
| Iterator it = ((ITextRegionContainer) value).getRegions().iterator(); |
| while (it.hasNext()) { |
| String type = ((ITextRegion) it.next()).getType(); |
| if (type == DOMJSPRegionContexts.JSP_EL_OPEN) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Determines if EL should be ignored. Checks |
| * <ol> |
| * <li>JSP version</li> |
| * <li>Page directive isELIgnored</li> |
| * <li>Deployment descriptor's el-ignored</li> |
| * </ol> |
| * @return true if EL should be ignored, false otherwise. If the JSP version is < 2.0, EL is ignored by default |
| */ |
| private boolean isElIgnored(IPath path, IStructuredModel model) { |
| if (DeploymentDescriptorPropertyCache.getInstance().getJSPVersion(path) < 2.0f) |
| return true; |
| |
| PageDirectiveAdapter pdAdapter = ((PageDirectiveAdapter) (((IDOMModel) model).getDocument().getAdapterFor(PageDirectiveAdapter.class))); |
| if (pdAdapter == null) { |
| // double-check the factory (although there just might not be a page directive in the file) |
| if (model.getFactoryRegistry().getFactoryFor(PageDirectiveAdapter.class) == null) { |
| model.getFactoryRegistry().addFactory(new PageDirectiveAdapterFactory()); |
| pdAdapter = ((PageDirectiveAdapter) (((IDOMModel) model).getDocument().getAdapterFor(PageDirectiveAdapter.class))); |
| } |
| } |
| if (pdAdapter != null) { |
| String directiveIsELIgnored = pdAdapter.getElIgnored(); |
| // isELIgnored directive found |
| if (directiveIsELIgnored != null) |
| return Boolean.valueOf(directiveIsELIgnored).booleanValue(); |
| } |
| |
| // Check the deployment descriptor for el-ignored |
| PropertyGroup[] groups = DeploymentDescriptorPropertyCache.getInstance().getPropertyGroups(path); |
| if (groups.length > 0) |
| return groups[0].isELignored(); |
| // JSP version >= 2.0 defaults to evaluating EL |
| return false; |
| } |
| |
| private void checkRequiredAttributes(IDOMElement element, CMNamedNodeMap attrMap, IReporter reporter, IFile file, IStructuredDocument document, IStructuredDocumentRegion documentRegion) { |
| Iterator it = attrMap.iterator(); |
| CMAttributeDeclaration attr = null; |
| while (it.hasNext()) { |
| attr = (CMAttributeDeclaration) it.next(); |
| if (attr.getUsage() == CMAttributeDeclaration.REQUIRED) { |
| Attr a = element.getAttributeNode(attr.getAttrName()); |
| if (a == null) { |
| // Attribute may be defined using a jsp:attribute action |
| if (!checkJSPAttributeAction(element, attr)) { |
| String msgText = NLS.bind(JSPCoreMessages.JSPDirectiveValidator_5, attr.getAttrName()); |
| LocalizedMessage message = new LocalizedMessage(fSeverityMissingRequiredAttribute, msgText, file); |
| int start = element.getStartOffset(); |
| int length = element.getStartEndOffset() - start; |
| int lineNo = document.getLineOfOffset(start); |
| message.setLineNo(lineNo); |
| message.setOffset(start); |
| message.setLength(length); |
| |
| reporter.addMessage(fMessageOriginator, message); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Checks for jsp:attribute actions of <code>element</code> to see if they |
| * satisfy the required attribute <code>attr</code> |
| * |
| * @param element The element with a required attribute |
| * @param attr The required attribute |
| * @return <code>true</code> if a jsp:attribute action has the name of |
| * the required attribute <code>attr</code>; <code>false</code> otherwise. |
| */ |
| private boolean checkJSPAttributeAction(IDOMElement element, CMAttributeDeclaration attr) { |
| if (element != null && attr != null) { |
| NodeList elements = element.getElementsByTagName("jsp:attribute"); //$NON-NLS-1$ |
| String neededAttrName = attr.getNodeName(); |
| for (int i = 0; i < elements.getLength(); i++) { |
| Element childElement = (Element) elements.item(i); |
| /* |
| * Get the name attribute of jsp:attribute and compare its |
| * value to the required attribute name |
| */ |
| if (childElement.hasAttribute("name") && neededAttrName.equals(childElement.getAttribute("name"))) {//$NON-NLS-1$ //$NON-NLS-2$ |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private boolean checkUnknownAttributes(IDOMElement element, CMElementDeclaration elementDecl, CMNamedNodeMap cmAttrs, IReporter reporter, IFile file, IStructuredDocument document, IStructuredDocumentRegion documentRegion) { |
| boolean foundjspattribute = false; |
| boolean dynamicAttributesAllowed = false; |
| CMElementDeclaration decl = elementDecl; |
| if (decl instanceof CMNodeWrapper) |
| decl = (CMElementDeclaration) ((CMNodeWrapper) decl).getOriginNode(); |
| if (decl instanceof TLDElementDeclaration) { |
| String dynamicAttributes = ((TLDElementDeclaration) decl).getDynamicAttributes(); |
| dynamicAttributesAllowed = dynamicAttributes != null ? Boolean.valueOf(dynamicAttributes).booleanValue() : false; |
| } |
| |
| NamedNodeMap attrs = element.getAttributes(); |
| for (int i = 0; i < attrs.getLength(); i++) { |
| Attr a = (Attr) attrs.item(i); |
| CMAttributeDeclaration adec = (CMAttributeDeclaration) cmAttrs.getNamedItem(a.getName()); |
| if (adec == null) { |
| /* |
| * No attr declaration was found. That is, the attr name is |
| * undefined. Disregard it includes JSP structure or this |
| * element supports dynamic attributes |
| */ |
| if (!hasJSPRegion(((IDOMNode) a).getNameRegion()) && fSeverityUnknownAttribute != ValidationMessage.IGNORE) { |
| if (!dynamicAttributesAllowed) { |
| String msgText = NLS.bind(JSPCoreMessages.JSPDirectiveValidator_6, a.getName()); |
| LocalizedMessage message = new LocalizedMessage(fSeverityUnknownAttribute, msgText, file); |
| int start = ((IDOMAttr) a).getNameRegionStartOffset(); |
| int length = ((IDOMAttr) a).getNameRegionEndOffset() - start; |
| int lineNo = document.getLineOfOffset(start); |
| message.setLineNo(lineNo); |
| message.setOffset(start); |
| message.setLength(length); |
| |
| reporter.addMessage(fMessageOriginator, message); |
| } |
| } |
| else { |
| foundjspattribute = true; |
| } |
| } |
| else { |
| if (fSeverityUnexpectedRuntimeExpression != ValidationMessage.IGNORE && adec instanceof TLDAttributeDeclaration) { |
| // The attribute cannot have a runtime evaluation of an expression |
| if (!isTrue(((TLDAttributeDeclaration) adec).getRtexprvalue())) { |
| IDOMAttr attr = (IDOMAttr) a; |
| if(checkRuntimeValue(attr) && !fIsELIgnored) { |
| String msg = NLS.bind(JSPCoreMessages.JSPActionValidator_1, a.getName()); |
| LocalizedMessage message = new LocalizedMessage(fSeverityUnexpectedRuntimeExpression, msg, file); |
| ITextRegion region = attr.getValueRegion(); |
| int start = attr.getValueRegionStartOffset(); |
| int length = region != null ? region.getTextLength() : 0; |
| int lineNo = document.getLineOfOffset(start); |
| message.setLineNo(lineNo); |
| message.setOffset(start); |
| message.setLength(length); |
| reporter.addMessage(fMessageOriginator, message); |
| } |
| } |
| } |
| } |
| } |
| return foundjspattribute; |
| } |
| |
| private boolean isTrue(String value) { |
| return JSP11TLDNames.TRUE.equalsIgnoreCase(value) || JSP11TLDNames.YES.equalsIgnoreCase(value); |
| } |
| |
| public void cleanup(IReporter reporter) { |
| super.cleanup(reporter); |
| fTaglibPrefixes.clear(); |
| } |
| |
| int getMessageSeverity(String key) { |
| int sev = fPreferencesService.getInt(PREFERENCE_NODE_QUALIFIER, key, IMessage.NORMAL_SEVERITY, fScopes); |
| switch (sev) { |
| case ValidationMessage.ERROR : |
| return IMessage.HIGH_SEVERITY; |
| case ValidationMessage.WARNING : |
| return IMessage.NORMAL_SEVERITY; |
| case ValidationMessage.INFORMATION : |
| return IMessage.LOW_SEVERITY; |
| case ValidationMessage.IGNORE : |
| return ValidationMessage.IGNORE; |
| } |
| return IMessage.NORMAL_SEVERITY; |
| } |
| |
| private String getStartTagName(IStructuredDocumentRegion sdr) { |
| String name = new String(); |
| ITextRegionList subRegions = sdr.getRegions(); |
| if (subRegions.size() > 2) { |
| ITextRegion subRegion = subRegions.get(0); |
| if (subRegion.getType() == DOMRegionContext.XML_TAG_OPEN) { |
| subRegion = subRegions.get(1); |
| if (subRegion.getType() == DOMRegionContext.XML_TAG_NAME) { |
| name = sdr.getText(subRegion); |
| } |
| } |
| } |
| return name; |
| } |
| |
| private HashSet getTaglibPrefixes(IStructuredDocument document) { |
| if (fTaglibPrefixes.isEmpty()) { |
| // add all reserved prefixes |
| fTaglibPrefixes.add("jsp"); //$NON-NLS-1$ |
| fTaglibPrefixes.add("jspx"); //$NON-NLS-1$ |
| fTaglibPrefixes.add("java"); //$NON-NLS-1$ |
| fTaglibPrefixes.add("javax"); //$NON-NLS-1$ |
| fTaglibPrefixes.add("servlet"); //$NON-NLS-1$ |
| fTaglibPrefixes.add("sun"); //$NON-NLS-1$ |
| fTaglibPrefixes.add("sunw"); //$NON-NLS-1$ |
| |
| // add all taglib prefixes |
| TLDCMDocumentManager manager = TaglibController.getTLDCMDocumentManager(document); |
| if (manager != null) { |
| List trackers = manager.getTaglibTrackers(); |
| for (Iterator it = trackers.iterator(); it.hasNext();) { |
| TaglibTracker tracker = (TaglibTracker) it.next(); |
| if (tracker.getElements().getLength() == 0) |
| continue; |
| String prefix = tracker.getPrefix(); |
| fTaglibPrefixes.add(prefix); |
| } |
| } |
| } |
| return fTaglibPrefixes; |
| } |
| |
| private boolean hasJSPRegion(ITextRegion container) { |
| if (!(container instanceof ITextRegionContainer)) |
| return false; |
| ITextRegionList regions = ((ITextRegionContainer) container).getRegions(); |
| if (regions == null) |
| return false; |
| Iterator e = regions.iterator(); |
| while (e.hasNext()) { |
| ITextRegion region = (ITextRegion) e.next(); |
| if (region == null) |
| continue; |
| String regionType = region.getType(); |
| if (regionType == DOMRegionContext.XML_TAG_OPEN || (isNestedTagName(regionType))) |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean isNestedTagName(String regionType) { |
| boolean result = regionType.equals(DOMJSPRegionContexts.JSP_SCRIPTLET_OPEN) || regionType.equals(DOMJSPRegionContexts.JSP_EXPRESSION_OPEN) || regionType.equals(DOMJSPRegionContexts.JSP_DECLARATION_OPEN) || regionType.equals(DOMJSPRegionContexts.JSP_DIRECTIVE_OPEN); |
| return result; |
| } |
| |
| private void loadPreferences(IFile file) { |
| fScopes = new IScopeContext[]{new InstanceScope(), new DefaultScope()}; |
| |
| fPreferencesService = Platform.getPreferencesService(); |
| if (file != null && file.isAccessible()) { |
| ProjectScope projectScope = new ProjectScope(file.getProject()); |
| if (projectScope.getNode(PREFERENCE_NODE_QUALIFIER).getBoolean(JSPCorePreferenceNames.VALIDATION_USE_PROJECT_SETTINGS, false)) { |
| fScopes = new IScopeContext[]{projectScope, new InstanceScope(), new DefaultScope()}; |
| } |
| } |
| |
| fSeverityMissingRequiredAttribute = getMessageSeverity(JSPCorePreferenceNames.VALIDATION_ACTIONS_SEVERITY_MISSING_REQUIRED_ATTRIBUTE); |
| fSeverityNonEmptyInlineTag = getMessageSeverity(JSPCorePreferenceNames.VALIDATION_ACTIONS_SEVERITY_NON_EMPTY_INLINE_TAG); |
| fSeverityUnknownAttribute = getMessageSeverity(JSPCorePreferenceNames.VALIDATION_ACTIONS_SEVERITY_UNKNOWN_ATTRIBUTE); |
| fSeverityUnexpectedRuntimeExpression = getMessageSeverity(JSPCorePreferenceNames.VALIDATION_ACTIONS_SEVERITY_UNEXPECTED_RTEXPRVALUE); |
| } |
| |
| void performValidation(IFile f, IReporter reporter, IStructuredModel model) { |
| fTaglibPrefixes.clear(); |
| int length = model.getStructuredDocument().getLength(); |
| performValidation(f, reporter, model, new Region(0, length)); |
| } |
| |
| protected void performValidation(IFile f, IReporter reporter, IStructuredModel model, IRegion validateRegion) { |
| loadPreferences(f); |
| IStructuredDocument sDoc = model.getStructuredDocument(); |
| |
| fIsELIgnored = isElIgnored(f.getFullPath(), model); |
| // iterate all document regions |
| IStructuredDocumentRegion region = sDoc.getRegionAtCharacterOffset(validateRegion.getOffset()); |
| while (region != null && !reporter.isCancelled() && (region.getStartOffset() <= (validateRegion.getOffset() + validateRegion.getLength()))) { |
| if (region.getType() == DOMJSPRegionContexts.JSP_DIRECTIVE_NAME) { |
| // only checking directives |
| processDirective(reporter, f, model, region); |
| fTaglibPrefixes.clear(); |
| } |
| else if (region.getType() == DOMRegionContext.XML_TAG_NAME) { |
| // and jsp tags |
| String tagName = getStartTagName(region); |
| int colonPosition = tagName.indexOf(':'); |
| if (colonPosition > -1) { |
| // get tag's prefix and check if it's really a jsp action |
| // tag |
| String prefix = tagName.substring(0, colonPosition); |
| if (getTaglibPrefixes(sDoc).contains(prefix)) |
| processDirective(reporter, f, model, region); |
| } |
| } |
| region = region.getNext(); |
| } |
| unloadPreferences(); |
| } |
| |
| private void processDirective(IReporter reporter, IFile file, IStructuredModel model, IStructuredDocumentRegion documentRegion) { |
| IndexedRegion ir = model.getIndexedRegion(documentRegion.getStartOffset()); |
| if (ir instanceof IDOMElement) { |
| IDOMElement element = (IDOMElement) ir; |
| ModelQuery query = ModelQueryUtil.getModelQuery(model); |
| if (query != null) { |
| CMElementDeclaration cmElement = query.getCMElementDeclaration(element); |
| if (cmElement != null) { |
| CMNamedNodeMap cmAttributes = null; |
| |
| CMNamedNodeMapImpl allAttributes = new CMNamedNodeMapImpl(); |
| List nodes = query.getAvailableContent(element, cmElement, ModelQuery.INCLUDE_ATTRIBUTES); |
| for (int k = 0; k < nodes.size(); k++) { |
| CMNode cmnode = (CMNode) nodes.get(k); |
| if (cmnode.getNodeType() == CMNode.ATTRIBUTE_DECLARATION) { |
| allAttributes.put(cmnode); |
| } |
| } |
| cmAttributes = allAttributes; |
| |
| boolean foundjspattribute = checkUnknownAttributes(element, cmElement, cmAttributes, reporter, file, model.getStructuredDocument(), documentRegion); |
| // required attributes could be hidden in jsp regions in |
| // tags, so if jsp regions were detected, do not check for |
| // missing required attributes |
| if (!foundjspattribute && fSeverityMissingRequiredAttribute != ValidationMessage.IGNORE) |
| checkRequiredAttributes(element, cmAttributes, reporter, file, model.getStructuredDocument(), documentRegion); |
| |
| if (fSeverityNonEmptyInlineTag != ValidationMessage.IGNORE) |
| checkNonEmptyInlineTag(element, cmElement, reporter, file, model.getStructuredDocument()); |
| } |
| } |
| } |
| } |
| |
| private void unloadPreferences() { |
| fPreferencesService = null; |
| fScopes = null; |
| } |
| |
| protected void validateFile(IFile f, IReporter reporter) { |
| if (DEBUG) { |
| Logger.log(Logger.INFO, getClass().getName() + " validating: " + f); //$NON-NLS-1$ |
| } |
| |
| IStructuredModel sModel = null; |
| try { |
| sModel = StructuredModelManager.getModelManager().getModelForRead(f); |
| if (sModel != null && !reporter.isCancelled()) { |
| performValidation(f, reporter, sModel); |
| } |
| } |
| catch (Exception e) { |
| Logger.logException(e); |
| } |
| finally { |
| if (sModel != null) |
| sModel.releaseFromRead(); |
| } |
| } |
| } |