head	1.172;
access;
symbols
	TIGRIS_1_1_0RC2:1.55.2.1.4.21.2.1
	TIGRIS_1_1_0RC1:1.55.2.1.4.21
	TIGRIS_1_1:1.55.2.1.4.21.0.2
	TIGRIS_1_0_8:1.55.2.1.4.20
	TIGRIS_1_0_8RC3:1.55.2.1.4.20
	TIGRIS_1_0_8RC2:1.55.2.1.4.18
	TIGRIS_1_0_8RC1:1.55.2.1.4.18
	TIGRIS_1_0_7:1.55.2.1.4.18
	TIGRIS_1_0_7RC3:1.55.2.1.4.18
	TIGRIS_1_0_7RC2:1.55.2.1.4.18
	TIGRIS_1_0_7RC1:1.55.2.1.4.18
	TIGRIS_1_0_6:1.55.2.1.4.18
	TIGRIS_1_0_6RC5:1.55.2.1.4.18
	TIGRIS_1_0_6RC4:1.55.2.1.4.18
	TIGRIS_1_0_6RC3:1.55.2.1.4.17
	TIGRIS_1_0_6RC2:1.55.2.1.4.16
	TIGRIS_1_0_6RC1:1.55.2.1.4.16
	TIGRIS_1_0_5:1.55.2.1.4.9
	TIGRIS_1_0_5RC6:1.55.2.1.4.9
	TIGRIS_1_0_5RC5:1.55.2.1.4.9
	TIGRIS_1_0_5RC4:1.55.2.1.4.8
	TIGRIS_1_0_5RC3:1.55.2.1.4.8
	TIGRIS_1_0_5RC2:1.55.2.1.4.8
	TIGRIS_1_0_5RC1:1.55.2.1.4.6
	TIGRIS_1_0_4:1.55.2.1.4.5
	TIGRIS_1_0_3:1.55.2.1.4.4
	TIGRIS_1_0_2:1.55.2.1.4.3
	TIGRIS_1_0_1:1.55.2.1.4.1
	TIGRIS_1_0:1.55.2.1.0.4
	TIGRIS_1_0_0:1.55.2.1
	TIGRIS_1_0_0_RC1:1.55.0.2
	dlr:1.1.1
	HELM_PEER_PORT_BRANCH:1.1.1.1;
locks; strict;
comment	@# @;


1.172
date	2001.08.30.16.57.41;	author dlr;	state Exp;
branches;
next	1.171;

1.171
date	2001.08.29.18.15.44;	author leonardr;	state Exp;
branches;
next	1.170;

1.170
date	2001.08.29.16.29.13;	author leonardr;	state Exp;
branches;
next	1.169;

1.169
date	2001.08.27.08.41.27;	author dlr;	state Exp;
branches;
next	1.168;

1.168
date	2001.08.22.15.42.33;	author leonardr;	state Exp;
branches;
next	1.167;

1.167
date	2001.08.17.21.51.50;	author leonardr;	state Exp;
branches;
next	1.166;

1.166
date	2001.08.15.18.35.06;	author leonardr;	state Exp;
branches;
next	1.165;

1.165
date	2001.08.15.17.51.10;	author leonardr;	state Exp;
branches;
next	1.164;

1.164
date	2001.08.15.16.11.36;	author leonardr;	state Exp;
branches;
next	1.163;

1.163
date	2001.08.15.16.06.05;	author leonardr;	state Exp;
branches;
next	1.162;

1.162
date	2001.08.14.00.40.44;	author dlr;	state Exp;
branches;
next	1.161;

1.161
date	2001.08.13.19.04.32;	author leonardr;	state Exp;
branches;
next	1.160;

1.160
date	2001.08.11.09.08.03;	author dlr;	state Exp;
branches;
next	1.159;

1.159
date	2001.08.10.19.52.34;	author leonardr;	state Exp;
branches;
next	1.158;

1.158
date	2001.08.10.16.37.20;	author leonardr;	state Exp;
branches;
next	1.157;

1.157
date	2001.08.01.23.45.05;	author leonardr;	state Exp;
branches;
next	1.156;

1.156
date	2001.07.30.21.25.13;	author leonardr;	state Exp;
branches;
next	1.155;

1.155
date	2001.07.30.19.29.57;	author leonardr;	state Exp;
branches;
next	1.154;

1.154
date	2001.07.30.18.40.41;	author leonardr;	state Exp;
branches;
next	1.153;

1.153
date	2001.07.29.23.10.40;	author edk;	state Exp;
branches;
next	1.152;

1.152
date	2001.07.25.21.33.51;	author leonardr;	state Exp;
branches;
next	1.151;

1.151
date	2001.07.24.18.38.32;	author leonardr;	state Exp;
branches;
next	1.150;

1.150
date	2001.07.19.19.08.42;	author dlr;	state Exp;
branches;
next	1.149;

1.149
date	2001.07.19.18.42.22;	author dlr;	state Exp;
branches;
next	1.148;

1.148
date	2001.07.18.09.45.59;	author dlr;	state Exp;
branches;
next	1.147;

1.147
date	2001.07.17.03.22.04;	author jrobbins;	state Exp;
branches;
next	1.146;

1.146
date	2001.07.16.03.18.07;	author jrobbins;	state Exp;
branches;
next	1.145;

1.145
date	2001.07.16.02.23.42;	author jrobbins;	state Exp;
branches;
next	1.144;

1.144
date	2001.07.13.14.44.41;	author leonardr;	state Exp;
branches;
next	1.143;

1.143
date	2001.07.13.14.43.50;	author leonardr;	state Exp;
branches;
next	1.142;

1.142
date	2001.07.12.20.00.16;	author leonardr;	state Exp;
branches;
next	1.141;

1.141
date	2001.07.09.17.22.44;	author leonardr;	state Exp;
branches;
next	1.140;

1.140
date	2001.07.09.17.22.31;	author leonardr;	state Exp;
branches;
next	1.139;

1.139
date	2001.07.05.22.21.39;	author leonardr;	state Exp;
branches;
next	1.138;

1.138
date	2001.07.02.20.54.35;	author leonardr;	state Exp;
branches;
next	1.137;

1.137
date	2001.07.02.18.56.42;	author leonardr;	state Exp;
branches;
next	1.136;

1.136
date	2001.06.29.00.57.49;	author leonardr;	state Exp;
branches;
next	1.135;

1.135
date	2001.06.21.00.59.07;	author leonardr;	state Exp;
branches;
next	1.134;

1.134
date	2001.06.21.00.44.04;	author leonardr;	state Exp;
branches;
next	1.133;

1.133
date	2001.06.21.00.43.49;	author leonardr;	state Exp;
branches;
next	1.132;

1.132
date	2001.06.21.00.22.15;	author leonardr;	state Exp;
branches;
next	1.131;

1.131
date	2001.06.20.22.30.17;	author leonardr;	state Exp;
branches;
next	1.130;

1.130
date	2001.06.20.00.10.16;	author leonardr;	state Exp;
branches;
next	1.129;

1.129
date	2001.06.19.16.17.29;	author leonardr;	state Exp;
branches;
next	1.128;

1.128
date	2001.06.19.16.09.17;	author leonardr;	state Exp;
branches;
next	1.127;

1.127
date	2001.06.18.22.12.20;	author leonardr;	state Exp;
branches;
next	1.126;

1.126
date	2001.06.14.00.17.21;	author leonardr;	state Exp;
branches;
next	1.125;

1.125
date	2001.06.13.21.52.22;	author elicia;	state Exp;
branches;
next	1.124;

1.124
date	2001.06.13.19.11.11;	author elicia;	state Exp;
branches;
next	1.123;

1.123
date	2001.06.13.17.30.16;	author leonardr;	state Exp;
branches;
next	1.122;

1.122
date	2001.06.13.16.41.11;	author leonardr;	state Exp;
branches;
next	1.121;

1.121
date	2001.06.12.23.10.43;	author leonardr;	state Exp;
branches;
next	1.120;

1.120
date	2001.06.12.19.10.45;	author jrobbins;	state Exp;
branches;
next	1.119;

1.119
date	2001.06.12.16.55.33;	author jrobbins;	state Exp;
branches;
next	1.118;

1.118
date	2001.06.09.01.13.20;	author leonardr;	state Exp;
branches;
next	1.117;

1.117
date	2001.06.09.01.02.15;	author leonardr;	state Exp;
branches;
next	1.116;

1.116
date	2001.06.08.22.36.12;	author leonardr;	state Exp;
branches;
next	1.115;

1.115
date	2001.06.08.17.18.16;	author leonardr;	state Exp;
branches;
next	1.114;

1.114
date	2001.06.07.22.47.05;	author leonardr;	state Exp;
branches;
next	1.113;

1.113
date	2001.06.07.03.20.22;	author dlr;	state Exp;
branches;
next	1.112;

1.112
date	2001.06.07.02.57.45;	author leonardr;	state Exp;
branches;
next	1.111;

1.111
date	2001.06.01.22.01.41;	author leonardr;	state Exp;
branches;
next	1.110;

1.110
date	2001.05.31.22.43.08;	author leonardr;	state Exp;
branches;
next	1.109;

1.109
date	2001.05.31.19.12.27;	author elicia;	state Exp;
branches;
next	1.108;

1.108
date	2001.05.31.02.41.37;	author leonardr;	state Exp;
branches;
next	1.107;

1.107
date	2001.05.30.20.15.25;	author leonardr;	state Exp;
branches;
next	1.106;

1.106
date	2001.05.30.01.11.12;	author leonardr;	state Exp;
branches;
next	1.105;

1.105
date	2001.05.25.19.45.22;	author leonardr;	state Exp;
branches;
next	1.104;

1.104
date	2001.05.25.18.46.22;	author leonardr;	state Exp;
branches;
next	1.103;

1.103
date	2001.05.25.16.42.40;	author leonardr;	state Exp;
branches;
next	1.102;

1.102
date	2001.05.25.16.35.49;	author leonardr;	state Exp;
branches;
next	1.101;

1.101
date	2001.05.25.01.45.28;	author leonardr;	state Exp;
branches;
next	1.100;

1.100
date	2001.05.23.01.50.13;	author dlr;	state Exp;
branches;
next	1.99;

1.99
date	2001.05.22.18.41.21;	author leonardr;	state Exp;
branches;
next	1.98;

1.98
date	2001.05.18.22.01.15;	author edk;	state Exp;
branches;
next	1.97;

1.97
date	2001.05.18.21.56.26;	author leonardr;	state Exp;
branches;
next	1.96;

1.96
date	2001.05.18.02.07.52;	author dlr;	state Exp;
branches;
next	1.95;

1.95
date	2001.05.18.02.00.42;	author dlr;	state Exp;
branches;
next	1.94;

1.94
date	2001.05.18.01.49.59;	author dlr;	state Exp;
branches;
next	1.93;

1.93
date	2001.05.17.19.47.08;	author dlr;	state Exp;
branches;
next	1.92;

1.92
date	2001.05.17.16.56.44;	author dprice;	state Exp;
branches;
next	1.91;

1.91
date	2001.05.17.16.42.51;	author dprice;	state Exp;
branches;
next	1.90;

1.90
date	2001.05.17.14.51.17;	author leonardr;	state Exp;
branches;
next	1.89;

1.89
date	2001.05.15.16.05.04;	author leonardr;	state Exp;
branches;
next	1.88;

1.88
date	2001.05.15.15.06.05;	author leonardr;	state Exp;
branches;
next	1.87;

1.87
date	2001.05.11.19.38.55;	author leonardr;	state Exp;
branches;
next	1.86;

1.86
date	2001.05.10.21.58.26;	author leonardr;	state Exp;
branches;
next	1.85;

1.85
date	2001.05.09.01.59.49;	author leonardr;	state Exp;
branches;
next	1.84;

1.84
date	2001.05.05.00.54.47;	author ms;	state Exp;
branches;
next	1.83;

1.83
date	2001.05.02.08.11.04;	author dlr;	state Exp;
branches;
next	1.82;

1.82
date	2001.05.01.21.47.21;	author leonardr;	state Exp;
branches;
next	1.81;

1.81
date	2001.04.26.16.16.43;	author leonardr;	state Exp;
branches;
next	1.80;

1.80
date	2001.04.24.20.02.07;	author dlr;	state Exp;
branches;
next	1.79;

1.79
date	2001.04.24.19.52.08;	author dlr;	state Exp;
branches;
next	1.78;

1.78
date	2001.04.24.17.53.36;	author dlr;	state Exp;
branches;
next	1.77;

1.77
date	2001.04.24.10.33.27;	author ms;	state Exp;
branches;
next	1.76;

1.76
date	2001.04.23.17.54.38;	author dlr;	state Exp;
branches;
next	1.75;

1.75
date	2001.04.23.17.24.19;	author dlr;	state Exp;
branches;
next	1.74;

1.74
date	2001.04.21.00.51.06;	author leonardr;	state Exp;
branches;
next	1.73;

1.73
date	2001.04.21.00.28.07;	author leonardr;	state Exp;
branches;
next	1.72;

1.72
date	2001.04.20.23.51.20;	author dlr;	state Exp;
branches;
next	1.71;

1.71
date	2001.04.20.23.17.16;	author leonardr;	state Exp;
branches;
next	1.70;

1.70
date	2001.04.20.23.09.40;	author dlr;	state Exp;
branches;
next	1.69;

1.69
date	2001.04.20.21.19.40;	author leonardr;	state Exp;
branches;
next	1.68;

1.68
date	2001.04.19.23.56.54;	author leonardr;	state Exp;
branches;
next	1.67;

1.67
date	2001.04.19.20.03.24;	author leonardr;	state Exp;
branches;
next	1.66;

1.66
date	2001.04.19.19.09.03;	author dlr;	state Exp;
branches;
next	1.65;

1.65
date	2001.04.19.18.50.08;	author leonardr;	state Exp;
branches;
next	1.64;

1.64
date	2001.04.18.23.50.24;	author dlr;	state Exp;
branches;
next	1.63;

1.63
date	2001.04.18.23.25.44;	author dlr;	state Exp;
branches;
next	1.62;

1.62
date	2001.04.13.23.38.01;	author ms;	state Exp;
branches;
next	1.61;

1.61
date	2001.04.07.21.49.12;	author dlr;	state Exp;
branches;
next	1.60;

1.60
date	2001.04.07.00.04.37;	author leonardr;	state Exp;
branches;
next	1.59;

1.59
date	2001.04.02.23.00.09;	author dlr;	state Exp;
branches;
next	1.58;

1.58
date	2001.04.02.21.37.56;	author dlr;	state Exp;
branches;
next	1.57;

1.57
date	2001.03.30.17.55.57;	author dlr;	state Exp;
branches;
next	1.56;

1.56
date	2001.03.29.03.05.42;	author elicia;	state Exp;
branches;
next	1.55;

1.55
date	2001.03.27.00.42.14;	author dlr;	state Exp;
branches
	1.55.2.1;
next	1.54;

1.54
date	2001.03.27.00.40.10;	author dlr;	state Exp;
branches;
next	1.53;

1.53
date	2001.03.23.02.28.00;	author dlr;	state Exp;
branches;
next	1.52;

1.52
date	2001.03.22.23.53.44;	author dlr;	state Exp;
branches;
next	1.51;

1.51
date	2001.03.22.23.19.57;	author dlr;	state Exp;
branches;
next	1.50;

1.50
date	2001.03.15.23.23.44;	author edk;	state Exp;
branches;
next	1.49;

1.49
date	2001.03.15.19.47.06;	author leonardr;	state Exp;
branches;
next	1.48;

1.48
date	2001.03.15.02.55.47;	author edk;	state Exp;
branches;
next	1.47;

1.47
date	2001.03.14.20.57.23;	author elicia;	state Exp;
branches;
next	1.46;

1.46
date	2001.03.14.18.37.24;	author leonardr;	state Exp;
branches;
next	1.45;

1.45
date	2001.03.14.17.55.21;	author leonardr;	state Exp;
branches;
next	1.44;

1.44
date	2001.03.13.04.00.45;	author leonardr;	state Exp;
branches;
next	1.43;

1.43
date	2001.03.13.02.21.04;	author leonardr;	state Exp;
branches;
next	1.42;

1.42
date	2001.03.12.23.01.00;	author dlr;	state Exp;
branches;
next	1.41;

1.41
date	2001.03.12.20.08.02;	author dlr;	state Exp;
branches;
next	1.40;

1.40
date	2001.03.10.03.52.04;	author edk;	state Exp;
branches;
next	1.39;

1.39
date	2001.03.09.23.30.16;	author dlr;	state Exp;
branches;
next	1.38;

1.38
date	2001.03.09.22.38.18;	author dlr;	state Exp;
branches;
next	1.37;

1.37
date	2001.03.09.01.24.22;	author leonardr;	state Exp;
branches;
next	1.36;

1.36
date	2001.03.08.17.47.58;	author leonardr;	state Exp;
branches;
next	1.35;

1.35
date	2001.03.08.03.30.42;	author dlr;	state Exp;
branches;
next	1.34;

1.34
date	2001.03.08.03.24.02;	author leonardr;	state Exp;
branches;
next	1.33;

1.33
date	2001.03.08.00.34.08;	author leonardr;	state Exp;
branches;
next	1.32;

1.32
date	2001.03.07.18.09.09;	author leonardr;	state Exp;
branches;
next	1.31;

1.31
date	2001.03.07.02.41.33;	author leonardr;	state Exp;
branches;
next	1.30;

1.30
date	2001.03.07.02.24.11;	author leonardr;	state Exp;
branches;
next	1.29;

1.29
date	2001.03.07.01.53.22;	author lucas;	state Exp;
branches;
next	1.28;

1.28
date	2001.03.02.00.53.49;	author elicia;	state Exp;
branches;
next	1.27;

1.27
date	2001.03.01.01.29.48;	author leonardr;	state Exp;
branches;
next	1.26;

1.26
date	2001.02.28.22.12.34;	author leonardr;	state Exp;
branches;
next	1.25;

1.25
date	2001.02.27.19.25.39;	author leonardr;	state Exp;
branches;
next	1.24;

1.24
date	2001.02.27.19.04.24;	author leonardr;	state Exp;
branches;
next	1.23;

1.23
date	2001.02.27.02.55.46;	author dlr;	state Exp;
branches;
next	1.22;

1.22
date	2001.02.27.02.17.11;	author elicia;	state Exp;
branches;
next	1.21;

1.21
date	2001.02.27.01.58.47;	author leonardr;	state Exp;
branches;
next	1.20;

1.20
date	2001.02.27.01.11.53;	author leonardr;	state Exp;
branches;
next	1.19;

1.19
date	2001.02.26.22.51.43;	author leonardr;	state Exp;
branches;
next	1.18;

1.18
date	2001.02.26.22.27.44;	author leonardr;	state Exp;
branches;
next	1.17;

1.17
date	2001.02.26.22.03.25;	author leonardr;	state Exp;
branches;
next	1.16;

1.16
date	2001.02.24.11.43.01;	author dlr;	state Exp;
branches;
next	1.15;

1.15
date	2001.02.24.03.59.53;	author dlr;	state Exp;
branches;
next	1.14;

1.14
date	2001.02.24.01.33.00;	author dlr;	state Exp;
branches;
next	1.13;

1.13
date	2001.02.23.19.35.48;	author leonardr;	state Exp;
branches;
next	1.12;

1.12
date	2001.02.23.19.06.51;	author stack;	state Exp;
branches;
next	1.11;

1.11
date	2001.02.23.16.04.57;	author leonardr;	state Exp;
branches;
next	1.10;

1.10
date	2001.02.23.15.58.06;	author leonardr;	state Exp;
branches;
next	1.9;

1.9
date	2001.02.23.01.51.35;	author leonardr;	state Exp;
branches;
next	1.8;

1.8
date	2001.02.22.20.29.09;	author edk;	state Exp;
branches;
next	1.7;

1.7
date	2001.02.22.02.39.42;	author leonardr;	state Exp;
branches;
next	1.6;

1.6
date	2001.02.22.01.41.43;	author leonardr;	state Exp;
branches;
next	1.5;

1.5
date	2001.02.22.01.22.57;	author dlr;	state Exp;
branches;
next	1.4;

1.4
date	2001.02.22.01.08.04;	author elicia;	state Exp;
branches;
next	1.3;

1.3
date	2001.02.22.00.38.46;	author dlr;	state Exp;
branches;
next	1.2;

1.2
date	2001.02.21.03.02.22;	author edk;	state Exp;
branches;
next	1.1;

1.1
date	2001.01.28.01.18.22;	author dlr;	state dead;
branches
	1.1.1.1
	1.1.2.1;
next	;

1.1.1.1
date	2001.02.21.00.32.40;	author dlr;	state Exp;
branches;
next	;

1.1.2.1
date	2001.01.28.01.18.22;	author dlr;	state Exp;
branches;
next	1.1.2.2;

1.1.2.2
date	2001.01.29.04.23.47;	author dlr;	state Exp;
branches;
next	1.1.2.3;

1.1.2.3
date	2001.01.29.04.31.36;	author dlr;	state Exp;
branches;
next	1.1.2.4;

1.1.2.4
date	2001.01.29.16.43.26;	author dlr;	state Exp;
branches;
next	1.1.2.5;

1.1.2.5
date	2001.01.29.18.43.24;	author dlr;	state Exp;
branches;
next	1.1.2.6;

1.1.2.6
date	2001.01.29.22.07.02;	author dlr;	state Exp;
branches;
next	1.1.2.7;

1.1.2.7
date	2001.01.30.01.21.05;	author leonardr;	state Exp;
branches;
next	1.1.2.8;

1.1.2.8
date	2001.01.30.06.55.01;	author dlr;	state Exp;
branches;
next	1.1.2.9;

1.1.2.9
date	2001.01.30.07.36.25;	author dlr;	state Exp;
branches;
next	1.1.2.10;

1.1.2.10
date	2001.01.30.19.48.03;	author leonardr;	state Exp;
branches;
next	1.1.2.11;

1.1.2.11
date	2001.01.30.23.56.37;	author leonardr;	state Exp;
branches;
next	1.1.2.12;

1.1.2.12
date	2001.01.31.00.58.12;	author dlr;	state Exp;
branches;
next	1.1.2.13;

1.1.2.13
date	2001.01.31.01.05.57;	author dlr;	state Exp;
branches;
next	1.1.2.14;

1.1.2.14
date	2001.01.31.02.41.51;	author dlr;	state Exp;
branches;
next	1.1.2.15;

1.1.2.15
date	2001.01.31.21.51.37;	author dlr;	state Exp;
branches;
next	1.1.2.16;

1.1.2.16
date	2001.02.01.02.57.46;	author leonardr;	state Exp;
branches;
next	1.1.2.17;

1.1.2.17
date	2001.02.01.20.53.00;	author dlr;	state Exp;
branches;
next	1.1.2.18;

1.1.2.18
date	2001.02.05.16.36.18;	author leonardr;	state Exp;
branches;
next	1.1.2.19;

1.1.2.19
date	2001.02.05.17.04.46;	author leonardr;	state Exp;
branches;
next	1.1.2.20;

1.1.2.20
date	2001.02.05.17.55.46;	author leonardr;	state Exp;
branches;
next	1.1.2.21;

1.1.2.21
date	2001.02.05.20.24.02;	author leonardr;	state Exp;
branches;
next	1.1.2.22;

1.1.2.22
date	2001.02.05.20.52.23;	author dlr;	state Exp;
branches;
next	1.1.2.23;

1.1.2.23
date	2001.02.08.00.35.25;	author elicia;	state Exp;
branches;
next	1.1.2.24;

1.1.2.24
date	2001.02.08.02.28.35;	author dlr;	state Exp;
branches;
next	1.1.2.25;

1.1.2.25
date	2001.02.08.18.19.12;	author leonardr;	state Exp;
branches;
next	1.1.2.26;

1.1.2.26
date	2001.02.09.16.03.43;	author leonardr;	state Exp;
branches;
next	1.1.2.27;

1.1.2.27
date	2001.02.09.22.22.25;	author dlr;	state Exp;
branches;
next	1.1.2.28;

1.1.2.28
date	2001.02.09.22.29.35;	author dlr;	state Exp;
branches;
next	1.1.2.29;

1.1.2.29
date	2001.02.09.22.34.15;	author dlr;	state Exp;
branches;
next	1.1.2.30;

1.1.2.30
date	2001.02.09.22.45.37;	author dlr;	state Exp;
branches;
next	1.1.2.31;

1.1.2.31
date	2001.02.09.23.05.21;	author dlr;	state Exp;
branches;
next	1.1.2.32;

1.1.2.32
date	2001.02.09.23.20.59;	author dlr;	state Exp;
branches;
next	1.1.2.33;

1.1.2.33
date	2001.02.10.00.22.34;	author dlr;	state Exp;
branches;
next	1.1.2.34;

1.1.2.34
date	2001.02.10.21.01.58;	author dlr;	state Exp;
branches;
next	1.1.2.35;

1.1.2.35
date	2001.02.10.22.09.24;	author dlr;	state Exp;
branches;
next	1.1.2.36;

1.1.2.36
date	2001.02.10.22.20.01;	author dlr;	state Exp;
branches;
next	1.1.2.37;

1.1.2.37
date	2001.02.10.22.28.04;	author dlr;	state Exp;
branches;
next	1.1.2.38;

1.1.2.38
date	2001.02.12.16.24.32;	author leonardr;	state Exp;
branches;
next	1.1.2.39;

1.1.2.39
date	2001.02.12.21.24.43;	author leonardr;	state Exp;
branches;
next	1.1.2.40;

1.1.2.40
date	2001.02.13.03.35.34;	author leonardr;	state Exp;
branches;
next	1.1.2.41;

1.1.2.41
date	2001.02.13.19.13.28;	author elicia;	state Exp;
branches;
next	1.1.2.42;

1.1.2.42
date	2001.02.13.19.54.35;	author leonardr;	state Exp;
branches;
next	1.1.2.43;

1.1.2.43
date	2001.02.13.22.45.20;	author elicia;	state Exp;
branches;
next	1.1.2.44;

1.1.2.44
date	2001.02.13.22.48.17;	author dlr;	state Exp;
branches;
next	1.1.2.45;

1.1.2.45
date	2001.02.14.00.15.27;	author dlr;	state Exp;
branches;
next	1.1.2.46;

1.1.2.46
date	2001.02.14.00.34.10;	author leonardr;	state Exp;
branches;
next	1.1.2.47;

1.1.2.47
date	2001.02.14.01.09.57;	author leonardr;	state Exp;
branches;
next	1.1.2.48;

1.1.2.48
date	2001.02.14.01.18.02;	author dlr;	state Exp;
branches;
next	1.1.2.49;

1.1.2.49
date	2001.02.14.01.36.29;	author leonardr;	state Exp;
branches;
next	1.1.2.50;

1.1.2.50
date	2001.02.14.23.20.29;	author leonardr;	state Exp;
branches;
next	1.1.2.51;

1.1.2.51
date	2001.02.15.15.32.47;	author leonardr;	state Exp;
branches;
next	1.1.2.52;

1.1.2.52
date	2001.02.15.18.40.32;	author elicia;	state Exp;
branches;
next	1.1.2.53;

1.1.2.53
date	2001.02.15.19.09.47;	author leonardr;	state Exp;
branches;
next	1.1.2.54;

1.1.2.54
date	2001.02.15.19.59.07;	author leonardr;	state Exp;
branches;
next	1.1.2.55;

1.1.2.55
date	2001.02.15.20.50.37;	author leonardr;	state Exp;
branches;
next	1.1.2.56;

1.1.2.56
date	2001.02.16.01.54.57;	author dlr;	state Exp;
branches;
next	1.1.2.57;

1.1.2.57
date	2001.02.16.03.01.27;	author dlr;	state Exp;
branches;
next	1.1.2.58;

1.1.2.58
date	2001.02.16.16.09.06;	author leonardr;	state Exp;
branches;
next	1.1.2.59;

1.1.2.59
date	2001.02.16.16.26.36;	author leonardr;	state Exp;
branches;
next	1.1.2.60;

1.1.2.60
date	2001.02.16.20.01.32;	author elicia;	state Exp;
branches;
next	1.1.2.61;

1.1.2.61
date	2001.02.16.20.25.40;	author dlr;	state Exp;
branches;
next	1.1.2.62;

1.1.2.62
date	2001.02.17.00.17.21;	author dlr;	state Exp;
branches;
next	1.1.2.63;

1.1.2.63
date	2001.02.17.00.20.18;	author elicia;	state Exp;
branches;
next	1.1.2.64;

1.1.2.64
date	2001.02.17.00.24.05;	author dlr;	state Exp;
branches;
next	1.1.2.65;

1.1.2.65
date	2001.02.17.00.39.02;	author dlr;	state Exp;
branches;
next	1.1.2.66;

1.1.2.66
date	2001.02.17.21.02.29;	author leonardr;	state Exp;
branches;
next	1.1.2.67;

1.1.2.67
date	2001.02.18.00.48.53;	author elicia;	state Exp;
branches;
next	1.1.2.68;

1.1.2.68
date	2001.02.18.01.06.24;	author ms;	state Exp;
branches;
next	1.1.2.69;

1.1.2.69
date	2001.02.18.02.54.29;	author leonardr;	state Exp;
branches;
next	1.1.2.70;

1.1.2.70
date	2001.02.18.09.21.17;	author dlr;	state Exp;
branches;
next	1.1.2.71;

1.1.2.71
date	2001.02.20.01.05.50;	author dlr;	state Exp;
branches;
next	1.1.2.72;

1.1.2.72
date	2001.02.20.01.06.43;	author dlr;	state Exp;
branches;
next	1.1.2.73;

1.1.2.73
date	2001.02.20.23.14.20;	author leonardr;	state Exp;
branches;
next	;

1.55.2.1
date	2001.03.28.23.30.11;	author dlr;	state Exp;
branches
	1.55.2.1.4.1;
next	;

1.55.2.1.4.1
date	2001.04.17.10.40.25;	author jrobbins;	state Exp;
branches;
next	1.55.2.1.4.2;

1.55.2.1.4.2
date	2001.04.24.01.41.04;	author leonardr;	state Exp;
branches;
next	1.55.2.1.4.3;

1.55.2.1.4.3
date	2001.04.24.20.53.51;	author dlr;	state Exp;
branches;
next	1.55.2.1.4.4;

1.55.2.1.4.4
date	2001.04.26.03.54.56;	author dlr;	state Exp;
branches;
next	1.55.2.1.4.5;

1.55.2.1.4.5
date	2001.05.05.22.41.43;	author jrobbins;	state Exp;
branches;
next	1.55.2.1.4.6;

1.55.2.1.4.6
date	2001.05.10.22.15.21;	author leonardr;	state Exp;
branches;
next	1.55.2.1.4.7;

1.55.2.1.4.7
date	2001.05.17.16.38.06;	author dprice;	state Exp;
branches;
next	1.55.2.1.4.8;

1.55.2.1.4.8
date	2001.05.17.16.58.20;	author dprice;	state Exp;
branches;
next	1.55.2.1.4.9;

1.55.2.1.4.9
date	2001.05.18.22.03.54;	author edk;	state Exp;
branches;
next	1.55.2.1.4.10;

1.55.2.1.4.10
date	2001.05.24.23.31.09;	author leonardr;	state Exp;
branches;
next	1.55.2.1.4.11;

1.55.2.1.4.11
date	2001.05.25.21.53.14;	author leonardr;	state Exp;
branches;
next	1.55.2.1.4.12;

1.55.2.1.4.12
date	2001.05.25.22.06.33;	author leonardr;	state Exp;
branches;
next	1.55.2.1.4.13;

1.55.2.1.4.13
date	2001.06.01.19.18.07;	author leonardr;	state Exp;
branches;
next	1.55.2.1.4.14;

1.55.2.1.4.14
date	2001.06.04.04.27.03;	author dlr;	state Exp;
branches;
next	1.55.2.1.4.15;

1.55.2.1.4.15
date	2001.06.04.05.08.42;	author dlr;	state Exp;
branches;
next	1.55.2.1.4.16;

1.55.2.1.4.16
date	2001.06.04.07.05.12;	author dlr;	state Exp;
branches;
next	1.55.2.1.4.17;

1.55.2.1.4.17
date	2001.06.08.22.38.57;	author leonardr;	state Exp;
branches;
next	1.55.2.1.4.18;

1.55.2.1.4.18
date	2001.06.13.17.33.26;	author leonardr;	state Exp;
branches;
next	1.55.2.1.4.19;

1.55.2.1.4.19
date	2001.07.13.14.45.40;	author leonardr;	state Exp;
branches;
next	1.55.2.1.4.20;

1.55.2.1.4.20
date	2001.07.14.21.58.15;	author edk;	state Exp;
branches;
next	1.55.2.1.4.21;

1.55.2.1.4.21
date	2001.08.01.23.47.17;	author leonardr;	state Exp;
branches
	1.55.2.1.4.21.2.1;
next	1.55.2.1.4.22;

1.55.2.1.4.22
date	2001.08.10.19.54.49;	author leonardr;	state Exp;
branches;
next	;

1.55.2.1.4.21.2.1
date	2001.08.10.19.57.29;	author leonardr;	state Exp;
branches;
next	;


desc
@@


1.172
log
@Updated for latest Turbine API changes.
@
text
@/* ================================================================
 * Copyright (c) 2000-2001 CollabNet.  All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * 3. The end-user documentation included with the redistribution, if
 * any, must include the following acknowlegement: "This product includes
 * software developed by CollabNet (http://www.collab.net/)."
 * Alternately, this acknowlegement may appear in the software itself, if
 * and wherever such third-party acknowlegements normally appear.
 * 
 * 4. The hosted project names must not be used to endorse or promote
 * products derived from this software without prior written
 * permission. For written permission, please contact info@@collab.net.
 * 
 * 5. Products derived from this software may not use the "Tigris" name
 * nor may "Tigris" appear in their names without prior written
 * permission of CollabNet.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL COLLABNET OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by many
 * individuals on behalf of CollabNet.
 */

package org.tigris.helm.om;

import java.util.*;
import java.text.ParseException;
import java.io.*;
import java.math.BigInteger;
import java.net.InetAddress;

import org.apache.torque.util.BasePeer;
import org.apache.torque.util.Criteria;
import org.apache.commons.util.ObjectUtils;
import org.apache.commons.util.StringUtils;
import org.apache.turbine.ParameterParser;
import org.apache.turbine.util.Log;
import org.apache.commons.util.exception.NestableException;
import org.apache.torque.pool.DBConnection;
import org.apache.fulcrum.TurbineServices;
import org.apache.fulcrum.velocity.TurbineVelocity;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.context.Context;

import gnu.regexp.*;

import org.tigris.helm.AlreadyExists;
import org.tigris.helm.DoesNotExist;
import org.tigris.helm.om.profile.*;
import org.tigris.helm.security.*;
import org.tigris.helm.util.URL;
import org.tigris.helm.event.ToolEvent;
import org.tigris.helm.tool.*;
import org.tigris.helm.tool.audit.*;
import org.tigris.helm.tool.task.ScriptTool;
import org.tigris.helm.tool.paginate.*;


/** 
 * Project business object.
 *
 * @@author <a href="mailto:leonardr@@collab.net">Leonard Richardson</a>
 * @@author <a href="mailto:dlr@@collab.net">Daniel Rall</a>
 */
public class Project extends BaseProject 
    implements ProjectType, Comparable, ViewableBy
{
    /**
     * Flag indicating whether in debug mode.
     */
    private static final boolean DEBUG = false;

    /**
     * Message to use when a user tries to do something to a deleted
     * project.
     */
    private static final String DELETED_PROJECT_MESSAGE = 
        "This project has been deleted. You may not modify it";

    /**
     * The query string parameter name of a servlet display message.
     * <p>
     * HACK: This is totally hokey.  Get rid of it.
     */
    public static final String MESSAGE = "msg";

    /**
     * The name of the file containing the project's main screen.
     */
    private static final String INDEX_FILE = "index.html";

    /**
     * List of valid subproject types.
     */
    protected static String[] VALID_SUBPROJECT_TYPES = {REGULAR_TYPE};

    /**
     * List of valid superproject types.
     */
    protected static String[] VALID_SUPERPROJECT_TYPES = 
    {
        DOMAIN_TYPE, 
        REGULAR_TYPE
    };
    
    /**
     * This is used to construct email address which respect developer
     * sandboxes.
     */
    protected static String MAIL_PREFIX_KEY = "mailPrefix";

    /**
     * Key for IS_VHOSTING.
     */
    private static final String IS_VHOSTING_KEY = "IS_VHOSTING";

    /**
     * Description of the audit log entry created when a project of this
     * type is added.  
     */
    private static final String AUDIT_LOG_ON_ADD = "Project added: ";

    /**
     * Description of the audit log entry created when a project of this
     * type is deleted.
     */
    private static final String AUDIT_LOG_ON_DELETE = "Project deleted: ";

    /**
     * Description of the audit log entry created when a project of this
     * type is edited.
     */
    private static final String AUDIT_LOG_ON_EDIT = "Project edited: ";

    /**
     * Description of the audit log entry created when a project of this
     * type is locked.
     */
    private static final String AUDIT_LOG_ON_LOCK = "Project locked: ";

    /**
     * Description of the audit log entry created when a project of this
     * type is unlocked.
     */
    private static final String AUDIT_LOG_ON_UNLOCK = "Project unlocked: ";

    /**
     * The regular expression used to check project names for validity.
     */
    private static final String PROJECT_NAME_RE =
        "(^[a-zA-Z0-9]$|^[a-zA-Z0-9][-a-zA-Z0-9]{0,62}[a-zA-Z0-9]$)";

    /**
     * The name of the template used to create the body of the project 
     * creation announcement email.
     */
    private static final String CREATED_TMPL =
        TemplateTool.getPathToMessageTemplate("ProjectCreated.vm");

    /**
     * The name of the template used to create the body of the project 
     * disapproval email.
     */
    private static final String DISAPPROVE_TMPL =
        TemplateTool.getPathToMessageTemplate("ProjectDisapprove.vm");

    /**
     * The name of the template used to create the body of the email
     * sent when a project is locked.  
     */
    private static final String LOCK_TMPL =
        TemplateTool.getPathToMessageTemplate("ProjectLocked.vm");

    /**
     * The name of the template used to create the body of the email
     * sent when a project is locked.  
     */
    private static final String UNLOCK_TMPL =
        TemplateTool.getPathToMessageTemplate("ProjectUnlocked.vm");

    /**
     * When constructing absolute URLs to servlets, this is used.
     */
    private static final String SERVLET_PREFIX = "/servlets/"; 
    
    /**
     * A mutex for the addition of new projects.
     * <p>
     * TODO: Handle this via database table locking.
     */
    private static Object addLock = new Object();

    /**
     * Returns the types of projects which can be subprojects of this type
     * of project.
     */
    protected String[] getValidSubprojectTypes()
    {
        return this.VALID_SUBPROJECT_TYPES;
    }

    /**
     * Returns the types of projects which can be superprojects of this type
     * of project.
     */
    protected String[] getValidSuperprojectTypes()
    {
        return this.VALID_SUPERPROJECT_TYPES;
    }

    /**
     * Returns the business object type which corresponds to this project.
     */
    private String getCorrespondingBOType()
    {
        String type = BOType.PROJECT_TYPE;
        if (isHost())
        {
            type = BOType.HOST_TYPE;
        }
        else if (isDomain())
        {
            type = BOType.DOMAIN_TYPE;
        }
        return type;
    }

    /**
     * Checks whether a project name is valid.
     *
     * @@param name The project name to check.
     * @@return     Whether the name is valid.
     */
    public static boolean isValidName(String name)
    {
        boolean isValid = false;

        if (name != null && name.length() > 0)
        {
            RE projectRE = null;
            try
            {
                projectRE = new RE(PROJECT_NAME_RE);
                isValid = projectRE.isMatch(name);
            }
            catch (REException e)
            {
                Log.error(e);
            }
        }

        return isValid;
    }

    /**
     * Hashtable containing a description of the user account.
     *
     * Used by the xmlrpc handlers.  If value is null, we 
     * return the empty string.  xmlrpc does not support nulls
     * (nor exceptions).
     */
    public final Hashtable asHashtable()
    {
        Hashtable t = new Hashtable();
        hashPutCheckForNull(t,"id", getID());
        hashPutCheckForNull(t,"name", getName());
        hashPutCheckForNull(t,"wordMark", getWordMark());
        return t;
    }

    /**
     * Macro which encapsulates transformation of a word mark to a
     * canonical name.
     *
     * @@param wordMark The mixed case word mark to transform.
     * @@return         The name as lower case.
     */
    private static final String transformWordMarkToName(String wordMark)
    {
        return wordMark.toLowerCase();
    }

    /**
     * Throws an exception if the project has been deleted.
     */
    public void assertNondeletion()
        throws Exception
    {
        if (isDeleted())
        {
            throw new NestableException(DELETED_PROJECT_MESSAGE);
        }
    }

    /**
     * Asserts that a project with the specified name does not already
     * exist in the specified domain.
     *
     * @@param domain The domain to check for the project in.
     * @@param name   The name of the project to check for.
     *
     * @@exception AlreadyExists The project exists.  
     */
    public static void assertNonexistance(Domain domain, String name)
        throws AlreadyExists, Exception
    {
        Project p = null;
        try 
        {
            p = ProjectPeer.getInstanceByName(domain, name);
        }
        catch (DoesNotExist ignore) 
        {
        }

        if (p != null)
        {
            String type = p.getType();
            if (p.isDeleted())
            {
                type = "Project (deleted) ";
            }
            throw new AlreadyExists(type + " named " + name,
                                    "domain named " + domain.getName());
        }
    }

    /**
     * Asserts that the given child is not a recursive parent of the
     * given parent; otherwise, if it becomes a child as wellthere
     * will be circularity.
     *
     * @@param parent The potential parent
     * @@param child The potential child
     */
    public static void assertNoncircularity(Project parent, Project child)
        throws NestableException, Exception
    {
        if (parent.getPermissionedParents().contains(child))
        {
            throw new NestableException
                ("Making " + child.getName() + " a child of " 
                 + parent.getName() + " would cause circularity.");
        }
    }

    /**
     * Asserts that this given project can be deleted.
     */
    public void assertIsDeletable()
        throws NestableException, Exception
    {
        if (hasSubprojects())
        {
            throw new NestableException
                (getName() + " still has subprojects. To delete " + getName() 
                 + ", you must first delete or move all its subprojects.");
        }
    }
    
    //
    // Accessor methods for particular projects.
    //

    /**
     * Returns the name of the project in case which it is saved to the
     * database (i.e. it is possible that mixed case text may be returned).
     */
    public final String getWordMark()
    {
        return super.getName();
    }

    /**
     * Returns the name of the project in lower case.
     */
    public final String getName()
    {
        return super.getName().toLowerCase();
    }
    
    /**
     * Returns the brand of this project--not used yet.
     */
    public String getBrand()
    {
        return "";
    }

    /**
     * Returns this project's license.
     */
    public License getLicense()
        throws Exception
    {
        try
        {
        String myID = getLicenseId().toString();
        License license = null;
        if (StringUtils.isValid(myID))
        {            
            license = LicensePeer.getInstance(myID);
        }
        return license;
        }
        catch (Exception e)
        {
            Log.error(e);
        }
        return null;
    }

    /**
     * Returns the ID of this project's domain as a string.
     */
    public String getDomainID()
        throws Exception
    {
        return getDomain().getID();
    }   

    /**
     * Returns the name of this project's domain. dlr would like to see this
     * method deprecated.
     */
    public String getDomainName()
        throws Exception
    {        
        return getDomain().getName();
    }

    /**
     * Returns this project's domain.
     */
    public Domain getDomain()
        throws Exception
    {
        Domain myDomain = null;
        if (isHost() || isDomain())
        {
            myDomain = (Domain)this;
        }
        else
        {
            List parents = getPermissionedParents();
            for (Iterator i = parents.iterator(); i.hasNext();)
            {
                Project p = (Project)i.next(); 
                if ((p.isDomain() && !p.isHost()))
                {
                    myDomain = (Domain)p;
                    break;
                }
            }
        }
        if (myDomain == null)
        {
            throw new NestableException("Project " + getName() 
                                       + " is not in any domain.");
        }
        return myDomain;
    }

    /**
     * Gets the profile for this project.
     */
    public ProjectProfile getProjectProfile()
        throws Exception
    {
        return ProjectProfiles.getInstance().get(getProfile());
    }

    /**
     * Returns true iff this project is supposed to be using the
     * project home page instead of its description.
     */
    public boolean useIndex()
    {
        boolean useIndex;
        try
        {
            useIndex = DBFlag.booleanValueOf(getUseIndex());
        }
        catch (Exception e)
        {
            Log.warn("Error checking Project.useIndex()", e);
            useIndex = false;
        }
        return useIndex;
    }

    /**
     * Checks whether a project has been approved. A project is approved
     * if it's "approved by" field is not set to the "nobody" user.
     *
     * @@return     Whether the project has been approved.
     *
     */
    public boolean isApproved()
        throws Exception
    {
        String approvedBy = String.valueOf(getApprovedBy());
        return (!approvedBy.equals(UserPeer.getNobodyUser().getID()));
    }   

    /**
     * Returns true iff the project is not publically viewable.
     */
    public boolean isPrivate()
    {
        return !isPublic();
    }

    /**
     * Returns true iff the project is publically viewable.
     */
    public boolean isPublic()
    {
        return DBFlag.booleanValueOf(getIsPublic());
    }

    /**
     * Returns true iff this project is locked.
     */
    public boolean isLocked()
    {
        return DBFlag.booleanValueOf(getLocked());
    }

    /**
     * Compares this project to another object for sorting purposes.
     */
    public int compareTo(Object o)
        throws ClassCastException
    {
        Project p = (Project)o;
        return getName().compareTo(p.getName());
    }

    /**
     * Returns true iff the project is the default project.
     */
    public boolean isDefaultProject()
    {
        return (getName().equals(ProjectPeer.DEFAULT_PROJECT_NAME));
    }

    /**
     * Returns the human-readable, capitalized type of this project.
     */
    public String getHumanReadableType()
    {
        String type = getType();
        if (type.equals(ProjectType.REGULAR_TYPE))
        {
            type = "Project";
        }
        else
        {
            type = StringUtils.firstLetterCaps(type);
        }
        return type;
    }

    /**
     * Returns true iff this project has been deleted.
     */
    public boolean isDeleted()
    {
        return getType().equals(DELETED_TYPE);
    }

    /**
     * Returns true iff the project is that special project representing
     * all projects on the host.
     */
    public boolean isHost()
    {
        return (this instanceof Host);
    }

    /**
     * Returns true iff the project is a special project representing
     * the projects in a domain.
     */
    public boolean isDomain()
    {
        return (this instanceof Domain);
    }

    /**
     * Returns true iff the project is a category project.
     */
    public boolean isCategory()
    {
        return (this instanceof Category);
    }

    /**
     * Returns true iff the project is a project group.
     */
    public boolean isGroup()
    {
        return (this instanceof ProjectGroup);
    }

    /**
     * Returns true iff the project represents a base project.
     */
    public boolean isBase()
    {
        return ("base".equals(this.getType()));
    }

    //
    // Methods related to groups. 
    //

    //// Display methods

    /**
     * Returns the project of which this is a subproject. May return a
     * domain project, the host project (if called upon a domain project),
     * or null (if called upon the host project).
     */
    public Project getParentProject()
        throws Exception
    {
        List possibleParents = getDirectParents();
        Project parent = null;
        for (Iterator i = possibleParents.iterator(); i.hasNext();)
        {
            Project p = (Project)i.next();
            if (potentialChildOf(p.getType()))
            {
                parent = p;
                break;
            }
        }
        return parent;
    }

    /**
     * subproject.getNameWithParent() = "subproject (in superproject)"
     * subsubproject.getNameWithParent() = "subsubproject (in subproject)"
     * superproject.getNameWithParent() = "subproject"
     */
    public String getNameWithParent()
        throws Exception
    {        
        StringBuffer s = new StringBuffer(getName());
        Project parent = getParentProject();
        if (!(parent.isHost() || parent.isDomain()))
        {
            s.append(" (in ").append(parent.getName()).append(')');
        }
        return s.toString();
    }

    /**
     * Returns an array containing this project's ID and the ID
     * of every parent project.
     */
    public String[] getRecursiveParentIDs()
        throws Exception
    {
        List parents = getInheritancePath();
        String[] parentIDs = new String[parents.size()];
        for (int i = 0; i < parentIDs.length; i++)
        {
           parentIDs[i] = ((Project)parents.get(i)).getID();
        }
        return parentIDs;
    }

    /**
     * Returns a list of this projects' recursive parents, starting at
     * the host and ending in the project itself.
     */
    public List getInheritancePath()
        throws Exception
    {        
        Stack s = new Stack();
        Project p = this;
        while (p != null)
        {
            s.push(p);
            p = p.getParentProject();
        }
        List l = new ArrayList(s.size());
        while (!s.empty())
        {
            l.add(s.pop());
        }
        return l;
    }

    /**
     * Returns the project's parentage, each parent project separated
     * by a dot, not including the domain or host projects.
     * eg. "supersuperproject.superproject.project"
     */
    public String getDottedPath()
        throws Exception
    {        
        StringBuffer s = new StringBuffer();
        boolean first = true;
        for (Iterator i = getInheritancePath().iterator(); i.hasNext();)
        {
            Project p = (Project)i.next();            
            if (!(p.isHost() || p.isDomain()))
            {
                if (!first)
                {
                    s.append('.');
                }
                else
                {
                    first=false;
                }
                s.append(p.getName());
            }
        }
        return s.toString();
    }

    /**
     * Returns all the projects which could conceivably be made a
     * parent of this project.  
     */
    public List getPotentialParents(AccessControlList acl)
        throws Exception
    {
        String myID = getID();
        Criteria c = new Criteria(3);
        c.addDescendingOrderByColumn(ProjectPeer.TYPE);
        c.addAscendingOrderByColumn(ProjectPeer.NAME);

        String[] validTypes = getValidSuperprojectTypes();
        List filtered = null;
        if (validTypes.length == 0)
        {
            filtered = new ArrayList(0);
        }
        else
        {
            c.addIn(ProjectPeer.TYPE, validTypes);

            if (myID != null)
            {
                List myChildren = getPermissionedChildren();
                String[] childIDs = new String[myChildren.size()];
                if (childIDs.length > 0)
                {
                    for (int i = 0; i < childIDs.length; i++)
                    {
                        childIDs[i] = ((Project)myChildren.get(i)).getID();
                    }
                    c.addNotIn(ProjectPeer.PROJECT_ID, childIDs);
                }
            }
            
            List unfiltered = ProjectPeer.doSelect(c);
            List toRemove = new ArrayList(unfiltered.size());
            filtered = new ArrayList(unfiltered.size());
            
            for (Iterator i = unfiltered.iterator(); i.hasNext();)
            {            
                Project p = (Project)i.next();
                if (p.isDomain())
                {
                    filtered.add(p);
                    toRemove.add(p);
                }
                
                if (!acl.hasPermission("Project - Designate Subproject", p) 
                    || (myID != null && myID.equals(p.getID())))
                {
                    toRemove.add(p);
                }
            }
            unfiltered.removeAll(toRemove);
            filtered.addAll(unfiltered);
        }
        return filtered;
    }

    /**
     * Throws an exception iff the given project is not allowed to be
     * a subproject of this project.  
     */
    public void assertPotentialParenthoodOf(String type)
        throws NestableException
    {
        if (!potentialParentOf(type))
        {
            throw new NestableException
                (getName() + " cannot have as a subproject a project of type " 
                 + type);            
        }
    }

    /**
     * Returns true iff this project is allowed to be a superproject
     * of a project of the given type.  
     */
    public boolean potentialParentOf(String type)
    {
        return typeIsIn(type, getValidSubprojectTypes());
    }

    /**
     * Returns true iff this given project is allowed to be a subproject
     * of a project of the given type.
     */
    public boolean potentialChildOf(String type)
    {
        return typeIsIn(type, getValidSuperprojectTypes());
    }

    /**
     * Returns a list of all the project groups to which this project
     * directly belongs.  
     */
    public List getProjectGroups()
        throws Exception
    {
        return getGroupsOfType(Project.GROUP_TYPE);
    }

    /**
     * Returns a list of all the categories to which this project belongs.
     */
    public List getCategories()
        throws Exception
    {
        return getGroupsOfType(Project.CATEGORY_TYPE);
    }

    /**
     * Returns a list of all the project groups which are of the specified
     * type and to which this project directly belongs.
     */
    private List getGroupsOfType(String type)
        throws Exception
    {
        Criteria c = new Criteria()
            .addJoin(ProjectProjectgroupPeer.PROJECT_GROUP_ID, 
                     ProjectPeer.PROJECT_ID)
            .add(ProjectProjectgroupPeer.PROJECT_ID, getProjectId());
        if (type != null)
        {
            c.add(ProjectPeer.TYPE, type);
        }
        return ProjectPeer.doSelect(c);
    }

    /**
     * Returns true iff this project has subprojects.
     */
    public boolean hasSubprojects()
        throws Exception
    {
        return (getNumberOfSubprojects(null) > 0);
    }

    /**
     * Returns the number of subprojects this project has.
     */
    public int getNumberOfSubprojects(AccessControlList acl)
        throws Exception
    {
        return getSubprojects(acl).size();
    }    

    /**
     * Gets the regular subprojects of this project.
     */
    public PeerBackedList getSubprojects(AccessControlList acl)
        throws ACLException, Exception
    {
        return getSubprojects(null, null, acl);
    }

    /**
     * Gets the regular subprojects of this project.
     */
    public PeerBackedList getSubprojects
        (Integer itemsPerPage, Criteria filter, AccessControlList acl)
        throws ACLException, Exception
    {
        return getSubprojects(itemsPerPage, null, filter, acl);
    }

    /**
     * Gets this project's subprojects of the specified type.
     */
    protected PeerBackedList getSubprojects
        (Integer itemsPerPage, Criteria filter, AccessControlList acl, 
         String type)
        throws Exception
    {
        return getSubprojects(itemsPerPage, null, filter, acl, type);
    }

    /**
     * Gets the regular subprojects of this project.
     */
    public PeerBackedList getSubprojects
        (Integer itemsPerPage, Criteria base, Criteria filter, 
         AccessControlList acl)
        throws ACLException, Exception
    {
        return getSubprojects(itemsPerPage, base, filter, acl, 
                              this.getValidSubprojectTypes());
    }

    /**
     * Gets this project's subprojects of the specified type.
     */
    protected PeerBackedList getSubprojects
        (Integer itemsPerPage, Criteria base, Criteria filter, 
         AccessControlList acl, String type)
        throws Exception
    {
        String[] types = {type};
        return getSubprojects(itemsPerPage, null, filter, acl, types);
    }

    /**
     * Gets this project's subprojects which are of one of the
     * specified types.  
     */
    protected PeerBackedList getSubprojects
        (Integer itemsPerPage, Criteria base, Criteria filter, 
         AccessControlList acl, String[] types)
        throws Exception
    {
        return getDirectChildren(itemsPerPage, base, filter, acl, types);
    }

    /**
     * Returns all direct parents of this project; ie. all direct
     * mappings in the project group mapping table for which this is
     * the child project. 
     */
    public List getDirectParents()
        throws Exception
    {
        return ProjectProjectgroupPeer.getDirectParents(this);
    }


    /**
     * Returns true iff this project has the given project as a direct
     * child.
     */
    public boolean hasDirectChild(Project potentialChild)
        throws Exception
    {
        return (getDirectChildren().contains(potentialChild));
    }

    /**
     * Returns all direct children of this project; ie. all direct
     * mappings in the project group mapping table for which this is
     * the parent project. 
     */
    public List getDirectChildren()
        throws Exception
    {
        return ProjectProjectgroupPeer.getDirectChildren(this);
    }

    /**
     * Gets this project's direct children which are of one of the
     * specified types.  
     */
    protected PeerBackedList getDirectChildren
        (Integer itemsPerPage, Criteria base, Criteria filter, 
         AccessControlList acl, String[] types)
        throws Exception
    {
        if (base == null)
        {
            base = new Criteria(3);
        }
        base.putAll(ProjectProjectgroupPeer.directChildrenCriteria(this));
        base.addIn(ProjectPeer.TYPE, types);
        base.addAscendingOrderByColumn(ProjectPeer.TYPE);
        return new PeerBackedList(acl, ProjectPeer.class, itemsPerPage, 
                                  base, filter);
    }

    /**
     * Returns all projects such that a role in that project recurses
     * into a role in this project.  
     */
    public List getPermissionedParents()
        throws Exception
    {
        List parents = getPermissionedParents(true);
        parents.addAll(getPermissionedParents(false));
        return parents;
    }

    /**
     * Returns all projects such that a role in that project recurses
     * into a role in this project. The boolean indicates whether to
     * return projects which can reach this project through a path
     * consisting entirely of public projects, or whether to return
     * projects which can only reach this project through a path
     * containing one or more private projects.     
     */
    public List getPermissionedParents(boolean publicParents)
        throws Exception
    {
        return ProjectProjectgroupPeer.getPermissionedParents
            (this, publicParents);
    }

    /**
     * Returns all projects which might receive recursive roles in
     * this project. Project y is a permissioned child of project x
     * iff having a role in project x implies that you have that
     * role in project y according to the role recursion algorithm.
     * The degenerate case in which x==y means that any project is a
     * permissioned child of itself.
     */
    public List getPermissionedChildren()
        throws Exception
    {
        return ProjectProjectgroupPeer.getPermissionedChildren(this);
    }

    /**
     * Project y is a recursive subproject of project x if y is a direct
     * subproject of x, or if y is a direct subproject of a recursive
     * subproject of x.  
     */
    public PaginatableList getRecursiveSubprojects(AccessControlList acl)
        throws Exception
    {
        return getRecursiveSubprojects(null, acl);
    }

    /**
     * Project y is a recursive subproject of project x if y is a direct
     * subproject of x, or if y is a direct subproject of a recursive
     * subproject of x.  
     */
    public PaginatableList getRecursiveSubprojects
        (Integer itemsPerPage, AccessControlList acl)
        throws Exception
    {
        List projects = ProjectProjectgroupPeer.getRecursiveSubprojects(this);
        for (Iterator i = projects.iterator(); i.hasNext();)
        {
            Project p = (Project)i.next();
            if (!p.isViewableBy(acl))
            {
                i.remove();
            }
        }
        PaginatableList list = new PrebuiltList(itemsPerPage, projects);
        return list;
    }

    /**
     * Returns a Vector of the project IDs of the projects obtained by the
     * rules described in <code>getPermissionedParents</code>.
     */
    public Vector getPermissionedParentIDs(boolean publicParents)
        throws Exception
    {
        List parents = getPermissionedParents(publicParents);
        Vector ids = new Vector(parents.size());
        for (Iterator i = parents.iterator(); i.hasNext();)
        {
            Project p = (Project)i.next();
            ids.add(p.getID());
        }
        return ids;
    }

    /**
     * Returns true if the given string is contained in the given array.
     */
    private boolean typeIsIn(String type, String[] acceptableTypes)
    {
        boolean acceptable = false;
        for (int i = 0; i < acceptableTypes.length; i++)
        {
            if (type.equals(acceptableTypes[i]))
            {
                acceptable = true;
                break;
            }
        }
        return acceptable;
    }

    //// Mutator methods

    /**
     * Change this project's parent project.
     */
    public void changeParent(AccessControlList acl, Project newParent)
        throws ACLException, Exception
    {
        getParentProject().moveSubproject(acl, this, newParent);
    }

    /**
     * You can't remove a subproject from its parent without putting it
     * somewhere else.
     */
    public void moveSubproject(AccessControlList acl, Project subproject,
                               Project newDestination)
        throws ACLException, Exception
    {
        String perm = "Project - Designate Subproject";
        acl.assertPermission(perm, this);
        acl.assertPermission(perm, newDestination);
        acl.assertPermission("Project - Designate Superproject", subproject);
        newDestination.addChild(subproject);
        removeChild(subproject);
    }   

    /**
     * Makes the given project a subproject of this project.  Any
     * consistency violations this causes must be handled outside of
     * this method.
     */
    protected void addSubproject(Project subproject)
        throws NestableException, Exception
    {
        assertPotentialParenthoodOf(subproject.getType());
        addChild(subproject);
    }

    /**
     * Frees the given project from being a subproject of this project.
     * Any consistency violations this causes must be handled outside
     * of this method.  
     */
    protected void removeSubproject(Project subproject)
        throws Exception
    {
        removeChild(subproject);
    }

    /**
     * Makes the given project a child of this project.  Any
     * consistency violations this causes must be handled outside of
     * this method.
     */
    protected void addChild(Project child)
        throws NestableException, Exception
    {
        assertNoncircularity(this, child);
        ProjectProjectgroup mapping = new ProjectProjectgroup();
        mapping.setProjectRelatedByProjectId(child);
        mapping.setProjectRelatedByProjectGroupId(this);
        mapping.save();
    }


    /**
     * Frees the given project from being a child of this project.
     * Any consistency violations this causes must be handled outside
     * of this method.  
     */
    protected void removeChild(Project child)
        throws NestableException, Exception
    {    
        Criteria c = new Criteria()
            .add(ProjectProjectgroupPeer.PROJECT_ID,
                 PeerTool.getKey(child))
            .add(ProjectProjectgroupPeer.PROJECT_GROUP_ID,
                 PeerTool.getKey(this));
        ProjectProjectgroupPeer.doDelete(c);
    }
    
    //
    // Methods to get related objects.
    //

    /**
     * Returns all the licenses avaliable to this object.
     */
    public List getAvailableLicenses()
        throws Exception
    {
        Criteria c = new Criteria(0)
            .addAscendingOrderByColumn(LicensePeer.NAME);
        return LicensePeer.doSelect(c);
    }

    /**
     * Returns all the resources defined for this project and parent projects.
     * Returns a Vector because it overrides a BaseProject method.
     */
    public Vector getResources()
        throws Exception
    {
        Project parent = getParentProject();
        Vector resources = null;
        if (parent == null)
        {
            resources = new Vector(0);
        }
        else
        {
            resources = parent.getResources();
        }
        Criteria c = new Criteria(1).add(ResourcePeer.PROJECT_ID, getID());
        resources.addAll(ResourcePeer.doSelect(c));
        return resources;
    }

    /**
     * Returns all the roles defined for this project and parent projects.
     * Returns a Vector because it overrides a BaseProject method.
     */
    public Vector getRoles()
        throws Exception
    {
        return getRoles(getCorrespondingBOType(), false);
    }    

    /**
     * Returns all the requestable roles defined for this project and
     * parent projects.  Returns a Vector because it overrides a
     * BaseProject method.  
     */
    public Vector getRequestableRoles()
        throws Exception
    {
        return getRoles(getCorrespondingBOType(), true);
    }    

    /**
     * Returns all the roles defined for this project and parent projects,
     * ordered with topmost parent first.
     */
    protected Vector getRoles(String type, boolean requestableOnly)
        throws Exception
    {
        Project parent = getParentProject();
        Vector roles = null;
        if (parent == null)
        {
            roles = new Vector(0);
        }
        else
        {
            roles = parent.getRoles(type, requestableOnly);
        }        
        Criteria c = null;
        if (!requestableOnly)
        {
            c = new Criteria(2);
        }
        else
        {
            c = RolePeer.getCriteriaForRolesWithBooleanAttribute
                (null, Attribute.REQUESTABLE);
        }
        c.add(RolePeer.PROJECT_ID, getID()).add(RolePeer.TYPE, type);
        roles.addAll(RolePeer.doSelect(c));
        return roles;
    }

    /**
     * Returns all the roles visible from this project which have the
     * named attribute set to "Y".
     * @@param name The name of the attribute to check.
     */
    public List getRolesWithBooleanAttribute(String attribute)
        throws Exception
    {
        return RolePeer.getRolesWithBooleanAttribute(this, attribute);
    }

    /**
     * Returns all users who have an ownership role in this project.
     */
    public List getOwners()
        throws Exception
    {        
        Criteria c = RolePeer.getCriteriaForRolesWithBooleanAttribute
            (this, Attribute.DENOTES_OWNERSHIP)
            .addJoin(UserRoleProjectPeer.USER_ID, UserPeer.USER_ID)
            .addJoin(UserRoleProjectPeer.ROLE_ID, RolePeer.ROLE_ID)
            .add(UserRoleProjectPeer.PROJECT_ID, getProjectId());
        UserRoleProjectPeer.addApprovedRoleCriteria(c);

        return getMembers(new Integer(0), c, null);
    }

    /**
     * Returns all users who have any approved role in this project.
     */
    public PeerBackedList getMembers()
        throws Exception
    {
        return getMembers(new Integer(0), null);
    }

    /**
     * Returns all users who have any approved role in this project.
     */
    public PeerBackedList getMembers(Integer itemsPerPage, Criteria filter)
        throws Exception
    {
        Criteria base = UserRoleProjectPeer.approvedRoleCriteria();
        return getMembers(itemsPerPage, base, filter);
    }

    /**
     * Returns all users who have any unapproved role in this project.
     */
    public PeerBackedList getRequestedMembers()
        throws Exception
    {
        return getRequestedMembers(new Integer(0), HelmPeer.NO_CRITERIA);
    }

    /**
     * Returns all users who have any unapproved role in this project.
     */
    public PeerBackedList getRequestedMembers
        (Integer itemsPerPage, Criteria filter)
        throws Exception
    {
        Criteria base = new Criteria()
            .add(UserRoleProjectPeer.APPROVED_BY,
                 UserPeer.getNobodyUser().getID());
        return getMembers(itemsPerPage, base, filter);
    }

    /**
     * Returns a paginatable list of users who have a role in this
     * project. Appends the appropriate restrictions on a base
     * criterion.  
     */
    public PeerBackedList
        getMembers(Integer itemsPerPage, Criteria base, Criteria filter)
        throws Exception
    {
        if (base == null)
        {
            base = new Criteria(3);
        }

        base.addJoin(UserPeer.USER_ID, UserRoleProjectPeer.USER_ID)
            .add(UserRoleProjectPeer.PROJECT_ID, getProjectId());
        base.addDescendingOrderByColumn(UserPeer.TYPE);
        base.setDistinct();
        return new PeerBackedList(UserPeer.class, itemsPerPage, base, filter);
    }

    /**
     * Returns a paginatable list of news items for this project.
     */
    public PeerBackedList getNews(Integer itemsPerPage, Criteria filter, 
                                  AccessControlList acl)
        throws Exception
    {
        Criteria base = new Criteria()
            .add(NewsItemPeer.PROJECT_ID, getProjectId());
        PeerBackedList newsItems = new PeerBackedList
            (acl, NewsItemPeer.class, itemsPerPage, base, filter);
        return newsItems;
    }

    /**
     * Get a paginated list of unapproved news items for this project
     * which are viewable by the specified ACL.  
     */
    public PeerBackedList getUnapprovedNews(Integer itemsPerPage,
                                            Criteria filter,
                                            AccessControlList acl)
        throws Exception
    {
        List toRemove = new ArrayList();
        Criteria base = new Criteria()
            .add(NewsItemPeer.APPROVED_BY, UserPeer.getNobodyUser().getID())
            .add(NewsItemPeer.PROJECT_ID, getID());
        PeerBackedList p = new PeerBackedList
            (acl, NewsItemPeer.class, itemsPerPage, base, filter);
        return p;
    }

    /**
     * Get a big list of all unapproved news items for this project which
     * are viewable by the specified ACL.  
     */
    public List getUnapprovedNews(AccessControlList acl)
        throws Exception
    {
        return getUnapprovedNews(new Integer(0), null, acl);
    }

    //
    // Mutator methods.
    //


    /**
     * Adds a project.
     *
     * @@param acl               The ACL of the user.
     * @@param parent            Parent of this project.
     * @@param name              Name of the project.
     * @@param license           License of the project.
     * @@param shortDescription  A short description of the project.
     * @@param longDescription   A long description of the project.
     * @@param useIndex          Whether to use the index file contents for the
     *                          long description.
     * @@param aliasURL          URL to use as alias, if any.
     * @@param proposedBy        User who proposed the project.
     * @@param approvedBy        User who approved the project, if any.
     * @@return                  The database record for the project.
     *
     * @@exception AlreadyExists A project in this domain with this name exists.
     */
    public static Project add
        (AccessControlList acl, Project parent,
         String wordMark, License license, String isPublic,
         String shortDescription, String longDescription, 
         String useIndex, String status, String aliasURL, 
         User proposedBy, User projectOwner)
        throws ACLException, AlreadyExists, Exception
    {
        ProjectProfile profile = new DevelopmentProfile();
        return add(acl, profile, parent, wordMark, license, isPublic,
                   shortDescription, longDescription, useIndex, status, 
                   aliasURL, proposedBy, projectOwner);
    }

    /**
     * Adds a project.
     *
     * @@param acl               The ACL of the user.
     * @@param profile           The type of project.
     * @@param parent            Parent of this project.
     * @@param name              Name of the project.
     * @@param license           License of the project.
     * @@param isPublic          Whether or not the project is public.
     * @@param shortDescription  A short description of the project.
     * @@param longDescription   A long description of the project.
     * @@param useIndex          Whether to use the index file contents for the
     *                          long description.
     * @@param aliasURL          URL to use as alias, if any.
     * @@param proposedBy        User who proposed the project.
     * @@param approvedBy        User who approved the project, if any.
     * @@return                  The database record for the project.
     *
     * @@exception AlreadyExists A project in this domain with this name exists.
     */
    public static Project add
        (AccessControlList acl, ProjectProfile profile, Project parent,
         String wordMark, License license, String isPublic,
         String shortDescription, String longDescription, 
         String useIndex, String status, String aliasURL, 
         User proposedBy, User projectOwner)
        throws ACLException, AlreadyExists, NestableException, Exception
    {
        parent.assertPotentialParenthoodOf(profile.getType());
        Domain domain = parent.getDomain();
        
        Project project = null;
        synchronized (addLock)
        {
            assertNonexistance(domain, wordMark);

            if (profile instanceof CategoryProfile)
            {
                project = new Category();
            }
            else if (profile instanceof GroupProfile)
            {
                project = new ProjectGroup();
            }
            else
            {
                project = new Project();
            }
            project.assertAddPermission(acl, parent);

            project.setName(wordMark);
            if (license != null)
            {
                project.setLicense(license);
            }
            project.setIsPublic(DBFlag.flagValueOf(isPublic));
            project.setShortDescription(shortDescription);
            project.setLongDescription(longDescription);
            project.setUseIndex(DBFlag.flagValueOf(useIndex));
            project.setStatus(status);
            project.setAliasUrl(aliasURL);
            project.setProposedBy(proposedBy.getUserId());
            project.setApprovedBy(UserPeer.getNobodyUser().getUserId());
            project.setProfile(profile.getKey());
            project.setType(profile.getType());

            // Add the project to the database.            
            project.save();
        }

        parent.addSubproject(project);

        // Add the project to CVS, Issuezilla, etc.
        project.runCreationScripts(acl.getUser());

        //Give someone the appropriate roles on this project.
        //
        //If the ACL has Project Approve permission, make
        //the owner whomever they designate.
        //
        //Otherwise, make the ACL's user the owner.        
        //
        //Whomever we designate will own the project's mailing lists.
        User   newOwner   = null;
        if (projectOwner == null ||
            !acl.hasPermission("Project - Approve", domain)) 
        {
            newOwner = acl.getUser();
        }
        else 
        {
            newOwner = projectOwner;
        }

        List ownerRoles = project.getRolesWithBooleanAttribute
            (Attribute.GRANT_ON_PROJECT_CREATION);
        for (Iterator i = ownerRoles.iterator(); i.hasNext();)
        {
            Role r = (Role)i.next();
            newOwner.grantRole(UserPeer.getSuperUser(), r, project);
        }

        //If the user created a project for themselves, they have a
        //new role. Reget their ACL.
        if (newOwner.equals(acl.getUser()))
        {
            acl = acl.getUser().getACL();
        }

        // before we grant the role of project owner, we should set up
        // the project in anzu (since currently a special task is used
        // to maintain the owner alias)
        AnzuTask.addProject(newOwner, domain, project);
        AnzuTask.setOwnerAlias(newOwner, project, false);
        profile.addDefaultMailingLists(project, newOwner);                
        String name = transformWordMarkToName(wordMark);
            
        //Approve the project, if user has the right permission.
        if (acl.hasPermission("Project - Approve", domain)) 
        {
            project.approve(acl);
        }
        else 
        {
            //Send a message to the domain admin(s) informing them of
            //the newly requested project.
            User createdUser = acl.getUser();
            String subject = "New unapproved project: " + wordMark;
            Context ctx = new VelocityContext();
            ctx.put("project", project);
            ctx.put("createdUser", createdUser);
            String body = TurbineVelocity.handleRequest(ctx, CREATED_TMPL);
            HelmEmailer emailer = new HelmEmailer();
            try 
            {
                emailer.messageUsersWithDomainOwnership
                    (domain, subject, body);
            }
            catch (Exception e)
            {
                Log.error
                    ("Unable to mail domain owners about new project.", e);
            }
        }

        AuditLog.logEvent(acl.getUser(), project, 
                          project.AUDIT_LOG_ON_ADD + project.getName());

        ToolEvent.projectAdded(project);
        return project;
    }

    /**
     * Updates the project's information.
     */
    public void updateInfo(AccessControlList acl,
                           License license,
                           String isPublic,
                           String shortDescription,
                           String longDescription,
                           String useIndex,
                           String status, 
                           String aliasURL)
        throws ACLException, Exception
    {
        assertEditPermission(acl);
        setLicense(license);
        setIsPublic(isPublic);
        setShortDescription(shortDescription);
        setLongDescription(longDescription);
        setUseIndex(useIndex);
        setStatus(status);
        setAliasUrl(aliasURL);        
        save();
        AuditLog.logEvent(acl.getUser(), this, 
                          this.AUDIT_LOG_ON_EDIT + getName());
    }

    /**
     * Approves the project in the name of the approving user..
     *
     * @@param acl The ACL of the user approving the project.
     *
     * @@exception ACLException The ACL does not have approval permission.
     * @@exception DoesNotExist If the specified project is invalid.
     * @@exception Exception    If a database error occurs.
     */
    public void approve(AccessControlList acl)
        throws ACLException, DoesNotExist, Exception
    {
        if (acl != null)
        {
            acl.assertPermission("Project - Approve", getDomain());

            setApprovedBy(acl.getUser().getUserId());
            save();
        }
    }

    /**
     * Deletes an unapproved project, sending email to the owners
     * about it.  
     */
    public void disapprove(AccessControlList acl)
        throws ACLException, DoesNotExist, Exception
    {
        disapprove(acl, null);
    }

    /**
     * Deletes an unapproved project, sending email to the owners
     * about it.  
     */
    public void disapprove(AccessControlList acl, String reason)
        throws ACLException, DoesNotExist, Exception
    {
        delete(acl);
        //Send email to the owners.
        User disapproving = acl.getUser();
        String subject = "Project " + getWordMark() + " disapproved.";
        Context ctx = new VelocityContext();
        ctx.put("project", this);
        ctx.put("disapprovingUser", disapproving);
        if (StringUtils.isValid(reason))
        {
            ctx.put("reason", reason);
        }
        String body = TurbineVelocity.handleRequest(ctx, DISAPPROVE_TMPL);
        HelmEmailer emailer = new HelmEmailer();
        try
        {
             emailer.messageUsersWithProjectOwnership(this, subject, body);
        }
        catch (Exception e)
        {
            Log.error
                ("Unable to mail project owner about project disapproval", e);
        }
    }
    
    /**
     * Changes the project category--removes from all old categories
     * and adds to the new ones.
     */
    public void changeCategories(List newCategories)
        throws Exception
    {
        for (Iterator i = getCategories().iterator(); i.hasNext();)
        {
            Category c = (Category)i.next();
            if (newCategories.contains(c))
            {
                //A category is staying the same; do nothing rather than
                //remove it and add it back.
                newCategories.remove(c);
            }
            else
            {
                c.disassociate(this);
            }
        }

        for (Iterator i = newCategories.iterator(); i.hasNext();)
        {
            Category c = (Category)i.next();
            c.associate(this);
        }
    }

    /**
     * Makes sure the project is locked or unlocked, according to the
     * provided flag.
     */
    public void setLockedState(AccessControlList acl, boolean newState)
        throws ACLException, Exception
    {
        if (newState != isLocked())
        {
            String prefix = null;
            String subject = null;
            String template = null;
            subject = "Project " + getWordMark() + " ";
            if (newState == true)
            {
                prefix = AUDIT_LOG_ON_LOCK;
                AnzuTask.lockProject(acl.getUser(), getDomain(), this);
                subject += "locked.";
                template = LOCK_TMPL;
            }
            else
            {
                prefix = AUDIT_LOG_ON_UNLOCK;                
                AnzuTask.unlockProject(acl.getUser(), getDomain(), this);
                subject += "unlocked.";
                template = UNLOCK_TMPL;
            }
            
            acl.assertPermission("Project - Lock", this);
            setLocked(DBFlag.flagValueOf(newState));
            save();

            Context ctx = new VelocityContext();
            ctx.put("project", this);
            String body = TurbineVelocity.handleRequest(ctx, template);
            HelmEmailer emailer = new HelmEmailer();
            try 
            {
                emailer.messageUsersWithProjectOwnership
                    (this, subject, body);
            }
            catch (Exception e) 
            {
                Log.error
                    ("Unable to mail project owners about project lock", e);
            }

            AuditLog.logEvent(acl.getUser(), this, prefix + getName());
        }        
    }


    //Overridden methods

    /**
     * Deletes this project.
     */
    public void delete(AccessControlList acl)
        throws ACLException, Exception
    {
        assertDeletePermission(acl);
        assertIsDeletable();

        // delete the directory in the anzu project as well
        AnzuTask.deleteProject(acl.getUser(), getDomain(), this);

        //Delete the project itself, along with its CVS repository
        //and whatnot.
        HelmTask.deleteProject(acl.getUser(), this);

        //Remove all roles in the project.
        UserRoleProjectPeer.doDelete
            (new Criteria().add(UserRoleProjectPeer.PROJECT_ID, getID()));

        //Remove all mailing lists.
        MailingListPeer.doDelete
            (new Criteria().add(MailingListPeer.PROJECT_ID, getID()));

        //Remove all news items.
        NewsItemPeer.doDelete
            (new Criteria().add(NewsItemPeer.PROJECT_ID, getID()));

        //Remove the project from all its groups and its parent.
        List groups = getDirectParents();
        for (Iterator i = groups.iterator(); i.hasNext();)
        {
            Project p = (Project)i.next();
            if (DEBUG)
            {
                Log.debug("Removing project " + getWordMark() + " from group "
                          + p.getWordMark());
            }
            p.removeChild(this);
        }

        //Remove the project itself (soft delete).
        setType(DELETED_TYPE);
        save(true);

        ToolEvent.projectDeleted(this);

        String message = this.AUDIT_LOG_ON_DELETE + getName();
        AuditLog.logEvent(acl.getUser(), this, message);
    }

    /**
     * Helper method to run any scripts needed for the creation of the
     * project. It's public because the Tigris initialization task
     * needs to run it.
     */
    public void runCreationScripts(User initiatingUser)
        throws Exception
    {
        for (Iterator i = getPermissionedChildren().iterator(); i.hasNext();)
        {
            Project p = (Project)i.next();
            if (!p.isDomain() && !p.isHost())
            {
                HelmTask.addProject(initiatingUser, p);
                
                ProjectProfile profile = p.getProjectProfile();
                if (profile != null && profile.isAutoPublishedToWeb())
                {
                    HelmTask.addModule(initiatingUser, p,
                                       ProjectPeer.DEFAULT_PROJECT_NAME, 
                                       ProjectPeer.DEFAULT_PROJECT_DESCRIP,
                                       "1");
                }
            }
        }
    }

    /**
     * Filter out the mailing lists which the client can't view
     */
    public Vector getMailingLists(AccessControlList acl) throws Exception
    {
        Vector rawLists = super.getMailingLists();
        Vector filteredLists = new Vector();
        Iterator i = rawLists.iterator();
        while (i.hasNext())
        {
            MailingList ml = (MailingList)i.next();
            if (ml.isViewableBy(acl))
            {
                filteredLists.add(ml);
            }
        }
        return filteredLists;
    }

    /**
     * Returns all users who can exercise the given action in this
     * project (to any extent).
     */
    public List getUsersWithAction(String action)
        throws Exception
    {
        List users = null;
        if (isLocked())
        {
            Action actionObj = ActionPeer.getInstanceByName(action);
            if (actionObj.isAffectedByProjectLock())
            {
                users = new ArrayList(0);
            }
            else
            {
                users = getUsersWithRoles
                    (RolePeer.getRoleIDsForAction(action));
            }
        }
        return users;
    }

    /**
     * Returns all users who have the given roles in this project
     * or any of its superprojects from which permissions recurse.  
     *
     * The first item of the Vector array is a Vector containing IDs
     * for all roles to be checked. The second item contains IDs for
     * the roles to consider for subprojects reachable from this
     * project only through private subprojects.  
     */
    public List getUsersWithRoles(Vector[] roles)
        throws Exception
    {
        Set usersAndGroups = new HashSet();
        
        for (int i = 0; i <= 1; i++)
        {
            Vector myIDs = getPermissionedParentIDs(i==0?true:false);
            
            if (myIDs.size() > 0)
            {
                Criteria c = new Criteria(4)
                    .addJoin(UserPeer.USER_ID, UserRoleProjectPeer.USER_ID)
                    .addIn(UserRoleProjectPeer.ROLE_ID, roles[i])
                    .addIn(UserRoleProjectPeer.PROJECT_ID, myIDs);
                UserRoleProjectPeer.addApprovedRoleCriteria(c);                
                c.setDistinct();
                usersAndGroups.addAll(UserPeer.doSelect(c));
            }
        } 

        Set effectiveUsers = new HashSet();
        Set baseUsers = new HashSet();
        for (Iterator i = usersAndGroups.iterator(); i.hasNext();)
        {
            User u = (User)i.next();
            effectiveUsers.addAll(u.expand());
        }

        for (Iterator i = effectiveUsers.iterator(); i.hasNext();)
        {
            User u = (User)i.next();
            if (u.isBase())
            {
                baseUsers.add(u);
            }
        }
        return Arrays.asList(baseUsers.toArray());
    }
            
    //
    // Permission checking methods
    //

    /**
     * Returns the action which should be checked for this project before
     * performing a grant or revoke involving it.
     */
    public String requiredRoleGrantAction()
        throws Exception
    {
        String requiredAction = "Role - Grant";
        if (!isApproved())
        {
            requiredAction = "Unapproved Project Role - Grant";
        }
        return requiredAction;
    }

    /**
     * Asserts addition and/or approval permission for this project.
     *
     * @@param acl The ACL to check for permission.
     */
    public void assertAddPermission(AccessControlList acl)
        throws ACLException, Exception
    {
        assertAddPermission(acl, getDomain());
    }

    /**
     * Asserts addition and/or approval permission for this project
     * to be created as a subproject of the given project.
     *
     * @@param acl The ACL to check for permission.  
     * @@param project The parent of the new project.
     */
    public void assertAddPermission(AccessControlList acl, Project parent)
        throws ACLException, Exception
    {        
        Domain domain = parent.getDomain();
        if (isBase())
        {
            if (!acl.hasPermission("Project - Suggest", domain) &&
                !acl.hasPermission("Project - Approve", domain))
            {
                throw new ACLException(acl, 
                                       "Project - Suggest or Project - Approve", 
                                       "Domain " + domain.getName(), null);
            }
        } 
        else
        {
            if (isGroup())
            {
                acl.assertPermission("ProjectGroup - Add", domain);
            }
            else if (isCategory())
            {
                acl.assertPermission("Category - Add", domain);
            }
        }

        //They can create a project, but can they create a subproject
        //of the given parent?
        if (!parent.isDomain())
        {
            acl.assertPermission("Project - Designate Subproject", parent);
        }
    }

    /**
     * Asserts edit permission for this project.
     *
     * @@param acl The ACL to check for permission.
     */
    public void assertEditPermission(AccessControlList acl)
        throws ACLException, Exception
    {
        acl.assertPermission("Project - Edit", this);
    }

    /**
     * Asserts deletion and/or disapproval permission for this project.
     *
     * @@param acl The ACL to check for permission.
     */
    public void assertDeletePermission(AccessControlList acl)
        throws ACLException, Exception
    {
        if (!isDeletableBy(acl))
        {
            if (isDefaultProject())
            {
                throw new Exception
                    (getName() 
                     + " is the default project, and cannot be deleted.");
            }
            
            acl.assertPermission("Project - Delete", this);
        }
    }

    /**
     * Returns true iff the given ACL has permission to delete this project.
     */
    public boolean isDeletableBy(AccessControlList acl)
        throws ACLException, Exception
    {
        boolean canDelete = false;
        if (!isDefaultProject())
        {
            canDelete = acl.hasPermission("Project - Delete", this);
        }
        return canDelete;
    }

    /**
     * Returns true iff this project can be viewed by the permissions
     * of the given ACL.
     *
     * @@param acl The ACL to check for permission.
     */
    public boolean isViewableBy(AccessControlList acl)
        throws Exception
    {
        boolean ok = true;
        if (!acl.hasPermission("Project Page - View", this)) 
        {            
            ok = false;
        }
        else if (!isApproved() && 
                 !acl.hasPermission("Project - Approve", getDomain())) 
        {
            ok = false;       
        }
        return ok;
    }

    /**
     * Returns the number of outstanding role requests for this
     * project.  
     */
    public int getNumberOfRequestedRoles()
        throws Exception
    {
        Criteria c = new Criteria()
            .add(UserRoleProjectPeer.PROJECT_ID, getProjectId())
            .add(UserRoleProjectPeer.APPROVED_BY, 
                 UserPeer.getNobodyUser().getUserId());
        List mappings = UserRoleProjectPeer.doSelect(c);
        return mappings.size();
    }

    //
    // Hairy URL methods
    //

    /**
     * Return the ProjectHome url.
     *
     * @@see #getProjectHome( String )
     */
    public String getProjectHome( )
        throws NullPointerException, IllegalArgumentException
    {
        return getProjectHome( ( String )null );
    }
    
    
    /**
     * Return the ProjectHome url.
     *
     * Will return either '/servlets/ProjectHome?projectID=THIS_PROJECT_ID
     * or 'http://THIS_PROJECTNAME.THIS_PROJECT_DOMAIN/servlets/ProjectHome.
     * The switch turns on the <code>env.sh</code> setting IS_VHOSTING.  (The
     * ${SANDOBX}/scripts/configure.pl writes the IS_VHOSTING value to 
     * <code>${SANDBOX}/newhelm/conf/40helm.zone.properties</code>)
     * <p>
     * You would use this method to write out links to this project's
     * ProjectHome page.  For example, say you are browsing 
     * www.DOMAIN and you come upon the ProjectList page that
     * holds a list of all projects.  Calling this method on each of the
     * projects found to write the links for ProjectList will 
     * return the appropriate ProjectHome link. 
     *
     * @@param inProtocol Pass 'http' or 'https'.  If null, we assume 'http'.
     *
     * @@return Either 'PROJECTHOME_SERVLETPATH?projectID=THIS_PROJECT_ID' or
     * 'http://THIS_PROJECTNAME.THIS_PROJECT_DOMAIN/SERVLET_PATH'
     *
     * @@exception NullPointerException If i can't get HelmProperties instance.
     * @@exception IllegalArgumentException Thrown if you pass anything but 
     * 'http' or 'http' in the inProtocol param.
     */
    public String getProjectHome( String inProtocol )
        throws NullPointerException, IllegalArgumentException
    {
        return buildProjectPageURL("ProjectHome", null, inProtocol, false, true);
    }

    public String projectMembersURL()
        throws NullPointerException
    {
        return buildProjectPageURL
            ("ProjectMemberList", null, "http", false, true);
    }

    /**
     * Return the mail address to use to reach the owner(s) of this project
     *
     * TODO : provide a mail address service which provides this, rather
     * than putting it in the business object?  This implementation puts
     * a small amount of knowledge about mailing list addresses in helm.
     *
     * @@return String which is an email alias which will forward to the 
     * owner(s) of this project.
     */
    public String getProjectOwnerEmailAlias() throws Exception
    {
        String mailPrefix = HelmProperties.getProperty(MAIL_PREFIX_KEY);
        if (mailPrefix == null)
        {
            mailPrefix = "";
        }
        return mailPrefix + "owner@@" + getName() + "." + getDomain().getName();
    }

    /**
     * Test if vhosting is turned on.
     */
    private boolean isVhosting()
    {
        String isVhosting = HelmProperties.getProperty(IS_VHOSTING_KEY);
        return (isVhosting != null &&
                new Boolean(isVhosting).booleanValue());
    }

    /**
     * This provides an URL (or UIR) to use for a given page in a project.
     * This version can be used with a template.
     */
    public String getProjectPageURL(String page)
        throws NullPointerException
    {
        return buildProjectPageURL(page, null, null, false, true);
    }

    /**
     * This provides an URL (or URI) to use for a given page in a project.
     * This version can be used within a template.
     */
    public String getProjectPageURL(String page, String queryArgs)
        throws NullPointerException
    {
        if ("".equals(queryArgs))
        {
            queryArgs = null;
        }
        return buildProjectPageURL(page, queryArgs, null, false, true);
    }

    /**
     * This provides an URL to use for a given page in a project.  This
     * verion is typically used within e-mail and in other contexts
     * where an absolute link is required.
     */
    public String getProjectPageFullURL(String page, String queryArgs,
            String inProtocol)
        throws NullPointerException
    {
        return buildProjectPageURL(page, queryArgs, inProtocol, true, true);
    }

    /**
     * This provides an URL (or URI) to use for a given page in a project.
     */
    public String getProjectPageURL(String page, String queryArgs,
            String inProtocol)
        throws NullPointerException
    {
        return buildProjectPageURL(page, queryArgs, inProtocol, false, true);
    }


    /**
     * This provides an URL (or URI) to use for a given (non-servlet) file
     *  in a project.
     */
    public String getProjectFileURL(String page)
        throws NullPointerException
    {
        return buildProjectPageURL(page, null, null, false, false);
    }

    /**
     * This provides an URL (or URI) to use for a given (non-servlet) file
     *  in a project.
     */
    public String getProjectFileURL(String page, String queryArgs)
        throws NullPointerException
    {
        return buildProjectPageURL(page, queryArgs, null, false, false);
    }

    /**
     * This provides an URL (or URI) to use for a given (non-servlet) file
     *  in a project.
     */
    public String getProjectFileURL(String page, String queryArgs,
                                    String inProtocol)
        throws NullPointerException
    {
        return buildProjectPageURL(page, queryArgs, inProtocol, false, false);
    }

    /**
     * This provides a URL to use for a given page in a project.  It uses
     * a configuration flag -- which is part of env.sh -- to determine
     * whether projects should be represented as
     * <code>http://$project.domain/...</code> or
     * <code>http://domain/...?projectID=$project.getID()</code>.
     * <p>
     * The logic is: if forceFullyQualifiedDomain is true or if vhosting
     * (the former form) is being used, then start with
     * http://[{project}].domain.  Next, add /servlets/$page.  Finally,
     * if a queryArgs are provided or if the latter form is being used,
     * generate an appropriate query string.
     *
     * Look at the JavaDocs for getProjectHome for more an explanation
     * of one use of this function.
     *
     * @@param page The servlet to which to point
     * @@param queryArgs Any query arguments which are needed
     * @@param inProtocol The protocol being used, so far as we know
     * @@param forceFullyQualifiedDomain Whether to insist on including
     * the protocol, domain, and port number.  This is useful eg. for
     * e-mail messages, where relative URIs will not work.
     * @@return a URI/URL to send to the client
     */
    private String buildProjectPageURL
        (String page, String queryArgs, String inProtocol, 
         boolean forceFullyQualifiedDomain, boolean isServlet)
        throws NullPointerException
    {
        StringBuffer buff = new StringBuffer();

        // Gather some information.
        boolean isVhosting = isVhosting();
        URL urlUtil = new URL( );
        String protocol = urlUtil.getProtocol( inProtocol );
        String port = urlUtil.getPort( protocol );
        String domain = null;
        try
        {
            domain = getDomain().getName();
        }
        catch (Exception e)
        {
            Log.error("Unable to get domain name");
            Log.error(e);
            // TODO : use a better exception
            throw new NullPointerException ( "Unable to get domain name");
        }

        // Build the URL.
        if (isVhosting || forceFullyQualifiedDomain)
        {
            buff.append( protocol );
            buff.append( "://" );
            if (isVhosting)
            {
                buff.append( getName() ); // Add this project's name as host.
                buff.append( '.' ); // Dot between project name and domain
            }
            buff.append( domain );  
            if( port != null)
            {
                buff.append( ":" );
                buff.append( port );
            }
        }
        if (isServlet)
        {
            buff.append(SERVLET_PREFIX);
        }
        buff.append(page);
        if (!isVhosting)
        {
            urlUtil.appendCGIParam(buff, "projectID", getID());
        }
        if (queryArgs != null)
        {
            // ASSUMPTION: queryArgs should not be URL encoded.
            urlUtil.appendCGISeparator(buff, buff.toString());
            buff.append(queryArgs);
        }

        return buff.toString();
    }



    /**
     * Gets the directory to save uploads to.
     *
     * @@param baseDir The base directory to build the upload directory off of.
     * @@param user    The user to save a file for.
     * @@return        The directory to save uploads to.
     */
    public String getUploadDirectory(String baseDir, User user)
    {
        // Save to /pub-fileserver/projectID/userID/fileNameAsUploaded.
        return (baseDir + File.separatorChar + getID() + File.separatorChar +
		user.getID());
    }

    /**
     * Returns the project's hypertext document directory (i.e.
     * <code>SANDBOX/data/helm/htdoc/PROJECTNAME/www</code>).  Used to create
     * file system paths.
     *
     * NOTE: shuold be moved to separate component
     *
     * @@return The path to the directory.
     */
    protected String getHtdocDir()
    {
        return HelmProperties.getProperty("SANDBOX") + File.separator + 
            "data" + File.separator + "helm" + File.separator + "htdoc" +
            File.separator + getName() + File.separator + "www";
    }

    /**
     * Gets the full description of the project, or the contents of htdoc 
     * <code>INDEX_FILE</code> if {@@link org.tigris.helm.om.Project.useIndex()}
     * is <code>true</code>.
     *
     * @@return The full description.
     */
    public String getLongDescription()
    {
        // Check for alternate description.
        if (useIndex())
        {
            // Dump the contents of the alternate description file.
            try
            {
                FileReader reader = new FileReader(getHtdocDir() +
                                                   File.separator +
                                                   INDEX_FILE);
                StringBuffer buf = new StringBuffer();
                if (reader.ready())
                {
                    char[] cbuf = new char[1024];
                    int nbrRead;
                    while ((nbrRead = reader.read(cbuf, 0, cbuf.length)) != -1)
                        buf.append(cbuf, 0, nbrRead);
                    reader.close();
                    return buf.toString();
                }
            }
            catch (IOException ioe)
            {
                // Either the file wasn't found or there was an error reading
                // it.  Punt and use the in memory description.
                if (ioe instanceof FileNotFoundException)
                {
                    // TODO: Report missing index.html to user.
                    Log.warn(INDEX_FILE + " missing for project " + getID());
                }
            }
        }

        // No alternate description used.
        return super.getLongDescription();
    }

    /**
     * Overrides BaseUser save to remove this user from the cache.  There might
     * be a cached representation of the user associated with this key floating
     * around.
     */ 
    public void save()
        throws Exception
    {
        save(false);
    }

    /**
     * Overrides BaseProject save to remove this project from the cache.
     * There might be a cached representation of the project associated
     * with this key floating around.  
     */
    public void save(boolean deleting)
        throws Exception
    {
        if (deleting)
        {
            if (isHost() || isDomain())
            {
                throw new NestableException("This project cannot be deleted.");
            }
        }
        else
        {
            if (isDeleted())
            {
                throw new NestableException(DELETED_PROJECT_MESSAGE);
            }
        }

        super.save();
        uncache();
    }

    // Deprecated methods.

    /**     
     * @@deprecated You shouldn't care. The license is just a designation
     * with no permission effects.
     */
    public boolean isProprietary()
        throws Exception
    {
        boolean isProprietary = true;
        License license = getLicense();
        if (license != null)
        {
            isProprietary = license.getName().equals("Proprietary");
        }
        return isProprietary;
    }

    /**
     * Returns a ProjectGroup version of this object.
     *
     * @@deprecated Cast instead; if you used doSelect you'll have no
     *             problems.
     */
    public ProjectGroup asGroup()
    {
        return (ProjectGroup)this;
    }

    /**
     * @@deprecated use getRequestedMembers() instead
     */
    public PeerBackedList getRequestedUsers()
        throws Exception
    {
        return getRequestedMembers();
    }

    /**
     * @@deprecated use getRequestedMembers(itemsPerPage, filter) instead
     */
    public PeerBackedList getRequestedUsers
        (Integer itemsPerPage, Criteria filter)
        throws Exception
    {
        return getRequestedMembers(itemsPerPage, filter);
    }

    /**
     * Changes the project category--removes from all old categories
     * and adds to the new one.
     *
     * @@deprecated use changeCategories([list containing newCategory]) instead.
     */
    public void changeCategory(Category newCategory)
        throws Exception
    {
        List newCategories = new ArrayList(1);
        newCategories.add(newCategory);
        changeCategories(newCategories);
    }

    /**
     * Adds the project to a category.
     * @@deprecated Use category.associate(this) instead.
     */
    public void addToCategory(Category cat)
        throws Exception
    {
        cat.associate(this);
    }

    /**
     * Removes the project from a category.
     * @@deprecated Use category.disassociate(this) instead.
     */
    public void removeFromCategory(Category cat)
        throws Exception
    {
        cat.disassociate(this);
    }

    /**
     * Adds this project to the specified group.
     * @@deprecated Use group.associate(this) instead.
     */
    public void addToGroup(AccessControlList acl, ProjectGroup g)
        throws Exception
    {
        g.associate(acl, this);
    }

    /**
     * Removes this project from the specified group.
     * @@deprecated Use group.disassociate(this) instead.
     */
    public void removeFromGroup(AccessControlList acl, ProjectGroup g)
        throws Exception
    {
        g.disassociate(acl, this);
    }
}
@


1.171
log
@Added new method for getting the recursive subprojects of a project.
@
text
@d59 1
a59 1
import org.apache.fulcrum.util.parser.ParameterParser;
@


1.170
log
@Added definition of permission childhood.
@
text
@d1061 33
d2088 1
a2088 1
        {
@


1.169
log
@Updated for Turbine API change.
@
text
@d1048 5
a1052 1
     * this project.
@


1.168
log
@Made project deletions soft.
@
text
@d59 1
a59 1
import org.apache.turbine.util.ParameterParser;
@


1.167
log
@Replaced 'plugins' architecture with ToolEventPropagationService.
@
text
@d98 7
d308 12
d331 1
d334 1
a334 1
            Project p = ProjectPeer.getInstanceByName(domain, name);
d336 1
a336 1
        catch (DoesNotExist success) 
a337 1
            return;
d339 11
a349 2
        throw new AlreadyExists("Project named " + name,
                                "domain named " + domain.getName());
d590 8
a1780 2
        ToolEvent.projectDeleted(this);

d1794 5
a1798 3
        //Remove the project itself.
        ProjectPeer.doDelete(new Criteria()
            .add(ProjectPeer.PROJECT_ID,getID()));
d2402 11
d2417 1
a2417 1
    public void save()
d2420 15
@


1.166
log
@Refactored methods for filtering out unapproved roles.
@
text
@d76 1
a80 1
import org.tigris.helm.tool.plugin.Plugins;
d1542 1
a1542 1
        Plugins.fireProjectAdded(project);
d1745 1
a1745 1
        Plugins.fireProjectDeleted(this);
@


1.165
log
@Removed redundant join which was causing bogus records to show up.
@
text
@d1248 2
d1268 1
a1268 4
        Criteria base = new Criteria()
            .add(UserRoleProjectPeer.APPROVED_BY, 
                 (Object)UserPeer.getNobodyUser().getID(), 
                 Criteria.NOT_EQUAL);
d1861 2
a1862 5
                    .addIn(UserRoleProjectPeer.PROJECT_ID, myIDs)
                    .add(UserRoleProjectPeer.APPROVED_BY, 
                         (Object)UserPeer.getNobodyUser().getID(), 
                         Criteria.NOT_EQUAL);
                
@


1.164
log
@Remove the project from groups only after everything that might need its dependancies has run.
@
text
@a1247 1
        c.putAll(UserRoleProjectPeer.approvedRoleCriteria());
@


1.163
log
@A project may have as its parent a Domain, which is not a ProjectGroup.
@
text
@a1730 13
        //Remove the project from all its groups and its parent.
        List groups = getDirectParents();
        for (Iterator i = groups.iterator(); i.hasNext();)
        {
            Project p = (Project)i.next();
            if (DEBUG)
            {
                Log.debug("Removing project " + getWordMark() + " from group "
                          + p.getWordMark());
            }
            p.disassociate(this);
        }

d1748 13
@


1.162
log
@Use DBFlag to get the appropriate CHAR value to insert into the
database for single character fields which hold boolean values (but
are not of a boolean type).
@
text
@d1735 1
a1735 1
            ProjectGroup p = (ProjectGroup)i.next();
@


1.161
log
@PeerBackedList now knows about objects that come in grouped flavors, and can explain behavior in which a list consists of separate alphabetical lists of groups and of users.
@
text
@d1451 1
a1451 1
            project.setIsPublic(isPublic);
d1454 1
a1454 1
            project.setUseIndex(useIndex);
@


1.160
log
@Upgrading to Turbine 3.0-dev, Fulcrum 1.0, and Torque 1.0.
@
text
@d545 17
@


1.159
log
@Corrected cast to call the right version of Criteria.add.
@
text
@d55 4
a58 5
import org.apache.turbine.om.*;
import org.apache.turbine.om.peer.BasePeer;
import org.apache.turbine.util.db.Criteria;
import org.apache.turbine.util.ObjectUtils;
import org.apache.turbine.util.StringUtils;
d61 4
a64 4
import org.apache.turbine.util.TurbineException;
import org.apache.turbine.util.db.pool.DBConnection;
import org.apache.turbine.services.TurbineServices;
import org.apache.turbine.services.velocity.TurbineVelocity;
d66 1
d333 1
a333 1
        throws TurbineException, Exception
d337 1
a337 1
            throw new TurbineException
d347 1
a347 1
        throws TurbineException, Exception
d351 1
a351 1
            throw new TurbineException
d454 1
a454 1
            throw new TurbineException("Project " + getName() 
d764 1
a764 1
        throws TurbineException
d768 1
a768 1
            throw new TurbineException
d1070 1
a1070 1
        throws TurbineException, Exception
d1093 1
a1093 1
        throws TurbineException, Exception
d1109 1
a1109 1
        throws TurbineException, Exception
d1405 1
a1405 1
        throws ACLException, AlreadyExists, TurbineException, Exception
d1507 1
a1507 1
            Context ctx = TurbineVelocity.getContext();
d1599 1
a1599 1
        Context ctx = TurbineVelocity.getContext();
d1680 1
a1680 1
            Context ctx = TurbineVelocity.getContext();
@


1.158
log
@Added Hashtable representation of Project for use in future XML-RPC handlers.
@
text
@d1848 2
a1849 2
                         UserPeer.getNobodyUser().getID(), 
                         (Object)Criteria.NOT_EQUAL);
@


1.157
log
@Requested roles don't count for permission purposes.
@
text
@d273 16
@


1.156
log
@Permission check changes: don't return duplicate resources, and make all permission checks of any kind fail if the project is locked and the action is affected by the lock.
@
text
@d1830 5
a1834 1
                    .addIn(UserRoleProjectPeer.PROJECT_ID, myIDs);
@


1.155
log
@Stop coddling IZ in terms of user accounts; assume that IZ can get its permissions from Helm through the XML-RPC interface.
@
text
@d1790 15
a1804 1
        return getUsersWithRoles(RolePeer.getRoleIDsForAction(action));
d1820 1
a1820 1

d1824 1
a1824 1

@


1.154
log
@Action cleanup. Some role actions, and Audit Log - View, had three actions apiece when one would do. We're now down to 76 actions.
@
text
@a77 1
import org.tigris.helm.tool.task.IssueTrackingRebuildService;
a1083 1
        child.rebuildIssueTracking();
a1100 1
        child.rebuildIssueTracking();
a1643 1
            boolean rebuild = false;
a1648 1
                HelmTask.disableAllAccounts(acl.getUser(), this);
a1654 1
                rebuild = true;
a1663 5
            if (rebuild)
            {
                rebuildIssueTracking();
            }

a1732 5
        //Remove any outstanding requests for issue tracking for this
        //project.
        ClusterEventPeer.deleteInstance
            (IssueTrackingRebuildService.getService().getName(), getID());

a1780 104
    }

    /**
     * Returns an estimated worst case time for rebuilding
     * this project, in minutes.
     */
    public double estimateWorstCaseIssueTrackingRebuildTime()
        throws ACLException, Exception
    {
        long averageTime = IssueTrackingRebuildService.getService()
            .getAverageRebuildTime();
        if (averageTime == 0)
        {
            averageTime = 5000;
        }
        int numberOfProjects = ProjectPeer.numberOfRecords
            (ProjectPeer.TABLE_NAME);
        long worstCaseMs = averageTime * numberOfProjects;
        return worstCaseMs / (1000.00 * 60.00);
    }

    /**
     * Register this project and all its permissioned children
     * with the issue tracking rebuilder.
     */
    public void rebuildIssueTracking()
        throws ACLException, Exception
    {
        IssueTrackingRebuildService.getService().registerProject(this);
    }

    /**
     * Resyncs a project's issue tracking accounts with the Helm roles
     * for that project. 
     */
    public void doIssueTrackingRebuild()
        throws Exception, ACLException
    {
       
        String[] accountTypes = {"observer", "developer", "admin"};
        String[] actions = 
        {
            "Project Issue Tracking - Submit",
            "Project Issue Tracking - Change",
            "Project Issue Tracking - Configure"
        };

        int types = accountTypes.length;
        List[] userLists = new List[types];
        Vector[][] roleIDs = new Vector[types][2];

        //See which roles and which private-recursive roles have the
        //given permission.

        for (int i = 0; i < types; i++)
        {
            roleIDs[i] = RolePeer.getRoleIDsForAction(actions[i]);
        }

        if (DEBUG)
        {
            Log.debug("ITR processing project " + getName());
        }

        if (!(isHost() || isDomain()))
        {
            //For each relevant action, find the users who have that
            //action in this project.
            for (int j = 0; j < types; j++)
            {
                userLists[j] = getUsersWithRoles(roleIDs[j]);
            }
            
            //The account types are arranged in increasing order of
            //power. Thus, a user with account type i should not be in
            //the list of users with account type j < i.
            for (int j = 0; j < types; j++)
            {
                for (int k = types-1; k > j; k--)
                {
                    userLists[j].removeAll(userLists[k]);
                }
            }
            
            //Disable all accounts for the project, and then add the
            //appropriate accounts back.
            Map args = ScriptTool.makeProjectArgs(this);
            ScriptTool.runScript("MemberDisableAll", args);
            for (int j = 0; j < types; j++)
            {
                if (DEBUG)
                {
                    Log.debug(userLists[j].size() + " accounts of type " 
                              + accountTypes[j]);
                }
                if (userLists[j].size() > 0)
                {
                    Map memberArgs = ScriptTool.makeProjectArgs(this);
                    memberArgs.putAll(ScriptTool.makeUserArgs(userLists[j], 
                                                            accountTypes[j]));
                    ScriptTool.runScript("MemberAdd", memberArgs);
                }
            }
        }
@


1.153
log
@cleanup:
* use getDomain().getDomain() rather than retrieving the domain from a
  property file
* put all static magic strings at the top of the file
* provide a new function, getProjectOwnerEmailAlias, to retrieve an email
  alias which will forward to all owners of the project (or to the domain
  owners, as per the logic used in java code).
@
text
@d1972 2
a1973 2
        String requiredAction = "Host Role - Grant";
        if (isHost())
d1975 1
a1975 10
            //Taken care of above.
        } else if (isDomain()) {
            requiredAction = "Domain Role - Grant";
        } else {
            if (isApproved())
            {
                requiredAction = "Project Role - Grant";
            } else {
                requiredAction = "Unapproved Project Role - Grant";
            }
@


1.152
log
@Use sets instead of lists to avoid having duplicate users.
@
text
@d125 11
d198 5
d2179 8
a2186 1
     * Key for IS_VHOSTING.
d2188 9
a2196 1
    private static final String IS_VHOSTING_KEY = "IS_VHOSTING";
a2285 4
    private static final String DOMAIN_KEY = "DOMAIN";

    private static final String SERVLET_PREFIX = "/servlets/"; 

d2322 2
a2323 2
        String domain = HelmProperties.getProperty(DOMAIN_KEY);
        if (domain == null || domain.length() <= 0)
d2325 8
a2332 1
            throw new NullPointerException( DOMAIN_KEY + " is null" );
@


1.151
log
@If there are no valid superproject types, this method should return an empty list.
@
text
@d1909 1
a1909 1
        List usersAndGroups = new ArrayList();
d1926 2
a1927 2
        List effectiveUsers = new ArrayList(usersAndGroups.size());
        List baseUsers = new ArrayList(usersAndGroups.size());
d1931 1
a1931 1
            effectiveUsers.addAll(u.expand());            
d1942 1
a1942 1
        return baseUsers;
@


1.150
log
@Updated to use Helm URL class (just ported from Joist).
@
text
@d675 1
a675 1
        Criteria c = new Criteria(2);
d680 6
a685 1
        if (validTypes.length > 0)
a687 1
        }
d689 1
a689 5
        if (myID != null)
        {
            List myChildren = getPermissionedChildren();
            String[] childIDs = new String[myChildren.size()];
            if (childIDs.length > 0)
d691 3
a693 1
                for (int i = 0; i < childIDs.length; i++)
d695 5
a699 1
                    childIDs[i] = ((Project)myChildren.get(i)).getID();
a700 1
                c.addNotIn(ProjectPeer.PROJECT_ID, childIDs);
d702 19
a720 19
        }

        List unfiltered = ProjectPeer.doSelect(c);
        List toRemove = new ArrayList(unfiltered.size());
        List filtered = new ArrayList(unfiltered.size());

        for (Iterator i = unfiltered.iterator(); i.hasNext();)
        {            
            Project p = (Project)i.next();
            if (p.isDomain())
            {
                filtered.add(p);
                toRemove.add(p);
            }

            if (!acl.hasPermission("Project - Designate Subproject", p) 
                || (myID != null && myID.equals(p.getID())))
            {
                toRemove.add(p);
d722 2
a724 2
        unfiltered.removeAll(toRemove);
        filtered.addAll(unfiltered);
@


1.149
log
@Updated in light of the port of Joist JoistProperties class to Helm (as class HelmProperties).
@
text
@d71 2
a72 3
import org.tigris.helm.tool.HelmProperties;
import org.joist.util.URL;

d75 1
a75 2
import org.tigris.helm.AlreadyExists;
import org.tigris.helm.DoesNotExist;
d2287 1
a2287 1
        org.joist.util.URL urlUtil = new org.joist.util.URL( );
@


1.148
log
@Separated PluginService into non-static description and separate static wrapper class (Plugins), which makes use of a PluginService singleton.
@
text
@d71 1
a71 1
import org.joist.util.JoistProperties;
d2142 1
a2142 1
     * @@exception NullPointerException If i can't get JoistProperties instance.
d2169 1
a2169 1
        String isVhosting = JoistProperties.getProperty(IS_VHOSTING_KEY);
d2292 1
a2292 1
        String domain = JoistProperties.getProperty(DOMAIN_KEY);
d2361 1
a2361 1
        return JoistProperties.getProperty("SANDBOX") + File.separator + 
@


1.147
log
@added super simple plugin notification service
@
text
@d83 1
a83 1
import org.tigris.helm.tool.plugin.PluginService;
d1495 1
a1495 1
        PluginService.fireProjectAdded(project);
d1719 1
a1719 1
        PluginService.fireProjectDeleted(this);
@


1.146
log
@shifted code from helm to nidaba
@
text
@d83 1
d1494 2
d1719 1
a1719 13
        //Remove all document sections.
        // TODO, this is a BUG, need a new way to delete objects in plugins
        /*
        ProjectDocumentSectionPeer.doDelete
            (new Criteria().addJoin(ProjectDocumentSectionPeer.DOCUMENT_ID, 
                                    ProjectDocumentPeer.DOCUMENT_ID)
                .add(ProjectDocumentPeer.PROJECT_ID, getID()));

        //Remove all documents.
        ProjectDocumentPeer.doDelete
            (new Criteria().add(ProjectDocumentPeer.PROJECT_ID, getID()));

        */
@


1.145
log
@factoring project documents out to project nidaba.tigris.org
@
text
@d79 1
a84 2
import org.tigris.nidaba.om.*;

a1186 82
    public List getSections()
        throws Exception
    {        
        return getSections("0");
    }

    /**
     * Returns top level sections associated with this project.
     */
    public List getSections(String parentID)
        throws Exception
    {        
        Criteria c = new Criteria()
            .add(ProjectSectionPeer.PROJECT_ID, getID())
            .add(ProjectSectionPeer.PARENT_ID, parentID);
        return ProjectSectionPeer.doSelect(c);
    }


    /**
     * Returns unapproved sections associated with this project.
     */
    public List getUnapprovedSections()
        throws Exception
    {        
        Criteria c = new Criteria()
            .add(ProjectSectionPeer.PROJECT_ID, getID())
            .add(ProjectSectionPeer.APPROVED_BY, 
                 UserPeer.getNobodyUser().getUserId());
        return ProjectSectionPeer.doSelect(c);
    }

    /**
     * Returns all documents associated with this project.
     */
    public List getDocuments()
        throws Exception
    {        
        Criteria c = new Criteria()
            .add(ProjectDocumentPeer.PROJECT_ID, getID());
        return ProjectDocumentPeer.doSelect(c);
    }

    /**
     * Returns list of unapproved documents associated with the project.
     */
    public List getUnapprovedDocuments()
        throws Exception
    {
        Criteria c = new Criteria()
            .add(ProjectDocumentPeer.PROJECT_ID, getID())
            .add(ProjectDocumentPeer.APPROVED_BY, 
                 UserPeer.getNobodyUser().getUserId());
        return ProjectDocumentPeer.doSelect(c);
    }


    /**
     * Returns top level documents associated with this project.
     * Top level documents are those that are not associated with a section.
     *
     * FIXME: Elicia, why does this method live in Project, whilst
     * getDocuments(acl) lives in ProjectSection?
     */
    public List getTopLevelDocuments()
        throws Exception
    {        
        List topLevelDocs = new ArrayList();
        Criteria c = new Criteria()
            .add(ProjectDocumentPeer.PROJECT_ID, getID());
        List allDocuments =  ProjectDocumentPeer.doSelect(c);
        for (Iterator i = allDocuments.iterator(); i.hasNext(); )
        {
            ProjectDocument dc = (ProjectDocument)i.next();
            if (dc.getSectionID() == 0)
            {
                topLevelDocs.add(dc);
            }
        }
        return topLevelDocs;
    }

d1717 2
d1727 2
@


1.144
log
@Java is not Python.
@
text
@d82 3
a84 1
import org.tigris.helm.tool.audit.AuditLog;
@


1.143
log
@Null profiles don't get modules.
@
text
@d1835 1
a1835 1
                ProjectProfile profile = p.getProjectProfile()
@


1.142
log
@The last vestiges of the old Proprietary==Private Republic have been swept away.
@
text
@d1834 3
a1836 1
                if (p.getProjectProfile().isAutoPublishedToWeb())
@


1.141
log
@As I said, there is no Project - Add action.
@
text
@d1478 4
a1481 1
            project.setLicense(license);
d1541 1
a1541 3
        profile.addDefaultMailingLists(project, newOwner,
                                       project.isProprietary());
                
d2509 1
a2509 3
    /**
     * Returns true iff the project has a proprietary license.
     *
d2516 7
a2522 1
        return getLicense().getName().equals("Proprietary");
@


1.140
log
@There is no Project - Add permission.
@
text
@d2081 1
a2081 1
                                       "Project - Suggest or Project - Add", 
@


1.139
log
@Licenses are now contained in a database-managed table rather than in a custom Java class (yay!)
@
text
@d2078 1
a2078 1
                !acl.hasPermission("Project - Add", domain))
@


1.138
log
@Corrected stupid method call mistake.
@
text
@d356 23
d385 1
a385 1
    }
a471 10
     * Returns true iff the project has a proprietary license.
     *
     * FIXME: This should probably be deprecated.
     */
    public boolean isProprietary()
    {
        return Licenses.isProprietary(getLicense());
    }

    /**
d1090 11
d1415 1
a1415 1
         String wordMark, String license, String isPublic,
d1449 1
a1449 1
         String wordMark, String license, String isPublic,
d1580 1
a1580 1
                           String license,
d2507 12
@


1.137
log
@Implemented project-scoped roles.
@
text
@d1105 1
a1105 1
        return getRoles(getCorrespondingBOType(), true);
@


1.136
log
@Implemented basic project group and category nesting.
@
text
@d214 17
d588 16
d626 29
d1076 86
d1250 2
a1251 1
        Criteria c = RolePeer.getOwnershipRoleCriteria()
d1494 2
a1495 1
        List ownerRoles = RolePeer.getRolesToGrantOnProjectCreation();
@


1.135
log
@Corrected compile error.
@
text
@d111 14
d196 18
d264 2
a265 1
        try {
d268 2
a269 1
        catch (DoesNotExist success) {
d276 18
d295 14
a530 1

d537 261
d799 1
a799 2
        //TODO: Implement.
        return this;
d813 11
d836 20
d910 1
a910 3
     * Returns a list of all the project groups to which this project
     * directly belongs.  Does not include the special host or domain
     * project groups.  
d912 1
a912 2
    public List getProjectGroups()
        throws Exception
d914 10
a923 1
        return getGroupsOfType(Project.GROUP_TYPE);
d926 2
d929 1
a929 1
     * Returns a list of all the categories to which this project belongs.
d931 2
a932 2
    public List getCategories()
        throws Exception
d934 1
a934 1
        return getGroupsOfType(Project.CATEGORY_TYPE);
d938 19
a956 2
     * Returns a list of all the project groups which are of the specified
     * type and to which this project directly belongs.
d958 2
a959 2
    private List getGroupsOfType(String type)
        throws Exception
d961 2
a962 9
        Criteria c = new Criteria()
            .addJoin(ProjectProjectgroupPeer.PROJECT_GROUP_ID, 
                     ProjectPeer.PROJECT_ID)
            .add(ProjectProjectgroupPeer.PROJECT_ID, getProjectId());
        if (type != null)
        {
            c.add(ProjectPeer.TYPE, type);
        }
        return ProjectPeer.doSelect(c);
d965 7
a971 3
    //// Mutator methods

    public void addSubproject(AccessControlList ACL, Project newSubproject)
d973 1
a973 1

d977 3
a979 3
     * You can't remove a subproject from its parent without putting it
     * somewhere else, though you can remove subprojects from project
     * groups and categories.
d981 2
a982 2
    public void moveSubproject(AccessControlList ACL, Project subProject,
                               Project newDestination)
d984 7
a991 1
    }   
d993 17
d1226 1
a1226 1
     * @@param domain            Domain in which the project resides.
d1241 1
a1241 1
        (AccessControlList acl, Domain domain,
d1249 1
a1249 1
        return add(acl, profile, domain, wordMark, license, isPublic,
d1259 1
a1259 1
     * @@param domain            Domain in which the project resides.
d1275 1
a1275 1
        (AccessControlList acl, ProjectProfile profile, Domain domain,
d1280 1
a1280 1
        throws ACLException, AlreadyExists, Exception
d1282 3
a1284 1

d1302 2
a1315 1
            project.assertAddPermission(acl, domain);
d1321 1
a1321 1
        domain.addProject(project);
a1344 1

a1490 36
     * Adds the project to a category.
     */
    public void addToCategory(Category cat)
        throws Exception
    {
        cat.addProject(this);
    }

    /**
     * Removes the project from a category.
     */
    public void removeFromCategory(Category cat)
        throws Exception
    {
        cat.removeProject(this);
    }

    /**
     * Adds this project to the specified group.
     */
    public void addToGroup(AccessControlList acl, ProjectGroup g)
        throws Exception
    {
        g.addProject(acl, this);
    }

    /**
     * Removes this project from the specified group.
     */
    public void removeFromGroup(AccessControlList acl, ProjectGroup g)
        throws Exception
    {
        g.removeProject(acl, this);
    }

    /**
d1508 1
a1508 1
                removeFromCategory(c);
d1514 2
a1515 1
            addToCategory((Category)i.next());
d1588 1
d1593 1
d1603 1
a1603 1
            p.removeProject(this);
d1891 2
a1892 2
     * Asserts addition and/or approval permission for this project in
     * the given domain.
d1895 1
a1895 1
     * @@param domain The domain to check.
d1897 1
a1897 1
    public void assertAddPermission(AccessControlList acl, Domain domain)
d1899 2
a1900 1
    {
d1910 18
a1927 2
        } else {
            acl.assertPermission("ProjectGroup - Add", domain);
d1939 1
a1939 8
        if (isBase()) 
        {
            acl.assertPermission("Project - Edit", this);
        }
        else 
        {
            acl.assertPermission("ProjectGroup - Edit", getDomain());
        }        
d1959 1
a1959 8
            if (isBase()) 
            {
                acl.assertPermission("Project - Delete", this);
            }
            else 
            {
                acl.assertPermission("ProjectGroup - Delete", getDomain());
            }        
d1972 1
a1972 9
            if (isBase()) 
            {
                canDelete = acl.hasPermission("Project - Delete", this);
            }
            else 
            {
                canDelete = acl.hasPermission("ProjectGroup - Delete",
                                              getDomain());
            }        
d2376 40
@


1.134
log
@Actually this is better semantically.
@
text
@d300 1
a300 1
            myDomain = this;
@


1.133
log
@Corrected getDomain to handle domains and hosts.
@
text
@d300 1
a300 1
            myDomain = ProjectPeer.getHost();
@


1.132
log
@Projects can now belong to more than one category.
@
text
@d298 1
a298 2
        List parents = getPermissionedParents();
        for (Iterator i = parents.iterator(); i.hasNext();)
d300 6
a305 2
            Project p = (Project)i.next(); 
            if (p.isDomain())
d307 6
a312 2
                myDomain = (Domain)p;
                break;
a319 1
        Log.debug("Domain of " + getName() + ": " + myDomain.getName());
@


1.131
log
@Corrected getDomain() to work in cases where a project is not a direct child of a domain.
@
text
@d1119 1
a1119 1
     * and adds to the new one.
d1121 1
a1121 1
    public void changeCategory(Category newCat)
d1126 16
a1141 1
            removeFromCategory((Category)i.next());
a1142 2
        //Add the project to the new category.
        addToCategory(newCat);
d1991 14
@


1.130
log
@This is taken care of automatically by save()
@
text
@d276 1
a276 1
        throws DoesNotExist, Exception
d282 10
d295 1
a295 1
        throws DoesNotExist, Exception
d297 18
a314 7
        Criteria c = new Criteria()
            .addJoin(ProjectPeer.PROJECT_ID, 
                     ProjectProjectgroupPeer.PROJECT_GROUP_ID)
            .add(ProjectProjectgroupPeer.PROJECT_ID, getID())
            .add(ProjectPeer.TYPE, DOMAIN_TYPE);
        return (Domain)PeerTool.getFirstBO(ProjectPeer.doSelect(c), 
                                   "Domain for project " + getName());
a345 10
     * Returns the name of this project's domain. dlr would like to see this
     * method deprecated.
     */
    public String getDomainName()
        throws Exception
    {
        return getDomain().getName();
    }

    /**
d491 12
@


1.129
log
@Consistently used getMembers() instead of getUsers()
@
text
@a993 3

        //LEONARDR-FIXME: Rebuild this project's mappings if the public
        //status changed.
@


1.128
log
@Corrected code which assumed the project belonged to a domain before being added to a domain group.
@
text
@d677 1
a677 1
        return getUsers(new Integer(0), c, null);
d699 1
a699 1
        return getUsers(itemsPerPage, base, filter);
d705 1
a705 1
    public PeerBackedList getRequestedUsers()
d708 1
a708 1
        return getRequestedUsers(new Integer(0), HelmPeer.NO_CRITERIA);
d714 1
a714 1
    public PeerBackedList getRequestedUsers
d721 1
a721 1
        return getUsers(itemsPerPage, base, filter);
d730 1
a730 1
        getUsers(Integer itemsPerPage, Criteria base, Criteria filter)
d1941 18
@


1.127
log
@Huge patch of the day: Got rid of redundant HELM_DOMAIN table. The project representation of the domain and the host is now the canonical one. Host and Domain now subclass ProjectGroup. The canonical representation of a project's membership in a domain group is now the corresponding mapping in HELM_PROJECT_PROJECTGROUP. Schema changes: No more HELM_DOMAIN table (obviously); no more DOMAIN_ID in HELM_PROJECT; HELM_USER and HELM_VC_VIEW now have foreign keys in HELM_PROJECT instead of HELM_DOMAIN.
@
text
@d882 1
a882 1
            project.assertAddPermission(acl);
d888 2
a892 2
        domain.addProject(project);

d1478 13
a1490 1
        Domain domain = getDomain();
@


1.126
log
@Use StringUtils instead of doing validity check manually.
@
text
@d276 1
d278 1
a278 1
        return String.valueOf(getDomainId());
d285 1
a285 1
        throws Exception
d287 7
a293 1
        return DomainPeer.getInstance(getDomainID());
d406 1
a406 1
        return (this instanceof HostProjectGroup);
d415 1
a415 1
        return (this instanceof DomainProjectGroup);
d683 1
a683 1
    public PeerBackedList getUsers()
d686 1
a686 1
        return getUsers(new Integer(0), null);
d692 1
a692 1
    public PeerBackedList getUsers(Integer itemsPerPage, Criteria filter)
a869 1
            project.setDomain(domain);
d891 1
a891 1
        project.addToDomainGroup();
d1441 1
a1441 26
    
    //
    // Methods for dealing with this project's associations with project
    // groups.
    //

    /**
     * Adds a project to the list of projects on the host.
     */
    protected void addToHostGroup()
        throws Exception
    {
        AccessControlList acl = AccessControlList.getSuperUserACL();
        ProjectPeer.getHostProject().addProject(acl, this);
    }

    /**
     * Adds a project to the list of projects in the domain.
     */
    protected void addToDomainGroup()
        throws Exception
    {
        AccessControlList acl = AccessControlList.getSuperUserACL();
        ProjectPeer.getDomainProject(getDomain()).addProject(acl, this);
    }
        
d1928 1
@


1.125
log
@removed methods with references to ProjectDownloads
@
text
@d1038 1
a1038 1
        if (reason != null && !"".equals(reason))
@


1.124
log
@removed htaccess methods and put them into a utility class.
@
text
@a632 12
    /**
     * Returns list of unapproved downloads associated with the project.
     */
    public List getUnapprovedDownloads()
        throws Exception
    {
        Criteria c = new Criteria()
            .add(ProjectDownloadPeer.PROJECT_ID, getID())
            .add(ProjectDownloadPeer.APPROVED_BY, 
                 UserPeer.getNobodyUser().getUserId());
        return ProjectDownloadPeer.doSelect(c);
    }
a783 13
    /**
     * Returns a paginatable list of downloads for this project.
     */
    public PeerBackedList getDownloads(Integer itemsPerPage, Criteria filter, 
                                  AccessControlList acl)
        throws Exception
    {
        Criteria base = new Criteria()
            .add(ProjectDownloadPeer.PROJECT_ID, getProjectId());
        return new PeerBackedList
            (acl, ProjectDownloadPeer.class, itemsPerPage, base, filter);
    }

a1205 4

        //Remove all downloads.
        ProjectDownloadPeer.doDelete
            (new Criteria().add(ProjectDownloadPeer.PROJECT_ID, getID()));
@


1.123
log
@Added more places where unapproved roles are filtered out.
@
text
@a174 7
     * The full path to the program used to write .htaccess files.
     */
    private static final String HTACCESS_WRITER =
        JoistProperties.getProperty("SANDBOX") +
        "/helm/perl/cgi/writeHtaccessFile";

    /**
a181 6
     * A hash of mutex objects for handling file write locking to .htaccess 
     * files, keyed by file name.
     */
    private static Map htaccessLocks = new HashMap();

    /**
a1874 77
    /**
     * Gets the full path to the <code>.htaccess</code> file for a protected
     * file's path.
     * <p>
     * TODO: Refactor into Htaccess utility class.
     *
     * @@param path The path to the secured file.
     * @@return     The full path to the <code>.htaccess</code> file.
     */
    protected static final String getHtaccess(ProposableFileArtifact artifact)
        throws ParseException, Exception
    {
        String path = artifact.getLocation();
        // HELP: This is suspect...why do we trim the last dir?
        int dirSepPos = indexOfLastElement(path) - 1;
        String fileServPath = path.substring(0, dirSepPos);
        if (DEBUG)
        {
            Log.debug("Project.getHtaccess: fileServPath=" + fileServPath);
        }
        return (fileServPath + File.separatorChar + artifact.getID() +
                File.separatorChar + ".htaccess");
    }

    /**
     * Finds the index of the last element in the supplied directory path.
     * <p>
     * TODO: Refactor into Htaccess utility class.
     *
     * @@exception ParseException The supplied path doesn't parse.
     */
    private static int indexOfLastElement(String path)
        throws ParseException
    {
        int dirSepPos = path.lastIndexOf(File.separatorChar);
        if (dirSepPos == path.length() - 1)
        {
            // The provided path ended in the directory separator.
            dirSepPos = path.lastIndexOf(File.separatorChar, dirSepPos);
        }
        if (dirSepPos != -1)
        {
            return (dirSepPos + 1);
        }
        else
        {
            throw new ParseException("Project.indexOfLastElement(): Invalid  "
                                     + "file path: " + path, dirSepPos);
        }
    }

    /**
     * Writes the .htaccess file in the right directory for this project.
     * <p>
     * TODO: Refactor into Htaccess utility class.
     *
     * @@param htaccess  The full path to the .htaccess file.
     * @@param url       The URL for auth and timeout (when <code>null</code>,
     *                  defaults to <code>http://[JVM host]/</code>).
     * @@param artifacts The {@@link org.tigris.helm.om.ProjectFileArtifact}
     *                  objects to provide access control for.
     */
    protected void writeHtaccessFile(String htaccess, String url,
                                     List artifacts)
        throws Exception
    {
        if (url == null)
        {
            url = URL.DEFAULT_PROTOCOL + "://" +
                InetAddress.getLocalHost().getHostName() + '/';
        }

        // If not in vhosting mode, append projectID as CGI param.
        if (!isVhosting())
        {
            url = URL.appendCGIParam(url, "projectID", getID());
        }
a1875 74
        // Get a mutex for the given .htaccess file.
        Object htaccessWriteLock = null;
        synchronized (htaccessLocks)
        {
            if (!htaccessLocks.containsKey(htaccess))
            {
                htaccessLocks.put(htaccess, new Object());
            }
            htaccessWriteLock = htaccessLocks.get(htaccess);
        }

        // Append each entry to the .htaccess file.
        synchronized (htaccessWriteLock)
        {
            // Format inputs for writeHtaccessFile script.
            StringBuffer fakedUrl = new StringBuffer(url).append("?null");
            String path;
            for (Iterator i = artifacts.iterator(); i.hasNext(); )
            {
                ProjectFileArtifact artifact = (ProjectFileArtifact) i.next();
                String fileName = artifact.getFileName();
                Log.debug("fileName=" + fileName);
                if (fileName != null)
                {
                   path = artifact.getLocation();
                   fakedUrl.append('&')
                   .append(URL.encode(artifact.getFileName()))
                   .append('=')
                   .append(generateMDARealmMask(artifact.isPublic()));
                }
            }

	    //Log.debug("Project.writeHtaccessFile: .htaccess file name=" +
	    //          htaccess);
            String[] cmd = { HTACCESS_WRITER, htaccess, fakedUrl.toString() };
            int exitValue = 0;
            try
            {
                Process proc = Runtime.getRuntime().exec(cmd);
                exitValue = proc.waitFor();
            }
            catch (RuntimeException e)
            {
                exitValue = 1;
            }

            if (exitValue != 0)
            {
                throw new Exception("Unable to write .htaccess file");
            }
        }
    }

    /**
     * All the .htaccess files within a specific projectID will share that
     * same realm.  The return value will be used in creation of an auth
     * ticket for file downloads.
     * <p>
     * TODO: Refactor into Htaccess utility class.
     *
     * @@param isPublicFile Whether the download requires auth for a
     *                    <i>specific domain</i>.
     * @@return            The hexidecimal representation of the project's 
     *                    unique identifier.
     */
    public String generateMDARealmMask(boolean isPublicFile)
    {
        // If the file requested is private, set the projectID bit.
        BigInteger i = (isPublicFile ?
                        BigInteger.ZERO : 
                        BigInteger.ZERO.setBit(Integer.parseInt(getID())));
        // Return as hex (a la tohex(2 ^ projectID)).
        return i.toString(16);
    }
@


1.122
log
@Added reason for denying role requests and disapproving projects.
@
text
@d689 2
a690 3
    {
        Criteria c = RolePeer.getCriteriaForRolesWithBooleanAttribute
            (AttributePeer.DENOTES_OWNERSHIP_ATTRIBUTE)
d694 1
@


1.121
log
@PeerBackedList performance boost and refactoring. This may be more complicated than it needs to be; rather than getting ever-increasing proportions of the table, it might be faster in most cases to get the whole table, so long as you stop processing results when the list is full enough.
@
text
@d1059 10
d1076 4
@


1.120
log
@reversing change for now since these methods really shuld not be part of the Project api, will move
@
text
@d90 2
a91 1
public class Project extends BaseProject implements ProjectType, Comparable
d773 1
a773 11
            (NewsItemPeer.class, itemsPerPage, base, filter);
        List toRemove = new ArrayList();
        for (Iterator i = newsItems.iterator(); i.hasNext();)
        {
            NewsItem n = (NewsItem)i.next();
            if (!n.isViewableBy(acl))
            {
                toRemove.add(n);
            }
        }
        newsItems.removeAll(toRemove);
d791 1
a791 11
            (NewsItemPeer.class, itemsPerPage, base, filter);
        for (Iterator i = p.iterator(); i.hasNext();)
        {
            NewsItem n = (NewsItem)i.next();
            if (!acl.hasPermission 
                (n.requiredViewPermission(acl), this))
            {
                toRemove.add(n);
            }           
        }
        p.removeAll(toRemove);
d818 2
a819 13
        PeerBackedList downloads = new PeerBackedList
            (ProjectDownloadPeer.class, itemsPerPage, base, filter);
        List toRemove = new ArrayList();
        for (Iterator i = downloads.iterator(); i.hasNext();)
        {
            ProjectDownload dl = (ProjectDownload)i.next();
            if (!dl.isViewableBy(acl))
            {
                toRemove.add(dl);
            }
        }
        downloads.removeAll(toRemove);
        return downloads;
@


1.119
log
@opened up access to these methods so that project documents can be refactored to a different component
@
text
@d1913 1
a1913 1
    public static final String getHtaccess(ProposableFileArtifact artifact)
d1966 1
a1966 1
    public void writeHtaccessFile(String htaccess, String url,
d2076 2
d2080 1
a2080 1
    public String getHtdocDir()
@


1.118
log
@Reinstated asGroup() methods as deprecated.
@
text
@d1913 1
a1913 1
    protected static final String getHtaccess(ProposableFileArtifact artifact)
d1966 1
a1966 1
    protected void writeHtaccessFile(String htaccess, String url,
d2078 1
a2078 1
    protected String getHtdocDir()
@


1.117
log
@Got rid of the ugly and unneccessary asGroup() and getGroupInstance() methods.
@
text
@d2141 13
@


1.116
log
@The more comments the better.
@
text
@a566 16
    /**
     * Turns a Project representation of a project representing a group
     * into a ProjectGroup representation of the project.
     */
    public ProjectGroup asGroup()
        throws Exception
    {
        if (getType().equals(Project.REGULAR_TYPE))
        {
            throw new Exception
                ("Attempt to transform non-group project " + getName() 
                 + " to ProjectGroup.");
        } 
        return ProjectPeer.getGroupInstance(getID());
    }

d919 12
a930 1
            project = new Project();
@


1.115
log
@Added general records-in-table method.
@
text
@d1280 1
@


1.114
log
@Refactored mass user permission-checking code.
@
text
@d1349 2
a1350 1
        int numberOfProjects = ProjectPeer.numberOfProjects();
@


1.113
log
@Added FIXME note for Elicia.
@
text
@a1386 1
        // This should be moved into a RegisterableCache.
d1389 1
a1389 15
            List roles = RolePeer.getRolesWithAction(actions[i]);
            for (int j = 0; j <= 1; j++)
            {
                roleIDs[i][j] = new Vector(roles.size());
            }

            for (Iterator j = roles.iterator(); j.hasNext();)
            {
                Role r = (Role)j.next();
                roleIDs[i][0].add(r.getID());
                if (r.canRecurseIntoPrivateProjects())
                {
                    roleIDs[i][1].add(r.getID());
                }
            }
d1440 11
a1450 1
     * Returns all users who have the given permission in this project
@


1.112
log
@Added method to estimate the worst-case cost of rebuilding a project's issue tracking.
@
text
@d677 3
d1925 1
@


1.111
log
@Project names may not end with a dash.
@
text
@d1334 18
@


1.110
log
@When deleting a project, delete outstanding issue tracking requests
for that project.
@
text
@d143 1
a143 1
        "^[a-zA-Z0-9][-a-zA-Z0-9]{0,63}$";
@


1.109
log
@Many changes to the Project Documents component.  It is now replacing ProjectDownloads (will remove it from cvs after a while). Took out the "type" variable so that a Document can have a file upload attachment and/or a remote URL and/or pasted text, instead of only one of the three. Added fields for modified by and modified date. Also added isPublic field for file uploads, and a status field.

Document file uploads are now being saved in helm/data/fileserv/documents/[document_id]. Refactored htAccess file code to reflect that and to fix other bugs.
Dropped $type parameter from URL arguments."

UI has changed: ProjectDocumentList now has a 2-pane UI with a folder tree on the left pane and the document list on the right.  When in the list you have a "root folder" that is the root of the tree.  Folders can now be infinitely nested instead of just two levels deep.  You add a document or subfolder directly to the root section.
@
text
@d1280 5
@


1.108
log
@Factored out script running code to support new IssueTrackingRebuildService.
@
text
@a180 6
     * The list of glob characters used in an Apache .htaccess file Files
     * directive.
     */
    private static final char[] HTACCESS_GLOB_CHARS = { '*', '?' };

    /**
d605 6
d614 1
a614 1
    public List getSections()
d619 1
a619 1
            .add(ProjectSectionPeer.PARENT_ID, 0);
d623 1
d1889 2
d1900 1
a1900 1
        String relativePath = path.substring(0, dirSepPos);
d1903 1
a1903 1
            Log.debug("Project.getHtaccess: relativePath=" + relativePath);
d1905 1
a1905 2
        return (relativePath + File.separatorChar +
                artifact.getProposedByUser().getID() +
d1911 2
d1938 2
d1983 10
a1992 9
                path = artifact.getLocation();
                fakedUrl.append('&')
                    .append(artifact.getFileArtifactType())
                    .append('-')
                    .append(path.substring(indexOfLastElement(path)))
                    .append(File.separatorChar)
                    .append(URL.encode(artifact.getFileName()))
                    .append('=')
                    .append(generateMDARealmMask(artifact.isPublic()));
d1995 3
a1997 3
            String[] cmd = { HTACCESS_WRITER,
                             escapeHtaccessFileName(htaccess),
                             fakedUrl.toString() };
d2020 2
a2035 30
    }

    /**
     * Escapes the <code>.htaccess</code> file glob characters in the
     * specified file name.
     *
     * @@param fileName The file name to escape.
     * @@return         The glob-escaped file name.
     */
    private String escapeHtaccessFileName(String fileName)
    {
        StringBuffer buf = new StringBuffer();
        int cur, last;
        char ch;

        for (int j = 0; j < HTACCESS_GLOB_CHARS.length; j++)
        {
            buf.setLength(0);
            ch = HTACCESS_GLOB_CHARS[j];
            cur = last = 0;

            while ((cur = fileName.indexOf(ch, last)) != -1)
            {
                buf.append(fileName.substring(last, ++cur)).append('\\');
                last = cur;
            }
            fileName = buf.toString();
        }

        return fileName;
@


1.107
log
@Big cleanup of cache self-descriptions. Also added an admin tool to do manual selective cache invalidation.
@
text
@d79 2
d1328 2
a1329 2
     * Clear the Issuezilla accounts and rebuild them according to
     * what's in the database.
d1334 91
a1424 1
        HelmTask.rebuildIssueTracking(this);
d1436 1
a1436 1
    protected List getUsersWithRoles(Vector[] roles)
@


1.106
log
@Got rid of denormalized project-project group mapping table in favor of per-JVM caching solution.
@
text
@d477 1
a477 5
        Criteria c = new Criteria(2)
            .addJoin(ProjectProjectgroupPeer.PROJECT_GROUP_ID, 
                     ProjectPeer.PROJECT_ID)
            .add(ProjectProjectgroupPeer.PROJECT_ID, getID());
        return ProjectPeer.doSelect(c);
d488 1
a488 5
        Criteria c = new Criteria(2)
            .addJoin(ProjectProjectgroupPeer.PROJECT_ID, 
                     ProjectPeer.PROJECT_ID)
            .add(ProjectProjectgroupPeer.PROJECT_GROUP_ID, getID());
        return ProjectPeer.doSelect(c);
@


1.105
log
@Fixed build error.
@
text
@a456 9
    /**
     * Returns a list of all the project groups to which this project belongs.
     * Does not include the special host or domain project groups.
     */
    public List getProjectGroups()
        throws Exception
    {
        return getGroupsOfType(Project.GROUP_TYPE);
    }
d459 3
a461 1
     * Returns a list of all the categories to which this project belongs.
d463 1
a463 2
    public List getCategories()
        throws Exception
d465 2
a466 1
        return getGroupsOfType(Project.CATEGORY_TYPE);
d470 3
a472 2
     * Returns true iff a public path exists between this project and
     * the given path.
d474 1
a474 1
    public boolean publicPathExistsTo(Project potentialChild)
d477 5
a481 7
        Criteria c = new Criteria(3)
            .add(DenormalizedProjectProjectgroupPeer.PARENT_ID, getID())
            .add(DenormalizedProjectProjectgroupPeer.CHILD_ID, 
                 potentialChild.getID())
            .add(DenormalizedProjectProjectgroupPeer.PUBLIC_PATH, "Y");
        List results = DenormalizedProjectProjectgroupPeer.doSelect(c);
        return (results.size() > 0);
d485 3
a487 2
     * Returns a list of all the project groups which are of the specified
     * type and to which this project belongs.
d489 1
a489 1
    private List getGroupsOfType(String type)
d492 2
a493 2
        Criteria c = new Criteria()
            .addJoin(ProjectProjectgroupPeer.PROJECT_GROUP_ID, 
d495 1
a495 5
            .add(ProjectProjectgroupPeer.PROJECT_ID, getProjectId());
        if (type != null)
        {
            c.add(ProjectPeer.TYPE, type);
        }
d500 6
a505 3
     * Returns the project of which this is a subproject. May return a
     * domain project, the host project (if called upon a domain project),
     * or null (if called upon the host project).
d507 2
a508 1
    public Project getParentProject()
d510 2
a511 1
        return this;
d515 2
a516 2
     * Returns all the projects to which permissions in this project
     * recurse.
d518 1
a518 1
    public List getEffectiveProjects()
d521 1
a521 19
        return getRecursiveChildren();
    }

    public List expand()
        throws Exception
    {
        return getEffectiveProjects();
    }

    public List getRecursiveChildren()
        throws Exception
    {
        return getRecursiveChildren(null);
    }

    public List getGroups()
        throws Exception
    {
        return getRecursiveParents();
d525 2
a526 5
     * Returns all denormalized children of this project. The recursion
     * is that of the denormalized project group mapping table; ie.
     * this project is treated as a recursive child of itself, and any
     * non-category projects in its recursive children which are
     * categories are not treated as recursive children.
d528 1
a528 1
    public List getRecursiveChildren(Boolean findPublic)
d531 3
a533 7
        Criteria c = new Criteria(3)
            .addJoin(ProjectPeer.PROJECT_ID,
                     DenormalizedProjectProjectgroupPeer.CHILD_ID)
            .add(DenormalizedProjectProjectgroupPeer.PARENT_ID, 
                 getID());

        if (findPublic != null)
d535 2
a536 2
            c.add(DenormalizedProjectProjectgroupPeer.PUBLIC_PATH,
                  DBFlag.flagValueOf(findPublic.booleanValue()));
d538 1
a538 1
        return ProjectPeer.doSelect(c);
d542 3
a544 5
     * Returns all recursive parents of this project. The recursion is
     * that of the denormalized project group mapping table; ie.  this
     * project is treated as a recursive parent of itself, categories
     * to which this project belongs are not treated as parents
     * (unless this project is itself a category).
d546 1
a546 1
    public List getRecursiveParents()
d549 1
a549 15
        Criteria c = new Criteria(3)
            .addJoin(DenormalizedProjectProjectgroupPeer.PARENT_ID,
                     ProjectPeer.PROJECT_ID)
            .add(DenormalizedProjectProjectgroupPeer.CHILD_ID, 
                 getID());
        return ProjectPeer.doSelect(c);
    }

    public List getDenormalizedMappingsForParents()
        throws Exception
    {
        Criteria c = new Criteria(3)
            .add(DenormalizedProjectProjectgroupPeer.CHILD_ID, 
                 getID());
        return DenormalizedProjectProjectgroupPeer.doSelect(c);
d553 1
a553 2
     * Returns the IDs of all this project's recursive parents, as defined
     * in getRecursiveParents().
d555 1
a555 1
    public Vector getParentIDs(boolean publicPath)
d558 1
a558 14
        Criteria c = new Criteria(2)
            .add(DenormalizedProjectProjectgroupPeer.CHILD_ID, 
                 getID())
            .add(DenormalizedProjectProjectgroupPeer.PUBLIC_PATH,
                 DBFlag.flagValueOf(publicPath));
        List objs = DenormalizedProjectProjectgroupPeer.doSelect(c);
        Vector ids = new Vector(objs.size());
        for (Iterator i = objs.iterator(); i.hasNext();)
        {
            DenormalizedProjectProjectgroup o = 
                (DenormalizedProjectProjectgroup)i.next();
            ids.add(o.getParentID());
        }
        return ids;
d562 2
a563 3
     * Returns all direct children of this project; ie. all direct
     * mappings in the project group mapping table for which this is
     * the parent project. 
d565 1
a565 1
    public List getDirectChildren()
d568 2
a569 2
        Criteria c = new Criteria(2)
            .addJoin(ProjectProjectgroupPeer.PROJECT_ID, 
d571 5
a575 1
            .add(ProjectProjectgroupPeer.PROJECT_GROUP_ID, getID());
d589 2
a590 1
                ("Attempt to transform non-group project " + getName() + " to ProjectGroup.");
d775 1
a775 1
        base.addOrderByColumn(UserPeer.TYPE + " DESC");
d1240 1
a1240 1
        List groups = getGroups();
d1297 1
a1297 1
        for (Iterator i = expand().iterator(); i.hasNext(); )
d1359 1
a1359 1
            Vector myIDs = getParentIDs(i==0?true:false);            
@


1.104
log
@Corrected getOwners() method.
@
text
@d1033 2
a1034 1
        else {
a1037 7
        // before we grant the role of project owner, we should set up
        // the project in anzu (since currently a special task is used
        // to maintain the owner alias)
        AnzuTask.addProject(newOwner, domain, project);
        AnzuTask.setOwnerAlias(newOwner, project, false);
        profile.addDefaultMailingLists(project, newOwner,
                project.isProprietary());
a1038 1
        AccessControlList rootACL = UserPeer.getSuperUser().getACL();
d1043 1
a1043 1
            newOwner.grantRole(rootACL, r, project);
d1052 8
@


1.103
log
@.wm is now .vm
@
text
@d766 3
a768 2
            .add(UserRoleProjectPeer.APPROVED_BY, 
                 (Object)UserPeer.getNobodyUser().getID(), Criteria.NOT_EQUAL);
@


1.102
log
@Owners are only those with approved roles.
@
text
@d148 1
a148 1
        TemplateTool.getPathToMessageTemplate("ProjectCreated.wm");
d155 1
a155 1
        TemplateTool.getPathToMessageTemplate("ProjectDisapprove.wm");
d162 1
a162 1
        TemplateTool.getPathToMessageTemplate("ProjectLocked.wm");
d169 1
a169 1
        TemplateTool.getPathToMessageTemplate("ProjectUnlocked.wm");
@


1.101
log
@Finally got rid of REQUESTABLE and DENOTES_OWNER fields in HELM_ROLE.
@
text
@d765 3
a767 1
            (AttributePeer.DENOTES_OWNERSHIP_ATTRIBUTE);
@


1.100
log
@Ported Helm from WebMacro to Velocity.
@
text
@d764 2
a765 1
        Criteria c = new Criteria(4).add(RolePeer.DENOTES_OWNER, true);
@


1.99
log
@Don't add a new project to the host group now that the group membership table is normalized.
@
text
@d62 1
d65 3
a67 1
import org.apache.turbine.services.webmacro.TurbineWebMacro;
a70 2
import org.webmacro.servlet.WebContext;

d179 6
d1069 1
a1069 1
            WebContext ctx = TurbineWebMacro.getContext();
d1072 1
a1072 1
            String body = TurbineWebMacro.handleRequest(ctx, CREATED_TMPL);
d1152 1
a1152 1
        WebContext ctx = TurbineWebMacro.getContext();
d1155 1
a1155 1
        String body = TurbineWebMacro.handleRequest(ctx, DISAPPROVE_TMPL);
d1259 1
a1259 1
            WebContext ctx = TurbineWebMacro.getContext();
d1261 1
a1261 1
            String body = TurbineWebMacro.handleRequest(ctx, template);
d1956 3
a1958 1
            String[] cmd = { HTACCESS_WRITER, htaccess, fakedUrl.toString() };
d1995 30
@


1.98
log
@correct reference to ProjectMemberList (no longer ProjectMembers)
@
text
@a1005 1
        project.addToHostGroup();
@


1.97
log
@Fixed up cluster singleton interface and made the task runner into a cluster singleton.
@
text
@d1669 1
a1669 1
            ("ProjectMembers", null, "http", false, true);
@


1.96
log
@getProposedByUser() throws Exception...propogated.
@
text
@d1391 1
a1391 1
     * or any of its subprojects to which permissions recurse.  
d1396 1
a1396 1
     * project only through private subprojects.
@


1.95
log
@.htaccess file are now be written to <project.id>/<user.id>/<file_name>.
@
text
@d1855 1
a1855 1
        throws ParseException
@


1.94
log
@Added to JavaDoc.
@
text
@d1854 1
a1854 1
    protected static final String getHtaccess(String path)
d1857 1
d1864 3
a1866 1
        return (relativePath + File.separatorChar + ".htaccess");
@


1.93
log
@Added a note about what the getHtdocDir() instance method should be
used for.
@
text
@d1897 2
a1898 1
     * @@param artifacts The file artifacts to provide access control for.
@


1.92
log
@Back out the last change since it is for directories and not URLs.
@
text
@d2001 2
a2002 1
     * <code>SANDBOX/data/helm/htdoc/PROJECTNAME/www</code>).
d2006 1
a2006 1
    protected String getHtdocDir ()
@


1.91
log
@Declare getHtdocDir() public.
@
text
@d2005 1
a2005 1
    public String getHtdocDir ()
@


1.90
log
@Refactored a lot of code to get rid of the good-looking but very slow IssueTrackingAccount{Descriptor,Map} classes. Also cleaned up the script-running interface quite a bit.
@
text
@d2005 1
a2005 1
    protected String getHtdocDir ()
@


1.89
log
@Reverting bad commit.
@
text
@a79 1
import org.tigris.helm.tool.task.IssueTrackingAccountDescriptor;
a1219 2
        IssueTrackingAccountDescriptor before = null;
        IssueTrackingAccountDescriptor after = null;
d1225 1
d1238 1
a1238 1
                before = new IssueTrackingAccountDescriptor();
d1247 2
a1248 2
            
            if (before != null)
d1250 1
a1250 2
                after = new IssueTrackingAccountDescriptor(this);
                before.syncTo(after);
d1383 1
a1383 1
    public void rebuildIssueTracking(AccessControlList acl)
d1386 1
a1386 76
        acl.assertPermission("Project Role - Grant", this);
        User onBehalfOf = acl.getUser();

        String[] accountTypes = {"observer", "developer", "admin"};
        String[] actions = 
        {
            "Project Issue Tracking - Submit",
            "Project Issue Tracking - Change",
            "Project Issue Tracking - Configure"
        };

        int types = accountTypes.length;
        List[] userLists = new List[types];
        Vector[][] roleIDs = new Vector[types][2];

        //See which roles and which private-recursive roles have the
        //given permission.
        for (int i = 0; i < types; i++)
        {
            List roles = RolePeer.getRolesWithAction(actions[i]);
            for (int j = 0; j <= 1; j++)
            {
                roleIDs[i][j] = new Vector(roles.size());
            }

            for (Iterator j = roles.iterator(); j.hasNext();)
            {
                Role r = (Role)j.next();
                roleIDs[i][0].add(r.getID());
                if (r.canRecurseIntoPrivateProjects())
                {
                    roleIDs[i][1].add(r.getID());
                }
            }
        }

        //Process all of this project's denormalized children (this
        //project is a denormalized child of itself).
        for (Iterator i = getRecursiveChildren().iterator(); i.hasNext();)
        {
            Project project = (Project)i.next();
            Log.debug("Processing project " + project.getName());
            if (!(project.isHost() || project.isDomain()))
            {

                //For each relevant action, find the users who have that
                //action in this project.
                for (int j = 0; j < types; j++)
                {
                    userLists[j] = project.getUsersWithRoles(roleIDs[j]);
                }
                
                Log.debug("i");
                //The account types are arranged in increasing order of
                //power. Thus, a user with account type i should not be in
                //the list of users with account type j < i.
                for (int j = 0; j < types; j++)
                {
                    for (int k = types-1; k > j; k--)
                    {
                        userLists[j].removeAll(userLists[k]);
                    }
                }
                
                //Disable all accounts for the project, and then add the
                //appropriate accounts back.
                HelmTask.disableAllAccounts(onBehalfOf, project);
                for (int j = 0; j < types; j++)
                {
                    Log.debug(userLists[j].size() + " accounts of type " 
                              + accountTypes[j]);
                    HelmTask.addMembers(onBehalfOf, project, userLists[j], 
                                        accountTypes[j]);
                }
            }
        }
d1398 1
a1398 1
    private List getUsersWithRoles(Vector[] roles)
@


1.88
log
@Removed obsolete TODO
@
text
@d88 1
a88 2
public class Project extends NestedProjectSpace
    implements GroupAssociableProjectSpace, CategoryAssociableProjectSpace
d126 18
d192 204
d403 42
d451 9
d487 18
d1110 3
@


1.87
log
@A Heartbreaking Patch of Staggering Genius: adds capacity (but no interface) for deeply nested projects, categories, and project groups. As a side effect, gets rid of the hacky special 'all public projects in domain' group. There are probably bugs in this, and some of those bugs are probably security holes, so watch out.
@
text
@d88 2
a89 1
public class Project extends BaseProject implements ProjectType, Comparable
a126 18
     * Description of the audit log entry created when a project of this
     * type is locked.
     */
    private static final String AUDIT_LOG_ON_LOCK = "Project locked: ";

    /**
     * Description of the audit log entry created when a project of this
     * type is unlocked.
     */
    private static final String AUDIT_LOG_ON_UNLOCK = "Project unlocked: ";

    /**
     * The regular expression used to check project names for validity.
     */
    private static final String PROJECT_NAME_RE =
        "^[a-zA-Z0-9][-a-zA-Z0-9]{0,63}$";

    /**
a174 204
     * Checks whether a project name is valid.
     *
     * @@param name The project name to check.
     * @@return     Whether the name is valid.
     */
    public static boolean isValidName(String name)
    {
        boolean isValid = false;

        if (name != null && name.length() > 0)
        {
            RE projectRE = null;
            try
            {
                projectRE = new RE(PROJECT_NAME_RE);
                isValid = projectRE.isMatch(name);
            }
            catch (REException e)
            {
                Log.error(e);
            }
        }

        return isValid;
    }

    /**
     * Macro which encapsulates transformation of a word mark to a
     * canonical name.
     *
     * @@param wordMark The mixed case word mark to transform.
     * @@return         The name as lower case.
     */
    private static final String transformWordMarkToName(String wordMark)
    {
        return wordMark.toLowerCase();
    }

    /**
     * Asserts that a project with the specified name does not already
     * exist in the specified domain.
     *
     * @@param domain The domain to check for the project in.
     * @@param name   The name of the project to check for.
     *
     * @@exception AlreadyExists The project exists.  
     */
    public static void assertNonexistance(Domain domain, String name)
        throws AlreadyExists, Exception
    {
        try {
            Project p = ProjectPeer.getInstanceByName(domain, name);
        }
        catch (DoesNotExist success) {
            return;
        }
        throw new AlreadyExists("Project named " + name,
                                "domain named " + domain.getName());
    }


    //
    // Accessor methods for particular projects.
    //

    /**
     * Returns the name of the project in case which it is saved to the
     * database (i.e. it is possible that mixed case text may be returned).
     */
    public final String getWordMark()
    {
        return super.getName();
    }

    /**
     * Returns the name of the project in lower case.
     */
    public final String getName()
    {
        return super.getName().toLowerCase();
    }
    
    /**
     * Returns the brand of this project--not used yet.
     */
    public String getBrand()
    {
        return "";
    }

    /**
     * Returns the ID of this project's domain as a string.
     */
    public String getDomainID()
    {
        return String.valueOf(getDomainId());
    }

    /**
     * Returns this project's domain.
     */
    public Domain getDomain()
        throws Exception
    {
        return DomainPeer.getInstance(getDomainID());
    }

    /**
     * Gets the profile for this project.
     */
    public ProjectProfile getProjectProfile()
        throws Exception
    {
        return ProjectProfiles.getInstance().get(getProfile());
    }

    /**
     * Returns true iff this project is supposed to be using the
     * project home page instead of its description.
     */
    public boolean useIndex()
    {
        boolean useIndex;
        try
        {
            useIndex = DBFlag.booleanValueOf(getUseIndex());
        }
        catch (Exception e)
        {
            Log.warn("Error checking Project.useIndex()", e);
            useIndex = false;
        }
        return useIndex;
    }

    /**
     * Returns the name of this project's domain. dlr would like to see this
     * method deprecated.
     */
    public String getDomainName()
        throws Exception
    {
        return getDomain().getName();
    }

    /**
     * Checks whether a project has been approved. A project is approved
     * if it's "approved by" field is not set to the "nobody" user.
     *
     * @@return     Whether the project has been approved.
     *
     */
    public boolean isApproved()
        throws Exception
    {
        String approvedBy = String.valueOf(getApprovedBy());
        return (!approvedBy.equals(UserPeer.getNobodyUser().getID()));
    }   

    /**
     * Returns true iff the project has a proprietary license.
     *
     * FIXME: This should probably be deprecated.
     */
    public boolean isProprietary()
    {
        return Licenses.isProprietary(getLicense());
    }

    /**
     * Returns true iff the project is not publically viewable.
     */
    public boolean isPrivate()
    {
        return !isPublic();
    }

    /**
     * Returns true iff the project is publically viewable.
     */
    public boolean isPublic()
    {
        return DBFlag.booleanValueOf(getIsPublic());
    }

    /**
     * Returns true iff this project is locked.
     */
    public boolean isLocked()
    {
        return DBFlag.booleanValueOf(getLocked());
    }

    /**
     * Compares this project to another object for sorting purposes.
     */
    public int compareTo(Object o)
        throws ClassCastException
    {
        Project p = (Project)o;
        return getName().compareTo(p.getName());
    }

    /**
a181 42
    /**
     * Returns true iff the project is that special project representing
     * all projects on the host.
     */
    public boolean isHost()
    {
        return (this instanceof HostProjectGroup);
    }

    /**
     * Returns true iff the project is a special project representing
     * the projects in a domain.
     */
    public boolean isDomain()
    {
        return (this instanceof DomainProjectGroup);
    }

    /**
     * Returns true iff the project is a category project.
     */
    public boolean isCategory()
    {
        return (this instanceof Category);
    }

    /**
     * Returns true iff the project is a project group.
     */
    public boolean isGroup()
    {
        return (this instanceof ProjectGroup);
    }

    /**
     * Returns true iff the project represents a base project.
     */
    public boolean isBase()
    {
        return ("base".equals(this.getType()));
    }

a187 9
    /**
     * Returns a list of all the project groups to which this project belongs.
     * Does not include the special host or domain project groups.
     */
    public List getProjectGroups()
        throws Exception
    {
        return getGroupsOfType(Project.GROUP_TYPE);
    }
a214 18
     * Returns a list of all the project groups which are of the specified
     * type and to which this project belongs.
     */
    private List getGroupsOfType(String type)
        throws Exception
    {
        Criteria c = new Criteria()
            .addJoin(ProjectProjectgroupPeer.PROJECT_GROUP_ID, 
                     ProjectPeer.PROJECT_ID)
            .add(ProjectProjectgroupPeer.PROJECT_ID, getProjectId());
        if (type != null)
        {
            c.add(ProjectPeer.TYPE, type);
        }
        return ProjectPeer.doSelect(c);
    }

    /**
a819 3

        //LEONARDR-FIXME: Rebuild this project's mappings if the public
        //status changed.
@


1.86
log
@We still throw ACLException.
@
text
@d338 5
a342 2
     * Returns a list of all the project groups, including categories and
     * special project groups.
d344 1
a344 1
    public List getGroups()
d347 96
a442 1
        return getGroupsOfType(null);
d445 6
d452 2
a453 2
     * Returns a list of all this project's groups, excluding categories and
     * special project groups.
d455 1
a455 1
    public List getNonSpecialGroups()
d471 16
d505 43
a547 2
     * Turns a Project representation of a project representing a group
     * into a ProjectGroup representation of the project.
d549 1
a549 1
    public ProjectGroup asGroup()
d552 7
a558 1
        if (getType().equals(Project.REGULAR_TYPE))
d560 4
a563 4
            throw new Exception
                ("Attempt to transform non-group project " + getName() + " to ProjectGroup.");
        } 
        return ProjectPeer.getGroupInstance(getID());
d567 5
a571 3
     * Returns all the projects equivalent to this one. For a regular project,
     * returns a list containing itself. For a group project, returns a list
     * containing all the projects in the group.
d573 1
a573 1
    public List expand()
d576 6
a581 3
        List l = new ArrayList(1);
        l.add(this);
        return l;
d584 1
a584 1
    public List selfAndChildren()
d587 4
a590 7
        Criteria c = new Criteria()
            .addJoin(ProjectPeer.PROJECT_ID, 
                     ProjectProjectgroupPeer.PROJECT_ID)
            .add(ProjectProjectgroupPeer.PROJECT_GROUP_ID, getID());
        List projects = ProjectPeer.doSelect(c);
        projects.add(this);
        return projects;
d593 5
a597 1
    public List selfAndParents()
d600 14
a613 7
        Criteria c = new Criteria()
            .addJoin(ProjectPeer.PROJECT_ID, 
                     ProjectProjectgroupPeer.PROJECT_GROUP_ID)
            .add(ProjectProjectgroupPeer.PROJECT_ID, getID());
        List projects = ProjectPeer.doSelect(c);
        projects.add(this);
        return projects;
d617 3
a619 5
     * Checks whether a project has been approved. A project is approved
     * if it's "approved by" field is not set to the "nobody" user.
     *
     * @@return     Whether the project has been approved.
     *
d621 1
a621 1
    public boolean isApproved()
d624 6
a629 3
        String approvedBy = String.valueOf(getApprovedBy());
        return (!approvedBy.equals(UserPeer.getNobodyUser().getID()));
    }   
d632 2
a633 2
     * Returns true iff the project is that special project representing
     * all projects on the host.
d635 14
a648 1
    public boolean isHost()
d650 1
a650 1
        return (this instanceof HostProjectGroup);
d654 3
a656 1
     * Returns true iff the project is the default project.
d658 2
a659 1
    public boolean isDefaultProject()
d661 6
a666 2
        return (getName().equals(ProjectPeer.DEFAULT_PROJECT_NAME));
    }
a752 79
     * Returns true iff the project is a special project representing
     * the projects in a domain.
     */
    public boolean isDomain()
    {
        return (this instanceof DomainProjectGroup);
    }

    /**
     * Returns true iff the project is a special category project group.
     */
    public boolean isCategory()
    {
        return (this instanceof Category);
    }

    /**
     * Returns true iff the project represents a project group of some kind.
     */
    public boolean isGroup()
    {
        return (this instanceof ProjectGroup);
    }

    /**
     * Returns true iff the project represents a base project.
     */
    public boolean isBase()
    {
        return !isGroup();
    }

    /**
     * Returns true iff the project has a proprietary license.
     */
    public boolean isProprietary()
    {
        return Licenses.isProprietary(getLicense());
    }

    /**
     * Returns true iff the project is not publically viewable.
     */
    public boolean isPrivate()
    {
        return !isPublic();
    }

    /**
     * Returns true iff the project is publically viewable.
     */
    public boolean isPublic()
    {
        return DBFlag.booleanValueOf(getIsPublic());
    }

    /**
     * Returns true iff this project is locked.
     */
    public boolean isLocked()
    {
        return DBFlag.booleanValueOf(getLocked());
    }

    /**
     * Compares this project to another object for sorting purposes.
     */
    public int compareTo(Object o)
        throws ClassCastException
    {
        Project p = (Project)o;
        return getName().compareTo(p.getName());
    }

    //
    // Methods to get related objects.
    //

    /**
d988 1
a1001 1
            project.changePublicStatus(DBFlag.flagValueOf(isPublic));
d1101 1
a1107 1
        changePublicStatus(isPublic);
d1110 3
a1132 4
            if (isPublic())
            {
                addToPublicGroup();
            }
a1278 25
     * Makes the project public or private
     */
    public void changePublicStatus(String isPublicS)
        throws Exception
    {
        boolean isPublic = DBFlag.booleanValueOf(isPublicS);
        boolean wasPublic = DBFlag.booleanValueOf(getIsPublic());
        //If we are approved, change appropriate groups.
        if (isApproved() && wasPublic != isPublic)
        {
            if (isPublic)
            {
                addToPublicGroup();
            }
            else
            {
                removeFromPublicGroup();
            }
        }

        super.setIsPublic(isPublicS);
        save();
    }

    /**
a1388 1
        Log.debug("Rebuilding issue tracking for " + getName());
d1392 32
a1423 9
        Vector observerRoleIDs = RolePeer.getRoleIDsWithAction
            ("Project Issue Tracking - Submit");
        Vector developerRoleIDs = RolePeer.getRoleIDsWithAction
            ("Project Issue Tracking - Change");
        Vector adminRoleIDs = RolePeer.getRoleIDsWithAction
            ("Project Issue Tracking - Configure");
        Log.debug(adminRoleIDs.size() + " admin roles, "
                  + developerRoleIDs.size() + " developer roles, "
                  + observerRoleIDs.size() + " observer roles.");
d1425 3
a1427 1
        for (Iterator i = selfAndChildren().iterator(); i.hasNext();)
d1430 1
d1433 4
a1436 1
                if (project.isLocked())
d1438 1
a1438 1
                    HelmTask.disableAllAccounts(onBehalfOf, project);
d1440 6
a1445 1
                else
d1447 15
a1461 22
                    String projectID = project.getID();
                    
                    List observers = project.
                        getUsersWithRoles(observerRoleIDs);
                    List developers = project.
                        getUsersWithRoles(developerRoleIDs);
                    List admins = project.
                        getUsersWithRoles(adminRoleIDs);
                    
                    observers.removeAll(developers);
                    observers.removeAll(admins);
                    developers.removeAll(admins);
                    
                    Log.debug(admins.size() + " admins, "
                              + developers.size() + " developers, "
                              + observers.size() + " observers.");
                    HelmTask.disableAllAccounts(onBehalfOf, project);
                    HelmTask.addMembers
                        (onBehalfOf, project, observers, "observer");
                    HelmTask.addMembers
                        (onBehalfOf, project, developers, "developer");
                    HelmTask.addMembers(onBehalfOf, project, admins, "admin");
d1467 10
a1476 1
    private List getUsersWithRoles(Vector roleIDs)
d1479 17
a1495 7
        Vector myIDs = getEffectiveProjectIDs();
        Criteria c = new Criteria(4)
            .addJoin(UserPeer.USER_ID, UserRoleProjectPeer.USER_ID)
            .addIn(UserRoleProjectPeer.ROLE_ID, roleIDs)
            .addIn(UserRoleProjectPeer.PROJECT_ID, myIDs);
        c.setDistinct();
        List usersAndGroups = UserPeer.doSelect(c);
d1503 1
a1514 13
    public Vector getEffectiveProjectIDs()
        throws Exception
    {
        List objs = selfAndParents();
        Vector ids = new Vector(objs.size());
        for (Iterator i = objs.iterator(); i.hasNext();)
        {
            Project o = (Project)i.next();
            ids.add(o.getID());            
        }
        return ids;
    }

d1539 1
a1539 22
    
    /**
     * Adds a project to the list of public projects in the domain.
     */
    protected void addToPublicGroup()
        throws Exception
    {
        AccessControlList acl = AccessControlList.getSuperUserACL();
        ProjectPeer.getPublicDomainProject(getDomain()).addProject(acl, this);
    }
    
    /**
     * Removes a project from the list of public projects in the domain.
     */
    protected void removeFromPublicGroup()
        throws Exception
    {
        AccessControlList acl = AccessControlList.getSuperUserACL();
        ProjectPeer.getPublicDomainProject(getDomain())
            .removeProject(acl, this);
    }

@


1.85
log
@Made IssueTrackingRebuild about a million times faster.
@
text
@d1291 1
a1291 1
        throws Exception
@


1.84
log
@encode name
@
text
@d412 24
d1291 1
a1291 1
        throws ACLException, Exception
d1293 1
d1295 1
d1297 9
a1305 2
        List projects = new ArrayList();
        projects.addAll(expand());
d1307 1
a1307 3
        List users = null;
        Host host = new Host();
        if (isHost())
d1309 33
a1341 1
            users = host.getAllSiteUsers();
d1343 15
a1357 1
        else
d1359 2
a1360 2
            users = getDomain().getUsers();
            users.addAll(host.getUsers());
d1362 1
a1362 4

        //First, disable everybody in each project.
        List useProjects = new ArrayList();
        for (Iterator i = projects.iterator(); i.hasNext();)
d1364 2
a1365 2
            Project p = (Project)i.next();
            if (!p.isDomain() && !p.isHost())
d1367 1
a1367 3
                HelmTask.addProject(acl.getUser(), p);
                HelmTask.disableAllAccounts(acl.getUser(), p);
                useProjects.add(p);
d1370 14
a1383 5
        IssueTrackingAccountDescriptor before = 
            new IssueTrackingAccountDescriptor();
        IssueTrackingAccountDescriptor after = 
            new IssueTrackingAccountDescriptor(useProjects,users);
        before.syncTo(after, acl.getUser());
@


1.83
log
@Corrected getAttributes() call in Role and made use of HelmPeer's new NO_CRITERIA constant.
@
text
@d1830 1
a1830 1
                    .append(artifact.getFileName())
@


1.82
log
@Big architecture and schema change: booleans in the HELM_ROLE table have been replaced by entries in the HELM_ROLE_ATTRIBUTE_VALUE table. Also, a new attribute has been added which controls which roles a user gets upon creation of a project.
@
text
@d644 1
a644 1
        return getRequestedUsers(new Integer(0), new Criteria(0));
@


1.81
log
@Patches I forgot to commit before, for decoupling privateness from proprietariness.
@
text
@d864 1
a864 1
        //Give someone Project Owner role on this project.
d874 2
a875 1
            !acl.hasPermission("Project - Approve", domain)) {
a889 1
        Role ownerRole = RolePeer.getInstanceByName("Project Owner");
d891 6
a896 1
        newOwner.grantRole(rootACL, ownerRole, project);
@


1.80
log
@Broke long lines.
@
text
@d569 8
d579 1
a579 1
    public boolean isPrivate()
d581 1
a581 1
        return isProprietary();
d792 4
a795 3
         String wordMark, String license, String shortDescription, 
         String longDescription, String useIndex, String status, 
         String aliasURL, User proposedBy, User projectOwner)
d799 3
a801 3
        return add(acl, profile, domain, wordMark, license, shortDescription,
                   longDescription, useIndex, status, aliasURL, proposedBy,
                   projectOwner);
d812 1
d826 4
a829 3
         String wordMark, String license, String shortDescription, 
         String longDescription, String useIndex, String status, 
         String aliasURL, User proposedBy, User projectOwner)
d841 1
a846 2
            project.setIsProprietary(DBFlag.flagValueOf
                                     (Licenses.isProprietary(license)));
d855 1
a857 2
        project.changeLicense(license);

d940 1
d949 1
a949 1
        changeLicense(license);
d956 1
d979 1
a979 1
            if (!isProprietary())
d1129 1
a1129 1
     * Change this project's license.
d1131 1
a1131 1
    private void changeLicense(String license)
d1134 2
a1135 15
        //Get the old license--see if it's proprietary.
        String oldLicense = getLicense();
        if (oldLicense != null && oldLicense.equals(license))
        {
            return;
        }
      
        // Approved non-proprietary projects can't go proprietary.
        boolean wasProprietary = Licenses.isProprietary(oldLicense);
        boolean isProprietary = Licenses.isProprietary(license);            

        //Set the new license.
        super.setLicense(license);
        save();

d1137 1
a1137 1
        if (isApproved())
d1139 1
a1139 5
            String domainID = getDomainID();

            //If we used not to be proprietary and now we are, remove
            //this project from the public projects list.
            if ((!wasProprietary || oldLicense == null) && isProprietary)
d1141 1
a1141 1
                removeFromPublicGroup();
d1143 1
a1143 4
            
            //If we used to be proprietary and now we're not, add this project
            //to the public projects list.
            if ((wasProprietary || oldLicense == null) && !isProprietary)
d1145 1
a1145 1
                addToPublicGroup();
d1148 3
@


1.79
log
@Implemented clean OO-style suggested by edk instead of my quick hack.
@
text
@d1176 2
a1177 2
                Log.debug("Removing project " + getWordMark() + " from group " +
                          p.getWordMark());
d1464 2
a1465 1
                canDelete = acl.hasPermission("ProjectGroup - Delete", getDomain());
@


1.78
log
@Modifed Project's writeHtaccessFile() instance method to take inputs (rather than decide what data to act on by itself).  Stil does the same dumb 'write both documents and downloads to the same directory tree' business.
@
text
@d1825 3
a1827 2
                fakedUrl.append(artifact instanceof ProjectDownload
                                ? "&Download-" : "&Document-")
@


1.77
log
@Be more intelligent about what data gets written to what .htaccess file.
The way it's distinguished betwen DOCUMENTS and DOWNLOADS is kind of stupid but
ignoring downloads for documents and vice versa is going to save much more time
then speeding up the distinguishing though.
@
text
@d1783 4
a1786 5
     * @@param htaccess The full path to the .htaccess file.
     * @@param url      The URL for auth and timeout (when <code>null</code>,
     *                 defaults to <code>http://[JVM host]/</code>).
     * @@param type     Defines whether this is a DOWNLOADS or DOCUMENTS htaccess
     *                 file to be written.
d1788 2
a1789 1
    protected void writeHtaccessFile(String htaccess, String url, String type)
a1791 1
        int exitValue = 0;
d1821 1
a1821 1
            if (type.equals("DOWNLOADS"))
d1823 9
a1831 29
                for (Iterator i = getProjectDownloads().iterator(); i.hasNext(); )
                {
                    ProjectDownload dl = (ProjectDownload)i.next();
                    path = dl.getLocation();
                    fakedUrl.append("&Download-")
                        .append(path.substring(indexOfLastElement(path)))
                        .append(File.separatorChar)
                        .append(dl.getFileName())
                        .append('=')
                        .append(generateMDARealmMask(dl.isPublic()));
                }
            }
            else if(type.equals("DOCUMENTS"))
            {
                for (Iterator i = getProjectDocuments().iterator(); i.hasNext(); )
                {
                    ProjectDocument dc = (ProjectDocument)i.next();
                    path = dc.getLocation();
                    fakedUrl.append("&Document-")
                        .append(path.substring(indexOfLastElement(path)))
                        .append(File.separatorChar)
                        .append(dc.getFileName())
                        .append('=')
                        .append(generateMDARealmMask(dc.isPublic()));
                }
            }
            else 
            {
                exitValue = 1;
d1835 1
@


1.76
log
@Corrected formatting of input arguments to writeHtaccessFile script.
They were malformed in my last commit.
@
text
@d1786 2
d1789 1
a1789 1
    protected void writeHtaccessFile(String htaccess, String url)
d1792 1
d1822 1
a1822 1
            for (Iterator i = getProjectDownloads().iterator(); i.hasNext(); )
d1824 11
a1834 8
                ProjectDownload dl = (ProjectDownload)i.next();
                path = dl.getLocation();
                fakedUrl.append("&Download-")
                    .append(path.substring(indexOfLastElement(path)))
                    .append(File.separatorChar)
                    .append(dl.getFileName())
                    .append('=')
                    .append(generateMDARealmMask(dl.isPublic()));
d1836 1
a1836 1
            for (Iterator i = getProjectDocuments().iterator(); i.hasNext(); )
d1838 15
a1852 8
                ProjectDocument dc = (ProjectDocument)i.next();
                path = dc.getLocation();
                fakedUrl.append("&Document-")
                    .append(path.substring(indexOfLastElement(path)))
                    .append(File.separatorChar)
                    .append(dc.getFileName())
                    .append('=')
                    .append(generateMDARealmMask(dc.isPublic()));
a1855 1
            int exitValue = 0;
@


1.75
log
@Write only one .htaccess per project (instead of one per user).
@
text
@d93 1
a93 1
    private static final boolean DEBUG = true;
d1748 4
a1751 1
        Log.debug("Project.getHtaccess: relativePath=" + relativePath);
d1771 1
a1771 1
            return dirSepPos + 1;
d1790 2
a1791 1
        if (url == null) {
d1797 2
a1798 1
        if (!isVhosting()) {
d1804 4
a1807 2
        synchronized (htaccessLocks) {
            if (!htaccessLocks.containsKey(htaccess)) {
d1814 3
a1816 1
        synchronized (htaccessWriteLock) {
d1819 2
a1820 1
            for (Iterator i = getProjectDownloads().iterator(); i.hasNext();) {
d1823 1
a1823 2
                fakedUrl.append("&Download-").append(dl.getFileName())
                    .append('=')
d1826 2
d1830 2
a1831 1
            for (Iterator i = getProjectDocuments().iterator(); i.hasNext();) {
d1834 1
a1834 2
                fakedUrl.append("&Document-").append(dc.getFileName())
                    .append('=')
d1837 2
d1844 2
a1845 1
            try {
d1849 2
a1850 1
            catch (RuntimeException e) {
d1853 3
a1855 1
            if (exitValue != 0) {
@


1.74
log
@Delete mailing lists too.
@
text
@d1190 1
a1190 1
        //Remove all news items.
d1743 15
a1757 1
    protected static String getHtaccess(String path)
d1766 1
a1766 2

        if (dirSepPos > 0)
d1768 1
a1768 4
            Log.debug("htaccess=" + path.substring(0, dirSepPos) +
                      File.separatorChar + ".htaccess");
            return (path.substring(0, dirSepPos) +
                    File.separatorChar + ".htaccess");
d1772 2
a1773 2
            throw new ParseException("Project.getHtaccess(): Invalid file " +
                                     "path: " + path, dirSepPos);
d1809 1
d1812 1
d1814 4
a1817 1
                    .append('=').append(generateMDARealmMask(dl.isPublic()));
d1821 1
d1823 4
a1826 1
                    .append('=').append(generateMDARealmMask(dc.isPublic()));
a1842 1

@


1.73
log
@Delete all of a project's database artifacts when deleting it.
@
text
@d1191 4
@


1.72
log
@Added constant naming the full path to the program used to write
.htaccess files.
@
text
@a1166 2
        // TODO: Delete this project's version controlled artifacts.
        
d1182 4
d1190 13
a1202 1
        //TODO: Remove all other references to it. (Downloads etc.)
d1204 3
a1206 3
        //TODO: Delete this project's download directory.
        
        String message = this.AUDIT_LOG_ON_DELETE + getName();
a1207 3
        //Delete the project itself, along with its CVS repository
        //and whatnot.
        HelmTask.deleteProject(acl.getUser(), this);
d1210 2
@


1.71
log
@Argh commented-out code.
@
text
@d172 7
d1793 1
a1793 3
            String prog = JoistProperties.getProperty("SANDBOX") +
                "/helm/perl/cgi/writeHtaccessFile";
            String[] cmd = { prog, htaccess, fakedUrl.toString() };
@


1.70
log
@Write .htaccess files per project instead of per user.
@
text
@d1174 1
a1174 2
            // TODO: FIXME
//            p.removeProjectNoPerms(this);
@


1.69
log
@Send CacheInvalidationEvents on deletes, and on saves of objects which are not themselves cached but which invalidate other caches when they are changed.
@
text
@d50 1
d1714 31
d1747 3
a1749 3
     * @@param path The full directory path to where to write a .htaccess file.
     * @@param url  The URL for auth and timeout (when <code>null</code>,
     *             defaults to <code>http://[JVM host]/</code>).
d1751 1
a1751 1
    protected void writeHtaccessFile(String path, String url)
a1753 1
        String fileName = path + File.separatorChar + ".htaccess";
d1767 2
a1768 2
            if (!htaccessLocks.containsKey(fileName)) {
                htaccessLocks.put(fileName, new Object());
d1770 1
a1770 1
            htaccessWriteLock = htaccessLocks.get(fileName);
d1775 1
a1775 1
            StringBuffer fakedUrl = new StringBuffer(url + "?null");
d1789 1
a1789 1
            String[] cmd = { prog, fileName, fakedUrl.toString() };
@


1.68
log
@Added debug flag.
@
text
@a1191 1
        ProjectPeer.uncache(this);
d1875 1
a1875 1
        ProjectPeer.uncache(this);
@


1.67
log
@Protect the default project from deletion.
@
text
@d90 5
@


1.66
log
@Reverting commit.  This is still needed for the
Project{Download,Document}List, even though someone forgot to include
its use when refactoring.
@
text
@d423 8
d1163 5
a1167 2
            Log.debug("Removing project " + getWordMark() + " from group " +
                      p.getWordMark());
d1383 2
a1384 1
        if (isBase()) {
d1387 2
a1388 1
        else {
d1401 17
a1417 2
        if (isBase()) {
            acl.assertPermission("Project - Delete", this);
d1419 21
a1439 3
        else {
            acl.assertPermission("ProjectGroup - Delete", getDomain());
        }        
@


1.65
log
@Got rid of hack. Well, it told me to.
@
text
@d89 6
@


1.64
log
@Corrected comment.  Using the project ID in generateMDARealmMask, not
the download ID.
@
text
@a88 6
    /**
     * The query string parameter name of a servlet display message.
     * <p>
     * HACK: This is totally hokey.  Get rid of it.
     */
    public static final String MESSAGE = "msg";
@


1.63
log
@Removed unused code.
@
text
@d1740 1
a1740 1
        // Return as hex (a la tohex(2 ^ downloadID)).
@


1.62
log
@constructing valid url with query string
@
text
@d1678 1
a1678 5
        // If not in vhosting mode, append projectID as cgi param
        JoistProperties properties = JoistProperties.getInstance();
        if (properties == null) {
            throw new NullPointerException( "JoistProperties is null" );
        }
@


1.61
log
@URL encoding queryArgs was causing name=value pairs to be scrambled.
No longer doing so--leveraging new Joist URL macro to aid in doing
queryArgs appending manually.
@
text
@d1698 1
a1698 1
            StringBuffer fakedUrl = new StringBuffer(url);
@


1.60
log
@Added is_private flag to scripts.
@
text
@d1630 1
a1630 1
            buff.append( "://" ); 
d1654 3
a1656 1
            urlUtil.appendCGIParam(buff, queryArgs);
@


1.59
log
@Reworked buildProjectPageURL() instance method to make better use of
Joist's URL class.
@
text
@d548 8
@


1.58
log
@Cleaned up isVhosting() business.
@
text
@d1572 3
d1579 4
a1582 3
     * whether projects should be represented as http://$project.domain/...
     * or http://domain/...?projectID=$project.getID()
     *
a1604 1
        boolean isVhosting = isVhosting();
d1607 2
a1608 3
        String DOMAIN_KEY = "DOMAIN";
        String SERVLET_PREFIX = "/servlets/"; 

d1613 2
a1614 1
        if( domain == null || domain.length( ) <= 0 )
d1616 1
d1618 3
a1620 1
        if (isVhosting || forceFullyQualifiedDomain) {
d1623 2
a1624 1
            if (isVhosting) {
d1636 1
d1638 1
a1639 1
        String projectArgs = null;
d1641 7
a1647 1
            projectArgs = "projectID=" + getID();
a1648 7
        if (projectArgs != null && queryArgs != null) {
            buff.append("?").append(projectArgs).append("&").append(queryArgs);
        } else if (projectArgs != null) {
            buff.append("?").append(projectArgs);
        } else if (queryArgs != null) {
            buff.append("?").append(queryArgs);
        }
@


1.57
log
@removed whitespace
@
text
@d1479 13
a1491 8
    private boolean isVhosting(org.joist.util.JoistProperties properties) {
        String IS_VHOSTING_KEY = "IS_VHOSTING"; // Key for VHOSTING.
        // Test if vhosting is turned on.
        String isVhosting = properties.getProperty( IS_VHOSTING_KEY );
        if( isVhosting != null &&
                (new Boolean(isVhosting).booleanValue()) )
            return true;
        return false;
d1601 1
a1601 7
        // JoistProperties is a singleton of system properties.
        org.joist.util.JoistProperties properties
            = org.joist.util.JoistProperties.getInstance( );
        if( properties == null )
            throw new NullPointerException( "JoistProperties is null" );
                    
        boolean isVhosting = isVhosting(properties);
d1610 1
a1610 1
        String domain = properties.getProperty( DOMAIN_KEY );
d1666 1
a1666 1
        if (!isVhosting(properties)) {
@


1.56
log
@added getDownloads method with criteria and acl args`
@
text
@d936 1
a936 1
                                          
@


1.55
log
@80 column formatting
@
text
@d711 25
@


1.55.2.1
log
@Merged in bug fixes and clean up from HEAD.
@
text
@d911 1
a911 1

@


1.55.2.1.4.1
log
@buddied MDA .htaccess changes from head to branch
@
text
@d1657 1
a1657 1
            StringBuffer fakedUrl = new StringBuffer(url + "?null");
@


1.55.2.1.4.2
log
@Backported changes: prevent the default project from being deleted, and delete group memberships and artifacts along with a project.
@
text
@a89 5
     * Flag indicating whether in debug mode.
     */
    private static final boolean DEBUG = false;

    /**
a421 9

    /**
     * Returns true iff the project is the default project.
     */
    public boolean isDefaultProject()
    {
        return (getName().equals(ProjectPeer.DEFAULT_PROJECT_NAME));
    }

d1113 5
d1122 4
a1125 6
            if (DEBUG)
            {
                Log.debug("Removing project " + getWordMark() + " from group " +
                          p.getWordMark());
            }
            p.removeProject(this);
a1127 4
        //Delete the project itself, along with its CVS repository
        //and whatnot.
        HelmTask.deleteProject(acl.getUser(), this);

d1132 5
a1136 21
        //Remove all mailing lists.
        MailingListPeer.doDelete
            (new Criteria().add(MailingListPeer.PROJECT_ID, getID()));

        //Remove all news items.
        NewsItemPeer.doDelete
            (new Criteria().add(NewsItemPeer.PROJECT_ID, getID()));

        //Remove all downloads.
        ProjectDownloadPeer.doDelete
            (new Criteria().add(ProjectDownloadPeer.PROJECT_ID, getID()));

        //Remove all document sections.
        ProjectDocumentSectionPeer.doDelete
            (new Criteria().addJoin(ProjectDocumentSectionPeer.DOCUMENT_ID, 
                                    ProjectDocumentPeer.DOCUMENT_ID)
                .add(ProjectDocumentPeer.PROJECT_ID, getID()));

        //Remove all documents.
        ProjectDocumentPeer.doDelete
            (new Criteria().add(ProjectDocumentPeer.PROJECT_ID, getID()));
d1138 3
a1142 1

a1143 1
        String message = this.AUDIT_LOG_ON_DELETE + getName();
d1346 1
a1346 1
    
d1355 2
a1356 17
        if (!isDeletableBy(acl))
        {
            if (isDefaultProject())
            {
                throw new Exception
                    (getName() 
                     + " is the default project, and cannot be deleted.");
            }
            
            if (isBase()) 
            {
                acl.assertPermission("Project - Delete", this);
            }
            else 
            {
                acl.assertPermission("ProjectGroup - Delete", getDomain());
            }        
d1358 3
a1360 21
    }

    /**
     * Returns true iff the given ACL has permission to delete this project.
     */
    public boolean isDeletableBy(AccessControlList acl)
        throws ACLException, Exception
    {
        boolean canDelete = false;
        if (!isDefaultProject())
        {
            if (isBase()) 
            {
                canDelete = acl.hasPermission("Project - Delete", this);
            }
            else 
            {
                canDelete = acl.hasPermission("ProjectGroup - Delete", getDomain());
            }        
        }
        return canDelete;
@


1.55.2.1.4.3
log
@Back porting changes from HEAD of trunk:

* Added HTACCESS_WRITER constant for the script used to write
.htaccess files.

* Added Anzu project deletion to delete() and cleaned up debugging.

* Cleaned up assertEditPermission().

* Extensive refactoring to project page URL methods, making use of new
Joist URL class method.

* Extensive refactoring of writeHtaccessFile() and supporting cast.
@
text
@a49 1
import java.text.ParseException;
a170 7
     * The full path to the program used to write .htaccess files.
     */
    private static final String HTACCESS_WRITER =
        JoistProperties.getProperty("SANDBOX") +
        "/helm/perl/cgi/writeHtaccessFile";

    /**
d427 1
a1126 3
        // delete the directory in the anzu project as well
        AnzuTask.deleteProject(acl.getUser(), getDomain(), this);

d1133 2
a1134 2
                Log.debug("Removing project " + getWordMark() + " from group "
                          + p.getWordMark());
d1369 1
a1369 2
        if (isBase()) 
        {
d1372 1
a1372 2
        else 
        {
d1376 1
a1376 1

d1420 1
a1420 2
                canDelete = acl.hasPermission("ProjectGroup - Delete",
                                              getDomain());
d1517 8
a1524 13
    /**
     * Key for IS_VHOSTING.
     */
    private static final String IS_VHOSTING_KEY = "IS_VHOSTING";

    /**
     * Test if vhosting is turned on.
     */
    private boolean isVhosting()
    {
        String isVhosting = JoistProperties.getProperty(IS_VHOSTING_KEY);
        return (isVhosting != null &&
                new Boolean(isVhosting).booleanValue());
a1604 3
    private static final String DOMAIN_KEY = "DOMAIN";

    private static final String SERVLET_PREFIX = "/servlets/"; 
d1609 3
a1611 4
     * whether projects should be represented as
     * <code>http://$project.domain/...</code> or
     * <code>http://domain/...?projectID=$project.getID()</code>.
     * <p>
d1634 7
d1643 3
a1645 2
        // Gather some information.
        boolean isVhosting = isVhosting();
d1649 2
a1650 3
        String domain = JoistProperties.getProperty(DOMAIN_KEY);
        if (domain == null || domain.length() <= 0)
        {
a1651 1
        }
d1653 1
a1653 3
        // Build the URL.
        if (isVhosting || forceFullyQualifiedDomain)
        {
d1655 2
a1656 3
            buff.append( "://" );
            if (isVhosting)
            {
a1667 1
        {
a1668 1
        }
d1670 1
d1672 8
a1679 8
        {
            urlUtil.appendCGIParam(buff, "projectID", getID());
        }
        if (queryArgs != null)
        {
            // ASSUMPTION: queryArgs should not be URL encoded.
            urlUtil.appendCGISeparator(buff, buff.toString());
            buff.append(queryArgs);
a1680 1

a1684 44
     * Gets the full path to the <code>.htaccess</code> file for a protected
     * file's path.
     *
     * @@param path The path to the secured file.
     * @@return     The full path to the <code>.htaccess</code> file.
     */
    protected static final String getHtaccess(String path)
        throws ParseException
    {
        int dirSepPos = indexOfLastElement(path) - 1;
        String relativePath = path.substring(0, dirSepPos);
        if (DEBUG)
        {
            Log.debug("Project.getHtaccess: relativePath=" + relativePath);
        }
        return (relativePath + File.separatorChar + ".htaccess");
    }

    /**
     * Finds the index of the last element in the supplied directory path.
     *
     * @@exception ParseException The supplied path doesn't parse.
     */
    private static int indexOfLastElement(String path)
        throws ParseException
    {
        int dirSepPos = path.lastIndexOf(File.separatorChar);
        if (dirSepPos == path.length() - 1)
        {
            // The provided path ended in the directory separator.
            dirSepPos = path.lastIndexOf(File.separatorChar, dirSepPos);
        }
        if (dirSepPos != -1)
        {
            return (dirSepPos + 1);
        }
        else
        {
            throw new ParseException("Project.indexOfLastElement(): Invalid  "
                                     + "file path: " + path, dirSepPos);
        }
    }

    /**
d1687 3
a1689 4
     * @@param htaccess  The full path to the .htaccess file.
     * @@param url       The URL for auth and timeout (when <code>null</code>,
     *                  defaults to <code>http://[JVM host]/</code>).
     * @@param artifacts The file artifacts to provide access control for.
d1691 1
a1691 2
    protected void writeHtaccessFile(String htaccess, String url,
                                     List artifacts)
d1694 2
a1695 2
        if (url == null)
        {
d1700 6
a1705 3
        // If not in vhosting mode, append projectID as CGI param.
        if (!isVhosting())
        {
d1711 3
a1713 5
        synchronized (htaccessLocks)
        {
            if (!htaccessLocks.containsKey(htaccess))
            {
                htaccessLocks.put(htaccess, new Object());
d1715 1
a1715 1
            htaccessWriteLock = htaccessLocks.get(htaccess);
d1719 16
a1734 20
        synchronized (htaccessWriteLock)
        {
            // Format inputs for writeHtaccessFile script.
            StringBuffer fakedUrl = new StringBuffer(url).append("?null");
            String path;
            for (Iterator i = artifacts.iterator(); i.hasNext(); )
            {
                ProjectFileArtifact artifact = (ProjectFileArtifact) i.next();
                path = artifact.getLocation();
                fakedUrl.append('&')
                    .append(artifact.getFileArtifactType())
                    .append('-')
                    .append(path.substring(indexOfLastElement(path)))
                    .append(File.separatorChar)
                    .append(artifact.getFileName())
                    .append('=')
                    .append(generateMDARealmMask(artifact.isPublic()));
            }

            String[] cmd = { HTACCESS_WRITER, htaccess, fakedUrl.toString() };
d1736 1
a1736 2
            try
            {
d1740 1
a1740 2
            catch (RuntimeException e)
            {
d1743 1
a1743 3

            if (exitValue != 0)
            {
d1749 1
d1766 1
a1766 1
        // Return as hex (a la tohex(2 ^ projectID)).
@


1.55.2.1.4.4
log
@Back ported the getDownloads() pagination method that was just used in
a template.
@
text
@a731 25
     * Returns a paginatable list of downloads for this project.
     */
    public PeerBackedList getDownloads(Integer itemsPerPage, Criteria filter, 
                                  AccessControlList acl)
        throws Exception
    {
        Criteria base = new Criteria()
            .add(ProjectDownloadPeer.PROJECT_ID, getProjectId());
        PeerBackedList downloads = new PeerBackedList
            (ProjectDownloadPeer.class, itemsPerPage, base, filter);
        List toRemove = new ArrayList();
        for (Iterator i = downloads.iterator(); i.hasNext();)
        {
            ProjectDownload dl = (ProjectDownload)i.next();
            if (!dl.isViewableBy(acl))
            {
                toRemove.add(dl);
            }
        }
        downloads.removeAll(toRemove);
        return downloads;
    }


    /**
@


1.55.2.1.4.5
log
@guard against null path.  how null in the first place?
@
text
@d1819 8
a1826 10
                if (path != null) {
                    fakedUrl.append('&')
                        .append(artifact.getFileArtifactType())
                        .append('-')
                        .append(path.substring(indexOfLastElement(path)))
                        .append(File.separatorChar)
                        .append(artifact.getFileName())
                        .append('=')
                        .append(generateMDARealmMask(artifact.isPublic()));
                }
@


1.55.2.1.4.6
log
@Patched change in HEAD to speed up issue tracking rebuilds.
@
text
@a411 24
    public List selfAndChildren()
        throws Exception
    {
        Criteria c = new Criteria()
            .addJoin(ProjectPeer.PROJECT_ID, 
                     ProjectProjectgroupPeer.PROJECT_ID)
            .add(ProjectProjectgroupPeer.PROJECT_GROUP_ID, getID());
        List projects = ProjectPeer.doSelect(c);
        projects.add(this);
        return projects;
    }

    public List selfAndParents()
        throws Exception
    {
        Criteria c = new Criteria()
            .addJoin(ProjectPeer.PROJECT_ID, 
                     ProjectProjectgroupPeer.PROJECT_GROUP_ID)
            .add(ProjectProjectgroupPeer.PROJECT_ID, getID());
        List projects = ProjectPeer.doSelect(c);
        projects.add(this);
        return projects;
    }

a1262 1
        Log.debug("Rebuilding issue tracking for " + getName());
a1263 1
        User onBehalfOf = acl.getUser();
d1265 2
a1266 9
        Vector observerRoleIDs = RolePeer.getRoleIDsWithAction
            ("Project Issue Tracking - Submit");
        Vector developerRoleIDs = RolePeer.getRoleIDsWithAction
            ("Project Issue Tracking - Change");
        Vector adminRoleIDs = RolePeer.getRoleIDsWithAction
            ("Project Issue Tracking - Configure");
        Log.debug(adminRoleIDs.size() + " admin roles, "
                  + developerRoleIDs.size() + " developer roles, "
                  + observerRoleIDs.size() + " observer roles.");
d1268 3
a1270 1
        for (Iterator i = selfAndChildren().iterator(); i.hasNext();)
d1272 1
a1272 33
            Project project = (Project)i.next();
            if (!(project.isHost() || project.isDomain()))
            {
                if (project.isLocked())
                {
                    HelmTask.disableAllAccounts(onBehalfOf, project);
                }
                else
                {
                    String projectID = project.getID();
                    
                    List observers = project.
                        getUsersWithRoles(observerRoleIDs);
                    List developers = project.
                        getUsersWithRoles(developerRoleIDs);
                    List admins = project.
                        getUsersWithRoles(adminRoleIDs);
                    
                    observers.removeAll(developers);
                    observers.removeAll(admins);
                    developers.removeAll(admins);
                    
                    Log.debug(admins.size() + " admins, "
                              + developers.size() + " developers, "
                              + observers.size() + " observers.");
                    HelmTask.disableAllAccounts(onBehalfOf, project);
                    HelmTask.addMembers
                        (onBehalfOf, project, observers, "observer");
                    HelmTask.addMembers
                        (onBehalfOf, project, developers, "developer");
                    HelmTask.addMembers(onBehalfOf, project, admins, "admin");
                }
            }
d1274 1
a1274 15
    }

    private List getUsersWithRoles(Vector roleIDs)
        throws Exception
    {
        Vector myIDs = getEffectiveProjectIDs();
        Criteria c = new Criteria(4)
            .addJoin(UserPeer.USER_ID, UserRoleProjectPeer.USER_ID)
            .addIn(UserRoleProjectPeer.ROLE_ID, roleIDs)
            .addIn(UserRoleProjectPeer.PROJECT_ID, myIDs);
        c.setDistinct();
        List usersAndGroups = UserPeer.doSelect(c);
        List effectiveUsers = new ArrayList(usersAndGroups.size());
        List baseUsers = new ArrayList(usersAndGroups.size());
        for (Iterator i = usersAndGroups.iterator(); i.hasNext();)
d1276 2
a1277 2
            User u = (User)i.next();
            effectiveUsers.addAll(u.expand());            
d1279 4
a1282 1
        for (Iterator i = effectiveUsers.iterator(); i.hasNext();)
d1284 2
a1285 2
            User u = (User)i.next();
            if (u.isBase())
d1287 3
a1289 1
                baseUsers.add(u);
d1292 5
a1296 14
        return baseUsers;
    }
    
    public Vector getEffectiveProjectIDs()
        throws Exception
    {
        List objs = selfAndParents();
        Vector ids = new Vector(objs.size());
        for (Iterator i = objs.iterator(); i.hasNext();)
        {
            Project o = (Project)i.next();
            ids.add(o.getID());            
        }
        return ids;
@


1.55.2.1.4.7
log
@Declare getHtdocDir public.
@
text
@d1971 1
a1971 1
    public String getHtdocDir ()
@


1.55.2.1.4.8
log
@Back out the last change since the getHtdocDir() function is intended for
directories and not URLs.
@
text
@d1971 1
a1971 1
    protected String getHtdocDir ()
@


1.55.2.1.4.9
log
@correct reference to ProjectMemberList (no longer ProjectMembers)
@
text
@d1633 1
a1633 1
            ("ProjectMemberList", null, "http", false, true);
@


1.55.2.1.4.10
log
@Ported task running system in from HEAD.
@
text
@d80 1
d1076 2
a1077 1
        boolean recalculateIssueTracking = false;
d1095 1
a1095 1
                recalculateIssueTracking = true;
d1105 1
a1105 1
            if (recalculateIssueTracking)
d1107 2
a1108 1
                rebuildIssueTracking();
a1261 11
     * Clear the Issuezilla accounts and rebuild them according to
     * what's in the database.
     */
    public void rebuildIssueTracking()
        throws ACLException, Exception
    {
        HelmTask.rebuildIssueTracking(this);
    }


    /**
d1280 60
a1339 1
    public List getUsersWithRoles(Vector roleIDs)
@


1.55.2.1.4.11
log
@Backported implementation from HEAD.
@
text
@d619 1
a619 5
        Criteria c = new Criteria(4)
            .addJoin(UserPeer.USER_ID, UserRoleProjectPeer.USER_ID)
            .addJoin(RolePeer.ROLE_ID, UserRoleProjectPeer.ROLE_ID)
            .add(UserRoleProjectPeer.PROJECT_ID, getProjectId())
            .add(RolePeer.DENOTES_OWNER, true);       
@


1.55.2.1.4.12
log
@Backported fix to mail-sending system.
@
text
@d623 2
a624 2
            .add(RolePeer.DENOTES_OWNER, DBFlag.ON);
        return getUsers(new Integer(0), c, null);        
@


1.55.2.1.4.13
log
@Ported minimal cluster functionality so that I could also port IssueTrackingRebuildService, which builds on it.
@
text
@a77 1
import org.tigris.helm.tool.task.*;
d1269 1
a1269 1
        IssueTrackingRebuildService.getService().registerProject(this);
a1271 78
    /**
     * Resyncs a project's issue tracking accounts with the Helm roles
     * for that project. 
     */
    public void doIssueTrackingRebuild()
        throws ACLException, Exception
    {
        if (DEBUG)
        {
            Log.debug("Rebuilding issue tracking for " + getName());
        }

        Vector observerRoleIDs = RolePeer.getRoleIDsWithAction
            ("Project Issue Tracking - Submit");
        Vector developerRoleIDs = RolePeer.getRoleIDsWithAction
            ("Project Issue Tracking - Change");
        Vector adminRoleIDs = RolePeer.getRoleIDsWithAction
            ("Project Issue Tracking - Configure");
        if (DEBUG)
        {
            Log.debug(adminRoleIDs.size() + " admin roles, "
                      + developerRoleIDs.size() + " developer roles, "
                      + observerRoleIDs.size() + " observer roles.");
        }

        if (!(isHost() || isDomain()))
        {
            Map args = ScriptTool.makeProjectArgs(this);
            if (isLocked())
            {
                ScriptTool.runScript("MemberDisableAll", args);
            }
            else
            {
                List observers = getUsersWithRoles(observerRoleIDs);
                List developers = getUsersWithRoles(developerRoleIDs);
                List admins = getUsersWithRoles(adminRoleIDs);
                
                observers.removeAll(developers);
                observers.removeAll(admins);
                developers.removeAll(admins);

                if (DEBUG)
                {
                    Log.debug(admins.size() + " admins, "
                              + developers.size() + " developers, "
                              + observers.size() + " observers.");
                }
                
                //NOTE: If one of these scripts fails, the rest
                //will not be run.
                ScriptTool.runScript("MemberDisableAll", args);
                
                if (observers.size() > 0)
                {
                    Map observerArgs = ScriptTool.makeProjectArgs(this);
                    observerArgs.putAll
                        (ScriptTool.makeUserArgs(observers, "observer"));
                    ScriptTool.runScript("MemberAdd", observerArgs);
                }
                
                if (developers.size() > 0)
                {                    
                    Map developerArgs = ScriptTool.makeProjectArgs(this);
                    developerArgs.putAll(ScriptTool.makeUserArgs(developers, 
                                                                 "developer"));
                    ScriptTool.runScript("MemberAdd", developerArgs);
                }
                
                if (admins.size() > 0)
                {
                    Map adminArgs = ScriptTool.makeProjectArgs(this);
                    adminArgs.putAll(ScriptTool.makeUserArgs(admins, "admin"));
                    ScriptTool.runScript("MemberAdd", adminArgs);
                }
            }
        }
    }
@


1.55.2.1.4.14
log
@Dashes can no longer be used as the last character in a project name.
@
text
@d141 1
a141 1
        "(^[a-zA-Z0-9]$|^[a-zA-Z0-9][-a-zA-Z0-9]{0,62}[a-zA-Z0-9]$)";
@


1.55.2.1.4.15
log
@Deprecated improperly named getNonSpecialProjects(); renamed to
getProjectGroups().
@
text
@d351 1
a351 1
    public List getProjectGroups()
a354 9
    }

    /**
     * @@deprecated Use getProjectGroups() instead.
     */
    public final List getNonSpecialGroups()
        throws Exception
    {
        return getProjectGroups();
@


1.55.2.1.4.16
log
@Write .htaccess files per project/user directory.
@
text
@d1862 7
a1868 1
        return (path + File.separatorChar + ".htaccess");
d1902 1
a1902 2
     * @@param artifacts The {@@link org.tigris.helm.om.ProjectFileArtifact} 
     *                  objects to provide access control for. 
d1940 10
a1949 11
                String fileName = artifact.getFileName(); 
                Log.debug("fileName=" + fileName); 
                if (fileName != null) 
                { 
                   path = artifact.getLocation(); 
                   fakedUrl.append('&')
                       .append(artifact.getFileArtifactType())
                       .append('-')
                       .append(URL.encode(artifact.getFileName()))
                       .append('=')
                       .append(generateMDARealmMask(artifact.isPublic()));
a1952 2
            //Log.debug("Project.writeHtaccessFile: .htaccess file name=" + 
            //          htaccess); 
d2008 1
a2008 2
     * <code>SANDBOX/data/helm/htdoc/PROJECTNAME/www</code>).  Used to create 
     * file system paths. 
d2012 1
a2012 1
    protected String getHtdocDir()
@


1.55.2.1.4.17
log
@Remove requests for issue tracking on project deletion.
@
text
@a1238 1
        //Remove the project itself.
a1240 5

        //Remove any outstanding requests for issue tracking for this
        //project.
        ClusterEventPeer.deleteInstance
            (IssueTrackingRebuildService.getService().getName(), getID());
@


1.55.2.1.4.18
log
@Users are not owners unless their ownership roles are approved.
@
text
@d633 1
a633 4
            .add(RolePeer.DENOTES_OWNER, DBFlag.ON)
            .add(UserRoleProjectPeer.APPROVED_BY, 
                 (Object)UserPeer.getNobodyUser().getID(), 
                 Criteria.NOT_EQUAL);
@


1.55.2.1.4.19
log
@Backported fix from HEAD which stops NPEs on site-wide ITRs.
@
text
@d1270 1
a1270 3

                ProjectProfile profile = p.getProjectProfile();
                if (profile != null && profile.isAutoPublishedToWeb())
@


1.55.2.1.4.20
log
@avoid the broken internal cache maintained by BaseProject
(which keeps a list of maliing lists)
@
text
@d1377 1
a1377 1
        List rawLists = MailingListPeer.getMailingListsByProject(this);
a1388 10
    }

    /**
     * Show all mailing lists -- avoid the cache maintained by BaseProject
     */
    public Vector getMailingLists() throws Exception
    {
        Vector lameHack = new Vector();
        lameHack.addAll(MailingListPeer.getMailingListsByProject(this));
        return lameHack;
@


1.55.2.1.4.21
log
@Requested roles don't count for permission purposes.
@
text
@d1408 1
a1408 5
            .addIn(UserRoleProjectPeer.PROJECT_ID, myIDs)
            .add(UserRoleProjectPeer.APPROVED_BY, 
                 UserPeer.getNobodyUser().getID(), (Object)Criteria.NOT_EQUAL);


@


1.55.2.1.4.21.2.1
log
@Corrected cast to use right version of Criteria.add.
@
text
@d1410 1
a1410 1
                 (Object)UserPeer.getNobodyUser().getID(), Criteria.NOT_EQUAL);
@


1.55.2.1.4.22
log
@Corrected cast to call the right version of Criteria.add
@
text
@d1410 1
a1410 1
                 (Object)UserPeer.getNobodyUser().getID(), Criteria.NOT_EQUAL);
@


1.54
log
@Brace consistancy.
@
text
@d1165 2
a1166 1
                                       ProjectPeer.DEFAULT_PROJECT_DESCRIP, "1");
@


1.53
log
@One less visible check for null.
@
text
@d908 2
a909 1
        if (acl != null) {
d914 2
a915 1
            if (!isProprietary()) {
d937 2
a938 1
         try {
d940 6
a945 5
         }
         catch (Exception e) {
             Log.error
                 ("Unable to mail project owner about project disapproval", e);
         }
d964 1
a964 1
   }
d1021 3
a1023 1
            } else {
d1207 3
a1209 1
        } else {
@


1.52
log
@The getLongDescription() override that checks useIndex() was totally
missing, causing getLongDescription() to ignore Project.useIndex() (IZ
#3238).  Added it and its supporting cast.
@
text
@d1472 2
a1473 1
        if (queryArgs != null && queryArgs.equals(""))
d1475 1
a1475 1

@


1.51
log
@Moved away from fugly method decl.
@
text
@d50 1
a50 2
import java.io.File;
import java.io.FileWriter;
d97 5
a299 1
        throws Exception
d301 11
a311 1
        return DBFlag.booleanValueOf(getUseIndex());
d1709 58
@


1.50
log
@create the anzu project before we grant roles, to avoid setting the owner
alias before the project exists in anzu.
@
text
@a654 1

d659 3
a661 2
    public PeerBackedList getUnapprovedNews
        (Integer itemsPerPage, Criteria filter, AccessControlList acl)
a681 1

@


1.49
log
@Got rid of all references to skins; they'll reappear in some form later on.
@
text
@d805 9
a813 1
        
a825 4
        AnzuTask.addProject(newOwner, domain, project);
        AnzuTask.setOwnerAlias(newOwner, project, false);
        profile.addDefaultMailingLists(project, newOwner,
                project.isProprietary());
@


1.48
log
@remove debugging breadcrumbs
@
text
@a282 9
     * Gets the skin for this project.
     */
    public Skin getSkin()
        throws Exception
    {
        return SkinPeer.retrieveByPK(getSkinId());
    }

    /**
@


1.47
log
@setting it so that user can switch between private and proprietary licenses
@
text
@a1170 1
                Log.warn("adding ml " + ml);
a1171 4
            }
            else
            {
                Log.warn("not adding ml " + ml);
@


1.46
log
@Also send mail on project unlock.
@
text
@d1062 1
a1062 1

a1065 4
        if (isApproved() && isProprietary && !wasProprietary)
        {
            return;
        }
@


1.45
log
@Mail project owners when a project is locked.
@
text
@d153 7
d1002 1
d1008 1
a1008 1
                subject = "Project " + getWordMark() + " locked.";
d1014 2
d1028 10
a1037 1
            if (template != null)
d1039 2
a1040 14
                WebContext ctx = TurbineWebMacro.getContext();
                ctx.put("project", this);
                String body = TurbineWebMacro.handleRequest(ctx, template);
                HelmEmailer emailer = new HelmEmailer();
                try 
                {
                    emailer.messageUsersWithProjectOwnership
                        (this, subject, body);
                }
                catch (Exception e) 
                {
                    Log.error
                        ("Unable to mail project owners about project lock", e);
                }
@


1.44
log
@Implemented caching of Project objects.
@
text
@d148 7
d993 2
d1000 2
d1018 18
d1039 1
@


1.43
log
@Cleanup.
@
text
@d939 1
a939 1
    }
d1097 1
d1676 11
@


1.42
log
@Re-org'd imports.
@
text
@a1158 1
        Host host = new Host();
d1161 1
d1171 1
d1179 1
d1185 1
a1185 1
            new IssueTrackingAccountDescriptor(projects,users);
@


1.41
log
@Removed getStatus() hack.  ProjectAddStep4 was passing null to
Project.add() instead of the empty string.
@
text
@a54 4
import gnu.regexp.*;

import org.webmacro.servlet.WebContext;

d65 4
@


1.40
log
@* implement MailingList.isViewableBy(AccessControlList)
* use that to filter the list of mailing lists for a project based on
  viewer
* cleanup/document TODOs
* call AnzuTask.deleteProject in Project when a project is deleted
@
text
@a286 13
     * HACK: Since Torque is not currently respecting the defaults defined in
     * our Torque XML project schema, be sure to return the empty string for
     * this field if the Torque-generated code returns <code>null</code>.
     *
     * IZ #: 2792
     */
    public final String getStatus()
    {
        String status = super.getName();
        return (status == null ? "" : status);
    }

    /**
@


1.39
log
@Since Torque is not currently respecting the defaults defined in
our Torque XML project schema, be sure to return the empty string for
this field if the Torque-generated code returns <code>null</code>.
@
text
@a1081 7
        // Delete the mailing lists.
        Iterator mailingLists = getMailingLists().iterator();
        while (mailingLists.hasNext()) {
            MailingList ml = (MailingList) mailingLists.next();
            ml.delete(acl);
        }

d1083 1
a1083 2
        //AnzuTigrisTask.deleteProject(dMgr, acl.getUserID(),
        //                             getDomainID(), getName());
d1135 24
@


1.38
log
@Broke long line.
@
text
@d287 13
@


1.37
log
@More renaming madness as the act of 'project disabling' becomes 'project locking'
@
text
@d600 2
a601 1
            .add(UserRoleProjectPeer.APPROVED_BY, UserPeer.getNobodyUser().getID());
@


1.36
log
@Added hook to Anzu's project disable/enable.
@
text
@d117 1
a117 1
     * type is disabled.
d119 1
a119 1
    private static final String AUDIT_LOG_ON_DISABLE = "Project disabled: ";
d123 1
a123 1
     * type is enabled.
d125 1
a125 1
    private static final String AUDIT_LOG_ON_ENABLE = "Project enabled: ";
d530 1
a530 1
     * Returns true iff this project is enabled.
d532 1
a532 1
    public boolean isEnabled()
d534 1
a534 1
        return DBFlag.booleanValueOf(getEnabled());
d974 1
a974 1
     * Makes sure the project is enabled or disabled, according to the
d977 1
a977 1
    public void setEnabledState(AccessControlList acl, boolean newState)
d980 3
a982 1
        if (newState != isEnabled())
d985 1
a985 1
            if (newState == false)
d987 3
a989 2
                prefix = AUDIT_LOG_ON_DISABLE;
                AnzuTask.disableProject(acl.getUser(), getDomain(), this);
d991 13
a1003 2
                prefix = AUDIT_LOG_ON_ENABLE;
                AnzuTask.enableProject(acl.getUser(), getDomain(), this);
a1005 3
            acl.assertPermission("Project - Disable", this);
            setEnabled(DBFlag.flagValueOf(newState));
            save();
@


1.35
log
@Pushed getID() method into base class as a final method.
@
text
@d982 1
a982 5
            acl.assertPermission("Project - Disable", this);
            setEnabled(DBFlag.flagValueOf(newState));
            save();

            String prefix = AUDIT_LOG_ON_ENABLE;
d986 4
d991 4
@


1.34
log
@Moved disabling/enabling ability from ProjectEdit to new servlet: ProjectEnabled.
@
text
@a243 8
     * Returns the ID of this project as a string.
     */
    public final String getID()
    {
        return getPrimaryKey().toString();
    }

    /**
@


1.33
log
@Added getNonSpecialProjectGroups method.
@
text
@d116 12
d993 8
a1000 1
        }
@


1.32
log
@Added the ability to disable projects.
@
text
@d313 10
@


1.31
log
@Corrected method call.
@
text
@d959 15
@


1.30
log
@Added isEnabled method.
@
text
@d520 1
a520 1
        return DBFlag.valueOf(getEnabled());
@


1.29
log
@Corrected regex madness for Torque upgrade.  -dlr
@
text
@d516 8
@


1.28
log
@updated getUnapprovedNews method to get only the pending news items for that project
@
text
@a72 2
import org.tigris.helm.om.peer.*;
import org.tigris.helm.om.base.*;
@


1.27
log
@Don't run disable and projectadd scripts for host or domain projects.
@
text
@d640 2
a641 1
            .add(NewsItemPeer.APPROVED_BY, UserPeer.getNobodyUser().getID());
@


1.26
log
@More rearranging to keep tasks from being run in the wrong order.
@
text
@d1099 5
a1103 2
            HelmTask.addProject(acl.getUser(), p);
            HelmTask.disableAllAccounts(acl.getUser(), p);
@


1.25
log
@Only get the ACL if we have to.
@
text
@d754 5
a788 5

        project.changeLicense(acl, license);

        // Add the project to CVS, Issuezilla, etc.
        project.runCreationScripts(acl.getUser());
d797 2
a798 1
        if (acl.hasPermission("Project - Approve", domain)) {
d801 2
a802 1
        else {
d842 1
a842 1
        changeLicense(acl, license);
d957 1
a957 1
    private void changeLicense(AccessControlList acl, String license)
a959 1
        assertEditPermission(acl);
@


1.24
log
@Fixed permissions problems which were causing plebs to be unable to create projects.
@
text
@a765 1
        String newOwnerID = null;
d780 4
a783 1
        acl = acl.getUser().getACL();
@


1.23
log
@Inline getID() method.
@
text
@a756 5
        project.changeLicense(acl, license);

        // Add the project to CVS, Issuezilla, etc.
        project.runCreationScripts(acl.getUser());
        
d774 1
a774 1

d778 10
a787 1
        
@


1.22
log
@fixed it so that unapproved news items will show up for users with permission to see them
@
text
@d236 1
a236 1
    public String getID()
@


1.21
log
@Grant the owner of a new project the 'project owner role'. I'm not happy about hardcoding the role name but I can't think of a better solution.
@
text
@a612 2
            .add(NewsItemPeer.APPROVED_BY, 
                 UserPeer.getNobodyUser().getUserId(), Criteria.NOT_EQUAL)
@


1.20
log
@jr informs me that you should be able to see projects in lists if you have Project Page - View, not Project - View.
@
text
@d782 3
a784 2
        // TODO: How to do this without hard-coding a role name?
        //newOwner.grantNamedRoleFromSystem("Project Owner", this);
@


1.19
log
@Commented out confusing and as-yet-unneccessary project profile selection *again*
@
text
@d1242 2
a1243 1
        if (!acl.hasPermission("Project - View", this)) {
d1246 3
a1248 1
        else if (!isApproved() && !acl.hasPermission("Project - Approve", getDomain())) {
@


1.18
log
@Add to the groups before changing anything so that permissions have a chance to take effect.
@
text
@d677 31
@


1.17
log
@Almost got rid of dependance on hacky one-argument IssueTrackingAccountDescriptor.syncto().
@
text
@d725 3
d759 1
a759 4
    
        project.addToHostGroup();
        project.addToDomainGroup();
        
@


1.16
log
@Make use of the TurbineWebMacro facade of WebMacroService for a more convienient interface.
@
text
@d1069 1
a1069 1
        before.syncTo(after);
@


1.15
log
@Sync'd with latest Turbine changes.  Mostly this consists of the use of the new SimpleKey class to handle primary database keys.
@
text
@d68 1
a68 1
import org.apache.turbine.services.webmacro.WebMacroService;
d769 1
a769 3
            WebMacroService wmSrvc = (WebMacroService)
                TurbineServices.getInstance().getService(WebMacroService.SERVICE_NAME);
            WebContext ctx = wmSrvc.getContext();
d772 1
a772 1
            String body = wmSrvc.handleRequest(ctx, CREATED_TMPL);
d849 1
a849 3
        WebMacroService wmSrvc = (WebMacroService)
            TurbineServices.getInstance().getService(WebMacroService.SERVICE_NAME);
        WebContext ctx = wmSrvc.getContext();
d852 1
a852 1
        String body = wmSrvc.handleRequest(ctx, DISAPPROVE_TMPL);
@


1.14
log
@* Changed getID() implementation to be forwards compatible (with the new
  Turbine JAR).
* Removed unnecessary method hashCode().
@
text
@d59 1
a59 1
import org.apache.turbine.om.BaseObject;
d706 1
a706 1
            project.setDomainId(domain.getPrimaryKeyAsInt());
d715 1
a715 1
            project.setProposedBy(proposedBy.getPrimaryKeyAsInt());
@


1.13
log
@Run project add before member disable all when rebuilding issue tracking.
@
text
@d238 1
a238 1
        return (String)getPrimaryKey();
a666 10
    }

    //
    // FIXME: Equality methods. I didn't think these would be
    // neccessary anymore because BaseObject supposedly implements
    // them, but when I took them out things stopped working.
    //
    public int hashCode()
    {
        return getProjectId();
@


1.12
log
@Remove documentation of an exception not thrown
@
text
@d1076 1
@


1.11
log
@Also, put user groups before users.
@
text
@a154 2
     *
     * @@exception REException Malformed regular expression.
@


1.10
log
@Return distinct users.
@
text
@d602 1
@


1.9
log
@Added no-argument getUsers method.
@
text
@d602 1
a602 1
        
@


1.8
log
@create mailing lists during project creation
@
text
@d546 9
@


1.7
log
@Standardized on User.getAvailableRolesInProject.
@
text
@d756 2
a789 13
//             try
//             {
//                 profile.addDefaultMailingLists
//                     (dMgr, getID(), newOwner, Licenses.isProprietary(license));
//             }
//             catch (HandlerException e)
//             {
//                 log.exception(e);
//                 if (debug) log.info("HandlerException adding default MLs: " +
//                                     e.getMessage());
//                 throw new UpdateException
//                     ("HandlerException adding default MLs: " + e.getMessage());
//             }
@


1.6
log
@Don't do that join, McGee!
@
text
@a542 37
    public List getAvailableRoles(User user)
        throws Exception
    {
        Host host = new Host();
        List allRoles = host.getProjectRoles();
        ArrayList availableRoles = new ArrayList();

        Map approved = new HashMap();
        List approvedRoles = user.getRolesInProject(this);
        for (int i = 0; i < approvedRoles.size(); i++)
        {
            approved.put(((Role)approvedRoles.get(i)).getID(),
                         Boolean.TRUE);
        }

       
        Map requested = new HashMap();
        List requestedRoles = user.getRequestedRolesInProject(this);
        for (int i = 0; i < requestedRoles.size(); i++)
        {
            requested.put(((Role)requestedRoles.get(i)).getID(),
                          Boolean.TRUE);
        }
       
        for (int i = 0; i < allRoles.size(); i++)
        {
            Role role = (Role)allRoles.get(i);
            if (!requested.containsKey(role.getID())
                && !approved.containsKey(role.getID()) 
                && role.isRequestable())
            {
                availableRoles.add(role);
            }
        }
        return availableRoles;
    }

d554 9
@


1.5
log
@Find the template in the right spot.
@
text
@a619 1
            .addJoin(RolePeer.ROLE_ID, UserRoleProjectPeer.ROLE_ID)
@


1.4
log
@added getAvailableRoles method
@
text
@d127 2
a128 1
    private static final String CREATED_TMPL = "ProjectCreated.wm";
d134 2
a135 1
    private static final String DISAPPROVE_TMPL = "ProjectDisapprove.wm";
@


1.3
log
@Now using TurbineWebMacroService instead of TemplateInterpolator.
@
text
@d541 37
@


1.2
log
@adding files from the branch to the head
@
text
@d57 1
a57 1
import org.webmacro.engine.BuildContext;
d67 2
a75 1

a76 1

a78 1

d759 3
a761 1
            BuildContext ctx = TemplateInterpolator.createContext();
d764 1
a764 2
            String body = TemplateInterpolator.interpolate
                (CREATED_TMPL, ctx, project.getClass());
d854 3
a856 1
        BuildContext ctx = TemplateInterpolator.createContext();
d859 1
a859 2
        String body = TemplateInterpolator.interpolate(DISAPPROVE_TMPL, ctx,
                                                       getClass());
@


1.1
log
@file Project.java was initially added on branch HELM_PEER_PORT_BRANCH.
@
text
@d1 1560
@


1.1.1.1
log
@Replacing the HEAD with the HELM_PEER_PORT_BRANCH.
@
text
@a0 1560
/* ================================================================
 * Copyright (c) 2000-2001 CollabNet.  All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * 3. The end-user documentation included with the redistribution, if
 * any, must include the following acknowlegement: "This product includes
 * software developed by CollabNet (http://www.collab.net/)."
 * Alternately, this acknowlegement may appear in the software itself, if
 * and wherever such third-party acknowlegements normally appear.
 * 
 * 4. The hosted project names must not be used to endorse or promote
 * products derived from this software without prior written
 * permission. For written permission, please contact info@@collab.net.
 * 
 * 5. Products derived from this software may not use the "Tigris" name
 * nor may "Tigris" appear in their names without prior written
 * permission of CollabNet.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL COLLABNET OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by many
 * individuals on behalf of CollabNet.
 */

package org.tigris.helm.om;

import java.util.*;
import java.io.File;
import java.io.FileWriter;
import java.math.BigInteger;
import java.net.InetAddress;

import gnu.regexp.*;

import org.webmacro.engine.BuildContext;

import org.apache.turbine.om.BaseObject;
import org.apache.turbine.om.peer.BasePeer;
import org.apache.turbine.util.db.Criteria;
import org.apache.turbine.util.ObjectUtils;
import org.apache.turbine.util.StringUtils;
import org.apache.turbine.util.ParameterParser;
import org.apache.turbine.util.Log;
import org.apache.turbine.util.db.pool.DBConnection;

import org.joist.util.JoistProperties;
import org.joist.util.URL;

import org.tigris.helm.om.peer.*;
import org.tigris.helm.om.base.*;
import org.tigris.helm.om.profile.*;

import org.tigris.helm.security.*;

import org.tigris.helm.AlreadyExists;
import org.tigris.helm.DoesNotExist;

import org.tigris.helm.tool.*;
import org.tigris.helm.tool.paginate.*;
import org.tigris.helm.tool.audit.AuditLog;
import org.tigris.helm.tool.task.IssueTrackingAccountDescriptor;

/** 
 * Project business object.
 *
 * @@author <a href="mailto:leonardr@@collab.net">Leonard Richardson</a>
 * @@author <a href="mailto:dlr@@collab.net">Daniel Rall</a>
 */
public class Project extends BaseProject implements ProjectType, Comparable
{
    /**
     * The query string parameter name of a servlet display message.
     * <p>
     * HACK: This is totally hokey.  Get rid of it.
     */
    public static final String MESSAGE = "msg";

    /**
     * Description of the audit log entry created when a project of this
     * type is added.  
     */
    private static final String AUDIT_LOG_ON_ADD = "Project added: ";

    /**
     * Description of the audit log entry created when a project of this
     * type is deleted.
     */
    private static final String AUDIT_LOG_ON_DELETE = "Project deleted: ";

    /**
     * Description of the audit log entry created when a project of this
     * type is edited.
     */
    private static final String AUDIT_LOG_ON_EDIT = "Project edited: ";

    /**
     * The regular expression used to check project names for validity.
     */
    private static final String PROJECT_NAME_RE =
        "^[a-zA-Z0-9][-a-zA-Z0-9]{0,63}$";

    /**
     * The name of the template used to create the body of the project 
     * creation announcement email.
     */
    private static final String CREATED_TMPL = "ProjectCreated.wm";

    /**
     * The name of the template used to create the body of the project 
     * disapproval email.
     */
    private static final String DISAPPROVE_TMPL = "ProjectDisapprove.wm";

    /**
     * A mutex for the addition of new projects.
     * <p>
     * TODO: Handle this via database table locking.
     */
    private static Object addLock = new Object();

    /**
     * A hash of mutex objects for handling file write locking to .htaccess 
     * files, keyed by file name.
     */
    private static Map htaccessLocks = new HashMap();

    /**
     * Checks whether a project name is valid.
     *
     * @@param name The project name to check.
     * @@return     Whether the name is valid.
     *
     * @@exception REException Malformed regular expression.
     */
    public static boolean isValidName(String name)
    {
        boolean isValid = false;

        if (name != null && name.length() > 0)
        {
            RE projectRE = null;
            try
            {
                projectRE = new RE(PROJECT_NAME_RE);
                isValid = projectRE.isMatch(name);
            }
            catch (REException e)
            {
                Log.error(e);
            }
        }

        return isValid;
    }

    /**
     * Macro which encapsulates transformation of a word mark to a
     * canonical name.
     *
     * @@param wordMark The mixed case word mark to transform.
     * @@return         The name as lower case.
     */
    private static final String transformWordMarkToName(String wordMark)
    {
        return wordMark.toLowerCase();
    }

    /**
     * Asserts that a project with the specified name does not already
     * exist in the specified domain.
     *
     * @@param domain The domain to check for the project in.
     * @@param name   The name of the project to check for.
     *
     * @@exception AlreadyExists The project exists.  
     */
    public static void assertNonexistance(Domain domain, String name)
        throws AlreadyExists, Exception
    {
        try {
            Project p = ProjectPeer.getInstanceByName(domain, name);
        }
        catch (DoesNotExist success) {
            return;
        }
        throw new AlreadyExists("Project named " + name,
                                "domain named " + domain.getName());
    }


    //
    // Accessor methods for particular projects.
    //

    /**
     * Returns the name of the project in case which it is saved to the
     * database (i.e. it is possible that mixed case text may be returned).
     */
    public final String getWordMark()
    {
        return super.getName();
    }

    /**
     * Returns the name of the project in lower case.
     */
    public final String getName()
    {
        return super.getName().toLowerCase();
    }
    
    /**
     * Returns the ID of this project as a string.
     */
    public String getID()
    {
        return (String)getPrimaryKey();
    }

    /**
     * Returns the brand of this project--not used yet.
     */
    public String getBrand()
    {
        return "";
    }

    /**
     * Returns the ID of this project's domain as a string.
     */
    public String getDomainID()
    {
        return String.valueOf(getDomainId());
    }

    /**
     * Returns this project's domain.
     */
    public Domain getDomain()
        throws Exception
    {
        return DomainPeer.getInstance(getDomainID());
    }

    /**
     * Gets the skin for this project.
     */
    public Skin getSkin()
        throws Exception
    {
        return SkinPeer.retrieveByPK(getSkinId());
    }

    /**
     * Gets the profile for this project.
     */
    public ProjectProfile getProjectProfile()
        throws Exception
    {
        return ProjectProfiles.getInstance().get(getProfile());
    }

    /**
     * Returns true iff this project is supposed to be using the
     * project home page instead of its description.
     */
    public boolean useIndex()
        throws Exception
    {
        return DBFlag.booleanValueOf(getUseIndex());
    }

    /**
     * Returns the name of this project's domain. dlr would like to see this
     * method deprecated.
     */
    public String getDomainName()
        throws Exception
    {
        return getDomain().getName();
    }

    /**
     * Returns a list of all the project groups, including categories and
     * special project groups.
     */
    public List getGroups()
        throws Exception
    {
        return getGroupsOfType(null);
    }

    /**
     * Returns a list of all the categories to which this project belongs.
     */
    public List getCategories()
        throws Exception
    {
        return getGroupsOfType(Project.CATEGORY_TYPE);
    }

    /**
     * Returns a list of all the project groups which are of the specified
     * type and to which this project belongs.
     */
    private List getGroupsOfType(String type)
        throws Exception
    {
        Criteria c = new Criteria()
            .addJoin(ProjectProjectgroupPeer.PROJECT_GROUP_ID, 
                     ProjectPeer.PROJECT_ID)
            .add(ProjectProjectgroupPeer.PROJECT_ID, getProjectId());
        if (type != null)
        {
            c.add(ProjectPeer.TYPE, type);
        }
        return ProjectPeer.doSelect(c);
    }

    /**
     * Turns a Project representation of a project representing a group
     * into a ProjectGroup representation of the project.
     */
    public ProjectGroup asGroup()
        throws Exception
    {
        if (getType().equals(Project.REGULAR_TYPE))
        {
            throw new Exception
                ("Attempt to transform non-group project " + getName() + " to ProjectGroup.");
        } 
        return ProjectPeer.getGroupInstance(getID());
    }

    /**
     * Returns all the projects equivalent to this one. For a regular project,
     * returns a list containing itself. For a group project, returns a list
     * containing all the projects in the group.
     */
    public List expand()
        throws Exception
    {
        List l = new ArrayList(1);
        l.add(this);
        return l;
    }

    /**
     * Checks whether a project has been approved. A project is approved
     * if it's "approved by" field is not set to the "nobody" user.
     *
     * @@return     Whether the project has been approved.
     *
     */
    public boolean isApproved()
        throws Exception
    {
        String approvedBy = String.valueOf(getApprovedBy());
        return (!approvedBy.equals(UserPeer.getNobodyUser().getID()));
    }   

    /**
     * Returns true iff the project is that special project representing
     * all projects on the host.
     */
    public boolean isHost()
    {
        return (this instanceof HostProjectGroup);
    }

    /**
     * Returns top level sections associated with this project.
     */
    public List getSections()
        throws Exception
    {        
        Criteria c = new Criteria()
            .add(ProjectSectionPeer.PROJECT_ID, getID())
            .add(ProjectSectionPeer.PARENT_ID, 0);
        return ProjectSectionPeer.doSelect(c);
    }

    /**
     * Returns unapproved sections associated with this project.
     */
    public List getUnapprovedSections()
        throws Exception
    {        
        Criteria c = new Criteria()
            .add(ProjectSectionPeer.PROJECT_ID, getID())
            .add(ProjectSectionPeer.APPROVED_BY, 
                 UserPeer.getNobodyUser().getUserId());
        return ProjectSectionPeer.doSelect(c);
    }

    /**
     * Returns all documents associated with this project.
     */
    public List getDocuments()
        throws Exception
    {        
        Criteria c = new Criteria()
            .add(ProjectDocumentPeer.PROJECT_ID, getID());
        return ProjectDocumentPeer.doSelect(c);
    }

    /**
     * Returns list of unapproved documents associated with the project.
     */
    public List getUnapprovedDocuments()
        throws Exception
    {
        Criteria c = new Criteria()
            .add(ProjectDocumentPeer.PROJECT_ID, getID())
            .add(ProjectDocumentPeer.APPROVED_BY, 
                 UserPeer.getNobodyUser().getUserId());
        return ProjectDocumentPeer.doSelect(c);
    }

    /**
     * Returns list of unapproved downloads associated with the project.
     */
    public List getUnapprovedDownloads()
        throws Exception
    {
        Criteria c = new Criteria()
            .add(ProjectDownloadPeer.PROJECT_ID, getID())
            .add(ProjectDownloadPeer.APPROVED_BY, 
                 UserPeer.getNobodyUser().getUserId());
        return ProjectDownloadPeer.doSelect(c);
    }

    /**
     * Returns top level documents associated with this project.
     * Top level documents are those that are not associated with a section.
     */
    public List getTopLevelDocuments()
        throws Exception
    {        
        List topLevelDocs = new ArrayList();
        Criteria c = new Criteria()
            .add(ProjectDocumentPeer.PROJECT_ID, getID());
        List allDocuments =  ProjectDocumentPeer.doSelect(c);
        for (Iterator i = allDocuments.iterator(); i.hasNext(); )
        {
            ProjectDocument dc = (ProjectDocument)i.next();
            if (dc.getSectionID() == 0)
            {
                topLevelDocs.add(dc);
            }
        }
        return topLevelDocs;
    }

    /**
     * Returns true iff the project is a special project representing
     * the projects in a domain.
     */
    public boolean isDomain()
    {
        return (this instanceof DomainProjectGroup);
    }

    /**
     * Returns true iff the project is a special category project group.
     */
    public boolean isCategory()
    {
        return (this instanceof Category);
    }

    /**
     * Returns true iff the project represents a project group of some kind.
     */
    public boolean isGroup()
    {
        return (this instanceof ProjectGroup);
    }

    /**
     * Returns true iff the project represents a base project.
     */
    public boolean isBase()
    {
        return !isGroup();
    }

    /**
     * Returns true iff the project has a proprietary license.
     */
    public boolean isProprietary()
    {
        return Licenses.isProprietary(getLicense());
    }

    /**
     * Compares this project to another object for sorting purposes.
     */
    public int compareTo(Object o)
        throws ClassCastException
    {
        Project p = (Project)o;
        return getName().compareTo(p.getName());
    }

    //
    // Methods to get related objects.
    //

    /**
     * Returns all users who have an ownership role in this project.
     */
    public List getOwners()
        throws Exception
    {
        Criteria c = new Criteria(4).add(RolePeer.DENOTES_OWNER, true);
        return getUsers(new Integer(0), c, null);
    }

    /**
     * Returns all users who have any approved role in this project.
     */
    public PeerBackedList getUsers(Integer itemsPerPage, Criteria filter)
        throws Exception
    {
        Criteria base = new Criteria()
            .add(UserRoleProjectPeer.APPROVED_BY, 
                 (Object)UserPeer.getNobodyUser().getID(), 
                 Criteria.NOT_EQUAL);
        return getUsers(itemsPerPage, base, filter);
    }

    /**
     * Returns all users who have any unapproved role in this project.
     */
    public PeerBackedList getRequestedUsers
        (Integer itemsPerPage, Criteria filter)
        throws Exception
    {
        Criteria base = new Criteria()
            .add(UserRoleProjectPeer.APPROVED_BY, UserPeer.getNobodyUser().getID());
        return getUsers(itemsPerPage, base, filter);
    }

    /**
     * Returns a paginatable list of users who have a role in this
     * project. Appends the appropriate restrictions on a base
     * criterion.  
     */
    public PeerBackedList
        getUsers(Integer itemsPerPage, Criteria base, Criteria filter)
        throws Exception
    {
        if (base == null)
        {
            base = new Criteria(3);
        }

        base.addJoin(UserPeer.USER_ID, UserRoleProjectPeer.USER_ID)
            .addJoin(RolePeer.ROLE_ID, UserRoleProjectPeer.ROLE_ID)
            .add(UserRoleProjectPeer.PROJECT_ID, getProjectId());
        
        return new PeerBackedList(UserPeer.class, itemsPerPage, base, filter);
    }

    /**
     * Returns a paginatable list of news items for this project.
     */
    public PeerBackedList getNews(Integer itemsPerPage, Criteria filter, 
                                  AccessControlList acl)
        throws Exception
    {
        Criteria base = new Criteria()
            .add(NewsItemPeer.APPROVED_BY, 
                 UserPeer.getNobodyUser().getUserId(), Criteria.NOT_EQUAL)
            .add(NewsItemPeer.PROJECT_ID, getProjectId());
        PeerBackedList newsItems = new PeerBackedList
            (NewsItemPeer.class, itemsPerPage, base, filter);
        List toRemove = new ArrayList();
        for (Iterator i = newsItems.iterator(); i.hasNext();)
        {
            NewsItem n = (NewsItem)i.next();
            if (!n.isViewableBy(acl))
            {
                toRemove.add(n);
            }
        }
        newsItems.removeAll(toRemove);
        return newsItems;
    }


    /**
     * Get a paginated list of unapproved news items for this project
     * which are viewable by the specified ACL.  
     */
    public PeerBackedList getUnapprovedNews
        (Integer itemsPerPage, Criteria filter, AccessControlList acl)
        throws Exception
    {
        List toRemove = new ArrayList();
        Criteria base = new Criteria()
            .add(NewsItemPeer.APPROVED_BY, UserPeer.getNobodyUser().getID());
        PeerBackedList p = new PeerBackedList
            (NewsItemPeer.class, itemsPerPage, base, filter);
        for (Iterator i = p.iterator(); i.hasNext();)
        {
            NewsItem n = (NewsItem)i.next();
            if (!acl.hasPermission 
                (n.requiredViewPermission(acl), this))
            {
                toRemove.add(n);
            }           
        }
        p.removeAll(toRemove);
        return p;
    }


    /**
     * Get a big list of all unapproved news items for this project which
     * are viewable by the specified ACL.  
     */
    public List getUnapprovedNews(AccessControlList acl)
        throws Exception
    {
        return getUnapprovedNews(new Integer(0), null, acl);
    }

    //
    // FIXME: Equality methods. I didn't think these would be
    // neccessary anymore because BaseObject supposedly implements
    // them, but when I took them out things stopped working.
    //
    public int hashCode()
    {
        return getProjectId();
    }

    //
    // Mutator methods.
    //

    /**
     * Adds a project.
     *
     * @@param acl               The ACL of the user.
     * @@param profile           The type of project.
     * @@param domain            Domain in which the project resides.
     * @@param name              Name of the project.
     * @@param license           License of the project.
     * @@param shortDescription  A short description of the project.
     * @@param longDescription   A long description of the project.
     * @@param useIndex          Whether to use the index file contents for the
     *                          long description.
     * @@param aliasURL          URL to use as alias, if any.
     * @@param proposedBy        User who proposed the project.
     * @@param approvedBy        User who approved the project, if any.
     * @@return                  The database record for the project.
     *
     * @@exception AlreadyExists A project in this domain with this name exists.
     */
    public static Project add
        (AccessControlList acl, ProjectProfile profile, Domain domain,
         String wordMark, String license, String shortDescription, 
         String longDescription, String useIndex, String status, 
         String aliasURL, User proposedBy, User projectOwner)
        throws ACLException, AlreadyExists, Exception
    {

        Project project = null;
        synchronized (addLock)
        {
            assertNonexistance(domain, wordMark);

            project = new Project();
            project.setDomainId(domain.getPrimaryKeyAsInt());
            project.setName(wordMark);
            project.setShortDescription(shortDescription);
            project.setLongDescription(longDescription);
            project.setUseIndex(useIndex);
            project.setStatus(status);
            project.setAliasUrl(aliasURL);
            project.setIsProprietary(DBFlag.flagValueOf
                                     (Licenses.isProprietary(license)));
            project.setProposedBy(proposedBy.getPrimaryKeyAsInt());
            project.setApprovedBy(UserPeer.getNobodyUser().getUserId());
            project.setProfile(profile.getKey());
            project.setType(profile.getType());
            project.assertAddPermission(acl);

            // Add the project to the database.            
            project.save();
        }

        project.changeLicense(acl, license);

        // Add the project to CVS, Issuezilla, etc.
        project.runCreationScripts(acl.getUser());
        
        //Give someone Project Owner role on this project.
        //
        //If the ACL has Project Approve permission, make
        //the owner whomever they designate.
        //
        //Otherwise, make the ACL's user the owner.        
        //
        //Whomever we designate will own the project's mailing lists.
        User   newOwner   = null;
        String newOwnerID = null;
        if (projectOwner == null ||
            !acl.hasPermission("Project - Approve", domain)) {
            newOwner = acl.getUser();
        }
        else {
            newOwner = projectOwner;
        }

        // TODO: How to do this without hard-coding a role name?
        //newOwner.grantNamedRoleFromSystem("Project Owner", this);
        
        String name = transformWordMarkToName(wordMark);
        AnzuTask.addProject(newOwner, domain, project);
        AnzuTask.setOwnerAlias(newOwner, project, false);
    
        project.addToHostGroup();
        project.addToDomainGroup();
        
        //Approve the project, if user has the right permission.
        if (acl.hasPermission("Project - Approve", domain)) {
            project.approve(acl);
        }
        else {
            //Send a message to the domain admin(s) informing them of
            //the newly requested project.
            User createdUser = acl.getUser();
            String subject = "New unapproved project: " + wordMark;
            BuildContext ctx = TemplateInterpolator.createContext();
            ctx.put("project", project);
            ctx.put("createdUser", createdUser);
            String body = TemplateInterpolator.interpolate
                (CREATED_TMPL, ctx, project.getClass());
            HelmEmailer emailer = new HelmEmailer();
            try 
            {
                emailer.messageUsersWithDomainOwnership
                    (domain, subject, body);
            }
            catch (Exception e)
            {
                Log.error
                    ("Unable to mail domain owners about new project.", e);
            }
        }

//             try
//             {
//                 profile.addDefaultMailingLists
//                     (dMgr, getID(), newOwner, Licenses.isProprietary(license));
//             }
//             catch (HandlerException e)
//             {
//                 log.exception(e);
//                 if (debug) log.info("HandlerException adding default MLs: " +
//                                     e.getMessage());
//                 throw new UpdateException
//                     ("HandlerException adding default MLs: " + e.getMessage());
//             }
        AuditLog.logEvent(acl.getUser(), project, 
                          project.AUDIT_LOG_ON_ADD + project.getName());
        return project;
    }

    /**
     * Updates the project's information.
     */
    public void updateInfo(AccessControlList acl,
                           String license,
                           String shortDescription,
                           String longDescription,
                           String useIndex,
                           String status, 
                           String aliasURL)
        throws ACLException, Exception
    {
        assertEditPermission(acl);
        changeLicense(acl, license);
        setShortDescription(shortDescription);
        setLongDescription(longDescription);
        setUseIndex(useIndex);
        setStatus(status);
        setAliasUrl(aliasURL);        
        save();
        AuditLog.logEvent(acl.getUser(), this, 
                          this.AUDIT_LOG_ON_EDIT + getName());
    }

    /**
     * Approves the project in the name of the approving user..
     *
     * @@param acl The ACL of the user approving the project.
     *
     * @@exception ACLException The ACL does not have approval permission.
     * @@exception DoesNotExist If the specified project is invalid.
     * @@exception Exception    If a database error occurs.
     */
    public void approve(AccessControlList acl)
        throws ACLException, DoesNotExist, Exception
    {
        if (acl != null) {
            acl.assertPermission("Project - Approve", getDomain());
                                          
            setApprovedBy(acl.getUser().getUserId());
            save();
            if (!isProprietary()) {
                addToPublicGroup();
            }
        }
    }

    /**
     * Deletes an unapproved project, sending email to the owners
     * about it.  
     */
    public void disapprove(AccessControlList acl)
        throws ACLException, DoesNotExist, Exception
    {
        delete(acl);
        //Send email to the owners.
        User disapproving = acl.getUser();
        String subject = "Project " + getWordMark() + " disapproved.";
        BuildContext ctx = TemplateInterpolator.createContext();
        ctx.put("project", this);
        ctx.put("disapprovingUser", disapproving);
        String body = TemplateInterpolator.interpolate(DISAPPROVE_TMPL, ctx,
                                                       getClass());
        HelmEmailer emailer = new HelmEmailer();
         try {
             emailer.messageUsersWithProjectOwnership(this, subject, body);
         }
         catch (Exception e) {
             Log.error
                 ("Unable to mail project owner about project disapproval", e);
         }
    }
    
    /**
     * Adds the project to a category.
     */
    public void addToCategory(Category cat)
        throws Exception
    {
        cat.addProject(this);
    }

    /**
     * Removes the project from a category.
     */
    public void removeFromCategory(Category cat)
        throws Exception
    {
        cat.removeProject(this);
    }

    /**
     * Adds this project to the specified group.
     */
    public void addToGroup(AccessControlList acl, ProjectGroup g)
        throws Exception
    {
        g.addProject(acl, this);
    }

    /**
     * Removes this project from the specified group.
     */
    public void removeFromGroup(AccessControlList acl, ProjectGroup g)
        throws Exception
    {
        g.removeProject(acl, this);
    }

    /**
     * Changes the project category--removes from all old categories
     * and adds to the new one.
     */
    public void changeCategory(Category newCat)
        throws Exception
    {
        for (Iterator i = getCategories().iterator(); i.hasNext();)
        {
            removeFromCategory((Category)i.next());
        }
        //Add the project to the new category.
        addToCategory(newCat);
    }

    //Overridden methods

    /**
     * Change this project's license.
     */
    private void changeLicense(AccessControlList acl, String license)
        throws Exception
    {
        assertEditPermission(acl);
        //Get the old license--see if it's proprietary.
        String oldLicense = getLicense();
        if (oldLicense != null && oldLicense.equals(license))
        {
            return;
        }

        // Approved non-proprietary projects can't go proprietary.
        boolean wasProprietary = Licenses.isProprietary(oldLicense);
        boolean isProprietary = Licenses.isProprietary(license);            
        if (isApproved() && isProprietary && !wasProprietary)
        {
            return;
        }

        //Set the new license.
        super.setLicense(license);
        save();

        //If we are approved, change appropriate groups.
        if (isApproved())
        {
            String domainID = getDomainID();

            //If we used not to be proprietary and now we are, remove
            //this project from the public projects list.
            if ((!wasProprietary || oldLicense == null) && isProprietary)
            {
                removeFromPublicGroup();
            }
            
            //If we used to be proprietary and now we're not, add this project
            //to the public projects list.
            if ((wasProprietary || oldLicense == null) && !isProprietary)
            {
                addToPublicGroup();
            }
        }
    }

    /**
     * Deletes this project.
     */
    public void delete(AccessControlList acl)
        throws ACLException, Exception
    {
        assertDeletePermission(acl);

        // TODO: Delete this project's version controlled artifacts.
        
        // Delete the mailing lists.
        Iterator mailingLists = getMailingLists().iterator();
        while (mailingLists.hasNext()) {
            MailingList ml = (MailingList) mailingLists.next();
            ml.delete(acl);
        }

        // delete the directory in the anzu project as well
        //AnzuTigrisTask.deleteProject(dMgr, acl.getUserID(),
        //                             getDomainID(), getName());

        List groups = getGroups();
        for (Iterator i = groups.iterator(); i.hasNext();)
        {
            ProjectGroup p = (ProjectGroup)i.next();
            Log.debug("Removing project " + getWordMark() + " from group " +
                      p.getWordMark());
            // TODO: FIXME
//            p.removeProjectNoPerms(this);
        }

        //Remove all roles in the project.
        UserRoleProjectPeer.doDelete
            (new Criteria().add(UserRoleProjectPeer.PROJECT_ID, getID()));

        //TODO: Remove all other references to it. (Downloads etc.)

        //TODO: Delete this project's download directory.
        
        String message = this.AUDIT_LOG_ON_DELETE + getName();

        //Delete the project itself, along with its CVS repository
        //and whatnot.
        HelmTask.deleteProject(acl.getUser(), this);
        ProjectPeer.doDelete(new Criteria()
            .add(ProjectPeer.PROJECT_ID,getID()));
        AuditLog.logEvent(acl.getUser(), this, message);
    }

    /**
     * Helper method to run any scripts needed for the creation of the
     * project. It's public because the Tigris initialization task
     * needs to run it.
     */
    public void runCreationScripts(User initiatingUser)
        throws Exception
    {
        for (Iterator i = expand().iterator(); i.hasNext(); )
        {
            Project p = (Project)i.next();
            if (!p.isDomain() && !p.isHost())
            {
                HelmTask.addProject(initiatingUser, p);
                if (p.getProjectProfile().isAutoPublishedToWeb())
                {
                    HelmTask.addModule(initiatingUser, p,
                                       ProjectPeer.DEFAULT_PROJECT_NAME, 
                                       ProjectPeer.DEFAULT_PROJECT_DESCRIP, "1");
                }
            }
        }
    }

    /**
     * Clear the Issuezilla accounts and rebuild them according to
     * what's in the database.
     */
    public void rebuildIssueTracking(AccessControlList acl)
        throws ACLException, Exception
    {
        acl.assertPermission("Project Role - Grant", this);

        List projects = new ArrayList();
        projects.addAll(expand());
        Host host = new Host();

        List users = null;
        if (isHost())
        {
            users = host.getAllSiteUsers();
        } else {
            users = getDomain().getUsers();
            users.addAll(host.getUsers());
        }

        //First, disable everybody in each project.
        for (Iterator i = projects.iterator(); i.hasNext();)
        {
            Project p = (Project)i.next();
            HelmTask.disableAllAccounts(acl.getUser(), p);
        }
        IssueTrackingAccountDescriptor before = 
            new IssueTrackingAccountDescriptor();
        IssueTrackingAccountDescriptor after = 
            new IssueTrackingAccountDescriptor(projects,users);
        before.syncTo(after);
    }

    //
    // Methods for dealing with this project's associations with project
    // groups.
    //

    /**
     * Adds a project to the list of projects on the host.
     */
    protected void addToHostGroup()
        throws Exception
    {
        AccessControlList acl = AccessControlList.getSuperUserACL();
        ProjectPeer.getHostProject().addProject(acl, this);
    }

    /**
     * Adds a project to the list of projects in the domain.
     */
    protected void addToDomainGroup()
        throws Exception
    {
        AccessControlList acl = AccessControlList.getSuperUserACL();
        ProjectPeer.getDomainProject(getDomain()).addProject(acl, this);
    }
    
    /**
     * Adds a project to the list of public projects in the domain.
     */
    protected void addToPublicGroup()
        throws Exception
    {
        AccessControlList acl = AccessControlList.getSuperUserACL();
        ProjectPeer.getPublicDomainProject(getDomain()).addProject(acl, this);
    }
    
    /**
     * Removes a project from the list of public projects in the domain.
     */
    protected void removeFromPublicGroup()
        throws Exception
    {
        AccessControlList acl = AccessControlList.getSuperUserACL();
        ProjectPeer.getPublicDomainProject(getDomain())
            .removeProject(acl, this);
    }

    //
    // Permission checking methods
    //

    /**
     * Returns the action which should be checked for this project before
     * performing a grant or revoke involving it.
     */
    public String requiredRoleGrantAction()
        throws Exception
    {
        String requiredAction = "Host Role - Grant";
        if (isHost())
        {
            //Taken care of above.
        } else if (isDomain()) {
            requiredAction = "Domain Role - Grant";
        } else {
            if (isApproved())
            {
                requiredAction = "Project Role - Grant";
            } else {
                requiredAction = "Unapproved Project Role - Grant";
            }
        }
        return requiredAction;
    }

    /**
     * Asserts addition and/or approval permission for this project.
     *
     * @@param acl The ACL to check for permission.
     */
    public void assertAddPermission(AccessControlList acl)
        throws ACLException, Exception
    {
        Domain domain = getDomain();
        if (isBase())
        {
            if (!acl.hasPermission("Project - Suggest", domain) &&
                !acl.hasPermission("Project - Add", domain))
            {
                throw new ACLException(acl, 
                                       "Project - Suggest or Project - Add", 
                                       "Domain " + domain.getName(), null);
            }
        } else {
            acl.assertPermission("ProjectGroup - Add", domain);
        }
    }

    /**
     * Asserts edit permission for this project.
     *
     * @@param acl The ACL to check for permission.
     */
    public void assertEditPermission(AccessControlList acl)
        throws ACLException, Exception
    {
        if (isBase()) {
            acl.assertPermission("Project - Edit", this);
        }
        else {
            acl.assertPermission("ProjectGroup - Edit", getDomain());
        }        
    }

    /**
     * Asserts deletion and/or disapproval permission for this project.
     *
     * @@param acl The ACL to check for permission.
     */
    public void assertDeletePermission(AccessControlList acl)
        throws ACLException, Exception
    {
        if (isBase()) {
            acl.assertPermission("Project - Delete", this);
        }
        else {
            acl.assertPermission("ProjectGroup - Delete", getDomain());
        }        
    }

    /**
     * Returns true iff this project can be viewed by the permissions
     * of the given ACL.
     *
     * @@param acl The ACL to check for permission.
     */
    public boolean isViewableBy(AccessControlList acl)
        throws Exception
    {
        boolean ok = true;
        if (!acl.hasPermission("Project - View", this)) {
            ok = false;
        }
        else if (!isApproved() && !acl.hasPermission("Project - Approve", getDomain())) {
            ok = false;       
        }
        return ok;
    }

    /**
     * Returns the number of outstanding role requests for this
     * project.  
     */
    public int getNumberOfRequestedRoles()
        throws Exception
    {
        Criteria c = new Criteria()
            .add(UserRoleProjectPeer.PROJECT_ID, getProjectId())
            .add(UserRoleProjectPeer.APPROVED_BY, 
                 UserPeer.getNobodyUser().getUserId());
        List mappings = UserRoleProjectPeer.doSelect(c);
        return mappings.size();
    }

    //
    // Hairy URL methods
    //

    /**
     * Return the ProjectHome url.
     *
     * @@see #getProjectHome( String )
     */
    public String getProjectHome( )
        throws NullPointerException, IllegalArgumentException
    {
        return getProjectHome( ( String )null );
    }
    
    
    /**
     * Return the ProjectHome url.
     *
     * Will return either '/servlets/ProjectHome?projectID=THIS_PROJECT_ID
     * or 'http://THIS_PROJECTNAME.THIS_PROJECT_DOMAIN/servlets/ProjectHome.
     * The switch turns on the <code>env.sh</code> setting IS_VHOSTING.  (The
     * ${SANDOBX}/scripts/configure.pl writes the IS_VHOSTING value to 
     * <code>${SANDBOX}/newhelm/conf/40helm.zone.properties</code>)
     * <p>
     * You would use this method to write out links to this project's
     * ProjectHome page.  For example, say you are browsing 
     * www.DOMAIN and you come upon the ProjectList page that
     * holds a list of all projects.  Calling this method on each of the
     * projects found to write the links for ProjectList will 
     * return the appropriate ProjectHome link. 
     *
     * @@param inProtocol Pass 'http' or 'https'.  If null, we assume 'http'.
     *
     * @@return Either 'PROJECTHOME_SERVLETPATH?projectID=THIS_PROJECT_ID' or
     * 'http://THIS_PROJECTNAME.THIS_PROJECT_DOMAIN/SERVLET_PATH'
     *
     * @@exception NullPointerException If i can't get JoistProperties instance.
     * @@exception IllegalArgumentException Thrown if you pass anything but 
     * 'http' or 'http' in the inProtocol param.
     */
    public String getProjectHome( String inProtocol )
        throws NullPointerException, IllegalArgumentException
    {
        return buildProjectPageURL("ProjectHome", null, inProtocol, false, true);
    }

    public String projectMembersURL()
        throws NullPointerException
    {
        return buildProjectPageURL
            ("ProjectMembers", null, "http", false, true);
    }

    private boolean isVhosting(org.joist.util.JoistProperties properties) {
        String IS_VHOSTING_KEY = "IS_VHOSTING"; // Key for VHOSTING.
        // Test if vhosting is turned on.
        String isVhosting = properties.getProperty( IS_VHOSTING_KEY );
        if( isVhosting != null &&
                (new Boolean(isVhosting).booleanValue()) )
            return true;
        return false;
    }

    /**
     * This provides an URL (or UIR) to use for a given page in a project.
     * This version can be used with a template.
     */
    public String getProjectPageURL(String page)
        throws NullPointerException
    {
        return buildProjectPageURL(page, null, null, false, true);
    }

    /**
     * This provides an URL (or URI) to use for a given page in a project.
     * This version can be used within a template.
     */
    public String getProjectPageURL(String page, String queryArgs)
        throws NullPointerException
    {
        if (queryArgs != null && queryArgs.equals(""))
            queryArgs = null;

        return buildProjectPageURL(page, queryArgs, null, false, true);
    }

    /**
     * This provides an URL to use for a given page in a project.  This
     * verion is typically used within e-mail and in other contexts
     * where an absolute link is required.
     */
    public String getProjectPageFullURL(String page, String queryArgs,
            String inProtocol)
        throws NullPointerException
    {
        return buildProjectPageURL(page, queryArgs, inProtocol, true, true);
    }

    /**
     * This provides an URL (or URI) to use for a given page in a project.
     */
    public String getProjectPageURL(String page, String queryArgs,
            String inProtocol)
        throws NullPointerException
    {
        return buildProjectPageURL(page, queryArgs, inProtocol, false, true);
    }


    /**
     * This provides an URL (or URI) to use for a given (non-servlet) file
     *  in a project.
     */
    public String getProjectFileURL(String page)
        throws NullPointerException
    {
        return buildProjectPageURL(page, null, null, false, false);
    }

    /**
     * This provides an URL (or URI) to use for a given (non-servlet) file
     *  in a project.
     */
    public String getProjectFileURL(String page, String queryArgs)
        throws NullPointerException
    {
        return buildProjectPageURL(page, queryArgs, null, false, false);
    }

    /**
     * This provides an URL (or URI) to use for a given (non-servlet) file
     *  in a project.
     */
    public String getProjectFileURL(String page, String queryArgs,
                                    String inProtocol)
        throws NullPointerException
    {
        return buildProjectPageURL(page, queryArgs, inProtocol, false, false);
    }


    /**
     * This provides a URL to use for a given page in a project.  It uses
     * a configuration flag -- which is part of env.sh -- to determine
     * whether projects should be represented as http://$project.domain/...
     * or http://domain/...?projectID=$project.getID()
     *
     * The logic is: if forceFullyQualifiedDomain is true or if vhosting
     * (the former form) is being used, then start with
     * http://[{project}].domain.  Next, add /servlets/$page.  Finally,
     * if a queryArgs are provided or if the latter form is being used,
     * generate an appropriate query string.
     *
     * Look at the JavaDocs for getProjectHome for more an explanation
     * of one use of this function.
     *
     * @@param page The servlet to which to point
     * @@param queryArgs Any query arguments which are needed
     * @@param inProtocol The protocol being used, so far as we know
     * @@param forceFullyQualifiedDomain Whether to insist on including
     * the protocol, domain, and port number.  This is useful eg. for
     * e-mail messages, where relative URIs will not work.
     * @@return a URI/URL to send to the client
     */
    private String buildProjectPageURL
        (String page, String queryArgs, String inProtocol, 
         boolean forceFullyQualifiedDomain, boolean isServlet)
        throws NullPointerException
    {
        // JoistProperties is a singleton of system properties.
        org.joist.util.JoistProperties properties
            = org.joist.util.JoistProperties.getInstance( );
        if( properties == null )
            throw new NullPointerException( "JoistProperties is null" );
                    
        boolean isVhosting = isVhosting(properties);
        StringBuffer buff = new StringBuffer();

        String DOMAIN_KEY = "DOMAIN";
        String SERVLET_PREFIX = "/servlets/"; 

        org.joist.util.URL urlUtil = new org.joist.util.URL( );
        String protocol = urlUtil.getProtocol( inProtocol );
        String port = urlUtil.getPort( protocol );
        String domain = properties.getProperty( DOMAIN_KEY );
        if( domain == null || domain.length( ) <= 0 )
            throw new NullPointerException( DOMAIN_KEY + " is null" );

        if (isVhosting || forceFullyQualifiedDomain) {
            buff.append( protocol );
            buff.append( "://" ); 
            if (isVhosting) {
                buff.append( getName() ); // Add this project's name as host.
                buff.append( '.' ); // Dot between project name and domain
            }
            buff.append( domain );  
            if( port != null)
            {
                buff.append( ":" );
                buff.append( port );
            }
        }
        if (isServlet)
            buff.append(SERVLET_PREFIX);
        buff.append(page);
        String projectArgs = null;
        if (!isVhosting)
            projectArgs = "projectID=" + getID();

        if (projectArgs != null && queryArgs != null) {
            buff.append("?").append(projectArgs).append("&").append(queryArgs);
        } else if (projectArgs != null) {
            buff.append("?").append(projectArgs);
        } else if (queryArgs != null) {
            buff.append("?").append(queryArgs);
        }
        return buff.toString();
    }

    /**
     * Writes the .htaccess file in the right directory for this project.
     *
     * @@param path The full directory path to where to write a .htaccess file.
     * @@param url  The URL for auth and timeout (when <code>null</code>,
     *             defaults to <code>http://[JVM host]/</code>).
     */
    protected void writeHtaccessFile(String path, String url)
        throws Exception
    {
        String fileName = path + File.separatorChar + ".htaccess";
        if (url == null) {
            url = URL.DEFAULT_PROTOCOL + "://" +
                InetAddress.getLocalHost().getHostName() + '/';
        }

        // If not in vhosting mode, append projectID as cgi param
        JoistProperties properties = JoistProperties.getInstance();
        if (properties == null) {
            throw new NullPointerException( "JoistProperties is null" );
        }
        if (!isVhosting(properties)) {
            url = URL.appendCGIParam(url, "projectID", getID());
        }

        // Get a mutex for the given .htaccess file.
        Object htaccessWriteLock = null;
        synchronized (htaccessLocks) {
            if (!htaccessLocks.containsKey(fileName)) {
                htaccessLocks.put(fileName, new Object());
            }
            htaccessWriteLock = htaccessLocks.get(fileName);
        }

        // Append each entry to the .htaccess file.
        synchronized (htaccessWriteLock) {
            StringBuffer fakedUrl = new StringBuffer(url);
            for (Iterator i = getProjectDownloads().iterator(); i.hasNext();) {
                ProjectDownload dl = (ProjectDownload)i.next();
                fakedUrl.append("&Download-").append(dl.getFileName())
                    .append('=').append(generateMDARealmMask(dl.isPublic()));
            }
            for (Iterator i = getProjectDocuments().iterator(); i.hasNext();) {
                ProjectDocument dc = (ProjectDocument)i.next();
                fakedUrl.append("&Document-").append(dc.getFileName())
                    .append('=').append(generateMDARealmMask(dc.isPublic()));
            }

            String prog = JoistProperties.getProperty("SANDBOX") +
                "/helm/perl/cgi/writeHtaccessFile";
            String[] cmd = { prog, fileName, fakedUrl.toString() };
            int exitValue = 0;
            try {
                Process proc = Runtime.getRuntime().exec(cmd);
                exitValue = proc.waitFor();
            }
            catch (RuntimeException e) {
                exitValue = 1;
            }
            if (exitValue != 0) {
                throw new Exception("Unable to write .htaccess file");
            }
        }
    }


    /**
     * All the .htaccess files within a specific projectID will share that
     * same realm.  The return value will be used in creation of an auth
     * ticket for file downloads.
     *
     * @@param isPublicFile Whether the download requires auth for a
     *                    <i>specific domain</i>.
     * @@return            The hexidecimal representation of the project's 
     *                    unique identifier.
     */
    public String generateMDARealmMask(boolean isPublicFile)
    {
        // If the file requested is private, set the projectID bit.
        BigInteger i = (isPublicFile ?
                        BigInteger.ZERO : 
                        BigInteger.ZERO.setBit(Integer.parseInt(getID())));
        // Return as hex (a la tohex(2 ^ downloadID)).
        return i.toString(16);
    }

    /**
     * Gets the directory to save uploads to.
     *
     * @@param baseDir The base directory to build the upload directory off of.
     * @@param user    The user to save a file for.
     * @@return        The directory to save uploads to.
     */
    public String getUploadDirectory(String baseDir, User user)
    {
        // Save to /pub-fileserver/projectID/userID/fileNameAsUploaded.
        return (baseDir + File.separatorChar + getID() + File.separatorChar +
		user.getID());
    }

}
@


1.1.2.1
log
@Classes ported to the Turbine Peer system. -dlr and leonardr
@
text
@a0 476
/* ================================================================
 * Copyright (c) 2001 CollabNet.  All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * 3. The end-user documentation included with the redistribution, if
 * any, must include the following acknowlegement: "This product includes
 * software developed by CollabNet (http://www.collab.net/)."
 * Alternately, this acknowlegement may appear in the software itself, if
 * and wherever such third-party acknowlegements normally appear.
 * 
 * 4. The hosted project names must not be used to endorse or promote
 * products derived from this software without prior written
 * permission. For written permission, please contact info@@collab.net.
 * 
 * 5. Products derived from this software may not use the "Tigris" name
 * nor may "Tigris" appear in their names without prior written
 * permission of CollabNet.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL COLLABNET OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by many
 * individuals on behalf of CollabNet.
 */

package org.tigris.helm.om;

import java.util.*;

import org.apache.turbine.om.BaseObject;
import org.apache.turbine.om.peer.BasePeer;
import org.apache.turbine.util.db.Criteria;
import org.apache.turbine.util.ObjectUtils;
import org.apache.turbine.util.StringUtils;
import org.apache.turbine.util.ParameterParser;
import org.apache.turbine.util.Log;
import org.apache.turbine.util.db.pool.DBConnection;

import org.tigris.helm.om.peer.*;
import org.tigris.helm.om.profile.ProjectProfile;

import org.tigris.helm.security.*;

import org.tigris.helm.tool.PeerTool;

import org.tigris.helm.AlreadyExists;
import org.tigris.helm.DoesNotExist;

/** 
 * Project business object.
 *
 * @@author <a href="mailto:leonardr@@collab.net">Leonard Richardson</a>
 * @@author <a href="mailto:dlr@@collab.net">Daniel Rall</a>
 */
public class Project extends BaseProject
{

    /**
     * A mutex for the addition of new projects.
     */
    private static Object addLock = new Object();

    //
    // Accessor methods for particular projects.
    //

    public String getID()
    {
        return getPrimaryKeyAsString();
    }

    public String getDomainID()
    {
        return ""+getDomainId();
    }

    public Domain getDomain()
        throws Exception
    {
        return DomainPeer.getInstance(getDomainID());
    }

    /**
     * Asserts that the specified project exists.
     *
     *
     * @@exception AlreadyExists The project exists.
     */
    public static void assertNonexistance(Domain domain, String name)
        throws AlreadyExists, Exception
    {
        try
        {
            Project p = ProjectPeer.getInstanceByName(domain, name);
        }
        catch (DoesNotExist success)
        {
            return;
        }
        throw new AlreadyExists("Project " + name, "in " + domain.getName());
    }

    /**
     * Checks whether a project has been approved. A project is approved
     * if it's "approved by" field is not set to the "nobody" user.
     *
     * @@return     Whether the project has been approved.
     *
     */
    public boolean isApproved()
        throws Exception
    {
        String approvedBy = ""+getApprovedBy();
        return (!approvedBy.equals(UserPeer.getNobodyUser().getID()));
    }   

    /**
     * Returns true if the project is that special project representing
     * all projects on the host.
     */
    public boolean isHost()
    {
        return getType().equals(ProjectPeer.HOST_PROJECT_TYPE);
    }

    public boolean isDomain()
    {
        return getType().equals(ProjectPeer.DOMAIN_PROJECT_TYPE);
    }

    public boolean isCategory()
    {
        return getType().equals(ProjectPeer.CATEGORY_PROJECT_TYPE);
    }

    public boolean isGroup()
    {
        return !(getType().equals(ProjectPeer.BASE_PROJECT_TYPE));
    }

    public boolean isBase()
    {
        return getType().equals(ProjectPeer.BASE_PROJECT_TYPE);
    }

    public boolean isProprietary()
    {
        return Licenses.isProprietary(getLicense());
    }

    //
    // Mutator methods.
    //

    /**
     * Add a new project.
     *
     * @@param wordMark The stylized name of the project.
     */
    public static Project add
        (AccessControlList acl, ProjectProfile profile, Domain domain,
         String wordMark, String license, String shortDescription, 
         String longDescription, String useIndex, String status, 
         String aliasURL, User proposedBy, User projectOwner)
        throws ACLException, AlreadyExists, Exception
    {
        if (!acl.hasPermissionForDomain("Project - Suggest", domain) &&
            !acl.hasPermissionForDomain("Project - Add", domain))
        {
            throw new ACLException(acl, "Project - Suggest or Project - Add", 
                                   "Domain " + domain.getName(), null);
        }

        Project project = null;
        synchronized (addLock)
        {
            assertNonexistance(domain, wordMark);

            // Add the project to the database.            
            project = new Project();
            project.setDomainId(domain.getPrimaryKeyAsInt());
            project.setLicense(license);
            project.setShortDescription(shortDescription);
            project.setLongDescription(longDescription);
            project.setUseIndex(useIndex);
            project.setStatus(status);
            project.setAliasUrl(aliasURL);
            project.setProposedBy(proposedBy.getPrimaryKeyAsInt());
            project.setApprovedBy(UserPeer.getNobodyUser().getPrimaryKeyAsInt());
            project.setProfile(profile.getKey());
            project.setType(profile.getType());
            project.save();
        }

        // Add the project to CVS, Issuezilla, etc.
        project.runCreationScripts(acl.getUserID());
        
        //Give someone Project Owner role on this project.
        //
        //If the ACL has Project Approve permission, make
        //the owner whomever they designate.
        //
        //Otherwise, make the ACL's user the owner.        
        //
        //Whomever we designate will own the project's mailing lists.
        User   newOwner   = null;
        String newOwnerID = null;
        if (projectOwner == null ||
            !acl.hasPermissionForDomain("Project - Approve", domain))
        {
            newOwner = acl.getUser();
        } else {
            newOwner = projectOwner;
        }

//         newOwner.grantNamedRoleFromSystem("Project Owner", this);
        
//         String name = getName(wordMark);
//         AnzuTigrisTask.addProject(dMgr, newOwner.getID(), domain, name);
//         AnzuTigrisTask.setOwnerAlias(dMgr, newOwner.getID(), getID(), false);
    
        //project.addToHostGroup();
        //project.addToDomainGroup();
        
        //Approve the project, if user has the right permission.
        if (acl.hasPermissionForDomain("Project - Approve", domain))
        {
            project.approve(acl);
        }
//             else
//             {
//                 //Send a message to the domain admin(s) informing them of
//                 //the newly requested project.
//                 User createdUser = acl.getUser();
//                 Role role = RolePeer.getInstanceByName("Domain Admin");
//                 Domain domain = new Domain(dMgr, domainID);
//                 String subject = "New unapproved project: " + wordMark;
//                 BuildContext ctx = TemplateInterpolator.createContext();
//                 ctx.put("project", this);
//                 ctx.put("createdUser", createdUser);
//                 String body = TemplateInterpolator.interpolate(CREATED_TMPL,
//                                                                ctx,
//                                                                getClass());
//                 HelmEmailer emailer = new HelmEmailer(dMgr);
//                 try 
//                 {
//                     emailer.messageUsersWithDomainRole(role, domain, subject, 
//                                                        body);
//                 }
//                 catch (JoistException e)
//                 {
//                     log.info("Unable to mail domain admins about new project.");
//                 }
//             }

//             try
//             {
//                 profile.addDefaultMailingLists
//                     (dMgr, getID(), newOwner, Licenses.isProprietary(license));
//             }
//             catch (HandlerException e)
//             {
//                 log.exception(e);
//                 if (debug) log.info("HandlerException adding default MLs: " +
//                                     e.getMessage());
//                 throw new UpdateException
//                     ("HandlerException adding default MLs: " + e.getMessage());
//             }
        return project;
    }

    /**
     * Approves the project.
     */
    public void approve(AccessControlList acl)
        throws ACLException, Exception
    {
        String action = "Project - Approve";
        if (!acl.hasPermissionForProject(action, this))
        {
            return;
        }
        acl.assertPermissionForProject(action, this);
        setApprovedBy(acl.getUser().getPrimaryKeyAsInt());
        save();
        //Add the project to the list of public projects, if it's not
        //proprietary.
        if (!isProprietary())
        {
            //addToPublicGroup();
        }
    }

    /**
     * Sends email and deletes the project.
     */
    public void disapprove(AccessControlList acl)
        throws ACLException, Exception
    {
        String action = "Project - Delete";
        if (!acl.hasPermissionForProject(action, this))
        {
            return;
        }
        acl.assertPermissionForProject(action, this);

//         //Send email to the owners.
//         User disapprovingUser = acl.getUser();
//         String subject = "Project " + getWordMark() + " disapproved.";
//         BuildContext ctx = TemplateInterpolator.createContext();
//         ctx.put("project", this);
//         ctx.put("disapprovingUser", disapprovingUser);
//         String body = TemplateInterpolator.interpolate(DISAPPROVE_TMPL, ctx,
//                                                        getClass());
//         Role role = RolePeer.getInstanceByName("Project Owner");
//         HelmEmailer emailer = new HelmEmailer();
//         try 
//         {
//             emailer.messageUsersWithProjectRole(role, this, subject, body);
//         }
//         catch (JoistException e)
//         {
//             log.info("Unable to mail project owner about project disapproval.");
//         }
        delete(acl);
    }

    //Overridden methods
    private void changeLicense (AccessControlList acl, String license)
        throws Exception
    {
        acl.assertPermissionForProject("Project - Edit", this);
        //Get the old license--see if it's proprietary.
        String oldLicense = getLicense();
        if (oldLicense != null && oldLicense.equals(license))
        {
            return;
        }

        /**
         * Approved non-proprietary projects can't go proprietary.
         */
        boolean wasProprietary = Licenses.isProprietary(oldLicense);
        boolean isProprietary = Licenses.isProprietary(license);            
        if (isApproved() && isProprietary && !wasProprietary)
        {
            return;
        }

        //Set the new license.
        super.setLicense(license);
        save();

        //If we are approved, change appropriate groups.
        if (isApproved())
        {
            String domainID = getDomainID();

            //If we used not to be proprietary and now we are, remove
            //this project from the public projects list.
            if ((!wasProprietary || oldLicense == null) && isProprietary)
            {
                //removeFromPublicGroup();
            }
            
            //If we used to be proprietary and now we're not, add this project
            //to the public projects list.
            if ((wasProprietary || oldLicense == null) && !isProprietary)
            {
                //addToPublicGroup();
            }
        }
    }

    public void delete(AccessControlList acl)
        throws Exception
    {
        if (isGroup())
        {
            acl.assertPermissionForDomain("ProjectGroup - Delete", 
                                          getDomain());
        }
        else
        {
            acl.assertPermissionForProject("Project - Delete", this);
        }

        // TODO: Delete this project's version controlled artifacts.
        
        // delete the mailing lists
        //Iterator mailingLists = getMailingLists().iterator();
        //while (mailingLists.hasNext()) {
        //    MailingList ml = (MailingList) mailingLists.next();
        //    try {
        //        ml.delete();
        //    } catch (HandlerException e) {
        //        log.exception(e);
        //       if (debug) log.info("Unable to delete mailing list " +
        //                            ml.getName() + " in project " +
        //                            getWordMark() + ": " + e);
        //    }
        //}

        // delete the directory in the anzu project as well
        //AnzuTigrisTask.deleteProject(dMgr, acl.getUserID(),
        //                             getDomainID(), getName());


        //List groups = getGroups();
        //for (Iterator i = groups.iterator(); i.hasNext();)
        //{
        //    ProjectGroup p = (ProjectGroup)i.next();
        //    if (debug) log.info("Removing project " + getWordMark() +
        //                        " from group " + p.getWordMark());
        //    p.removeProjectNoPerms(this);
        //}

        //Remove all roles in the project.
        UserRoleProjectPeer.doDelete
            (new Criteria().add(UserRoleProjectPeer.PROJECT_ID, getID()));

        //TODO: Remove all other references to it. (Downloads etc.)

        //TODO: Delete this project's download directory.

        //Delete the project itself, along with its CVS repository
        //and whatnot.
        //TigrisUpdate.deleteProject (dMgr, acl.getUserID(), this);
        ProjectPeer.doDelete(new Criteria()
            .add(ProjectPeer.PROJECT_ID,getID()));
    }

    /**
     * Helper method to run any scripts needed for the creation of the
     * project. It's public because the Tigris initialization task
     * needs to run it.
     */
    public void runCreationScripts(String initiatingUser)
        throws Exception
    {
//         for (Iterator i = expand().iterator(); i.hasNext();)
//         {
//             Project p = (Project)i.next();
//             TigrisUpdate.addProject(dMgr, initiatingUser, p);
//             if (p.getProfile().isAutoPublishedToWeb())
//             {
//                 TigrisUpdate.addModule(dMgr, null, p, "www",
//                                        "Web publishing module", "1");
//             }
//         }
    }



}
@


1.1.2.2
log
@* Over ride getName() to return the name in lower case only.
* Added getWordMark() to return the name in mixed case.
@
text
@a473 8
    /**
     * Returns the name of the project in case which it is saved to the
     * database (i.e. it is possible that mixed case text may be returned).
     */
    public final String getWordMark()
    {
        return super.getName();
    }
d475 1
a475 7
    /**
     * Returns the name of the project in lower case.
     */
    public final String getName()
    {
        return super.getName().toLowerCase();
    }
@


1.1.2.3
log
@Added note about the proper way to handle project addition locking.
@
text
@a80 2
     * <p>
     * TODO: Handle this via database table locking.
d95 1
a95 1
        return "" + getDomainId();
@


1.1.2.4
log
@Added MapBuilder classes.
@
text
@a108 2
     * @@param domain The domain to check for the project in.
     * @@param name   The name of the project to check for.
@


1.1.2.5
log
@Added transformWordMarkToName(String wordMark) macro.  Previous, this
method was getName(String wordMark).
@
text
@a85 12
    /**
     * Macro which encapsulates transformation of a word mark to a
     * canonical name.
     *
     * @@param wordMark The mixed case word mark to transform.
     * @@return         The name as lower case.
     */
    private static final String transformWordMarkToName(String wordMark)
    {
        return wordMark.toLowerCase();
    }

d243 1
a243 1
//         String name = transformWordMarkToName(wordMark);
@


1.1.2.6
log
@Implemented the ProjectType interface.
@
text
@d76 1
a76 1
public class Project extends BaseProject implements ProjectType
d78 1
d160 1
a160 1
        return getType().equals(HOST_TYPE);
d165 1
a165 1
        return getType().equals(DOMAIN_TYPE);
d170 1
a170 1
        return getType().equals(CATEGORY_TYPE);
d173 1
a173 1
    public boolean isBase()
d175 1
a175 1
        return getType().equals(REGULAR_TYPE);
d178 1
a178 1
    public boolean isGroup()
d180 1
a180 1
        return !isBase();
@


1.1.2.7
log
@Ported ProjectGroup, among other stuff.
@
text
@a116 10
    public ProjectGroup asGroup()
        throws Exception
    {
        if (isBase())
        {
            throw new Exception("Attempt to transform non-group project to ProjectGroup.");
        } 
        return ProjectPeer.getGroupInstanceByName(getDomain(), getName());
    }

d203 6
d218 1
a227 1
            project.assertAddPermission(acl);
a230 2
        project.changeLicense(acl, license);

d258 2
a259 2
        project.addToHostGroup();
        project.addToDomainGroup();
a308 23
     * Updates the project's information.
     */
    public void updateInfo(AccessControlList acl,
                           String license,
                           String shortDescription,
                           String longDescription,
                           String useIndex,
                           String status, 
                           String aliasURL
                           )
        throws ACLException, Exception
    {
        assertEditPermission(acl);
        changeLicense(acl, license);
        setShortDescription(shortDescription);
        setLongDescription(longDescription);
        setUseIndex(useIndex);
        setStatus(status);
        setAliasUrl(aliasURL);        
        save();
    }

    /**
d326 1
a326 1
            addToPublicGroup();
d336 6
a341 1
        assertDeletePermission(acl);
d368 1
a368 1
        assertEditPermission(acl);
d399 1
a399 1
                removeFromPublicGroup();
d406 1
a406 1
                addToPublicGroup();
d414 9
a422 1
        assertDeletePermission(acl);
a487 82

    //
    // Methods for dealing with this project's associations with project
    // groups.
    //

    protected void addToHostGroup()
        throws Exception
    {
        AccessControlList acl = AccessControlList.getSuperUserACL();
        ProjectPeer.getHostProject().addProject(acl, this);
    }

    protected void addToDomainGroup()
        throws Exception
    {
        AccessControlList acl = AccessControlList.getSuperUserACL();
        ProjectPeer.getDomainProject(getDomain()).addProject(acl, this);
    }
    
    protected void addToPublicGroup()
        throws Exception
    {
        AccessControlList acl = AccessControlList.getSuperUserACL();
        ProjectPeer.getPublicDomainProject(getDomain()).addProject(acl, this);
    }
    
    protected void removeFromPublicGroup()
        throws Exception
    {
        AccessControlList acl = AccessControlList.getSuperUserACL();
        ProjectPeer.getPublicDomainProject(getDomain()).removeProject(acl, 
                                                                      this);
    }

    //
    // Permission checking methods
    //

    public void assertAddPermission(AccessControlList acl)
        throws ACLException, Exception
    {
        Domain domain = getDomain();
        if (isBase())
        {
            if (!acl.hasPermissionForDomain("Project - Suggest", domain) &&
                !acl.hasPermissionForDomain("Project - Add", domain))
            {
                throw new ACLException(acl, 
                                       "Project - Suggest or Project - Add", 
                                       "Domain " + domain.getName(), null);
            }
        } else {
            acl.assertPermissionForDomain("ProjectGroup - Add", domain);
        }
    }

    public void assertEditPermission(AccessControlList acl)
        throws ACLException, Exception
    {
        if (isBase())
        {
            acl.assertPermissionForProject("Project - Edit", this);
        } else {
            acl.assertPermissionForDomain("ProjectGroup - Edit", getDomain());
        }        
    }

    public void assertDeletePermission(AccessControlList acl)
        throws ACLException, Exception
    {
        if (isBase())
        {
            acl.assertPermissionForProject("Project - Delete", this);
        } else {
            acl.assertPermissionForDomain("ProjectGroup - Delete", getDomain());
        }        
    }

    //
    // Accessor methods.
    //
@


1.1.2.8
log
@Re-added factory methods, moved in from peer.
@
text
@a97 20
    // Ways of retrieving Project objects.
    //

    public static Project getInstance(String projectID)
        throws Exception
    {
        return ProjectPeer.retrieveByPK(Integer.parseInt(projectID));
    }

    public static Project getInstanceByName(Domain domain, String name)
        throws DoesNotExist, Exception
    {
        Criteria c = new Criteria()
            .add(ProjectPeer.NAME,name)
            .add(ProjectPeer.DOMAIN_ID, domain.getID());
        List p = ProjectPeer.doSelect(c);
        return (Project)PeerTool.getFirstBO(p, "Project named " + name, domain);
    }

    //
d122 1
a122 2
            throw new Exception
                ("Attempt to transform non-group project to ProjectGroup.");
d138 3
a140 2
        try {
            Project p = getInstanceByName(domain, name);
d142 2
a143 1
        catch (DoesNotExist success) {
d146 1
a146 2
        throw new AlreadyExists("Project named " + name,
                                "domain named " + domain.getName());
d159 1
a159 1
        String approvedBy = String.valueOf(getApprovedBy());
@


1.1.2.9
log
@Use explicit String conversion...may provide better performance that
the implicit StringBuffer conversion used previously.
@
text
@d128 1
a128 1
        return String.valueOf(getDomainId());
@


1.1.2.10
log
@More porting for projects and user groups.
@
text
@a622 22

//     /**
//      * Returns an IssueTrackingAccountDescriptor describing the
//      * current state of the given user as regards the projects in
//      * which this user group has a role.
//      */
//     public IssueTrackingAccountDescriptor getDescriptorForUser(User baseUser)
//         throws SQLException, DataSetException, JoistDatabaseException,
//                DoesNotExist
//     {
//         List projectList = new ArrayList();
//         //All projects this usergroup has a role in are affected
//         DataSet projects = urpMgr.getProjectsForUser(conn, getID());
//         for (int i = 0; i < projects.size(); i++)
//         {
//             Project project = new Project(conn, dMgr, projects.getRecord(i));
//             projectList.add(project);
//         }

//         return new IssueTrackingAccountDescriptor(projectList, baseUser);
//     }

@


1.1.2.11
log
@More ACL porting.
@
text
@a623 14
    public List expand()
        throws Exception
    {
        List l = null;
        if (!isBase())
        {
            l = asGroup().expand();
        } else {
            l = new ArrayList();
            l.add(this);
        }
        return l;
    }

@


1.1.2.12
log
@We know there is only going to be one element in the list, so
pre-allocate this known amount of space.
@
text
@d632 1
a632 1
            l = new ArrayList(1);
@


1.1.2.13
log
@Moved Project factory methods back into ProjectPeer.  Ping pong.
@
text
@d98 20
d160 1
a160 1
            Project p = ProjectPeer.getInstanceByName(domain, name);
@


1.1.2.14
log
@Added a getSkin() method.
@
text
@a128 9
     * Gets the skin for this project.
     */
    public Skin getSkin()
        throws Exception
    {
        return SkinPeer.retrieveByPK(getSkinId());
    }

    /**
@


1.1.2.15
log
@Changed getPrimaryKeyAsString() to (String)getPrimaryKey().
@
text
@d103 1
a103 1
        return (String)getPrimaryKey();
@


1.1.2.16
log
@Big patch to make access control work.
@
text
@d76 1
a76 1
public class Project extends BaseProject implements ProjectType, Comparable
a116 16
    public List getGroups()
        throws Exception
    {
        List groups = new ArrayList();
        Criteria c = new Criteria()
            .add(ProjectProjectgroupPeer.PROJECT_ID, getID());
        List mappings = ProjectProjectgroupPeer.doSelect(c);
        for (Iterator i = mappings.iterator(); i.hasNext();)
        {
            ProjectProjectgroup mapping = (ProjectProjectgroup)i.next();
            ProjectGroup p = ProjectPeer.getGroupInstance(""+mapping.getProjectGroupId());
            groups.add(p);
        }
        return groups;
    }

d125 1
a125 2
        Domain d = getDomain();
        return ProjectPeer.getGroupInstance(getID());
a203 29
    }

    public int compareTo(Object o)
        throws ClassCastException
    {
        Project p = (Project)o;
        return getName().compareTo(p.getName());
    }

    public int hashCode()
    {
        return Integer.parseInt(getID());
    }

    public boolean equals(Object p)
    {
        //Log.debug("Equals on object called.");
        if (this == p) return true;
        if (p instanceof Project)
        {
            return (equals((Project)p));
        } else {
            return false;
        }
    }

    public boolean equals(Project p)
    {
        return (getID().equals(p.getID()));
@


1.1.2.17
log
@Object instantiation optimization.
@
text
@d127 1
a127 2
            ProjectGroup p = ProjectPeer.getGroupInstance
                (String.valueOf(mapping.getProjectGroupId()));
@


1.1.2.18
log
@Ported getBrand and getDomainName methods.
@
text
@a105 5
    public String getBrand()
    {
        return "";
    }

a114 6
    }

    public String getDomainName()
        throws Exception
    {
        return getDomain().getName();
@


1.1.2.19
log
@Put back usage of IssueTrackingAccountDescriptor in methods that should use it.
@
text
@d670 1
a670 1
    
d684 22
@


1.1.2.20
log
@Got rid of references to TigrisUpdate, and added a getProjectProfile method which returns a Profile object rather than the string returned by the base class' getProfile.
@
text
@d61 1
a61 1
import org.tigris.helm.om.profile.*;
a166 9
     * Gets the profile for this project.
     */
    public ProjectProfile getProjectProfile()
        throws Exception
    {
        return ProjectProfiles.getInstance().get(getProfile());
    }

    /**
d305 1
a305 1
        project.runCreationScripts(acl.getUser());
d328 2
a329 2
//         AnzuTask.addProject(dMgr, newOwner.getID(), domain, name);
//         AnzuTask.setOwnerAlias(dMgr, newOwner.getID(), getID(), false);
d547 1
a547 1
        HelmTask.deleteProject (acl.getUser(), this);
d557 1
a557 1
    public void runCreationScripts(User initiatingUser)
d560 10
a569 10
        for (Iterator i = expand().iterator(); i.hasNext();)
        {
            Project p = (Project)i.next();
            HelmTask.addProject(initiatingUser, p);
            if (p.getProjectProfile().isAutoPublishedToWeb())
            {
                HelmTask.addModule(initiatingUser, p, "www", 
                                   "Web publishing module", "1");
            }
        }
@


1.1.2.21
log
@Ported a bunch more methods.
@
text
@d153 1
a173 26

    /**
     * Returns the action which should be checked for this project before
     * performing a grant or revoke involving it.
     */
    public String requiredRoleGrantAction()
        throws Exception
    {
        String requiredAction = "Host Role - Grant";
        String type = getType();
        if (type.equals(HOST_TYPE))
        {
            //Taken care of above.
        } else if (type.equals(DOMAIN_TYPE)) {
            requiredAction = "Domain Role - Grant";
        } else {
            if (isApproved())
            {
                requiredAction = "Project Role - Grant";
            } else {
                requiredAction = "Unapproved Project Role - Grant";
            }
        }
        return requiredAction;
    }

@


1.1.2.22
log
@Made use of constant defined in associated Peer.
@
text
@d600 1
a600 2
                HelmTask.addModule(initiatingUser, p,
                                   ProjectPeer.DEFAULT_PROJECT_NAME, 
@


1.1.2.23
log
@Bunch of new ported methods.
@
text
@a684 14
    public boolean isViewableBy (AccessControlList acl)
        throws Exception
    {
        boolean ok = true;
        if (!acl.hasPermissionForProject("Project - View", this))
        {
            ok = false;
        } else if (!isApproved() && !acl.hasPermissionForDomain("Project - Approve", getDomain())) {
            ok = false;       
        }
        return ok;
    }


a717 219
    }

    //
    // Hairy URL methods
    //
    /**
     * Return the ProjectHome url.
     *
     * @@see #getProjectHome( String )
     */
    public String getProjectHome( )
        throws NullPointerException, IllegalArgumentException
    {
        return getProjectHome( ( String )null );
    }
    
    
    /**
     * Return the ProjectHome url.
     *
     * Will return either '/servlets/ProjectHome?projectID=THIS_PROJECT_ID
     * or 'http://THIS_PROJECTNAME.THIS_PROJECT_DOMAIN/servlets/ProjectHome.
     * The switch turns on the <code>env.sh</code> setting IS_VHOSTING.  (The
     * ${SANDOBX}/scripts/configure.pl writes the IS_VHOSTING value to 
     * <code>${SANDBOX}/newhelm/conf/40helm.zone.properties</code>)
     * <p>
     * You would use this method to write out links to this project's
     * ProjectHome page.  For example, say you are browsing 
     * www.wireopen.com and you come upon the ProjectList page that
     * holds a list of all projects.  Calling this method on each of the
     * projects found to write the links for ProjectList will 
     * return the appropriate ProjectHome link. 
     *
     * @@param inProtocol Pass 'http' or 'https'.  If null, we assume 'http'.
     *
     * @@return Either 'PROJECTHOME_SERVLETPATH?projectID=THIS_PROJECT_ID' or
     * 'http://THIS_PROJECTNAME.THIS_PROJECT_DOMAIN/SERVLET_PATH'
     *
     * @@exception NullPointerException If i can't get JoistProperties instance.
     * @@exception IllegalArgumentException Thrown if you pass anything but 
     * 'http' or 'http' in the inProtocol param.
     */
    public String getProjectHome( String inProtocol )
        throws NullPointerException, IllegalArgumentException
    {
        return buildProjectPageURL("ProjectHome", null, inProtocol, false, true);
    }

    public String projectMembersURL()
        throws NullPointerException
    {
        return buildProjectPageURL
            ("ProjectMembers", null, "http", false, true);
    }

    private boolean isVhosting(org.joist.util.JoistProperties properties) {
        String IS_VHOSTING_KEY = "IS_VHOSTING"; // Key for VHOSTING.
        // Test if vhosting is turned on.
        String isVhosting = properties.getProperty( IS_VHOSTING_KEY );
        if( isVhosting != null &&
                (new Boolean(isVhosting).booleanValue()) )
            return true;
        return false;
    }

    /**
     * This provides an URL (or UIR) to use for a given page in a project.
     * This version can be used with a template.
     */
    public String getProjectPageURL(String page)
        throws NullPointerException
    {
        return buildProjectPageURL(page, null, null, false, true);
    }

    /**
     * This provides an URL (or URI) to use for a given page in a project.
     * This version can be used within a template.
     */
    public String getProjectPageURL(String page, String queryArgs)
        throws NullPointerException
    {
        if (queryArgs != null && queryArgs.equals(""))
            queryArgs = null;

        return buildProjectPageURL(page, queryArgs, null, false, true);
    }

    /**
     * This provides an URL to use for a given page in a project.  This
     * verion is typically used within e-mail and in other contexts
     * where an absolute link is required.
     */
    public String getProjectPageFullURL(String page, String queryArgs,
            String inProtocol)
        throws NullPointerException
    {
        return buildProjectPageURL(page, queryArgs, inProtocol, true, true);
    }

    /**
     * This provides an URL (or URI) to use for a given page in a project.
     */
    public String getProjectPageURL(String page, String queryArgs,
            String inProtocol)
        throws NullPointerException
    {
        return buildProjectPageURL(page, queryArgs, inProtocol, false, true);
    }


    /**
     * This provides an URL (or URI) to use for a given (non-servlet) file
     *  in a project.
     */
    public String getProjectFileURL(String page)
        throws NullPointerException
    {
        return buildProjectPageURL(page, null, null, false, false);
    }

    /**
     * This provides an URL (or URI) to use for a given (non-servlet) file
     *  in a project.
     */
    public String getProjectFileURL(String page, String queryArgs)
        throws NullPointerException
    {
        return buildProjectPageURL(page, queryArgs, null, false, false);
    }

    /**
     * This provides an URL (or URI) to use for a given (non-servlet) file
     *  in a project.
     */
    public String getProjectFileURL(String page, String queryArgs,
                                    String inProtocol)
        throws NullPointerException
    {
        return buildProjectPageURL(page, queryArgs, inProtocol, false, false);
    }


    /**
     * This provides a URL to use for a given page in a project.  It uses
     * a configuration flag -- which is part of env.sh -- to determine
     * whether projects should be represented as http://$project.domain/...
     * or http://domain/...?projectID=$project.getID()
     *
     * The logic is: if forceFullyQualifiedDomain is true or if vhosting
     * (the former form) is being used, then start with
     * http://[{project}].domain.  Next, add /servlets/$page.  Finally,
     * if a queryArgs are provided or if the latter form is being used,
     * generate an appropriate query string.
     *
     * Look at the JavaDocs for getProjectHome for more an explanation
     * of one use of this function.
     *
     * @@param page The servlet to which to point
     * @@param queryArgs Any query arguments which are needed
     * @@param inProtocol The protocol being used, so far as we know
     * @@param forceFullyQualifiedDomain Whether to insist on including
     * the protocol, domain, and port number.  This is useful eg. for
     * e-mail messages, where relative URIs will not work.
     * @@return a URI/URL to send to the client
     */
    private String buildProjectPageURL
        (String page, String queryArgs, String inProtocol, 
         boolean forceFullyQualifiedDomain, boolean isServlet)
        throws NullPointerException
    {
        // JoistProperties is a singleton of system properties.
        org.joist.util.JoistProperties properties
            = org.joist.util.JoistProperties.getInstance( );
        if( properties == null )
            throw new NullPointerException( "JoistProperties is null" );
                    
        boolean isVhosting = isVhosting(properties);
        StringBuffer buff = new StringBuffer();

        String DOMAIN_KEY = "DOMAIN";
        String SERVLET_PREFIX = "/servlets/"; 

        org.joist.util.URL urlUtil = new org.joist.util.URL( );
        String protocol = urlUtil.getProtocol( inProtocol );
        String port = urlUtil.getPort( protocol );
        String domain = properties.getProperty( DOMAIN_KEY );
        if( domain == null || domain.length( ) <= 0 )
            throw new NullPointerException( DOMAIN_KEY + " is null" );

        if (isVhosting || forceFullyQualifiedDomain) {
            buff.append( protocol );
            buff.append( "://" ); 
            if (isVhosting) {
                buff.append( getName() ); // Add this project's name as host.
                buff.append( '.' ); // Dot between project name and domain
            }
            buff.append( domain );  
            if( port != null)
            {
                buff.append( ":" );
                buff.append( port );
            }
        }
        if (isServlet)
            buff.append(SERVLET_PREFIX);
        buff.append(page);
        String projectArgs = null;
        if (!isVhosting)
            projectArgs = "projectID=" + getID();

        if (projectArgs != null && queryArgs != null) {
            buff.append("?").append(projectArgs).append("&").append(queryArgs);
        } else if (projectArgs != null) {
            buff.append("?").append(projectArgs);
        } else if (queryArgs != null) {
            buff.append("?").append(queryArgs);
        }
        return buff.toString();
@


1.1.2.24
log
@Ported isValidName() method.
@
text
@a50 2
import gnu.regexp.*;

a78 6
     * The regular expression used to check project names for validity.
     */
    private static final String PROJECT_NAME_RE =
        "^[a-zA-Z0-9][-a-zA-Z0-9]{0,63}$";

    /**
a83 29

    /**
     * Checks whether a project name is valid.
     *
     * @@param name The project name to check.
     * @@return     Whether the name is valid.
     *
     * @@exception REException Malformed regular expression.
     */
    public static boolean isValidName(String name)
    {
        boolean isValid = false;

        if (name != null && name.length() > 0)
        {
            RE projectRE = null;
            try
            {
                projectRE = new RE(PROJECT_NAME_RE);
                isValid = projectRE.isMatch(name);
            }
            catch (REException e)
            {
                Log.error(e);
            }
        }

        return isValid;
    }
@


1.1.2.25
log
@New support for properly filtered paginated lists.
@
text
@a71 2
import org.tigris.helm.tool.paginate.PeerBackedList;

d211 24
a335 32
    // Methods to get related objects.
    //

    public PeerBackedList getUnapprovedNews
        (Integer itemsPerPage, Criteria filter, AccessControlList acl)
        throws Exception
    {
        List toRemove = new ArrayList();
        Criteria base = new Criteria()
            .add(ProjectNewsPeer.APPROVED_BY, UserPeer.getNobodyUser().getID());
        PeerBackedList p = new PeerBackedList
            (ProjectNewsPeer.class, itemsPerPage, base, filter);
        for (Iterator i = p.iterator(); i.hasNext();)
        {
            ProjectNews n = (ProjectNews)i.next();
            if (!acl.hasPermissionForProject 
                (n.requiredViewPermission(), this))
            {
                toRemove.add(n);
            }           
        }
        p.removeAll(toRemove);
        return p;
    }

    public PeerBackedList getUnapprovedNews(AccessControlList acl)
        throws Exception
    {
        return getUnapprovedNews(new Integer(0), null, acl);
    }

    //
a681 25
    /**
     * Returns the action which should be checked for this project before
     * performing a grant or revoke involving it.
     */
    public String requiredRoleGrantAction()
        throws Exception
    {
        String requiredAction = "Host Role - Grant";
        String type = getType();
        if (type.equals(HOST_TYPE))
        {
            //Taken care of above.
        } else if (type.equals(DOMAIN_TYPE)) {
            requiredAction = "Domain Role - Grant";
        } else {
            if (isApproved())
            {
                requiredAction = "Project Role - Grant";
            } else {
                requiredAction = "Unapproved Project Role - Grant";
            }
        }
        return requiredAction;
    }

a732 11
    }

    public int getNumberOfRequestedRoles()
        throws Exception
    {
        Criteria c = new Criteria()
            .add(UserRoleProjectPeer.PROJECT_ID, getProjectId())
            .add(UserRoleProjectPeer.APPROVED_BY, 
                 UserPeer.getNobodyUser().getUserId());
        List mappings = UserRoleProjectPeer.doSelect(c);
        return mappings.size();
@


1.1.2.26
log
@Added code that uses TemplateInterpolator and HelmEmailer.
@
text
@a52 2
import org.webmacro.engine.BuildContext;

d67 2
a71 1
import org.tigris.helm.tool.*;
a88 12
     * The name of the template used to create the body of the project 
     * creation announcement email.
     */
    private static final String CREATED_TMPL = "ProjectCreated.wm";

    /**
     * The name of the template used to create the body of the project 
     * disapproval email.
     */
    private static final String DISAPPROVE_TMPL = "ProjectDisapprove.wm";

    /**
d420 25
a444 23
            else
            {
                //Send a message to the domain admin(s) informing them of
                //the newly requested project.
                User createdUser = acl.getUser();
                String subject = "New unapproved project: " + wordMark;
                BuildContext ctx = TemplateInterpolator.createContext();
                ctx.put("project", project);
                ctx.put("createdUser", createdUser);
                String body = TemplateInterpolator.interpolate
                    (CREATED_TMPL, ctx, project.getClass());
                HelmEmailer emailer = new HelmEmailer();
                try 
                {
                    emailer.messageUsersWithDomainOwnership
                        (domain, subject, body);
                }
                catch (Exception e)
                {
                    Log.error
                        ("Unable to mail domain owners about new project.", e);
                }
            }
@


1.1.2.27
log
@Added approve(User approving) method.  Needs ACL checking.
@
text
@a249 17
     * Approves the project in the name of the given userID.
     *
     * @@param user The user approving the project.
     * @@return  The system-assigned project ID for the project.
     *
     * @@exception DoesNotExist If the specified project is invalid.
     * @@exception Exception    If a database error occurs.
     */
    public void approve(User approving)
        throws DoesNotExist, Exception
    {
        // TODO: Check ACL?
        setApprovedBy(approving.getUserId());
        save();
    }

    /**
@


1.1.2.28
log
@Added ACL checking to approve(User).
@
text
@d259 1
a259 1
        throws ACLException, DoesNotExist, Exception
d261 3
a263 6
        if (approving != null) {
            approving.getACL().assertPermissionForDomain
                ("Project - Approve", approving.getDomain());
            setApprovedBy(approving.getUserId());
            save();
        }
@


1.1.2.29
log
@Added addNews(ProjectNews) stub for elicia.
@
text
@a566 8
    /**
     * TODO
     */
    public void addNews(ProjectNews news)
    {
        // TODO
    }

@


1.1.2.30
log
@Removed stub.
@
text
@d567 8
d576 1
a576 1
    private void changeLicense(AccessControlList acl, String license)
@


1.1.2.31
log
@Corrected bug in approve() pointed out by leonardr.  We want to check
the project's domain (rather than the user's).
@
text
@d263 1
a263 1
                ("Project - Approve", getDomain());
@


1.1.2.32
log
@* Added JavaDoc.
* Made use of new constant.
@
text
@d383 1
a383 1
     * Adds a project.
d385 1
a385 15
     * @@param acl               The ACL of the user.
     * @@param profile           The type of project.
     * @@param domain            Domain in which the project resides.
     * @@param name              Name of the project.
     * @@param license           License of the project.
     * @@param shortDescription  A short description of the project.
     * @@param longDescription   A long description of the project.
     * @@param useIndex          Whether to use the index file contents for the
     *                          long description.
     * @@param aliasURL          URL to use as alias, if any.
     * @@param proposedBy        User who proposed the project.
     * @@param approvedBy        User who approved the project, if any.
     * @@return                  The database record for the project.
     *
     * @@exception AlreadyExists A project in this domain with this name exists.
d400 1
a412 2

            // Add the project to the database.            
d672 1
a672 1
        for (Iterator i = expand().iterator(); i.hasNext(); )
d680 1
a680 1
                                   ProjectPeer.DEFAULT_PROJECT_DESCRIP, "1");
@


1.1.2.33
log
@Ported more methods.
@
text
@d250 20
d356 1
a356 1
            .add(NewsItemPeer.APPROVED_BY, UserPeer.getNobodyUser().getID());
d358 1
a358 1
            (NewsItemPeer.class, itemsPerPage, base, filter);
d361 1
a361 1
            NewsItem n = (NewsItem)i.next();
d532 1
a532 7
     * Approves the project in the name of the approving user..
     *
     * @@param acl The ACL of the user approving the project.
     *
     * @@exception ACLException The ACL does not have approval permission.
     * @@exception DoesNotExist If the specified project is invalid.
     * @@exception Exception    If a database error occurs.
d535 1
a535 1
        throws ACLException, DoesNotExist, Exception
d537 13
a549 8
        if (acl != null) {
            acl.assertPermissionForDomain("Project - Approve", getDomain());
                                          
            setApprovedBy(acl.getUser().getUserId());
            save();
            if (!isProprietary()) {
                addToPublicGroup();
            }
a580 51

//     /**
//      * Adds the project to a category.
//      */
//     public void addToCategory(Category cat)
//         throws DoesNotExist
//     {
//         cat.addProject(this);
//     }

//     /**
//      * Removes the project from a category.
//      */
//     public void removeFromCategory(Category cat)
//         throws DoesNotExist
//     {
//         cat.removeProject(this);
//     }

    /**
     * Adds this project to the specified group.
     */
    public void addToGroup(AccessControlList acl, ProjectGroup g)
        throws ACLException, DoesNotExist, Exception
    {
        g.addProject(acl, this);
    }

    /**
     * Removes this project from the specified group.
     */
    public void removeFromGroup(AccessControlList acl, ProjectGroup g)
        throws ACLException, DoesNotExist, Exception
    {
        g.removeProject(acl, this);
    }

//     /**
//      * Changes the project category--removes from all old categories
//      * and adds to the new one.
//      */
//     public void changeCategory(Category newCat)
//         throws UpdateException, DoesNotExist
//     {
//         for (Iterator i = getCategories().iterator(); i.hasNext();)
//         {
//             removeFromCategory((Category)i.next());
//         }
//         //Add the project to the new category.
//         addToCategory(newCat);
//     }
@


1.1.2.34
log
@Ported message body generation for disapprove().
@
text
@d538 1
a538 1
        throws ACLException, DoesNotExist, Exception
d542 12
a553 12
        //Send email to the owners.
        User disapproving = acl.getUser();
        String subject = "Project " + getWordMark() + " disapproved.";
        BuildContext ctx = TemplateInterpolator.createContext();
        ctx.put("project", this);
        ctx.put("disapprovingUser", disapproving);
        String body = TemplateInterpolator.interpolate(DISAPPROVE_TMPL, ctx,
                                                       getClass());
        Role role = RolePeer.getInstanceByName("Project Owner");
        HelmEmailer emailer = new HelmEmailer();
            // TODO: FIXME
//         try {
d556 3
a558 2
//         catch (Exception e) {
//             Log.warn("Unable to mail project owner about project disapproval", e);
a660 3
    /**
     * Deletes this project.
     */
a794 5
    /**
     * Asserts addition and/or approval permission for this project.
     *
     * @@param acl The ACL to check for permission.
     */
d816 2
a817 1
        if (isBase()) {
d819 1
a819 2
        }
        else {
a823 5
    /**
     * Asserts deletion and/or disapproval permission for this project.
     *
     * @@param acl The ACL to check for permission.
     */
d827 2
a828 1
        if (isBase()) {
d830 1
a830 2
        }
        else {
d835 1
a835 1
    public boolean isViewableBy(AccessControlList acl)
d839 2
a840 1
        if (!acl.hasPermissionForProject("Project - View", this)) {
d842 1
a842 2
        }
        else if (!isApproved() && !acl.hasPermissionForDomain("Project - Approve", getDomain())) {
@


1.1.2.35
log
@Ported mailing list section of delete(AccessControlList).
@
text
@d664 1
a664 1
        throws ACLException, Exception
d670 13
a682 6
        // Delete the mailing lists.
        Iterator mailingLists = getMailingLists().iterator();
        while (mailingLists.hasNext()) {
            MailingList ml = (MailingList) mailingLists.next();
            ml.delete(acl);
        }
@


1.1.2.36
log
@Another couple lines of delete() ported.
@
text
@d681 9
a689 9
        List groups = getGroups();
        for (Iterator i = groups.iterator(); i.hasNext();)
        {
            ProjectGroup p = (ProjectGroup)i.next();
            Log.debug("Removing project " + getWordMark() + " from group " +
                      p.getWordMark());
            // TODO: FIXME
//            p.removeProjectNoPerms(this);
        }
d701 1
a701 1
        HelmTask.deleteProject(acl.getUser(), this);
@


1.1.2.37
log
@Cosmetic changes.
@
text
@d497 2
a498 1
                           String aliasURL)
d625 3
a627 1
        // Approved non-proprietary projects can't go proprietary.
@


1.1.2.38
log
@Changed code to no longer use deprecated methods.
@
text
@d342 1
a342 1
            if (!acl.hasPermission 
d427 1
a427 1
            !acl.hasPermission("Project - Approve", domain))
d444 1
a444 1
        if (acl.hasPermission("Project - Approve", domain))
d523 1
a523 1
            acl.assertPermission("Project - Approve", getDomain());
d798 2
a799 2
            if (!acl.hasPermission("Project - Suggest", domain) &&
                !acl.hasPermission("Project - Add", domain))
d806 1
a806 1
            acl.assertPermission("ProjectGroup - Add", domain);
d814 1
a814 1
            acl.assertPermission("Project - Edit", this);
d817 1
a817 1
            acl.assertPermission("ProjectGroup - Edit", getDomain());
d830 1
a830 1
            acl.assertPermission("Project - Delete", this);
d833 1
a833 1
            acl.assertPermission("ProjectGroup - Delete", getDomain());
d841 1
a841 1
        if (!acl.hasPermission("Project - View", this)) {
d844 1
a844 1
        else if (!isApproved() && !acl.hasPermission("Project - Approve", getDomain())) {
@


1.1.2.39
log
@Ported some more methods and added some JavaDoc.
@
text
@d73 1
a73 1
import org.tigris.helm.tool.paginate.*;
a152 3
    /**
     * Returns the ID of this project as a string.
     */
a157 3
    /**
     * Returns the brand of this project--not used yet.
     */
a162 3
    /**
     * Returns the ID of this project's domain as a string.
     */
a167 3
    /**
     * Returns this project's domain.
     */
a173 4
    /**
     * Returns the name of this project's domain. dlr would like to see this
     * method deprecated.
     */
a179 4
    /**
     * Returns a list of all the project groups (not counting categories or
     * special project groups).
     */
d183 1
a183 19
        return getGroupsOfType(Project.GROUP_TYPE);
    }

    /**
     * Returns a list of all the categories to which this project belongs.
     */
    public List getCategories()
        throws Exception
    {
        return getGroupsOfType(Project.CATEGORY_TYPE);
    }

    /**
     * Returns a list of all the project groups which are of the specified
     * type and to which this project belongs.
     */
    private List getGroupsOfType(String type)
        throws Exception
    {
a184 2
            .addJoin(ProjectProjectgroupPeer.PROJECT_ID, 
                     ProjectPeer.PROJECT_ID)
d186 2
a187 1
        if (type != null)
d189 4
a192 1
            c.add(ProjectPeer.TYPE, type);
d194 1
a194 1
        return ProjectPeer.doSelect(c);
a196 3
    /**
     * Turns this project into a group.
     */
a225 13
    /**
     * Returns all users who have an ownership role in this project.
     */
    public List getOwners()
        throws Exception
    {
        Criteria c = new Criteria()
            .addJoin(UserPeer.USER_ID, UserRoleProjectPeer.USER_ID)
            .addJoin(RolePeer.ROLE_ID, UserRoleProjectPeer.ROLE_ID)
            .add(UserRoleProjectPeer.PROJECT_ID, getProjectId())
            .add(RolePeer.DENOTES_OWNER, true);
        return UserPeer.doSelect(c);
    }
a226 25
    /**
     * Returns a paginatable list of news items for this project.
     */
    public PeerBackedList getNews(Integer itemsPerPage, Criteria filter, 
                                  AccessControlList acl)
        throws Exception
    {
        Criteria base = new Criteria()
            .add(NewsItemPeer.APPROVED_BY, 
                 UserPeer.getNobodyUser().getUserId(), Criteria.NOT_EQUAL)
            .add(NewsItemPeer.PROJECT_ID, getProjectId());
        PeerBackedList newsItems = new PeerBackedList
            (NewsItemPeer.class, itemsPerPage, base, filter);
        List toRemove = new ArrayList();
        for (Iterator i = newsItems.iterator(); i.hasNext();)
        {
            NewsItem n = (NewsItem)i.next();
            if (!n.isViewableBy(acl))
            {
                toRemove.add(n);
            }
        }
        newsItems.removeAll(toRemove);
        return newsItems;
    }
@


1.1.2.40
log
@Fixing compile errors.
@
text
@d346 1
a346 1
        return (this instanceof HostProjectGroup);
d351 1
a351 1
        return (this instanceof DomainProjectGroup);
d356 1
a356 1
        return (this instanceof Category);
d361 1
a361 1
        return !isGroup();
d366 1
a366 1
        return (this instanceof ProjectGroup);
d847 2
a848 1
        if (isHost())
d851 1
a851 1
        } else if (isDomain()) {
@


1.1.2.41
log
@added generate MDARealmMask and getUploadDirectory methods
@
text
@a49 2
import java.io.File;
import java.math.BigInteger;
a1112 33
    /**
     * All the .htaccess files within a specific projectID will share that
     * same realm.  The return value will be used in creation of an auth
     * ticket for file downloads.
     *
     * @@param isPublicFile Whether the download requires auth for a
     *                    <i>specific domain</i>.
     * @@return            The hexidecimal representation of the project's 
     *                    unique identifier.
     */
    public String generateMDARealmMask (boolean isPublicFile)
    {
        // If the file requested is private, set the projectID bit.
        BigInteger i = (isPublicFile ?
                        BigInteger.ZERO : 
                        BigInteger.ZERO.setBit(Integer.parseInt(getID())));
        // Return as hex (a la tohex(2 ^ downloadID)).
        return i.toString(16);
    }

    /**
     * Gets the directory to save uploads to.
     *
     * @@param baseDir The base directory to build the upload directory off of.
     * @@param user    The user to save a file for.
     * @@return        The directory to save uploads to.
     */
    public String getUploadDirectory (String baseDir, User user)
    {
        // Save to /pub-fileserver/projectID/userID/fileNameAsUploaded.
        return (baseDir + File.separatorChar + getID() + File.separatorChar +
		user.getID());
    }
@


1.1.2.42
log
@Added methods for getting lists of users with roles in the project.
@
text
@d273 2
a274 28
        Criteria c = new Criteria(4).add(RolePeer.DENOTES_OWNER, true);
        return getUsers(new Integer(0), c, null);
    }

    /**
     * Returns all users who have any role in this project.
     */
    public PeerBackedList getUsers(Integer itemsPerPage, Criteria filter)
        throws Exception
    {
        return getUsers(itemsPerPage, null, filter);
    }

    /**
     * Returns a paginatable list of users who have a role in this
     * project. Appends the appropriate restrictions on a base
     * criterion.  
     */
    public PeerBackedList
        getUsers(Integer itemsPerPage, Criteria base, Criteria filter)
        throws Exception
    {
        if (base == null)
        {
            base = new Criteria(3);
        }

        base.addJoin(UserPeer.USER_ID, UserRoleProjectPeer.USER_ID)
d276 3
a278 3
            .add(UserRoleProjectPeer.PROJECT_ID, getProjectId());
        
        return new PeerBackedList(UserPeer.class, itemsPerPage, base, filter);
@


1.1.2.43
log
@added permission methods
@
text
@d448 1
a448 1
                (n.requiredViewPermission(acl), this))
@


1.1.2.44
log
@Added writeHtaccessFile() method and supporting cast of dubious hacks.
@
text
@a50 2
import java.io.FileWriter;
import java.io.IOException;
a51 1
import java.net.InetAddress;
a65 3
import org.joist.util.JoistProperties;
import org.joist.util.URL;

a85 7
     * The query string parameter name of a servlet display message.
     * <p>
     * HACK: This is totally hokey.  Get rid of it.
     */
    public static final String MESSAGE = "msg";

    /**
a110 6
     * A hash of mutex objects for handling file write locking to .htaccess 
     * files, keyed by file name.
     */
    private static HashMap htaccessLocks = new HashMap();

    /**
d1054 1
a1054 1
    private boolean isVhosting(JoistProperties properties) {
a1141 93
     * Writes the .htaccess file in the right directory for this project.
     *
     * @@param path The full directory path to where to write a .htaccess file.
     * @@param url  The URL for auth and timeout (when <code>null</code>,
     *             defaults to <code>http://[JVM host]/</code>).
     */
    protected void writeHtaccessFile(String path, String url)
        throws IOException, Exception
    {
        String fileName = path + File.separatorChar + ".htaccess";
        if (url == null) {
            url = URL.DEFAULT_PROTOCOL + "://" +
                InetAddress.getLocalHost().getHostName() + '/';
        }

        // If not in vhosting mode, append projectID as cgi param
        JoistProperties properties = JoistProperties.getInstance();
        if (properties == null) {
            throw new NullPointerException( "JoistProperties is null" );
        }
        if (!isVhosting(properties)) {
            url = URL.appendCGIParam(url, "projectID", getID());
        }

        // Get a mutex for the given .htaccess file.
        Object htaccessWriteLock = null;
        synchronized (htaccessLocks) {
            if (!htaccessLocks.containsKey(fileName)) {
                htaccessLocks.put(fileName, new Object());
            }
            htaccessWriteLock = htaccessLocks.get(fileName);
        }

        // Append each entry to the .htaccess file.
        synchronized (htaccessWriteLock) {
            List downloads = getProjectDownloads();
            FileWriter out = new FileWriter(fileName);
            out.write("# DO NOT EDIT: Generated by Tigris on " + 
                      new java.util.Date() + "\n\n");
            int nbrDownloads = downloads.size();
            for (int i = 0; i < nbrDownloads; i++) {
                ProjectDownload d = (ProjectDownload)downloads.get(i);

                out.write("<Files \"");
                out.write(d.getFileName());
                out.write("\">\n");
                out.write("AuthName MDAAuth\n");
                out.write("AuthType Basic\n");
                out.write("require valid-user\n");
                out.write("MDARealmMask      \"");
                out.write(generateMDARealmMask(d.isPublic()));
                out.write("\"\n");
                out.write("MDAAuthLoginURL   \"");
                out.write(url);
                out.write("\"\n");
                out.write("MDAAuthTimeoutURL \"");
                out.write(URL.appendCGIParam
                          (url, MESSAGE, "Download authorization timed out."));
                out.write("\"\n");
                out.write("MDAAuthTimeoutMin 1\n");
                out.write("</Files>\n");
                if (i < nbrDownloads - 1) out.write('\n');
            }
            List documents = getProjectDocuments();
            int nbrDocuments = documents.size();
            for (int i = 0; i < nbrDocuments; i++) {
                ProjectDocument dc = (ProjectDocument)documents.get(i);

                out.write("<Files \"");
                out.write(dc.getName());
                out.write("\">\n");
                out.write("AuthName MDAAuth\n");
                out.write("AuthType Basic\n");
                out.write("require valid-user\n");
                out.write("MDARealmMask      \"");
                out.write(generateMDARealmMask(dc.isPublic()));
                out.write("\"\n");
                out.write("MDAAuthLoginURL   \"");
                out.write(url);
                out.write("\"\n");
                out.write("MDAAuthTimeoutURL \"");
                out.write(URL.appendCGIParam
                          (url, MESSAGE, "Document authorization timed out."));
                out.write("\"\n");
                out.write("MDAAuthTimeoutMin 1\n");
                out.write("</Files>\n");
                if (i < nbrDocuments - 1) out.write('\n');
            }
            out.close();
        }
    }

    /**
d1151 1
a1151 1
    public String generateMDARealmMask(boolean isPublicFile)
d1168 1
a1168 1
    public String getUploadDirectory(String baseDir, User user)
@


1.1.2.45
log
@Added setName() to Project's add() method.
@
text
@a519 1
            project.setName(wordMark);
d526 1
a526 1
            project.setApprovedBy(UserPeer.getNobodyUser().getUserId());
@


1.1.2.46
log
@Adding support for project group manipulation servlets.
@
text
@d260 1
a260 1
        if (getType().equals(Project.REGULAR_TYPE))
d263 1
a263 1
                ("Attempt to transform non-group project " + getName() + " to ProjectGroup.");
a525 1
            project.setIsProprietary(Licenses.isProprietary(license)?"Y":"N");
@


1.1.2.47
log
@Oops, wrong field.
@
text
@d246 1
a246 1
            .add(ProjectPeer.PROJECT_ID, getProjectId());
@


1.1.2.48
log
@Made use of DBFlag instead of string literals.
@
text
@d526 1
a526 2
            project.setIsProprietary(DBFlag.flagValueOf
                                     (Licenses.isProprietary(license)));
@


1.1.2.49
log
@Okay, *this* is what we want.
@
text
@d218 2
a219 2
     * Returns a list of all the project groups, including categories and
     * special project groups.
d224 1
a224 1
        return getGroupsOfType(null);
d244 1
a244 1
            .addJoin(ProjectProjectgroupPeer.PROJECT_GROUP_ID, 
d246 1
a246 1
            .add(ProjectProjectgroupPeer.PROJECT_ID, getProjectId());
@


1.1.2.50
log
@Big wide-ranging patch being committed all at once due to network downness. Features of this patch include: lots of added and updated JavaDoc, conversion of emails to use TemplateInterpolator and HelmEmailer, rearrangement of methods, changing code that gets mappings and loops over them to code that just gets the appropriate objects from the peer using AddJoin.
@
text
@a169 23
    /**
     * Asserts that a project with the specified name does not already
     * exist in the specified domain.
     *
     * @@param domain The domain to check for the project in.
     * @@param name   The name of the project to check for.
     *
     * @@exception AlreadyExists The project exists.  
     */
    public static void assertNonexistance(Domain domain, String name)
        throws AlreadyExists, Exception
    {
        try {
            Project p = ProjectPeer.getInstanceByName(domain, name);
        }
        catch (DoesNotExist success) {
            return;
        }
        throw new AlreadyExists("Project named " + name,
                                "domain named " + domain.getName());
    }


a174 17
     * Returns the name of the project in case which it is saved to the
     * database (i.e. it is possible that mixed case text may be returned).
     */
    public final String getWordMark()
    {
        return super.getName();
    }

    /**
     * Returns the name of the project in lower case.
     */
    public final String getName()
    {
        return super.getName().toLowerCase();
    }
    
    /**
a207 18
     * Gets the skin for this project.
     */
    public Skin getSkin()
        throws Exception
    {
        return SkinPeer.retrieveByPK(getSkinId());
    }

    /**
     * Gets the profile for this project.
     */
    public ProjectProfile getProjectProfile()
        throws Exception
    {
        return ProjectProfiles.getInstance().get(getProfile());
    }

    /**
d255 1
a255 2
     * Turns a Project representation of a project representing a group
     * into a ProjectGroup representation of the project.
d269 1
a269 3
     * Returns all the projects equivalent to this one. For a regular project,
     * returns a list containing itself. For a group project, returns a list
     * containing all the projects in the group.
d271 1
a271 1
    public List expand()
d274 1
a274 3
        List l = new ArrayList(1);
        l.add(this);
        return l;
d278 1
a278 5
     * Checks whether a project has been approved. A project is approved
     * if it's "approved by" field is not set to the "nobody" user.
     *
     * @@return     Whether the project has been approved.
     *
d280 1
a280 1
    public boolean isApproved()
d283 1
a283 28
        String approvedBy = String.valueOf(getApprovedBy());
        return (!approvedBy.equals(UserPeer.getNobodyUser().getID()));
    }   

    /**
     * Returns true iff the project is that special project representing
     * all projects on the host.
     */
    public boolean isHost()
    {
        return (this instanceof HostProjectGroup);
    }

    /**
     * Returns true iff the project is a special project representing
     * the projects in a domain.
     */
    public boolean isDomain()
    {
        return (this instanceof DomainProjectGroup);
    }

    /**
     * Returns true iff the project is a special category project group.
     */
    public boolean isCategory()
    {
        return (this instanceof Category);
a286 38
     * Returns true iff the project represents a project group of some kind.
     */
    public boolean isGroup()
    {
        return (this instanceof ProjectGroup);
    }

    /**
     * Returns true iff the project represents a base project.
     */
    public boolean isBase()
    {
        return !isGroup();
    }

    /**
     * Returns true iff the project has a proprietary license.
     */
    public boolean isProprietary()
    {
        return Licenses.isProprietary(getLicense());
    }

    /**
     * Compares this project to another object for sorting purposes.
     */
    public int compareTo(Object o)
        throws ClassCastException
    {
        Project p = (Project)o;
        return getName().compareTo(p.getName());
    }

    //
    // Methods to get related objects.
    //

    /**
d352 34
d388 2
a389 2
     * Get a paginated list of unapproved news items for this project
     * which are viewable by the specified ACL.  
d391 63
d476 1
a476 6

    /**
     * Get a big list of all unapproved news items for this project which
     * are viewable by the specified ACL.  
     */
    public List getUnapprovedNews(AccessControlList acl)
d561 1
a561 2
        // TODO: How to do this without hard-coding a role name?
        //newOwner.grantNamedRoleFromSystem("Project Owner", this);
d661 1
a661 2
     * Deletes an unapproved project, sending email to the owners
     * about it.  
d666 2
a667 1
        delete(acl);
d676 1
d678 8
a685 16
         try {
             emailer.messageUsersWithProjectOwnership(this, subject, body);
         }
         catch (Exception e) {
             Log.error
                 ("Unable to mail project owner about project disapproval", e);
         }
    }
    
    /**
     * Adds the project to a category.
     */
    public void addToCategory(Category cat)
        throws Exception
    {
        cat.addProject(this);
d688 17
a704 8
    /**
     * Removes the project from a category.
     */
    public void removeFromCategory(Category cat)
        throws Exception
    {
        cat.removeProject(this);
    }
d710 1
a710 1
        throws Exception
d719 1
a719 1
        throws Exception
d724 14
a737 14
    /**
     * Changes the project category--removes from all old categories
     * and adds to the new one.
     */
    public void changeCategory(Category newCat)
        throws Exception
    {
        for (Iterator i = getCategories().iterator(); i.hasNext();)
        {
            removeFromCategory((Category)i.next());
        }
        //Add the project to the new category.
        addToCategory(newCat);
    }
a739 4

    /**
     * Change this project's license.
     */
a855 3
    /**
     * Adds a project to the list of projects on the host.
     */
a862 3
    /**
     * Adds a project to the list of projects in the domain.
     */
a869 3
    /**
     * Adds a project to the list of public projects in the domain.
     */
a876 3
    /**
     * Removes a project from the list of public projects in the domain.
     */
d881 2
a882 2
        ProjectPeer.getPublicDomainProject(getDomain())
            .removeProject(acl, this);
a935 5
    /**
     * Asserts edit permission for this project.
     *
     * @@param acl The ACL to check for permission.
     */
a962 6
    /**
     * Returns true iff this project can be viewed by the permissions
     * of the given ACL.
     *
     * @@param acl The ACL to check for permission.
     */
a975 4
    /**
     * Returns the number of outstanding role requests for this
     * project.  
     */
d985 36
@


1.1.2.51
log
@Looks like we need equals() methods after all.
@
text
@a522 27
    // FIXME: Equality methods. I didn't think these would be
    // neccessary anymore because BaseObject supposedly implements
    // them, but when I took them out things stopped working.
    //
    public int hashCode()
    {
        return getProjectId();
    }

    public boolean equals(Object p)
    {
        //Log.debug("Equals on object called.");
        if (this == p) return true;
        if (p instanceof Project)
        {
            return (equals((Project)p));
        } else {
            return false;
        }
    }

    public boolean equals(Project p)
    {
        return (getID().equals(p.getID()));
    }    

    //
@


1.1.2.52
log
@added getSections and getDocuments methods
@
text
@a363 23
     * Returns top level sections associated with this project.
     */
    public List getSections()
        throws Exception
    {        
        Criteria c = new Criteria()
            .add(ProjectSectionPeer.PROJECT_ID, getID())
            .add(ProjectSectionPeer.PARENT_ID, 0);
        return ProjectSectionPeer.doSelect(c);
    }

    /**
     * Returns all documents associated with this project.
     */
    public List getDocuments()
        throws Exception
    {        
        Criteria c = new Criteria()
            .add(ProjectDocumentPeer.PROJECT_ID, getID());
        return ProjectDocumentPeer.doSelect(c);
    }

    /**
@


1.1.2.53
log
@Ported RebuildIssueTracking servlet.
@
text
@a81 1
import org.tigris.helm.tool.task.IssueTrackingAccountDescriptor;
a941 35
    }

    /**
     * Clear the Issuezilla accounts and rebuild them according to
     * what's in the database.
     */
    public void rebuildIssueTracking(AccessControlList acl)
        throws ACLException, Exception
    {
        acl.assertPermission("Project Role - Grant", this);

        List projects = new ArrayList();
        projects.addAll(expand());
        Host host = new Host();

        List users = null;
        if (isHost())
        {
            users = host.getAllSiteUsers();
        } else {
            users = getDomain().getUsers();
            users.addAll(host.getUsers());
        }

        //First, disable everybody in each project.
        for (Iterator i = projects.iterator(); i.hasNext();)
        {
            Project p = (Project)i.next();
            HelmTask.disableAllAccounts(acl.getUser(), p);
        }
        IssueTrackingAccountDescriptor before = 
            new IssueTrackingAccountDescriptor();
        IssueTrackingAccountDescriptor after = 
            new IssueTrackingAccountDescriptor(projects,users);
        before.syncTo(after);
@


1.1.2.54
log
@Ported useIndex method.
@
text
@a266 10
     * Returns true iff this project is supposed to be using the
     * project home page instead of its description.
     */
    public boolean useIndex()
        throws Exception
    {
        return DBFlag.booleanValueOf(getUseIndex());
    }

    /**
@


1.1.2.55
log
@Normalized copyright dates.
@
text
@d2 1
a2 1
 * Copyright (c) 2000-2001 CollabNet.  All rights reserved.
@


1.1.2.56
log
@Removed equals() methods duplicated in Turbine's BaseObject class.
@
text
@d566 17
@


1.1.2.57
log
@Added new imports necessitated by Torque.
@
text
@a72 1
import org.tigris.helm.om.base.*;
@


1.1.2.58
log
@Added audit logging in a bunch of places.
@
text
@a100 18
     * Description of the audit log entry created when a project of this
     * type is added.  
     */
    private static final AUDIT_LOG_ON_ADD = "Project added: ";

    /**
     * Description of the audit log entry created when a project of this
     * type is deleted.
     */
    private static final AUDIT_LOG_ON_DELETE = "Project deleted: ";

    /**
     * Description of the audit log entry created when a project of this
     * type is edited.
     */
    private static final AUDIT_LOG_ON_EDIT = "Project edited: ";

    /**
a697 2
        AuditLog.logEvent(acl.getUser(), project, 
                          project.AUDIT_LOG_ON_CREATE + project.getName());
a720 2
        AuditLog.logEvent(acl.getUser(), project, 
                          project.AUDIT_LOG_ON_EDIT + project.getName());
a909 2
        
        String message = project.AUDIT_LOG_ON_DELETE + project.getName();
a915 1
        AuditLog.logEvent(acl.getUser(), project, message);
@


1.1.2.59
log
@Fixed compile errors introduced in audit logging patch.
@
text
@a82 1
import org.tigris.helm.tool.audit.AuditLog;
d104 1
a104 1
    private static final String AUDIT_LOG_ON_ADD = "Project added: ";
d110 1
a110 1
    private static final String AUDIT_LOG_ON_DELETE = "Project deleted: ";
d116 1
a116 1
    private static final String AUDIT_LOG_ON_EDIT = "Project edited: ";
d717 1
a717 1
                          project.AUDIT_LOG_ON_ADD + project.getName());
d741 2
a742 2
        AuditLog.logEvent(acl.getUser(), this, 
                          this.AUDIT_LOG_ON_EDIT + getName());
d933 1
a933 1
        String message = this.AUDIT_LOG_ON_DELETE + getName();
d940 1
a940 1
        AuditLog.logEvent(acl.getUser(), this, message);
@


1.1.2.60
log
@added getTopLevelDocuments method
@
text
@a417 22
     * Returns top level documents associated with this project.
     * Top level documents are those that are not associated with a section.
     */
    public List getTopLevelDocuments()
        throws Exception
    {        
        ArrayList topLevelDocs = new ArrayList();
        Criteria c = new Criteria()
            .add(ProjectDocumentPeer.PROJECT_ID, getID());
        Vector allDocuments =  ProjectDocumentPeer.doSelect(c);
        for (int i=0; i < allDocuments.size(); i++)
        {
            ProjectDocument dc = (ProjectDocument)allDocuments.elementAt(i);
            if (dc.getSection() == null)
            {
                topLevelDocs.add(dc);
            }
        }
        return topLevelDocs;
    }

    /**
@


1.1.2.61
log
@Prefer the list interface methods over specific concretions.
@
text
@d424 1
a424 1
        List topLevelDocs = new ArrayList();
d428 1
a428 1
        for (Iterator i = allDocuments.iterator(); i.hasNext(); )
d430 1
a430 1
            ProjectDocument dc = (ProjectDocument)i.next();
@


1.1.2.62
log
@Ported code by Michael Salmon <ms@@collab.net> to use a Perl script
which performs Fcntl locking to when generating the .htaccess file.
@
text
@d1361 27
a1387 10
            StringBuffer fakedUrl = new StringBuffer(url);
            for (Iterator i = getProjectDownloads().iterator(); i.hasNext();) {
                ProjectDownload dl = (ProjectDownload)i.next();
                fakedUrl.append("&Download-").append(dl.getFileName())
                    .append('=').append(generateMDARealmMask(dl.isPublic()));
            }
            for (Iterator i = getProjectDocuments().iterator(); i.hasNext();) {
                ProjectDocument dc = (ProjectDocument)i.next();
                fakedUrl.append("&Document-").append(dc.getFileName())
                    .append('=').append(generateMDARealmMask(dc.isPublic()));
d1389 4
d1394 19
a1412 13
            String prog = JoistProperties.getProperty("SANDBOX") +
                "/helm/perl/scripts/writeHtaccessFile";
            String[] cmd = { prog, fileName, fakedUrl.toString() };
            int exitValue = 0;
            try {
                Process proc = Runtime.getRuntime().exec(cmd);
                exitValue = proc.waitFor();
            }
            catch (RuntimeException e) {
                exitValue = 1;
            }
            if (exitValue != 0) {
                throw new Exception("Unable to write .htaccess file");
d1414 1
@


1.1.2.63
log
@added getUnapprovedSections and getUnapprovedDocuments methods
@
text
@a406 13
     * Returns unapproved sections associated with this project.
     */
    public List getUnapprovedSections()
        throws Exception
    {        
        Criteria c = new Criteria()
            .add(ProjectSectionPeer.PROJECT_ID, getID())
            .add(ProjectSectionPeer.APPROVED_BY, 
                 UserPeer.getNobodyUser().getUserId());
        return ProjectSectionPeer.doSelect(c);
    }

    /**
a413 15
        return ProjectDocumentPeer.doSelect(c);
    }

    /**
     * Returns list of unapproved documents associated with the project.
     */
    public List getUnapprovedDocuments()
        throws Exception
    {
            Log.debug("hi");
            Log.debug("hi:" +  UserPeer.getNobodyUser().getUserId());
        Criteria c = new Criteria()
            .add(ProjectDocumentPeer.PROJECT_ID, getID())
            .add(ProjectDocumentPeer.APPROVED_BY, 
                 UserPeer.getNobodyUser().getUserId());
@


1.1.2.64
log
@The writeHtaccess() method no longer throws a IOException.
@
text
@d52 1
d1361 1
a1361 1
        throws Exception
@


1.1.2.65
log
@Use a reference to the implemented interface.
@
text
@d147 1
a147 1
    private static Map htaccessLocks = new HashMap();
@


1.1.2.66
log
@Ported changes from head.
@
text
@a1214 1

d1238 1
a1238 1
     * www.DOMAIN and you come upon the ProjectList page that
d1265 1
a1265 1
    private boolean isVhosting(org.joist.util.JoistProperties properties) {
a1351 79

    /**
     * This provides a URL to use for a given page in a project.  It uses
     * a configuration flag -- which is part of env.sh -- to determine
     * whether projects should be represented as http://$project.domain/...
     * or http://domain/...?projectID=$project.getID()
     *
     * The logic is: if forceFullyQualifiedDomain is true or if vhosting
     * (the former form) is being used, then start with
     * http://[{project}].domain.  Next, add /servlets/$page.  Finally,
     * if a queryArgs are provided or if the latter form is being used,
     * generate an appropriate query string.
     *
     * Look at the JavaDocs for getProjectHome for more an explanation
     * of one use of this function.
     *
     * @@param page The servlet to which to point
     * @@param queryArgs Any query arguments which are needed
     * @@param inProtocol The protocol being used, so far as we know
     * @@param forceFullyQualifiedDomain Whether to insist on including
     * the protocol, domain, and port number.  This is useful eg. for
     * e-mail messages, where relative URIs will not work.
     * @@return a URI/URL to send to the client
     */
    private String buildProjectPageURL
        (String page, String queryArgs, String inProtocol, 
         boolean forceFullyQualifiedDomain, boolean isServlet)
        throws NullPointerException
    {
        // JoistProperties is a singleton of system properties.
        org.joist.util.JoistProperties properties
            = org.joist.util.JoistProperties.getInstance( );
        if( properties == null )
            throw new NullPointerException( "JoistProperties is null" );
                    
        boolean isVhosting = isVhosting(properties);
        StringBuffer buff = new StringBuffer();

        String DOMAIN_KEY = "DOMAIN";
        String SERVLET_PREFIX = "/servlets/"; 

        org.joist.util.URL urlUtil = new org.joist.util.URL( );
        String protocol = urlUtil.getProtocol( inProtocol );
        String port = urlUtil.getPort( protocol );
        String domain = properties.getProperty( DOMAIN_KEY );
        if( domain == null || domain.length( ) <= 0 )
            throw new NullPointerException( DOMAIN_KEY + " is null" );

        if (isVhosting || forceFullyQualifiedDomain) {
            buff.append( protocol );
            buff.append( "://" ); 
            if (isVhosting) {
                buff.append( getName() ); // Add this project's name as host.
                buff.append( '.' ); // Dot between project name and domain
            }
            buff.append( domain );  
            if( port != null)
            {
                buff.append( ":" );
                buff.append( port );
            }
        }
        if (isServlet)
            buff.append(SERVLET_PREFIX);
        buff.append(page);
        String projectArgs = null;
        if (!isVhosting)
            projectArgs = "projectID=" + getID();

        if (projectArgs != null && queryArgs != null) {
            buff.append("?").append(projectArgs).append("&").append(queryArgs);
        } else if (projectArgs != null) {
            buff.append("?").append(projectArgs);
        } else if (queryArgs != null) {
            buff.append("?").append(queryArgs);
        }
        return buff.toString();
    }

a1416 1

d1451 77
@


1.1.2.67
log
@changed document.getSection to document.getSectionID
@
text
@d435 2
d458 1
a458 1
            if (dc.getSectionID() == 0)
@


1.1.2.68
log
@changed script path
@
text
@d1479 1
a1479 1
                "/helm/perl/cgi/writeHtaccessFile";
@


1.1.2.69
log
@Fixes for taskrunner.
@
text
@d1002 2
a1003 1
            if (!p.isDomain() && !p.isHost())
d1005 3
a1007 7
                HelmTask.addProject(initiatingUser, p);
                if (p.getProjectProfile().isAutoPublishedToWeb())
                {
                    HelmTask.addModule(initiatingUser, p,
                                       ProjectPeer.DEFAULT_PROJECT_NAME, 
                                       ProjectPeer.DEFAULT_PROJECT_DESCRIP, "1");
                }
@


1.1.2.70
log
@Add Anzu tasks during project addition.
@
text
@d705 2
a706 1
            !acl.hasPermission("Project - Approve", domain)) {
d708 1
a708 2
        }
        else {
d715 3
a717 3
        String name = transformWordMarkToName(wordMark);
        AnzuTask.addProject(newOwner, domain, project);
        AnzuTask.setOwnerAlias(newOwner, project, false);
d723 2
a724 1
        if (acl.hasPermission("Project - Approve", domain)) {
d727 1
a727 17
        else {
            //Send a message to the domain admin(s) informing them of
            //the newly requested project.
            User createdUser = acl.getUser();
            String subject = "New unapproved project: " + wordMark;
            BuildContext ctx = TemplateInterpolator.createContext();
            ctx.put("project", project);
            ctx.put("createdUser", createdUser);
            String body = TemplateInterpolator.interpolate
                (CREATED_TMPL, ctx, project.getClass());
            HelmEmailer emailer = new HelmEmailer();
            try 
            {
                emailer.messageUsersWithDomainOwnership
                    (domain, subject, body);
            }
            catch (Exception e)
d729 20
a748 2
                Log.error
                    ("Unable to mail domain owners about new project.", e);
a749 1
        }
@


1.1.2.71
log
@Added a getUnapprovedDownloads() method.
@
text
@a442 13
     * Returns list of unapproved downloads associated with the project.
     */
    public List getUnapprovedDownloads()
        throws Exception
    {
        Criteria c = new Criteria()
            .add(ProjectDownloadPeer.PROJECT_ID, getID())
            .add(ProjectDownloadPeer.APPROVED_BY, 
                 UserPeer.getNobodyUser().getUserId());
        return ProjectDownloadPeer.doSelect(c);
    }

    /**
@


1.1.2.72
log
@Prefer more generic interface over concretion.
@
text
@d465 1
a465 1
        List allDocuments =  ProjectDocumentPeer.doSelect(c);
@


1.1.2.73
log
@Added getRequestedUsers method.
@
text
@d543 1
a543 1
     * Returns all users who have any approved role in this project.
d548 1
a548 17
        Criteria base = new Criteria()
            .add(UserRoleProjectPeer.APPROVED_BY, 
                 (Object)UserPeer.getNobodyUser().getID(), 
                 Criteria.NOT_EQUAL);
        return getUsers(itemsPerPage, base, filter);
    }

    /**
     * Returns all users who have any unapproved role in this project.
     */
    public PeerBackedList getRequestedUsers
        (Integer itemsPerPage, Criteria filter)
        throws Exception
    {
        Criteria base = new Criteria()
            .add(UserRoleProjectPeer.APPROVED_BY, UserPeer.getNobodyUser().getID());
        return getUsers(itemsPerPage, base, filter);
@


