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: * If you wish your version of this file to be governed by only the CDDL
025: * or only the GPL Version 2, indicate your decision by adding
026: * "[Contributor] elects to include this software in this distribution
027: * under the [CDDL or GPL Version 2] license." If you do not indicate a
028: * single choice of license, a recipient has the option to distribute
029: * your version of this file under either the CDDL, the GPL Version 2 or
030: * to extend the choice of license to its licensees as provided above.
031: * However, if you add GPL Version 2 code and therefore, elected the GPL
032: * Version 2 license, then the option applies only if the new code is
033: * made subject to such option by the copyright holder.
034: *
035: * Contributor(s):
036: *
037: * Portions Copyrighted 2008 Sun Microsystems, Inc.
038: */
039:
040: package org.netbeans.modules.web.project;
041:
042: import java.io.IOException;
043: import java.net.URL;
044: import java.util.ArrayList;
045: import java.util.Iterator;
046: import java.util.List;
047: import javax.swing.JButton;
048: import org.netbeans.api.project.Project;
049: import org.netbeans.api.project.ProjectManager;
050: import org.netbeans.api.project.libraries.LibraryManager;
051: import org.netbeans.modules.j2ee.common.project.classpath.ClassPathSupport;
052: import org.netbeans.modules.j2ee.common.project.ui.ProjectProperties;
053: import org.netbeans.modules.java.api.common.ant.UpdateHelper;
054: import org.netbeans.modules.java.api.common.ant.UpdateImplementation;
055: import org.netbeans.modules.web.project.api.WebProjectUtilities;
056: import org.netbeans.modules.web.project.classpath.ClassPathSupportCallbackImpl;
057: import org.netbeans.modules.web.project.ui.customizer.WebProjectProperties;
058: import org.netbeans.spi.project.AuxiliaryConfiguration;
059: import org.netbeans.spi.project.support.ant.AntProjectHelper;
060: import org.netbeans.spi.project.support.ant.EditableProperties;
061: import org.netbeans.spi.project.support.ant.ReferenceHelper;
062: import org.openide.DialogDisplayer;
063: import org.openide.NotifyDescriptor;
064: import org.openide.filesystems.FileObject;
065: import org.openide.filesystems.FileUtil;
066: import org.openide.filesystems.Repository;
067: import org.openide.filesystems.URLMapper;
068: import org.openide.util.Mutex;
069: import org.openide.util.NbBundle;
070: import org.w3c.dom.Comment;
071: import org.w3c.dom.Document;
072: import org.w3c.dom.Element;
073: import org.w3c.dom.NamedNodeMap;
074: import org.w3c.dom.Node;
075: import org.w3c.dom.NodeList;
076: import org.w3c.dom.Text;
077:
078: /**
079: *
080: * @author Tomas Mysik
081: */
082: public class UpdateProjectImpl implements UpdateImplementation {
083:
084: private static final boolean TRANSPARENT_UPDATE = Boolean
085: .getBoolean("webproject.transparentUpdate");
086: private static final String BUILD_NUMBER = System
087: .getProperty("netbeans.buildnumber"); // NOI18N
088: private static final String TAG_MINIMUM_ANT_VERSION = "minimum-ant-version"; // NOI18N
089: private static final String TAG_FILE = "file"; //NOI18N
090: private static final String TAG_LIBRARY = "library"; //NOI18N
091: private static final String ATTR_FILES = "files"; //NOI18N
092: private static final String ATTR_DIRS = "dirs"; //NOI18N
093:
094: private final Project project;
095: private final AntProjectHelper helper;
096: private final AuxiliaryConfiguration cfg;
097: private boolean alreadyAskedInWriteAccess;
098: private Boolean isCurrent;
099: private Element cachedElement;
100: private ProjectUpdateListener projectUpdateListener = null;
101: private UpdateHelper updateHelper;
102: private EditableProperties cachedProperties;
103:
104: /**
105: * Creates new UpdateHelper
106: * @param project
107: * @param helper AntProjectHelper
108: * @param cfg AuxiliaryConfiguration
109: * @param genFileHelper GeneratedFilesHelper
110: * @param notifier used to ask user about project update
111: */
112: UpdateProjectImpl(Project project, AntProjectHelper helper,
113: AuxiliaryConfiguration cfg) {
114: assert project != null && helper != null && cfg != null;
115: this .project = project;
116: this .helper = helper;
117: this .cfg = cfg;
118: }
119:
120: public void setUpdateHelper(UpdateHelper updateHelper) {
121: this .updateHelper = updateHelper;
122: }
123:
124: public boolean isCurrent() {
125: return ProjectManager.mutex().readAccess(
126: new Mutex.Action<Boolean>() {
127: public Boolean run() {
128: synchronized (this ) {
129: if (isCurrent == null) {
130: if ((cfg
131: .getConfigurationFragment(
132: "data",
133: "http://www.netbeans.org/ns/web-project/1",
134: true) != null)
135: || // NOI18N
136: (cfg
137: .getConfigurationFragment(
138: "data",
139: "http://www.netbeans.org/ns/web-project/2",
140: true) != null)) { // NOI18N
141: isCurrent = Boolean.FALSE;
142: } else {
143: isCurrent = Boolean.TRUE;
144: }
145: }
146: return isCurrent;
147: }
148: }
149: }).booleanValue();
150: }
151:
152: public boolean canUpdate() {
153: if (TRANSPARENT_UPDATE) {
154: return true;
155: }
156: //Ask just once under a single write access
157: if (alreadyAskedInWriteAccess) {
158: return false;
159: } else {
160: boolean canUpdate = showUpdateDialog();
161: if (!canUpdate) {
162: alreadyAskedInWriteAccess = true;
163: ProjectManager.mutex().postReadRequest(new Runnable() {
164: public void run() {
165: alreadyAskedInWriteAccess = false;
166: }
167: });
168: }
169: return canUpdate;
170: }
171: }
172:
173: public void saveUpdate(EditableProperties props) throws IOException {
174: this .helper.putPrimaryConfigurationData(
175: getUpdatedSharedConfigurationData(), true);
176: if (this .cfg.getConfigurationFragment("data",
177: "http://www.netbeans.org/ns/web-project/1", true) != null) { //NOI18N
178: this .cfg.removeConfigurationFragment("data",
179: "http://www.netbeans.org/ns/web-project/1", true); //NOI18N
180: } else {
181: this .cfg.removeConfigurationFragment("data",
182: "http://www.netbeans.org/ns/web-project/2", true); //NOI18N
183: }
184:
185: boolean putProps = false;
186:
187: // AB: fix for #55597: should not update the project without adding the properties
188: // update is only done once, so if we don't add the properties now, we don't get another chance to do so
189: if (props == null) {
190: assert updateHelper != null;
191: props = updateHelper
192: .getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH);
193: putProps = true;
194: }
195:
196: //add properties needed by 4.1 project
197: if (props != null) {
198: props.put("test.src.dir", "test"); //NOI18N
199: props.put("build.test.classes.dir",
200: "${build.dir}/test/classes"); //NOI18N
201: props.put("build.test.results.dir",
202: "${build.dir}/test/results"); //NOI18N
203: props.put("conf.dir", "${source.root}/conf"); //NOI18N
204: props.put("jspcompilation.classpath",
205: "${jspc.classpath}:${javac.classpath}");
206:
207: props.setProperty(ProjectProperties.JAVAC_TEST_CLASSPATH,
208: new String[] { "${javac.classpath}:", // NOI18N
209: "${build.classes.dir}:", // NOI18N
210: "${libs.junit.classpath}:", // NOI18N
211: "${libs.junit_4.classpath}", // NOI18N
212: });
213: props.setProperty(ProjectProperties.RUN_TEST_CLASSPATH,
214: new String[] { "${javac.test.classpath}:", // NOI18N
215: "${build.test.classes.dir}", // NOI18N
216: });
217: props.setProperty(
218: WebProjectProperties.DEBUG_TEST_CLASSPATH,
219: new String[] { "${run.test.classpath}", // NOI18N
220: });
221:
222: props.put(WebProjectProperties.WAR_EAR_NAME, props
223: .getProperty(WebProjectProperties.WAR_NAME));
224: props.put(WebProjectProperties.DIST_WAR_EAR,
225: "${dist.dir}/${war.ear.name}");
226:
227: if (props.getProperty(WebProjectProperties.LIBRARIES_DIR) == null) {
228: props.setProperty(WebProjectProperties.LIBRARIES_DIR,
229: "${" + WebProjectProperties.WEB_DOCBASE_DIR
230: + "}/WEB-INF/lib"); //NOI18N
231: }
232: }
233:
234: if (props != null) {
235: //remove jsp20 and servlet24 libraries
236: ReferenceHelper refHelper = new ReferenceHelper(helper,
237: cfg, helper.getStandardPropertyEvaluator());
238: ClassPathSupport cs = new ClassPathSupport(helper
239: .getStandardPropertyEvaluator(), refHelper, helper,
240: updateHelper, new ClassPathSupportCallbackImpl(
241: helper));
242: Iterator items = cs
243: .itemsIterator(
244: (String) props
245: .get(ProjectProperties.JAVAC_CLASSPATH),
246: ClassPathSupportCallbackImpl.TAG_WEB_MODULE_LIBRARIES);
247: ArrayList cpItems = new ArrayList();
248: while (items.hasNext()) {
249: ClassPathSupport.Item cpti = (ClassPathSupport.Item) items
250: .next();
251: String propertyName = cpti.getReference();
252: if (propertyName != null) {
253: String libname = propertyName.substring("${libs."
254: .length());
255: if (libname.indexOf(".classpath}") != -1)
256: libname = libname.substring(0, libname
257: .indexOf(".classpath}"));
258:
259: if (!("servlet24".equals(libname) || "jsp20"
260: .equals(libname))) { //NOI18N
261: cpItems.add(cpti);
262: }
263: }
264: }
265: String[] javac_cp = cs
266: .encodeToStrings(
267: cpItems,
268: ClassPathSupportCallbackImpl.TAG_WEB_MODULE_LIBRARIES);
269: props.setProperty(ProjectProperties.JAVAC_CLASSPATH,
270: javac_cp);
271: }
272:
273: if (putProps) {
274: helper.putProperties(
275: AntProjectHelper.PROJECT_PROPERTIES_PATH, props);
276: }
277:
278: ProjectManager.getDefault().saveProject(this .project);
279: synchronized (this ) {
280: this .isCurrent = Boolean.TRUE;
281: }
282:
283: //fire project updated
284: if (projectUpdateListener != null)
285: projectUpdateListener.projectUpdated();
286:
287: //create conf dir if doesn't exist and copy default manifest inside
288: try {
289: //I cannot use ${conf.dir} since the PE doesn't know about it
290: //String confDir = helper.getStandardPropertyEvaluator().evaluate("${source.root}/conf"); //NOI18N
291: FileObject prjFO = project.getProjectDirectory();
292: // folder creation will throw IOE if already exists
293: // use the hard coded string due to issue #54882 - since the 4.0 supports creation of only jakarta structure projects the conf dir is always in project root
294: FileObject confDirFO = prjFO.createFolder("conf");//NOI18N
295: // copyfile will throw IOE if the file already exists
296: FileUtil
297: .copyFile(
298: Repository
299: .getDefault()
300: .getDefaultFileSystem()
301: .findResource(
302: "org-netbeans-modules-web-project/MANIFEST.MF"),
303: confDirFO, "MANIFEST"); //NOI18N
304: } catch (IOException e) {
305: //just ignore
306: }
307: }
308:
309: public synchronized Element getUpdatedSharedConfigurationData() {
310: if (cachedElement == null) {
311: int version = 1;
312: Element oldRoot = this .cfg.getConfigurationFragment("data",
313: "http://www.netbeans.org/ns/web-project/1", true); //NOI18N
314: if (oldRoot == null) {
315: version = 2;
316: oldRoot = this .cfg.getConfigurationFragment("data",
317: "http://www.netbeans.org/ns/web-project/2",
318: true); //NOI18N
319: }
320: final String ns = version == 1 ? "http://www.netbeans.org/ns/web-project/1"
321: : "http://www.netbeans.org/ns/web-project/2"; //NOI18N
322: if (oldRoot != null) {
323: Document doc = oldRoot.getOwnerDocument();
324: Element newRoot = doc.createElementNS(
325: WebProjectType.PROJECT_CONFIGURATION_NAMESPACE,
326: "data"); //NOI18N
327: copyDocument(doc, oldRoot, newRoot);
328: if (version == 1) {
329: //1->2 upgrade
330: Element sourceRoots = doc
331: .createElementNS(
332: WebProjectType.PROJECT_CONFIGURATION_NAMESPACE,
333: "source-roots"); //NOI18N
334: Element root = doc
335: .createElementNS(
336: WebProjectType.PROJECT_CONFIGURATION_NAMESPACE,
337: "root"); //NOI18N
338: root.setAttribute("id", "src.dir"); //NOI18N
339: sourceRoots.appendChild(root);
340: newRoot.appendChild(sourceRoots);
341: Element testRoots = doc
342: .createElementNS(
343: WebProjectType.PROJECT_CONFIGURATION_NAMESPACE,
344: "test-roots"); //NOI18N
345: root = doc
346: .createElementNS(
347: WebProjectType.PROJECT_CONFIGURATION_NAMESPACE,
348: "root"); //NOI18N
349: root.setAttribute("id", "test.src.dir"); //NOI18N
350: testRoots.appendChild(root);
351: newRoot.appendChild(testRoots);
352: }
353: if (version == 1 || version == 2) {
354: //2->3 upgrade
355: NodeList libList = newRoot.getElementsByTagNameNS(
356: ns, TAG_LIBRARY);
357: for (int i = 0; i < libList.getLength(); i++) {
358: if (libList.item(i).getNodeType() == Node.ELEMENT_NODE) {
359: Element library = (Element) libList.item(i);
360: Node webFile = library
361: .getElementsByTagNameNS(ns,
362: TAG_FILE).item(0);
363: //remove ${ and } from the beginning and end
364: String webFileText = findText(webFile);
365: webFileText = webFileText.substring(2,
366: webFileText.length() - 1);
367: // warIncludesMap.put(webFileText, pathInWarElements.getLength() > 0 ? findText((Element) pathInWarElements.item(0)) : Item.PATH_IN_WAR_NONE);
368: if (webFileText.startsWith("lib.")) {
369: String libName = webFileText.substring(
370: 6, webFileText
371: .indexOf(".classpath")); //NOI18N
372: List<URL> roots = LibraryManager
373: .getDefault().getLibrary(
374: libName).getContent(
375: "classpath"); //NOI18N
376: ArrayList<FileObject> files = new ArrayList<FileObject>();
377: ArrayList<FileObject> dirs = new ArrayList<FileObject>();
378: for (URL rootUrl : roots) {
379: FileObject root = URLMapper
380: .findFileObject(rootUrl);
381: if ("jar".equals(rootUrl
382: .getProtocol())) { //NOI18N
383: root = FileUtil
384: .getArchiveFile(root);
385: }
386: if (root != null) {
387: if (root.isData()) {
388: files.add(root);
389: } else {
390: dirs.add(root);
391: }
392: }
393: }
394: if (files.size() > 0) {
395: library.setAttribute(ATTR_FILES, ""
396: + files.size());
397: }
398: if (dirs.size() > 0) {
399: library.setAttribute(ATTR_DIRS, ""
400: + dirs.size());
401: }
402: }
403: }
404: }
405: }
406: cachedElement = updateMinAntVersion(newRoot, doc);
407: }
408: }
409: return cachedElement;
410: }
411:
412: public synchronized EditableProperties getUpdatedProjectProperties() {
413: if (cachedProperties == null) {
414: cachedProperties = this .helper
415: .getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH);
416: //The javadoc.additionalparam was not in NB 4.0
417: if (cachedProperties
418: .get(WebProjectProperties.JAVADOC_ADDITIONALPARAM) == null) {
419: cachedProperties.put(
420: WebProjectProperties.JAVADOC_ADDITIONALPARAM,
421: ""); //NOI18N
422: }
423: }
424: return this .cachedProperties;
425: }
426:
427: /**
428: * Extract nested text from a node.
429: * Currently does not handle coalescing text nodes, CDATA sections, etc.
430: * @param parent a parent node
431: * @return the nested text, or null if none was found
432: */
433: private static String findText(Node parent) {
434: NodeList l = parent.getChildNodes();
435: for (int i = 0; i < l.getLength(); i++) {
436: if (l.item(i).getNodeType() == Node.TEXT_NODE) {
437: Text text = (Text) l.item(i);
438: return text.getNodeValue();
439: }
440: }
441: return null;
442: }
443:
444: private static void copyDocument(Document doc, Element from,
445: Element to) {
446: NodeList nl = from.getChildNodes();
447: int length = nl.getLength();
448: for (int i = 0; i < length; i++) {
449: Node node = nl.item(i);
450: Node newNode = null;
451: switch (node.getNodeType()) {
452: case Node.ELEMENT_NODE:
453: Element oldElement = (Element) node;
454: newNode = doc.createElementNS(
455: WebProjectType.PROJECT_CONFIGURATION_NAMESPACE,
456: oldElement.getTagName());
457: NamedNodeMap m = oldElement.getAttributes();
458: Element newElement = (Element) newNode;
459: for (int index = 0; index < m.getLength(); index++) {
460: Node attr = m.item(index);
461: newElement.setAttribute(attr.getNodeName(), attr
462: .getNodeValue());
463: }
464: copyDocument(doc, oldElement, (Element) newNode);
465: break;
466: case Node.TEXT_NODE:
467: Text oldText = (Text) node;
468: newNode = doc.createTextNode(oldText.getData());
469: break;
470: case Node.COMMENT_NODE:
471: Comment oldComment = (Comment) node;
472: newNode = doc.createComment(oldComment.getData());
473: break;
474: }
475: if (newNode != null) {
476: to.appendChild(newNode);
477: }
478: }
479: }
480:
481: private static Element updateMinAntVersion(final Element root,
482: final Document doc) {
483: NodeList list = root.getElementsByTagNameNS(
484: WebProjectType.PROJECT_CONFIGURATION_NAMESPACE,
485: TAG_MINIMUM_ANT_VERSION);
486: if (list.getLength() == 1) {
487: Element me = (Element) list.item(0);
488: list = me.getChildNodes();
489: if (list.getLength() == 1) {
490: me
491: .replaceChild(
492: doc
493: .createTextNode(WebProjectUtilities.MINIMUM_ANT_VERSION),
494: list.item(0));
495: return root;
496: }
497: }
498: assert false : "Invalid project file"; //NOI18N
499: return root;
500: }
501:
502: private boolean showUpdateDialog() {
503: JButton updateOption = new JButton(NbBundle.getMessage(
504: UpdateProjectImpl.class, "CTL_UpdateOption"));
505: updateOption.getAccessibleContext().setAccessibleDescription(
506: NbBundle.getMessage(UpdateProjectImpl.class,
507: "AD_UpdateOption"));
508: return DialogDisplayer.getDefault().notify(
509: new NotifyDescriptor(NbBundle.getMessage(
510: UpdateProjectImpl.class, "TXT_ProjectUpdate",
511: BUILD_NUMBER), NbBundle.getMessage(
512: UpdateProjectImpl.class,
513: "TXT_ProjectUpdateTitle"),
514: NotifyDescriptor.DEFAULT_OPTION,
515: NotifyDescriptor.WARNING_MESSAGE, new Object[] {
516: updateOption,
517: NotifyDescriptor.CANCEL_OPTION },
518: updateOption)) == updateOption;
519: }
520:
521: public void setProjectUpdateListener(ProjectUpdateListener l) {
522: this .projectUpdateListener = l;
523: }
524:
525: /** Used to notify someone that the project needs to be updated.
526: * A workaround for #54077 - Import 4.0 project - remove Servlet/JSP APIs */
527: public static interface ProjectUpdateListener {
528: public void projectUpdated();
529: }
530: }
|