0001: /***************************************************************
0002: * This file is part of the [fleXive](R) project.
0003: *
0004: * Copyright (c) 1999-2008
0005: * UCS - unique computing solutions gmbh (http://www.ucs.at)
0006: * All rights reserved
0007: *
0008: * The [fleXive](R) project is free software; you can redistribute
0009: * it and/or modify it under the terms of the GNU General Public
0010: * License as published by the Free Software Foundation;
0011: * either version 2 of the License, or (at your option) any
0012: * later version.
0013: *
0014: * The GNU General Public License can be found at
0015: * http://www.gnu.org/copyleft/gpl.html.
0016: * A copy is found in the textfile GPL.txt and important notices to the
0017: * license from the author are found in LICENSE.txt distributed with
0018: * these libraries.
0019: *
0020: * This library is distributed in the hope that it will be useful,
0021: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0022: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0023: * GNU General Public License for more details.
0024: *
0025: * For further information about UCS - unique computing solutions gmbh,
0026: * please see the company website: http://www.ucs.at
0027: *
0028: * For further information about [fleXive](R), please see the
0029: * project website: http://www.flexive.org
0030: *
0031: *
0032: * This copyright notice MUST APPEAR in all copies of the file!
0033: ***************************************************************/package com.flexive.ejb.beans.structure;
0034:
0035: import com.flexive.core.Database;
0036: import static com.flexive.core.DatabaseConst.*;
0037: import com.flexive.core.storage.ContentStorage;
0038: import com.flexive.core.storage.StorageManager;
0039: import com.flexive.core.structure.StructureLoader;
0040: import com.flexive.shared.CacheAdmin;
0041: import com.flexive.shared.FxContext;
0042: import com.flexive.shared.FxLanguage;
0043: import com.flexive.shared.XPathElement;
0044: import com.flexive.shared.cache.FxCacheException;
0045: import com.flexive.shared.content.FxPermissionUtils;
0046: import com.flexive.shared.exceptions.*;
0047: import com.flexive.shared.interfaces.*;
0048: import com.flexive.shared.security.Role;
0049: import com.flexive.shared.security.UserTicket;
0050: import com.flexive.shared.structure.*;
0051: import com.flexive.shared.value.FxString;
0052: import org.apache.commons.logging.Log;
0053: import org.apache.commons.logging.LogFactory;
0054:
0055: import javax.annotation.Resource;
0056: import javax.ejb.*;
0057: import java.sql.*;
0058: import java.util.ArrayList;
0059: import java.util.List;
0060:
0061: /**
0062: * Structure Assignment management
0063: * <p/>
0064: * TODO's:
0065: * -property/group removal
0066: * -check if modification/creation even possible in case instances exist
0067: * -implement all known changeable flags
0068: *
0069: * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
0070: */
0071: @Stateless(name="AssignmentEngine")
0072: @TransactionAttribute(TransactionAttributeType.SUPPORTS)
0073: @TransactionManagement(TransactionManagementType.CONTAINER)
0074: public class AssignmentEngineBean implements AssignmentEngine,
0075: AssignmentEngineLocal {
0076:
0077: private static transient Log LOG = LogFactory
0078: .getLog(AssignmentEngineBean.class);
0079:
0080: @Resource
0081: javax.ejb.SessionContext ctx;
0082:
0083: @EJB
0084: SequencerEngineLocal seq;
0085:
0086: @EJB
0087: HistoryTrackerEngineLocal htracker;
0088:
0089: /**
0090: * {@inheritDoc}
0091: */
0092: @TransactionAttribute(TransactionAttributeType.REQUIRED)
0093: public long createProperty(FxPropertyEdit property,
0094: String parentXPath) throws FxApplicationException {
0095: return createProperty(FxType.ROOT_ID, property, parentXPath);
0096: }
0097:
0098: /**
0099: * {@inheritDoc}
0100: */
0101: @TransactionAttribute(TransactionAttributeType.REQUIRED)
0102: public long createProperty(long typeId, FxPropertyEdit property,
0103: String parentXPath) throws FxApplicationException {
0104: return createProperty(typeId, property, parentXPath, property
0105: .getName());
0106: }
0107:
0108: /**
0109: * {@inheritDoc}
0110: */
0111: @TransactionAttribute(TransactionAttributeType.REQUIRED)
0112: public long createProperty(long typeId, FxPropertyEdit property,
0113: String parentXPath, String assignmentAlias)
0114: throws FxApplicationException {
0115: FxPermissionUtils.checkRole(FxContext.get().getTicket(),
0116: Role.StructureManagement);
0117: Connection con = null;
0118: PreparedStatement ps = null;
0119: StringBuilder sql = new StringBuilder(2000);
0120: long newPropertyId;
0121: long newAssignmentId;
0122: try {
0123: parentXPath = parentXPath.toUpperCase();
0124: assignmentAlias = assignmentAlias.toUpperCase();
0125: FxType type = CacheAdmin.getEnvironment().getType(typeId);
0126: FxAssignment tmp = type.getAssignment(parentXPath);
0127: if (tmp != null && tmp instanceof FxPropertyAssignment)
0128: throw new FxInvalidParameterException(
0129: "ex.structure.assignment.noGroup", parentXPath);
0130: property.checkConsistency();
0131: //parentXPath is valid, create the property, then assign it to root
0132: newPropertyId = seq.getId(SequencerEngine.System.TYPEPROP);
0133: con = Database.getDbConnection();
0134: //create property, no checks for existing names are performed as this is handled with unique keys
0135: sql.append("INSERT INTO ").append(TBL_STRUCT_PROPERTIES).
0136: // 1 2 3 4 5 6 7
0137: append(
0138: "(ID,NAME,DEFMINMULT,DEFMAXMULT,MAYOVERRIDEMULT,DATATYPE,REFTYPE,"
0139: +
0140: //8 9 10 11 12
0141: "ISFULLTEXTINDEXED,ACL,MAYOVERRIDEACL,REFLIST,UNIQUEMODE,"
0142: + "SYSINTERNAL)VALUES("
0143: + "?,?,?,?,?,"
0144: + "?,?,?,?,?,?,?,FALSE)");
0145: ps = con.prepareStatement(sql.toString());
0146: ps.setLong(1, newPropertyId);
0147: ps.setString(2, property.getName());
0148: ps.setInt(3, property.getMultiplicity().getMin());
0149: ps.setInt(4, property.getMultiplicity().getMax());
0150: ps.setBoolean(5, property.mayOverrideBaseMultiplicity());
0151: ps.setLong(6, property.getDataType().getId());
0152: if (property.hasReferencedType())
0153: ps.setLong(7, property.getReferencedType().getId());
0154: else
0155: ps.setNull(7, java.sql.Types.NUMERIC);
0156: ps.setBoolean(8, property.isFulltextIndexed());
0157: ps.setLong(9, property.getACL().getId());
0158: ps.setBoolean(10, property.mayOverrideACL());
0159: if (property.hasReferencedList())
0160: ps.setLong(11, property.getReferencedList().getId());
0161: else
0162: ps.setNull(11, java.sql.Types.NUMERIC);
0163: ps.setInt(12, property.getUniqueMode().getId());
0164: if (!property.isAutoUniquePropertyName())
0165: ps.executeUpdate();
0166: else {
0167: int counter = 0;
0168: boolean isok = false;
0169: while (!isok && counter < 200) { //200 tries max
0170: try {
0171: if (counter > 0) {
0172: ps.setString(2, property.getName() + "_"
0173: + counter);
0174: }
0175: ps.executeUpdate();
0176: isok = true;
0177: } catch (SQLException e) {
0178: if (!Database.isUniqueConstraintViolation(e)
0179: || counter >= 200)
0180: throw e;
0181: }
0182: counter++;
0183: }
0184: }
0185: Database.storeFxString(new FxString[] {
0186: property.getLabel(), property.getHint(),
0187: property.getDefaultValue() }, con,
0188: TBL_STRUCT_PROPERTIES, new String[] {
0189: "DESCRIPTION", "HINT", "DEFAULT_VALUE" },
0190: "ID", newPropertyId);
0191: ps.close();
0192: sql.setLength(0);
0193: //calc new position
0194: sql.append("SELECT COALESCE(MAX(POS)+1,0) FROM ").append(
0195: TBL_STRUCT_ASSIGNMENTS).append(
0196: " WHERE PARENTGROUP=? AND TYPEDEF=?");
0197: ps = con.prepareStatement(sql.toString());
0198: ps.setLong(1, (tmp == null ? FxAssignment.NO_PARENT : tmp
0199: .getId()));
0200: ps.setLong(2, typeId);
0201: ResultSet rs = ps.executeQuery();
0202: long pos = 0;
0203: if (rs != null && rs.next()) {
0204: if (rs.wasNull())
0205: pos = 0; //actually impossible to happen
0206: else
0207: pos = rs.getLong(1);
0208: }
0209: ps.close();
0210: storeOptions(con, TBL_PROPERTY_OPTIONS, "ID",
0211: newPropertyId, null, property.getOptions());
0212: sql.setLength(0);
0213: //create root assignment
0214: sql.append("INSERT INTO ")
0215: .append(TBL_STRUCT_ASSIGNMENTS)
0216: .
0217: // 1 2 3 4 5 6 7 8 9 10 11 12 13
0218: append(
0219: "(ID,ATYPE,ENABLED,TYPEDEF,MINMULT,MAXMULT,DEFMULT,POS,XPATH,XALIAS,BASE,PARENTGROUP,APROPERTY,"
0220: +
0221: //14
0222: "ACL)"
0223: + "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
0224: ps = con.prepareStatement(sql.toString());
0225: newAssignmentId = seq
0226: .getId(SequencerEngine.System.ASSIGNMENT);
0227: ps.setLong(1, newAssignmentId);
0228: ps.setInt(2, FxAssignment.TYPE_PROPERTY);
0229: ps.setBoolean(3, true);
0230: ps.setLong(4, typeId);
0231: ps.setInt(5, property.getMultiplicity().getMin());
0232: ps.setInt(6, property.getMultiplicity().getMax());
0233: if (property.getMultiplicity().isValid(
0234: property.getAssignmentDefaultMultiplicity())) {
0235: ps.setInt(7, property
0236: .getAssignmentDefaultMultiplicity());
0237: } else {
0238: //default is min(min,1).
0239: ps
0240: .setInt(
0241: 7,
0242: property.getMultiplicity().getMin() > 1 ? property
0243: .getMultiplicity().getMin()
0244: : 1);
0245: }
0246: ps.setLong(8, pos);
0247: if (parentXPath == null || "/".equals(parentXPath))
0248: parentXPath = "";
0249: ps.setString(9, type.getName() + parentXPath + "/"
0250: + assignmentAlias);
0251: ps.setString(10, assignmentAlias);
0252: ps.setNull(11, Types.NUMERIC);
0253: if (tmp == null)
0254: ps.setLong(12, FxAssignment.NO_PARENT);
0255: else
0256: ps.setLong(12, tmp.getId());
0257: ps.setLong(13, newPropertyId);
0258: ps.setLong(14, property.getACL().getId());
0259: ps.executeUpdate();
0260: Database.storeFxString(new FxString[] {
0261: property.getLabel(), property.getHint(),
0262: property.getDefaultValue() }, con,
0263: TBL_STRUCT_ASSIGNMENTS, new String[] {
0264: "DESCRIPTION", "HINT", "DEFAULT_VALUE" },
0265: "ID", newAssignmentId);
0266: StructureLoader.reload(con);
0267: htracker.track(type, "history.assignment.createProperty",
0268: property.getName(), type.getId(), type.getName());
0269: createInheritedAssignments(CacheAdmin.getEnvironment()
0270: .getAssignment(newAssignmentId), con, ps, sql, type
0271: .getDerivedTypes());
0272: } catch (FxNotFoundException e) {
0273: ctx.setRollbackOnly();
0274: throw new FxCreateException(e);
0275: } catch (FxLoadException e) {
0276: ctx.setRollbackOnly();
0277: throw new FxCreateException(e);
0278: } catch (SQLException e) {
0279: final boolean uniqueConstraintViolation = Database
0280: .isUniqueConstraintViolation(e);
0281: ctx.setRollbackOnly();
0282: if (uniqueConstraintViolation)
0283: throw new FxEntryExistsException(
0284: "ex.structure.property.exists", property
0285: .getName(),
0286: (parentXPath.length() == 0 ? "/" : parentXPath));
0287: throw new FxCreateException(LOG, e, "ex.db.sqlError", e
0288: .getMessage());
0289: } catch (FxCacheException e) {
0290: ctx.setRollbackOnly();
0291: throw new FxCreateException(e, "ex.cache", e.getMessage());
0292: } finally {
0293: Database.closeObjects(AssignmentEngineBean.class, con, ps);
0294: }
0295: return newAssignmentId;
0296: }
0297:
0298: /*
0299: * Updates the options of an FxGroup
0300: * Before the options are updated, they are compared against the options that are
0301: * already stored in the DB. If there are changes, all present options are deleted
0302: * in the DB and newly created afterwards from the assignment's option list.
0303: *
0304: * @return if the options changed.
0305: */
0306:
0307: private boolean updateGroupOptions(Connection con, FxGroupEdit group)
0308: throws SQLException {
0309: boolean changed = false;
0310: FxGroupEdit org = new FxGroupEdit(CacheAdmin.getEnvironment()
0311: .getGroup(group.getId()));
0312: if (org.getOptions().size() != group.getOptions().size()) {
0313: changed = true;
0314: } else {
0315: for (int i = 0; i < org.getOptions().size(); i++) {
0316: FxStructureOption orgOpt = org.getOptions().get(i);
0317: FxStructureOption propOpt = group.getOption(orgOpt
0318: .getKey());
0319: if (!orgOpt.equals(propOpt)) {
0320: changed = true;
0321: break;
0322: }
0323: }
0324: }
0325: if (changed)
0326: storeOptions(con, TBL_GROUP_OPTIONS, "ID", group.getId(),
0327: null, group.getOptions());
0328: return changed;
0329: }
0330:
0331: /*
0332: * Updates the options of an FxProperty
0333: * Before the options are updated, they are compared against the options that are
0334: * already stored in the DB. If there are changes, all present options are deleted
0335: * in the DB and newly created afterwards from the assignment's option list.
0336: *
0337: * @return if the options changed.
0338: */
0339:
0340: private boolean updatePropertyOptions(Connection con,
0341: FxPropertyEdit prop) throws SQLException {
0342: boolean changed = false;
0343: FxPropertyEdit org = new FxPropertyEdit(CacheAdmin
0344: .getEnvironment().getProperty(prop.getId()));
0345: if (org.getOptions().size() != prop.getOptions().size()) {
0346: changed = true;
0347: } else {
0348: for (int i = 0; i < org.getOptions().size(); i++) {
0349: FxStructureOption orgOpt = org.getOptions().get(i);
0350: FxStructureOption propOpt = prop.getOption(orgOpt
0351: .getKey());
0352: if (!orgOpt.equals(propOpt)) {
0353: changed = true;
0354: break;
0355: }
0356: }
0357: }
0358: if (changed)
0359: storeOptions(con, TBL_PROPERTY_OPTIONS, "ID", prop.getId(),
0360: null, prop.getOptions());
0361: return changed;
0362: }
0363:
0364: /*
0365: * Updates the options of an FxGroupAssignment
0366: * Before the options are updated, they are compared against the options that are
0367: * already stored in the DB. If there are changes, all present options are deleted
0368: * in the DB and newly created afterwards from the assignment's option list.
0369: *
0370: * @return if the options changed.
0371: */
0372:
0373: private boolean updateGroupAssignmentOptions(Connection con,
0374: FxGroupAssignment ga) throws SQLException {
0375: boolean changed = false;
0376: FxGroupAssignmentEdit org = ((FxGroupAssignment) CacheAdmin
0377: .getEnvironment().getAssignment(ga.getId()))
0378: .asEditable();
0379: FxGroupAssignmentEdit group = ga.asEditable();
0380: if (org.getOptions().size() != group.getOptions().size()) {
0381: changed = true;
0382: } else {
0383: for (int i = 0; i < org.getOptions().size(); i++) {
0384: FxStructureOption orgOpt = org.getOptions().get(i);
0385: FxStructureOption propOpt = group.getOption(orgOpt
0386: .getKey());
0387: if (!orgOpt.equals(propOpt)) {
0388: changed = true;
0389: break;
0390: }
0391: }
0392: }
0393: if (changed)
0394: storeOptions(con, TBL_GROUP_OPTIONS, "ID", group.getGroup()
0395: .getId(), group.getId(), group.getOptions());
0396: return changed;
0397: }
0398:
0399: /*
0400: * Updates the options of an FxPropertyAssignment
0401: * Before the options are updated, they are compared against the options that are
0402: * already stored in the DB. If there are changes, all present options are deleted
0403: * in the DB and newly created afterwards from the assignment's option list.
0404: *
0405: * @return if the options changed.
0406: */
0407:
0408: private boolean updatePropertyAssignmentOptions(Connection con,
0409: FxPropertyAssignment original, FxPropertyAssignment modified)
0410: throws SQLException {
0411: boolean changed = false;
0412: FxPropertyAssignmentEdit org = original.asEditable();
0413: FxPropertyAssignmentEdit prop = modified.asEditable();
0414: if (org.getOptions().size() != prop.getOptions().size()) {
0415: changed = true;
0416: } else {
0417: for (int i = 0; i < org.getOptions().size(); i++) {
0418: FxStructureOption orgOpt = org.getOptions().get(i);
0419: FxStructureOption propOpt = prop.getOption(orgOpt
0420: .getKey());
0421: if (!orgOpt.equals(propOpt)) {
0422: changed = true;
0423: break;
0424: }
0425: }
0426: }
0427: if (changed)
0428: storeOptions(con, TBL_PROPERTY_OPTIONS, "ID", original
0429: .getProperty().getId(), original.getId(), prop
0430: .getOptions());
0431: return changed;
0432: }
0433:
0434: /*
0435: * Helper to store options, (the information in brackets expalains how to use this method to store the options
0436: * for an FxPropertyAssignment)
0437: *
0438: * @param con the DB connection
0439: * @param table the table name to store the options (e.g. TBL_PROPERTY_OPTIONS)
0440: * @param primaryColumn the column name of the primary key (where the property Id is stored, e.g. ID)
0441: * @param primaryId the primary key itself (the property Id, e.g. FxPropertyAssignment.getProperty().getId())
0442: * @param assignmentId the foreign key, may be <code>null</code> (the assignment Id, e.g. FxPropertyAssignment.getId())
0443: * @param options the option list to store (e.g. FxPropertyAssignmentEdit.getOptions())
0444: */
0445: private void storeOptions(Connection con, String table,
0446: String primaryColumn, long primaryId, Long assignmentId,
0447: List<FxStructureOption> options) throws SQLException {
0448: PreparedStatement ps = null;
0449: try {
0450: if (assignmentId == null) {
0451: ps = con.prepareStatement("DELETE FROM " + table
0452: + " WHERE " + primaryColumn
0453: + "=? AND ASSID IS NULL");
0454: } else {
0455: ps = con.prepareStatement("DELETE FROM " + table
0456: + " WHERE " + primaryColumn + "=? AND ASSID=?");
0457: ps.setLong(2, assignmentId);
0458: }
0459: ps.setLong(1, primaryId);
0460: ps.executeUpdate();
0461: if (options == null || options.size() == 0)
0462: return;
0463: ps.close();
0464: ps = con
0465: .prepareStatement("INSERT INTO "
0466: + table
0467: + " ("
0468: + primaryColumn
0469: + ",ASSID,OPTKEY,MAYOVERRIDE,OPTVALUE)VALUES(?,?,?,?,?)");
0470: for (FxStructureOption option : options) {
0471: ps.setLong(1, primaryId);
0472: if (assignmentId != null)
0473: ps.setLong(2, assignmentId);
0474: else
0475: ps.setNull(2, java.sql.Types.NUMERIC);
0476: ps.setString(3, option.getKey());
0477: ps.setBoolean(4, option.isOverrideable());
0478: ps.setString(5, option.getValue());
0479: ps.addBatch();
0480: }
0481: ps.executeBatch();
0482: } finally {
0483: if (ps != null)
0484: ps.close();
0485: }
0486: }
0487:
0488: /**
0489: * Helper to process all derived types and derivates of derived types
0490: *
0491: * @param assignment the assignment processed
0492: * @param con an open and valid connection
0493: * @param ps prepared statement being worked with
0494: * @param sb StringBuilder
0495: * @param types (derived) types to process - will recurse to derived types of these types
0496: * @throws FxApplicationException on errors
0497: */
0498: private void createInheritedAssignments(FxAssignment assignment,
0499: Connection con, PreparedStatement ps, StringBuilder sb,
0500: List<FxType> types) throws FxApplicationException {
0501: for (FxType derivedType : types) {
0502: if (assignment instanceof FxPropertyAssignment) {
0503: createPropertyAssignment(
0504: con,
0505: ps,
0506: sb,
0507: FxPropertyAssignmentEdit
0508: .createNew(
0509: (FxPropertyAssignment) assignment,
0510: derivedType,
0511: assignment.getAlias(),
0512: assignment
0513: .hasParentGroupAssignment() ? assignment
0514: .getParentGroupAssignment()
0515: .getXPath()
0516: : "/"));
0517: } else if (assignment instanceof FxGroupAssignment) {
0518: createGroupAssignment(
0519: con,
0520: ps,
0521: sb,
0522: FxGroupAssignmentEdit
0523: .createNew(
0524: (FxGroupAssignment) assignment,
0525: derivedType,
0526: assignment.getAlias(),
0527: assignment
0528: .hasParentGroupAssignment() ? assignment
0529: .getParentGroupAssignment()
0530: .getXPath()
0531: : "/"), true);
0532: }
0533: // if (derivedType.getDerivedTypes().size() > 0) //one level deeper ...
0534: // _inheritedAssignmentsCreate(assignment, con, ps, sb, derivedType.getDerivedTypes());
0535: }
0536: }
0537:
0538: /**
0539: * Remove a property, all its assignments and all attributes in contents referencing this property
0540: *
0541: * @param propertyId id of the property to remove
0542: * @throws FxApplicationException on errors
0543: */
0544: @TransactionAttribute(TransactionAttributeType.REQUIRED)
0545: public void removeProperty(long propertyId)
0546: throws FxApplicationException {
0547: //TODO
0548: }
0549:
0550: private boolean updateGroup(Connection _con, PreparedStatement ps,
0551: StringBuilder sql, FxGroupEdit group)
0552: throws FxApplicationException {
0553: if (sql == null)
0554: sql = new StringBuilder(1000);
0555: Connection con = _con;
0556: boolean changes = false;
0557: StringBuilder changesDesc = new StringBuilder(200);
0558: FxGroup org = CacheAdmin.getEnvironment().getGroup(
0559: group.getId());
0560: try {
0561: if (con == null)
0562: con = Database.getDbConnection();
0563: sql.setLength(0);
0564:
0565: //TODO: check valid multiplicity like in updateproperty
0566: if (org.getMultiplicity().getMin() != group
0567: .getMultiplicity().getMin()
0568: || org.getMultiplicity().getMax() != group
0569: .getMultiplicity().getMax()) {
0570: if (ps != null)
0571: ps.close();
0572: ps = con.prepareStatement("UPDATE " + TBL_STRUCT_GROUPS
0573: + " SET DEFMINMULT=? ,DEFMAXMULT=? WHERE ID=?");
0574: ps.setInt(1, group.getMultiplicity().getMin());
0575: ps.setInt(2, group.getMultiplicity().getMax());
0576: ps.setLong(3, group.getId());
0577: ps.executeUpdate();
0578: if (changes)
0579: changesDesc.append(',');
0580: changesDesc.append("multiplicity=").append(
0581: group.getMultiplicity());
0582: changes = true;
0583: }
0584:
0585: if (org.getLabel() != null
0586: && !org.getLabel().equals(group.getLabel())
0587: || org.getLabel() == null
0588: && group.getLabel() != null
0589: || org.getHint() != null
0590: && !org.getHint().equals(group.getHint())
0591: || org.getHint() == null && group.getHint() != null) {
0592: Database.storeFxString(new FxString[] {
0593: group.getLabel(), group.getHint() }, con,
0594: TBL_STRUCT_GROUPS, new String[] {
0595: "DESCRIPTION", "HINT" }, "ID", group
0596: .getId());
0597: if (changes)
0598: changesDesc.append(',');
0599: changesDesc.append("label=").append(group.getLabel())
0600: .append(',');
0601: changesDesc.append("hint=").append(group.getHint())
0602: .append(',');
0603: changes = true;
0604: }
0605: //TODO: thorw exceptipon that this is not supported yet
0606: if (!org.getName().equals(group.getName())) {
0607: if (ps != null)
0608: ps.close();
0609: ps = con.prepareStatement("UPDATE " + TBL_STRUCT_GROUPS
0610: + " SET NAME=? WHERE ID=?");
0611: ps.setString(1, group.getName());
0612: ps.setLong(2, group.getId());
0613: ps.executeUpdate();
0614: if (changes)
0615: changesDesc.append(',');
0616: changesDesc.append("name=").append(group.getName());
0617: changes = true;
0618: }
0619: if (org.mayOverrideBaseMultiplicity() != group
0620: .mayOverrideBaseMultiplicity()) {
0621: if (ps != null)
0622: ps.close();
0623: ps = con.prepareStatement("UPDATE " + TBL_STRUCT_GROUPS
0624: + " SET MAYOVERRIDEMULT=? WHERE ID=?");
0625: ps.setBoolean(1, group.mayOverrideBaseMultiplicity());
0626: ps.setLong(2, group.getId());
0627: ps.executeUpdate();
0628: if (changes)
0629: changesDesc.append(',');
0630: changesDesc.append("mayOverrideMultiplicity=").append(
0631: group.mayOverrideBaseMultiplicity());
0632: changes = true;
0633: }
0634:
0635: if (updateGroupOptions(con, group)) {
0636: changesDesc.append(",options:");
0637: List<FxStructureOption> options = group.getOptions();
0638: for (FxStructureOption option : options) {
0639: changesDesc.append(option.getKey()).append("=")
0640: .append(option.getValue()).append(
0641: " overridable=").append(
0642: option.isOverrideable()).append(
0643: " isSet=").append(option.isSet());
0644: }
0645: changes = true;
0646: }
0647:
0648: if (changes) {
0649: //TODO: invoke htracker with changeDesc
0650: }
0651:
0652: } catch (SQLException e) {
0653: ctx.setRollbackOnly();
0654: /*TODO: Determine if this must be checked
0655: if (Database.isUniqueConstraintViolation(e))
0656: throw new FxEntryExistsException("ex.structure.assignment.property.exists", prop.getAlias(), prop.getXPath());
0657: */
0658: throw new FxCreateException(LOG, e, "ex.db.sqlError", e
0659: .getMessage());
0660: } finally {
0661: if (_con == null) {
0662: Database.closeObjects(AssignmentEngineBean.class, con,
0663: ps);
0664: }
0665: }
0666: return changes;
0667: }
0668:
0669: /**
0670: * {@inheritDoc}
0671: */
0672: @TransactionAttribute(TransactionAttributeType.REQUIRED)
0673: public long createGroup(FxGroupEdit group, String parentXPath)
0674: throws FxApplicationException {
0675: return createGroup(FxType.ROOT_ID, group, parentXPath);
0676: }
0677:
0678: /**
0679: * {@inheritDoc}
0680: */
0681: @TransactionAttribute(TransactionAttributeType.REQUIRED)
0682: public long createGroup(long typeId, FxGroupEdit group,
0683: String parentXPath) throws FxApplicationException {
0684: FxPermissionUtils.checkRole(FxContext.get().getTicket(),
0685: Role.StructureManagement);
0686: Connection con = null;
0687: PreparedStatement ps = null;
0688: StringBuilder sql = new StringBuilder(2000);
0689: long newGroupId;
0690: long newAssignmentId;
0691: try {
0692: parentXPath = parentXPath.toUpperCase();
0693: FxType type = CacheAdmin.getEnvironment().getType(typeId);
0694: FxAssignment tmp = type.getAssignment(parentXPath);
0695: if (tmp != null && tmp instanceof FxPropertyAssignment)
0696: throw new FxInvalidParameterException(
0697: "ex.structure.assignment.noGroup", parentXPath);
0698: //parentXPath is valid, create the group, then assign it to root
0699: newGroupId = seq.getId(SequencerEngine.System.TYPEGROUP);
0700: con = Database.getDbConnection();
0701: //create group
0702: sql
0703: .append("INSERT INTO ")
0704: .append(TBL_STRUCT_GROUPS)
0705: .append(
0706: "(ID,NAME,DEFMINMULT,DEFMAXMULT,MAYOVERRIDEMULT)VALUES(?,?,?,?,?)");
0707: ps = con.prepareStatement(sql.toString());
0708: ps.setLong(1, newGroupId);
0709: ps.setString(2, group.getName());
0710: ps.setInt(3, group.getMultiplicity().getMin());
0711: ps.setInt(4, group.getMultiplicity().getMax());
0712: ps.setBoolean(5, group.mayOverrideBaseMultiplicity());
0713: ps.executeUpdate();
0714: ps.close();
0715: sql.setLength(0);
0716: Database.storeFxString(new FxString[] { group.getLabel(),
0717: group.getHint() }, con, TBL_STRUCT_GROUPS,
0718: new String[] { "DESCRIPTION", "HINT" }, "ID",
0719: newGroupId);
0720: //calc new position
0721: sql.append("SELECT COALESCE(MAX(POS)+1,0) FROM ").append(
0722: TBL_STRUCT_ASSIGNMENTS).append(
0723: " WHERE PARENTGROUP=? AND TYPEDEF=?");
0724: ps = con.prepareStatement(sql.toString());
0725: ps.setLong(1, (tmp == null ? FxAssignment.NO_PARENT : tmp
0726: .getId()));
0727: ps.setLong(2, typeId);
0728: ResultSet rs = ps.executeQuery();
0729: long pos = 0;
0730: if (rs != null && rs.next()) {
0731: if (rs.wasNull())
0732: pos = 0;
0733: else
0734: pos = rs.getLong(1);
0735: }
0736: ps.close();
0737: sql.setLength(0);
0738: //create root assignment
0739: sql.append("INSERT INTO ")
0740: .append(TBL_STRUCT_ASSIGNMENTS)
0741: .
0742: // 1 2 3 4 5 6 7 8 9 10 11 12 13 14
0743: append(
0744: "(ID,ATYPE,ENABLED,TYPEDEF,MINMULT,MAXMULT,DEFMULT,POS,XPATH,XALIAS,BASE,PARENTGROUP,AGROUP,GROUPMODE)"
0745: + "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
0746: ps = con.prepareStatement(sql.toString());
0747: newAssignmentId = seq
0748: .getId(SequencerEngine.System.ASSIGNMENT);
0749: ps.setLong(1, newAssignmentId);
0750: ps.setInt(2, FxAssignment.TYPE_GROUP);
0751: ps.setBoolean(3, true);
0752: ps.setLong(4, typeId);
0753: ps.setInt(5, group.getMultiplicity().getMin());
0754: ps.setInt(6, group.getMultiplicity().getMax());
0755: if (group.getMultiplicity().isValid(
0756: group.getAssignmentDefaultMultiplicity())) {
0757: ps.setInt(7, group.getAssignmentDefaultMultiplicity());
0758: } else {
0759: //default is min(min,1).
0760: ps.setInt(7,
0761: group.getMultiplicity().getMin() > 1 ? group
0762: .getMultiplicity().getMin() : 1);
0763: }
0764: ps.setLong(8, pos);
0765: if (parentXPath == null || "/".equals(parentXPath))
0766: parentXPath = "";
0767: ps.setString(9, type.getName() + parentXPath + "/"
0768: + group.getName());
0769: ps.setString(10, group.getName());
0770: ps.setNull(11, java.sql.Types.NUMERIC);
0771: ps.setLong(12, (tmp == null ? FxAssignment.NO_PARENT : tmp
0772: .getId()));
0773: ps.setLong(13, newGroupId);
0774: ps.setInt(14, group.getAssignmentGroupMode().getId());
0775: ps.executeUpdate();
0776: Database.storeFxString(new FxString[] { group.getLabel(),
0777: group.getHint() }, con, TBL_STRUCT_ASSIGNMENTS,
0778: new String[] { "DESCRIPTION", "HINT" }, "ID",
0779: newAssignmentId);
0780: StructureLoader.reload(con);
0781: htracker.track(type, "history.assignment.createGroup",
0782: group.getName(), type.getId(), type.getName());
0783: createInheritedAssignments(CacheAdmin.getEnvironment()
0784: .getAssignment(newAssignmentId), con, ps, sql, type
0785: .getDerivedTypes());
0786: } catch (FxNotFoundException e) {
0787: ctx.setRollbackOnly();
0788: throw new FxCreateException(e);
0789: } catch (FxLoadException e) {
0790: ctx.setRollbackOnly();
0791: throw new FxCreateException(e);
0792: } catch (SQLException e) {
0793: final boolean uniqueConstraintViolation = Database
0794: .isUniqueConstraintViolation(e);
0795: ctx.setRollbackOnly();
0796: if (uniqueConstraintViolation)
0797: throw new FxEntryExistsException(
0798: "ex.structure.group.exists", group.getName(),
0799: (parentXPath.length() == 0 ? "/" : parentXPath));
0800: throw new FxCreateException(LOG, e, "ex.db.sqlError", e
0801: .getMessage());
0802: } catch (FxCacheException e) {
0803: ctx.setRollbackOnly();
0804: throw new FxCreateException(e, "ex.cache", e.getMessage());
0805: } finally {
0806: Database.closeObjects(AssignmentEngineBean.class, con, ps);
0807: }
0808: return newAssignmentId;
0809: }
0810:
0811: /**
0812: * Removes a group and all subgroups and properties assigned to it as well as all references in content instances
0813: *
0814: * @param groupId id of the group to remove
0815: * @throws FxApplicationException on errors
0816: */
0817: @TransactionAttribute(TransactionAttributeType.REQUIRED)
0818: public void removeGroup(long groupId) throws FxApplicationException {
0819: //TODO
0820: }
0821:
0822: /**
0823: * {@inheritDoc}
0824: */
0825: @TransactionAttribute(TransactionAttributeType.REQUIRED)
0826: public long save(FxAssignment assignment,
0827: boolean createSubAssignments) throws FxApplicationException {
0828: FxPermissionUtils.checkRole(FxContext.get().getTicket(),
0829: Role.StructureManagement);
0830: long returnId;
0831: boolean reload = false;
0832: if (assignment instanceof FxPropertyAssignmentEdit) {
0833: if (((FxPropertyAssignmentEdit) assignment).isNew()) {
0834: returnId = createPropertyAssignment(null, null, null,
0835: (FxPropertyAssignmentEdit) assignment);
0836: } else {
0837: returnId = assignment.getId();
0838: try {
0839: reload = updatePropertyAssignment(null, null, null,
0840: null, (FxPropertyAssignmentEdit) assignment);
0841: } catch (FxLoadException e) {
0842: ctx.setRollbackOnly();
0843: throw new FxUpdateException(e);
0844: } catch (FxNotFoundException e) {
0845: ctx.setRollbackOnly();
0846: throw new FxUpdateException(e);
0847: }
0848: }
0849:
0850: } else if (assignment instanceof FxGroupAssignmentEdit) {
0851: if (((FxGroupAssignmentEdit) assignment).isNew()) {
0852: returnId = createGroupAssignment(null, null, null,
0853: (FxGroupAssignmentEdit) assignment,
0854: createSubAssignments);
0855: } else {
0856: returnId = assignment.getId();
0857: try {
0858: reload = updateGroupAssignment(null, null, null,
0859: (FxGroupAssignmentEdit) assignment);
0860: } catch (FxLoadException e) {
0861: ctx.setRollbackOnly();
0862: throw new FxUpdateException(e);
0863: } catch (FxNotFoundException e) {
0864: ctx.setRollbackOnly();
0865: throw new FxUpdateException(e);
0866: }
0867: }
0868: } else
0869: throw new FxInvalidParameterException("ASSIGNMENT",
0870: "ex.structure.assignment.noEditAssignment");
0871: try {
0872: if (reload) {
0873: StructureLoader.reload(null);
0874: //clear instance cache
0875: CacheAdmin.expireCachedContents();
0876: }
0877: } catch (FxCacheException e) {
0878: ctx.setRollbackOnly();
0879: throw new FxCreateException(e, "ex.cache", e.getMessage());
0880: } catch (FxLoadException e) {
0881: ctx.setRollbackOnly();
0882: throw new FxCreateException(e);
0883: }
0884: return returnId;
0885: }
0886:
0887: private boolean updateGroupAssignment(Connection _con,
0888: PreparedStatement ps, StringBuilder sql,
0889: FxGroupAssignmentEdit group) throws FxApplicationException {
0890: if (sql == null)
0891: sql = new StringBuilder(1000);
0892: if (group.isNew())
0893: throw new FxInvalidParameterException(
0894: "ex.structure.assignment.update.new", group
0895: .getXPath());
0896: Connection con = _con;
0897: boolean changes = false;
0898: StringBuilder changesDesc = new StringBuilder(200);
0899: FxGroupAssignment org = (FxGroupAssignment) CacheAdmin
0900: .getEnvironment().getAssignment(group.getId());
0901: try {
0902: if (con == null)
0903: con = Database.getDbConnection();
0904: sql.setLength(0);
0905: if (org.isEnabled() != group.isEnabled()) {
0906: if (changes)
0907: changesDesc.append(',');
0908: changesDesc.append("enabled=")
0909: .append(group.isEnabled());
0910: //apply for all child groups and properties as well!
0911: if (org.getAssignments().size() > 0)
0912: changesDesc.append(", ").append(
0913: group.isEnabled() ? "en" : "dis").append(
0914: "abled child assignments: ");
0915: for (FxAssignment as : org.getAssignments()) {
0916: changesDesc.append(as.getXPath()).append(',');
0917: }
0918: if (changesDesc.charAt(changesDesc.length() - 1) == ',')
0919: changesDesc.deleteCharAt(changesDesc.length() - 1);
0920: if (!group.isEnabled())
0921: removeAssignment(org.getId(), true, false, true);
0922: else {
0923: StringBuilder affectedAssignment = new StringBuilder(
0924: 500);
0925: affectedAssignment.append(org.getId());
0926: for (FxAssignment as : org.getAllChildAssignments())
0927: affectedAssignment.append(",").append(
0928: as.getId());
0929: if (ps != null)
0930: ps.close();
0931: ps = con.prepareStatement("UPDATE "
0932: + TBL_STRUCT_ASSIGNMENTS
0933: + " SET ENABLED=? WHERE ID IN ("
0934: + affectedAssignment + ")");
0935: ps.setBoolean(1, true);
0936: ps.executeUpdate();
0937: }
0938: changes = true;
0939: }
0940:
0941: //TODO: check if multiplicity is in valid ranges
0942: if (org.getDefaultMultiplicity() != group
0943: .getDefaultMultiplicity()) {
0944: if (ps != null)
0945: ps.close();
0946: ps = con.prepareStatement("UPDATE "
0947: + TBL_STRUCT_ASSIGNMENTS
0948: + " SET DEFMULT=? WHERE ID=?");
0949: ps.setInt(1, group.getDefaultMultiplicity());
0950: ps.setLong(2, group.getId());
0951: ps.executeUpdate();
0952: if (changes)
0953: changesDesc.append(',');
0954: changesDesc.append("defaultMultiplicity=").append(
0955: group.getDefaultMultiplicity());
0956: changes = true;
0957: }
0958:
0959: //TODO: must not be changed
0960: if (org.getAssignedType().getId() != group
0961: .getAssignedType().getId()) {
0962: if (ps != null)
0963: ps.close();
0964: ps = con.prepareStatement("UPDATE "
0965: + TBL_STRUCT_ASSIGNMENTS
0966: + " SET TYPEDEF=? WHERE ID=?");
0967: ps.setLong(1, group.getAssignedType().getId());
0968: ps.setLong(2, group.getId());
0969: ps.executeUpdate();
0970: if (changes)
0971: changesDesc.append(',');
0972: changesDesc.append("assignedType=").append(
0973: group.getAssignedType());
0974: changes = true;
0975: }
0976: //siehe prop
0977: if (org.getMultiplicity().getMin() != group
0978: .getMultiplicity().getMin()
0979: || org.getMultiplicity().getMax() != group
0980: .getMultiplicity().getMax()) {
0981: if (ps != null)
0982: ps.close();
0983: ps = con.prepareStatement("UPDATE "
0984: + TBL_STRUCT_ASSIGNMENTS
0985: + " SET MINMULT=? ,MAXMULT=? WHERE ID=?");
0986: ps.setInt(1, group.getMultiplicity().getMin());
0987: ps.setInt(2, group.getMultiplicity().getMax());
0988: ps.setLong(3, group.getId());
0989: ps.executeUpdate();
0990: if (changes)
0991: changesDesc.append(',');
0992: changesDesc.append("multiplicity=").append(
0993: group.getMultiplicity());
0994: changes = true;
0995: }
0996: if (org.getPosition() != group.getPosition()) {
0997: int finalPos = setAssignmentPosition(con,
0998: group.getId(), group.getPosition());
0999: if (changes)
1000: changesDesc.append(',');
1001: changesDesc.append("position=").append(finalPos);
1002: changes = true;
1003: }
1004: if (!org.getXPath().equals(group.getXPath())
1005: || !org.getAlias().equals(group.getAlias())) {
1006: if (!XPathElement.isValidXPath(XPathElement
1007: .stripType(group.getXPath()))
1008: || group.getAlias().equals(
1009: XPathElement.lastElement(
1010: XPathElement.stripType(org
1011: .getXPath()))
1012: .getAlias()))
1013: throw new FxUpdateException(
1014: "ex.structure.assignment.noXPath");
1015: try {
1016: //avoid duplicates
1017: org.getAssignedType().getAssignment(
1018: group.getXPath());
1019: throw new FxUpdateException(
1020: "ex.structure.assignment.exists", group
1021: .getXPath(), group
1022: .getAssignedType().getName());
1023: } catch (FxNotFoundException e) {
1024: //expected
1025: }
1026: //TODO: make sure just the alias changed
1027: if (ps != null)
1028: ps.close();
1029: ps = con.prepareStatement("UPDATE "
1030: + TBL_STRUCT_ASSIGNMENTS
1031: + " SET XPATH=?, XALIAS=? WHERE ID=?");
1032: ps.setString(1, group.getXPath());
1033: ps.setString(2, group.getAlias());
1034: ps.setLong(3, group.getId());
1035: ps.executeUpdate();
1036: ContentStorage storage = StorageManager
1037: .getContentStorage(TypeStorageMode.Hierarchical);
1038: storage.updateXPath(con, group.getId(), XPathElement
1039: .stripType(org.getXPath()), XPathElement
1040: .stripType(group.getXPath()));
1041: //update all child assignments
1042: ps.close();
1043: ps = con.prepareStatement("UPDATE "
1044: + TBL_STRUCT_ASSIGNMENTS
1045: + " SET XPATH=? WHERE ID=?");
1046: for (FxAssignment child : org.getAllChildAssignments()) {
1047: ps.setString(1, group.getXPath()
1048: + child.getXPath().substring(
1049: org.getXPath().length()));
1050: ps.setLong(2, child.getId());
1051: ps.executeUpdate();
1052: storage.updateXPath(con, child.getId(),
1053: XPathElement.stripType(child.getXPath()),
1054: XPathElement.stripType(group.getXPath()
1055: + child.getXPath().substring(
1056: org.getXPath().length())));
1057: }
1058: if (changes)
1059: changesDesc.append(',');
1060: changesDesc.append("xPath=").append(group.getXPath())
1061: .append(",alias=").append(group.getAlias());
1062: changes = true;
1063: }
1064:
1065: //TODO:may not be changed, throw exception
1066: /*if (org.getBaseAssignmentId() != group.getBaseAssignmentId()) {
1067: if (ps != null) ps.close();
1068: ps = con.prepareStatement("UPDATE " + TBL_STRUCT_ASSIGNMENTS + " SET BASE=? WHERE ID=?");
1069: ps.setLong(1, group.getBaseAssignmentId());
1070: ps.setLong(2, group.getId());
1071: ps.executeUpdate();
1072: if (changes)
1073: changesDesc.append(',');
1074: changesDesc.append("baseAssignment=").append(group.getBaseAssignmentId());
1075: changes = true;
1076: }*/
1077:
1078: //TODO:may not be changed, throw exception
1079: /*if (org.getParentGroupAssignment() != null &&
1080: !org.getParentGroupAssignment().equals(group.getParentGroupAssignment()) ||
1081: group.getParentGroupAssignment() != null &&
1082: !group.getParentGroupAssignment().equals(org.getParentGroupAssignment())) {
1083: if (ps != null) ps.close();
1084: ps = con.prepareStatement("UPDATE " + TBL_STRUCT_ASSIGNMENTS + " SET PARENTGROUP=? WHERE ID=?");
1085: if (group.getParentGroupAssignment() != null)
1086: ps.setLong(1, group.getParentGroupAssignment().getId());
1087: else
1088: ps.setLong(1, FxAssignment.NO_PARENT);
1089: ps.setLong(2, group.getId());
1090: ps.executeUpdate();
1091: if (changes)
1092: changesDesc.append(',');
1093: changesDesc.append("parentGroupAssignment=").append(group.getParentGroupAssignment());
1094: changes = true;
1095: }*/
1096:
1097: if (org.getLabel() != null
1098: && !org.getLabel().equals(group.getLabel())
1099: || org.getLabel() == null
1100: && group.getLabel() != null
1101: || org.getHint() != null
1102: && !org.getHint().equals(group.getHint())
1103: || org.getHint() == null && group.getHint() != null) {
1104: Database.storeFxString(new FxString[] {
1105: group.getLabel(), group.getHint() }, con,
1106: TBL_STRUCT_ASSIGNMENTS, new String[] {
1107: "DESCRIPTION", "HINT" }, "ID", group
1108: .getId());
1109: if (changes)
1110: changesDesc.append(',');
1111: changesDesc.append("label=").append(group.getLabel())
1112: .append(',');
1113: changesDesc.append("hint=").append(group.getHint())
1114: .append(',');
1115: changes = true;
1116: }
1117:
1118: //update SystemInternal flag, this is a one way function, so it can only be set, but not reset!!
1119: if (!org.isSystemInternal() && group.isSystemInternal()
1120: && FxContext.get().getTicket().isGlobalSupervisor()) {
1121: if (ps != null)
1122: ps.close();
1123: ps = con.prepareStatement("UPDATE "
1124: + TBL_STRUCT_ASSIGNMENTS
1125: + " SET SYSINTERNAL=? WHERE ID=?");
1126: ps.setBoolean(1, group.isSystemInternal());
1127: ps.setLong(2, group.getId());
1128: ps.executeUpdate();
1129: if (changes)
1130: changesDesc.append(',');
1131: changesDesc.append("systemInternal=").append(
1132: group.isSystemInternal());
1133: changes = true;
1134: }
1135:
1136: //TODO: only make changable if no instances of this group exist, or if the mode is changed from one.of to any.of
1137: if (org.getMode().getId() != group.getMode().getId()) {
1138: if (ps != null)
1139: ps.close();
1140: ps = con.prepareStatement("UPDATE "
1141: + TBL_STRUCT_ASSIGNMENTS
1142: + " SET GROUPMODE=? WHERE ID=?");
1143: ps.setLong(1, group.getMode().getId());
1144: ps.setLong(2, group.getId());
1145: ps.executeUpdate();
1146: if (changes)
1147: changesDesc.append(',');
1148: changesDesc.append("groupMode=").append(
1149: group.getMode().getId());
1150: changes = true;
1151: }
1152:
1153: if (updateGroupAssignmentOptions(con, group)) {
1154: changesDesc.append(",options:");
1155: List<FxStructureOption> options = group.getOptions();
1156: for (FxStructureOption option : options) {
1157: changesDesc.append(option.getKey()).append("=")
1158: .append(option.getValue()).append(
1159: " overridable=").append(
1160: option.isOverrideable()).append(
1161: " isSet=").append(option.isSet());
1162: }
1163: changes = true;
1164: }
1165: //TODO: compare all possible modifications
1166: if (changes)
1167: htracker.track(group.getAssignedType(),
1168: "history.assignment.updateGroupAssignment",
1169: group.getXPath(), group.getAssignedType()
1170: .getId(), group.getAssignedType()
1171: .getName(), group.getGroup().getId(),
1172: group.getGroup().getName(), changesDesc
1173: .toString());
1174:
1175: } catch (SQLException e) {
1176: final boolean uniqueConstraintViolation = Database
1177: .isUniqueConstraintViolation(e);
1178: ctx.setRollbackOnly();
1179: if (uniqueConstraintViolation)
1180: throw new FxEntryExistsException(
1181: "ex.structure.assignment.group.exists", group
1182: .getAlias(), group.getXPath());
1183: throw new FxCreateException(LOG, e, "ex.db.sqlError", e
1184: .getMessage());
1185: } finally {
1186: if (_con == null) {
1187: Database.closeObjects(AssignmentEngineBean.class, con,
1188: ps);
1189: }
1190: }
1191: return changes;
1192: }
1193:
1194: /**
1195: * Set an assignments position, updating positions of all assignments in the same hierarchy level
1196: *
1197: * @param con an open and valid connection
1198: * @param assignmentId the id of the assignment with the desired position
1199: * @param position desired position
1200: * @return the position that "really" was assigned
1201: * @throws FxUpdateException on errors
1202: */
1203: private int setAssignmentPosition(Connection con,
1204: long assignmentId, int position) throws FxUpdateException {
1205: if (position < 0)
1206: position = 0;
1207: PreparedStatement ps = null, ps2 = null;
1208: int retPosition = position;
1209: try {
1210: ps = con
1211: .prepareStatement("SELECT TYPEDEF, PARENTGROUP, POS, SYSINTERNAL FROM "
1212: + TBL_STRUCT_ASSIGNMENTS + " WHERE ID=?");
1213: ps.setLong(1, assignmentId);
1214: ResultSet rs = ps.executeQuery();
1215: if (rs == null || !rs.next())
1216: return position; //no record exists
1217: long typeId = rs.getLong(1);
1218: long parentGroupId = rs.getLong(2);
1219: int orgPos = rs.getInt(3);
1220: boolean sysinternal = rs.getBoolean(4);
1221: if (orgPos == position)
1222: return retPosition; //no need to change anything
1223:
1224: if (!sysinternal
1225: && parentGroupId == FxAssignment.NO_PARENT
1226: && position < CacheAdmin.getEnvironment()
1227: .getSystemInternalRootPropertyAssignments()
1228: .size()) {
1229: //adjust position to be above the sysinternal properties if connected to the root group
1230: position += CacheAdmin.getEnvironment()
1231: .getSystemInternalRootPropertyAssignments()
1232: .size();
1233: }
1234:
1235: //move all positions in a range of 10000+ without gaps
1236: ps.close();
1237: ps = con
1238: .prepareStatement("SELECT ID FROM "
1239: + TBL_STRUCT_ASSIGNMENTS
1240: + " WHERE TYPEDEF=? AND PARENTGROUP=? ORDER BY POS");
1241: ps.setLong(1, typeId);
1242: ps.setLong(2, parentGroupId);
1243: rs = ps.executeQuery();
1244: ps2 = con.prepareStatement("UPDATE "
1245: + TBL_STRUCT_ASSIGNMENTS + " SET POS=? WHERE ID=?");
1246: int counter = 10000;
1247: while (rs != null && rs.next()) {
1248: ps2.setInt(1, counter++);
1249: ps2.setLong(2, rs.getLong(1));
1250: ps2.addBatch();
1251: }
1252: ps2.executeBatch();
1253:
1254: ps.close();
1255: ps = con
1256: .prepareStatement("SELECT ID FROM "
1257: + TBL_STRUCT_ASSIGNMENTS
1258: + " WHERE TYPEDEF=? AND PARENTGROUP=? AND POS>=? AND ID<>? ORDER BY POS");
1259: ps.setLong(1, typeId);
1260: ps.setLong(2, parentGroupId);
1261: ps.setInt(3, 10000);
1262: ps.setLong(4, assignmentId);
1263: rs = ps.executeQuery();
1264: int currPos = 0;
1265: boolean written = false;
1266: while (rs != null && rs.next()) {
1267: ps2.setInt(1, currPos);
1268: if (!written && currPos == position) {
1269: written = true;
1270: retPosition = currPos;
1271: ps2.setLong(2, assignmentId);
1272: ps2.addBatch();
1273: ps2.setInt(1, ++currPos);
1274: }
1275: ps2.setLong(2, rs.getLong(1));
1276: ps2.addBatch();
1277: currPos++;
1278: }
1279: if (!written) {
1280: //last element
1281: retPosition = currPos;
1282: ps2.setInt(1, currPos);
1283: ps2.setLong(2, assignmentId);
1284: ps2.addBatch();
1285: }
1286: ps2.executeBatch();
1287: } catch (SQLException e) {
1288: throw new FxUpdateException(LOG, e, "ex.db.sqlError", e
1289: .getMessage());
1290: } finally {
1291: try {
1292: if (ps != null)
1293: ps.close();
1294: } catch (SQLException e) {
1295: //ignore
1296: }
1297: try {
1298: if (ps2 != null)
1299: ps2.close();
1300: } catch (SQLException e) {
1301: //ignore
1302: }
1303: }
1304: return retPosition;
1305: }
1306:
1307: private long createGroupAssignment(Connection _con,
1308: PreparedStatement ps, StringBuilder sql,
1309: FxGroupAssignmentEdit group, boolean createSubAssignments)
1310: throws FxApplicationException {
1311: if (sql == null)
1312: sql = new StringBuilder(1000);
1313: if (!group.isNew())
1314: throw new FxInvalidParameterException(
1315: "ex.structure.assignment.create.existing", group
1316: .getXPath());
1317: Connection con = _con;
1318: long newAssignmentId;
1319: try {
1320: if (con == null)
1321: con = Database.getDbConnection();
1322: FxGroupAssignment this GroupAssignment;
1323: String XPath;
1324: if (!group.getXPath().startsWith(
1325: group.getAssignedType().getName())) {
1326: if (group.getAlias() != null)
1327: XPath = XPathElement.buildXPath(false, group
1328: .getAssignedType().getName(), group
1329: .getXPath());
1330: else
1331: XPath = "/";
1332: } else
1333: XPath = group.getXPath();
1334: if (group.getAlias() != null) {
1335: sql.setLength(0);
1336: sql.append("INSERT INTO ")
1337: .append(TBL_STRUCT_ASSIGNMENTS)
1338: .
1339: // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
1340: append(
1341: "(ID,ATYPE,ENABLED,TYPEDEF,MINMULT,MAXMULT,DEFMULT,POS,XPATH,XALIAS,BASE,PARENTGROUP,AGROUP,SYSINTERNAL,GROUPMODE)"
1342: + "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
1343: if (ps != null)
1344: ps.close();
1345: ps = con.prepareStatement(sql.toString());
1346: newAssignmentId = seq
1347: .getId(SequencerEngine.System.ASSIGNMENT);
1348: ps.setLong(1, newAssignmentId);
1349: ps.setInt(2, FxAssignment.TYPE_GROUP);
1350: ps.setBoolean(3, group.isEnabled());
1351: ps.setLong(4, group.getAssignedType().getId());
1352: ps.setInt(5, group.getMultiplicity().getMin());
1353: ps.setInt(6, group.getMultiplicity().getMax());
1354: ps.setInt(7, group.getDefaultMultiplicity());
1355: int position = getValidPosition(con, sql, group
1356: .getPosition(),
1357: group.getAssignedType().getId(), group
1358: .getParentGroupAssignment());
1359: ps.setInt(8, position);
1360: ps.setString(9, XPath);
1361: ps.setString(10, group.getAlias());
1362: ps.setLong(11, group.getBaseAssignmentId());
1363: ps
1364: .setLong(
1365: 12,
1366: group.getParentGroupAssignment() == null ? FxAssignment.NO_PARENT
1367: : group
1368: .getParentGroupAssignment()
1369: .getId());
1370: ps.setLong(13, group.getGroup().getId());
1371: ps.setBoolean(14, group.isSystemInternal());
1372: ps.setInt(15, group.getMode().getId());
1373: ps.executeUpdate();
1374: Database.storeFxString(new FxString[] {
1375: group.getLabel(), group.getHint() }, con,
1376: TBL_STRUCT_ASSIGNMENTS, new String[] {
1377: "DESCRIPTION", "HINT" }, "ID",
1378: newAssignmentId);
1379: this GroupAssignment = new FxGroupAssignment(
1380: newAssignmentId, true, group.getAssignedType(),
1381: group.getAlias(), XPath, position, group
1382: .getMultiplicity(), group
1383: .getDefaultMultiplicity(), group
1384: .getParentGroupAssignment(), group
1385: .getBaseAssignmentId(), group
1386: .getLabel(), group.getHint(), group
1387: .getGroup(), group.getMode(), null);
1388: setAssignmentPosition(con, group.getId(), group
1389: .getPosition());
1390: } else {
1391: this GroupAssignment = null;
1392: newAssignmentId = FxAssignment.NO_PARENT;
1393: }
1394: htracker.track(group.getAssignedType(),
1395: "history.assignment.createGroupAssignment", XPath,
1396: group.getAssignedType().getId(), group
1397: .getAssignedType().getName(), group
1398: .getGroup().getId(), group.getGroup()
1399: .getName());
1400: if (group.getBaseAssignmentId() != FxAssignment.ROOT_BASE
1401: && createSubAssignments) {
1402: FxGroupAssignment baseGroup = (FxGroupAssignment) CacheAdmin
1403: .getEnvironment().getAssignment(
1404: group.getBaseAssignmentId());
1405: for (FxGroupAssignment ga : baseGroup
1406: .getAssignedGroups()) {
1407: FxGroupAssignmentEdit gae = new FxGroupAssignmentEdit(
1408: ga);
1409: gae.setEnabled(group.isEnabled());
1410: createGroupAssignment(con, null, sql,
1411: FxGroupAssignmentEdit.createNew(gae, group
1412: .getAssignedType(), ga.getAlias(),
1413: XPath, this GroupAssignment),
1414: createSubAssignments);
1415: }
1416: for (FxPropertyAssignment pa : baseGroup
1417: .getAssignedProperties()) {
1418: FxPropertyAssignmentEdit pae = new FxPropertyAssignmentEdit(
1419: pa);
1420: pae.setEnabled(group.isEnabled());
1421: createPropertyAssignment(con, null, sql,
1422: FxPropertyAssignmentEdit.createNew(pae,
1423: group.getAssignedType(), pa
1424: .getAlias(), XPath,
1425: this GroupAssignment));
1426: }
1427: }
1428: try {
1429: StructureLoader.reload(con);
1430: } catch (FxCacheException e) {
1431: ctx.setRollbackOnly();
1432: throw new FxCreateException(e, "ex.cache", e
1433: .getMessage());
1434: }
1435: createInheritedAssignments(CacheAdmin.getEnvironment()
1436: .getAssignment(newAssignmentId), con, ps, sql,
1437: group.getAssignedType().getDerivedTypes());
1438: } catch (SQLException e) {
1439: final boolean uniqueConstraintViolation = Database
1440: .isUniqueConstraintViolation(e);
1441: ctx.setRollbackOnly();
1442: if (uniqueConstraintViolation)
1443: throw new FxEntryExistsException(
1444: "ex.structure.assignment.group.exists", group
1445: .getAlias(), group.getXPath());
1446: throw new FxCreateException(LOG, e, "ex.db.sqlError", e
1447: .getMessage());
1448: } catch (FxNotFoundException e) {
1449: throw new FxCreateException(e);
1450: } finally {
1451: if (_con == null) {
1452: Database.closeObjects(AssignmentEngineBean.class, con,
1453: ps);
1454: }
1455: }
1456: return newAssignmentId;
1457: }
1458:
1459: private boolean updateProperty(Connection _con,
1460: PreparedStatement ps, StringBuilder sql, FxPropertyEdit prop)
1461: throws FxApplicationException {
1462: if (sql == null)
1463: sql = new StringBuilder(1000);
1464: if (prop.isNew())
1465: throw new FxInvalidParameterException(
1466: "ex.structure.property.update.new", prop.getName());
1467: Connection con = _con;
1468: boolean changes = false;
1469: StringBuilder changesDesc = new StringBuilder(200);
1470: FxProperty org = CacheAdmin.getEnvironment().getProperty(
1471: prop.getId());
1472:
1473: try {
1474: if (con == null)
1475: con = Database.getDbConnection();
1476: sql.setLength(0);
1477:
1478: if (!org.isSystemInternal()
1479: || FxContext.get().getTicket().isGlobalSupervisor()) {
1480:
1481: if (org.mayOverrideBaseMultiplicity() != prop
1482: .mayOverrideBaseMultiplicity()) {
1483: if (!prop.mayOverrideBaseMultiplicity()) {
1484: if (getInstanceMultiplicity(con, org.getId(),
1485: true) < prop.getMultiplicity().getMin())
1486: throw new FxUpdateException(
1487: "ex.structure.modification.contentExists",
1488: "minimumMultiplicity");
1489: if (getInstanceMultiplicity(con, org.getId(),
1490: false) > prop.getMultiplicity()
1491: .getMax())
1492: throw new FxUpdateException(
1493: "ex.structure.modification.contentExists",
1494: "maximumMultiplicity");
1495: }
1496: if (ps != null)
1497: ps.close();
1498: ps = con.prepareStatement("UPDATE "
1499: + TBL_STRUCT_PROPERTIES
1500: + " SET MAYOVERRIDEMULT=? WHERE ID=?");
1501: ps
1502: .setBoolean(1, prop
1503: .mayOverrideBaseMultiplicity());
1504: ps.setLong(2, prop.getId());
1505: ps.executeUpdate();
1506: if (changes)
1507: changesDesc.append(',');
1508: changesDesc.append("mayOverrideMultiplicity=")
1509: .append(prop.mayOverrideBaseMultiplicity());
1510: changes = true;
1511: }
1512:
1513: if (org.getMultiplicity().getMin() != prop
1514: .getMultiplicity().getMin()
1515: || org.getMultiplicity().getMax() != prop
1516: .getMultiplicity().getMax()) {
1517: if (!prop.mayOverrideBaseMultiplicity()) {
1518: if (org.getMultiplicity().getMin() < prop
1519: .getMultiplicity().getMin()) {
1520: if (getInstanceMultiplicity(con, org
1521: .getId(), true) < prop
1522: .getMultiplicity().getMin())
1523: throw new FxUpdateException(
1524: "ex.structure.modification.contentExists",
1525: "minimumMultiplicity");
1526: }
1527: if (org.getMultiplicity().getMax() > prop
1528: .getMultiplicity().getMax()) {
1529: if (getInstanceMultiplicity(con, org
1530: .getId(), false) > prop
1531: .getMultiplicity().getMax())
1532: throw new FxUpdateException(
1533: "ex.structure.modification.contentExists",
1534: "maximumMultiplicity");
1535: }
1536: }
1537: if (ps != null)
1538: ps.close();
1539: ps = con
1540: .prepareStatement("UPDATE "
1541: + TBL_STRUCT_PROPERTIES
1542: + " SET DEFMINMULT=? ,DEFMAXMULT=? WHERE ID=?");
1543: ps.setInt(1, prop.getMultiplicity().getMin());
1544: ps.setInt(2, prop.getMultiplicity().getMax());
1545: ps.setLong(3, prop.getId());
1546: ps.executeUpdate();
1547: if (changes)
1548: changesDesc.append(',');
1549: changesDesc.append("multiplicity=").append(
1550: prop.getMultiplicity());
1551: changes = true;
1552: }
1553: //not supported yet
1554: if (!org.getName().equals(prop.getName())) {
1555: throw new FxUpdateException(
1556: "ex.structure.modification.notSuppoerted",
1557: "name");
1558: /*
1559: if (ps != null) ps.close();
1560: ps = con.prepareStatement("UPDATE " + TBL_STRUCT_PROPERTIES + " SET NAME=? WHERE ID=?");
1561: ps.setString(1, prop.getName());
1562: ps.setLong(2, prop.getId());
1563: ps.executeUpdate();
1564: if (changes)
1565: changesDesc.append(',');
1566: changesDesc.append("name=").append(prop.getName());
1567: changes = true;
1568: */
1569: }
1570: //may only change if there are no existing content instances that use this property already
1571: if (org.getDataType().getId() != prop.getDataType()
1572: .getId()) {
1573: if (getPropertyInstanceCount(org.getId()) == 0) {
1574: if (ps != null)
1575: ps.close();
1576: ps = con.prepareStatement("UPDATE "
1577: + TBL_STRUCT_PROPERTIES
1578: + " SET DATATYPE=? WHERE ID=?");
1579: ps.setLong(1, prop.getDataType().getId());
1580: ps.setLong(2, prop.getId());
1581: ps.executeUpdate();
1582: if (changes)
1583: changesDesc.append(',');
1584: changesDesc.append("dataType=").append(
1585: prop.getDataType().getName());
1586: changes = true;
1587: } else
1588: throw new FxUpdateException(
1589: "ex.structure.modification.contentExists",
1590: "dataType");
1591: }
1592:
1593: //may only change if there are no existing content instances that use this property already
1594: if (org.getReferencedType() != null
1595: && prop.getReferencedType() != null
1596: && org.getReferencedType().getId() != prop
1597: .getReferencedType().getId()
1598: || org.hasReferencedType() != prop
1599: .hasReferencedType()) {
1600: if (getPropertyInstanceCount(org.getId()) == 0) {
1601: ps = con.prepareStatement("UPDATE "
1602: + TBL_STRUCT_PROPERTIES
1603: + " SET REFTYPE=? WHERE ID=?");
1604: ps.setLong(2, prop.getId());
1605: if (prop.hasReferencedType()) {
1606: ps.setLong(1, prop.getReferencedType()
1607: .getId());
1608: } else
1609: ps.setNull(1, java.sql.Types.NUMERIC);
1610: ps.executeUpdate();
1611: if (changes)
1612: changesDesc.append(',');
1613: changesDesc.append("referencedType=").append(
1614: prop.getReferencedType());
1615: changes = true;
1616: } else
1617: throw new FxUpdateException(
1618: "ex.structure.modification.contentExists",
1619: "referencedType");
1620: }
1621:
1622: if (org.isFulltextIndexed() != prop.isFulltextIndexed()) {
1623: if (ps != null)
1624: ps.close();
1625: ps = con.prepareStatement("UPDATE "
1626: + TBL_STRUCT_PROPERTIES
1627: + " SET ISFULLTEXTINDEXED=? WHERE ID=?");
1628: ps.setBoolean(1, prop.isFulltextIndexed());
1629: ps.setLong(2, prop.getId());
1630: ps.executeUpdate();
1631: if (changes)
1632: changesDesc.append(',');
1633: changesDesc.append("isFulltextIndexed=").append(
1634: prop.isFulltextIndexed());
1635: changes = true;
1636: }
1637: if (org.mayOverrideACL() != prop.mayOverrideACL()) {
1638: if (ps != null)
1639: ps.close();
1640: ps = con.prepareStatement("UPDATE "
1641: + TBL_STRUCT_PROPERTIES
1642: + " SET MAYOVERRIDEACL=? WHERE ID=?");
1643: ps.setBoolean(1, prop.mayOverrideACL());
1644: ps.setLong(2, prop.getId());
1645: ps.executeUpdate();
1646: if (changes)
1647: changesDesc.append(',');
1648: changesDesc.append("mayOverrideACL=").append(
1649: prop.mayOverrideACL());
1650: changes = true;
1651: }
1652: //may only change if there are no existing content instances that use this property already
1653: if (org.getReferencedList() != null
1654: && prop.getReferencedList() != null
1655: && org.getReferencedList().getId() != prop
1656: .getReferencedList().getId()
1657: || org.hasReferencedList() != prop
1658: .hasReferencedList()) {
1659: if (getPropertyInstanceCount(org.getId()) == 0) {
1660: ps = con.prepareStatement("UPDATE "
1661: + TBL_STRUCT_PROPERTIES
1662: + " SET REFLIST=? WHERE ID=?");
1663: ps.setLong(2, prop.getId());
1664: if (prop.hasReferencedList()) {
1665: ps.setLong(1, prop.getReferencedList()
1666: .getId());
1667: } else
1668: ps.setNull(1, java.sql.Types.NUMERIC);
1669: ps.executeUpdate();
1670: if (changes)
1671: changesDesc.append(',');
1672: changesDesc.append("referencedList=").append(
1673: prop.getReferencedList());
1674: changes = true;
1675: } else
1676: throw new FxUpdateException(
1677: "ex.structure.modification.contentExists",
1678: "referencedList");
1679: }
1680:
1681: if (org.getUniqueMode() != prop.getUniqueMode()) {
1682: boolean allowChange = getPropertyInstanceCount(org
1683: .getId()) == 0
1684: || prop.getUniqueMode().equals(
1685: UniqueMode.None);
1686: if (!allowChange) {
1687: boolean check = true;
1688: for (FxType type : CacheAdmin.getEnvironment()
1689: .getTypesForProperty(prop.getId())) {
1690: check = StorageManager.getContentStorage(
1691: TypeStorageMode.Hierarchical)
1692: .uniqueConditionValid(con,
1693: prop.getUniqueMode(), prop,
1694: type.getId(), null);
1695: if (!check)
1696: break;
1697: }
1698: allowChange = check;
1699: }
1700: if (allowChange) {
1701: if (ps != null)
1702: ps.close();
1703: ps = con.prepareStatement("UPDATE "
1704: + TBL_STRUCT_PROPERTIES
1705: + " SET UNIQUEMODE=? WHERE ID=?");
1706: ps.setLong(1, prop.getUniqueMode().getId());
1707: ps.setLong(2, prop.getId());
1708: ps.executeUpdate();
1709: if (changes)
1710: changesDesc.append(',');
1711: changesDesc.append("uniqueMode=").append(
1712: prop.getUniqueMode().getId());
1713: changes = true;
1714: } else
1715: throw new FxUpdateException(
1716: "ex.structure.modification.contentExists",
1717: "uniqueMode");
1718: }
1719:
1720: if (org.getACL().getId() != prop.getACL().getId()) {
1721: if (ps != null)
1722: ps.close();
1723: ps = con.prepareStatement("UPDATE "
1724: + TBL_STRUCT_PROPERTIES
1725: + " SET ACL=? WHERE ID=?");
1726: ps.setLong(1, prop.getACL().getId());
1727: ps.setLong(2, prop.getId());
1728: ps.executeUpdate();
1729: if (changes)
1730: changesDesc.append(',');
1731: changesDesc.append("acl=").append(
1732: prop.getACL().getId());
1733: changes = true;
1734: }
1735: if (org.getLabel() != null
1736: && !org.getLabel().equals(prop.getLabel())
1737: || org.getLabel() == null
1738: && prop.getLabel() != null
1739: || org.getHint() != null
1740: && !org.getHint().equals(prop.getHint())
1741: || org.getHint() == null
1742: && prop.getHint() != null
1743: || org.getDefaultValue() != null
1744: && !org.getDefaultValue().equals(
1745: prop.getDefaultValue())
1746: || org.getDefaultValue() == null
1747: && prop.getDefaultValue() != null) {
1748: Database.storeFxString(new FxString[] {
1749: prop.getLabel(), prop.getHint(),
1750: prop.getDefaultValue() }, con,
1751: TBL_STRUCT_PROPERTIES, new String[] {
1752: "DESCRIPTION", "HINT",
1753: "DEFAULT_VALUE" }, "ID", prop
1754: .getId());
1755: if (changes)
1756: changesDesc.append(',');
1757: changesDesc.append("label=")
1758: .append(prop.getLabel()).append(',');
1759: changesDesc.append("hint=").append(prop.getHint())
1760: .append(',');
1761: changesDesc.append("defaultValue=").append(
1762: prop.getDefaultValue());
1763: changes = true;
1764: }
1765:
1766: //update SystemInternal flag, this is a one way function, so it can only be set, but not reset!!
1767: if (!org.isSystemInternal() && prop.isSystemInternal()) {
1768: if (FxContext.get().getTicket()
1769: .isGlobalSupervisor()) {
1770: if (ps != null)
1771: ps.close();
1772: ps = con.prepareStatement("UPDATE "
1773: + TBL_STRUCT_PROPERTIES
1774: + " SET SYSINTERNAL=? WHERE ID=?");
1775: ps.setBoolean(1, prop.isSystemInternal());
1776: ps.setLong(2, prop.getId());
1777: ps.executeUpdate();
1778: if (changes)
1779: changesDesc.append(',');
1780: changesDesc.append("systemInternal=").append(
1781: prop.isSystemInternal());
1782: changes = true;
1783: } else
1784: throw new FxUpdateException(
1785: "ex.structure.modification.systemInternal.notGlobalSupervisor",
1786: prop.getName());
1787: }
1788: }
1789: if (updatePropertyOptions(con, prop)) {
1790: changesDesc.append(",options:");
1791: List<FxStructureOption> options = prop.getOptions();
1792: for (FxStructureOption option : options) {
1793: changesDesc.append(option.getKey()).append("=")
1794: .append(option.getValue()).append(
1795: " overridable=").append(
1796: option.isOverrideable()).append(
1797: " isSet=").append(option.isSet());
1798: }
1799: changes = true;
1800: }
1801:
1802: if (changes) {
1803: //TODO: invoke htracker with changeDesc
1804: }
1805: } catch (SQLException e) {
1806: ctx.setRollbackOnly();
1807: /*TODO: Determine if this must be checked
1808: if (Database.isUniqueConstraintViolation(e))
1809: throw new FxEntryExistsException("ex.structure.assignment.property.exists", prop.getAlias(), prop.getXPath());
1810: */
1811: throw new FxCreateException(LOG, e, "ex.db.sqlError", e
1812: .getMessage());
1813: } finally {
1814: if (_con == null) {
1815: Database.closeObjects(AssignmentEngineBean.class, con,
1816: ps);
1817: }
1818: }
1819: return changes;
1820: }
1821:
1822: /**
1823: * @param _con database conneciton
1824: * @param ps prepared statement
1825: * @param sql StringBuilder to cache query creation
1826: * @param original the original property assignment to compare changes
1827: * against and update. if==null, the original will be fetched from the cache
1828: * @param modified the modified property assignment
1829: * @return if any changes were found
1830: * @throws FxApplicationException on errors
1831: */
1832:
1833: private boolean updatePropertyAssignment(Connection _con,
1834: PreparedStatement ps, StringBuilder sql,
1835: FxPropertyAssignment original,
1836: FxPropertyAssignmentEdit modified)
1837: throws FxApplicationException {
1838: if (sql == null)
1839: sql = new StringBuilder(1000);
1840: if (modified.isNew())
1841: throw new FxInvalidParameterException(
1842: "ex.structure.assignment.update.new", modified
1843: .getXPath());
1844: Connection con = _con;
1845: boolean changes = false;
1846: StringBuilder changesDesc = new StringBuilder(200);
1847: if (original == null)
1848: original = (FxPropertyAssignment) CacheAdmin
1849: .getEnvironment().getAssignment(modified.getId());
1850: try {
1851: if (con == null)
1852: con = Database.getDbConnection();
1853: sql.setLength(0);
1854:
1855: if (!original.isSystemInternal()
1856: || FxContext.get().getTicket().isGlobalSupervisor()) {
1857: if (original.isEnabled() != modified.isEnabled()) {
1858: if (!modified.isEnabled())
1859: removeAssignment(original.getId(), true, false,
1860: true);
1861: else {
1862: if (ps != null)
1863: ps.close();
1864: ps = con.prepareStatement("UPDATE "
1865: + TBL_STRUCT_ASSIGNMENTS
1866: + " SET ENABLED=? WHERE ID=?");
1867: ps.setBoolean(1, modified.isEnabled());
1868: ps.setLong(2, original.getId());
1869: ps.executeUpdate();
1870: }
1871: if (changes)
1872: changesDesc.append(',');
1873: changesDesc.append("enabled=").append(
1874: modified.isEnabled());
1875: changes = true;
1876: }
1877: if (original.getDefaultMultiplicity() != modified
1878: .getDefaultMultiplicity()) {
1879: if (ps != null)
1880: ps.close();
1881: ps = con.prepareStatement("UPDATE "
1882: + TBL_STRUCT_ASSIGNMENTS
1883: + " SET DEFMULT=? WHERE ID=?");
1884: ps.setInt(1, modified.getDefaultMultiplicity());
1885: ps.setLong(2, original.getId());
1886: ps.executeUpdate();
1887: if (changes)
1888: changesDesc.append(',');
1889: changesDesc.append("defaultMultiplicity=").append(
1890: modified.getDefaultMultiplicity());
1891: changes = true;
1892: }
1893:
1894: //must not change
1895: /*
1896: if (org.getAssignedType().getId() != prop.getAssignedType().getId()) {
1897: if (ps != null) ps.close();
1898: ps = con.prepareStatement("UPDATE " + TBL_STRUCT_ASSIGNMENTS + " SET TYPEDEF=? WHERE ID=?");
1899: ps.setLong(1, prop.getAssignedType().getId());
1900: ps.setLong(2, prop.getId());
1901: ps.executeUpdate();
1902: if (changes)
1903: changesDesc.append(',');
1904: changesDesc.append("assignedType=").append(prop.getAssignedType());
1905: changes = true;
1906: }
1907: */
1908:
1909: if (original.getMultiplicity().getMin() != modified
1910: .getMultiplicity().getMin()
1911: || original.getMultiplicity().getMax() != modified
1912: .getMultiplicity().getMax()) {
1913: if (modified.getProperty()
1914: .mayOverrideBaseMultiplicity()) {
1915: if (getInstanceMultiplicity(con, modified
1916: .getProperty().getId(), true) < modified
1917: .getMultiplicity().getMin())
1918: throw new FxUpdateException(
1919: "ex.structure.modification.contentExists",
1920: "minimumMultiplicity");
1921: if (getInstanceMultiplicity(con, modified
1922: .getProperty().getId(), false) > modified
1923: .getMultiplicity().getMax())
1924: throw new FxUpdateException(
1925: "ex.structure.modification.contentExists",
1926: "maximumMultiplicity");
1927: }
1928: if (ps != null)
1929: ps.close();
1930: ps = con.prepareStatement("UPDATE "
1931: + TBL_STRUCT_ASSIGNMENTS
1932: + " SET MINMULT=? ,MAXMULT=? WHERE ID=?");
1933: ps.setInt(1, modified.getMultiplicity().getMin());
1934: ps.setInt(2, modified.getMultiplicity().getMax());
1935: ps.setLong(3, original.getId());
1936: ps.executeUpdate();
1937: if (changes)
1938: changesDesc.append(',');
1939: changesDesc.append("multiplicity=").append(
1940: modified.getMultiplicity());
1941: changes = true;
1942: }
1943: if (original.getPosition() != modified.getPosition()) {
1944: int finalPos = setAssignmentPosition(con, modified
1945: .getId(), modified.getPosition());
1946: if (changes)
1947: changesDesc.append(',');
1948: changesDesc.append("position=").append(finalPos);
1949: changes = true;
1950: }
1951: //not supported yet
1952: if (!original.getXPath().equals(modified.getXPath())) {
1953: throw new FxUpdateException(
1954: "ex.structure.modification.notSuppoerted",
1955: "xPath");
1956: /*
1957: //TODO:check for valid XPath
1958: if (ps != null) ps.close();
1959: ps = con.prepareStatement("UPDATE " + TBL_STRUCT_ASSIGNMENTS + " SET XPATH=? WHERE ID=?");
1960: ps.setString(1, prop.getXPath());
1961: ps.setLong(2, prop.getId());
1962: ps.executeUpdate();
1963: if (changes)
1964: changesDesc.append(',');
1965: changesDesc.append("xPath=").append(prop.getXPath());
1966: changes = true;
1967: */
1968: }
1969: //not supported yet
1970: if (!original.getAlias().equals(modified.getAlias())) {
1971: throw new FxUpdateException(
1972: "ex.structure.modification.notSuppoerted",
1973: "alias");
1974: /*
1975: if (ps != null) ps.close();
1976: ps = con.prepareStatement("UPDATE " + TBL_STRUCT_ASSIGNMENTS + " SET XALIAS=? WHERE ID=?");
1977: ps.setString(1, prop.getAlias());
1978: ps.setLong(2, prop.getId());
1979: ps.executeUpdate();
1980: if (changes)
1981: changesDesc.append(',');
1982: changesDesc.append("alias=").append(prop.getAlias());
1983: changes = true;
1984: */
1985: }
1986: //must not change
1987: /*
1988: if (org.getBaseAssignmentId() != prop.getBaseAssignmentId()) {
1989: if (ps != null) ps.close();
1990: ps = con.prepareStatement("UPDATE " + TBL_STRUCT_ASSIGNMENTS + " SET BASE=? WHERE ID=?");
1991: ps.setLong(1, prop.getBaseAssignmentId());
1992: ps.setLong(2, prop.getId());
1993: ps.executeUpdate();
1994: if (changes)
1995: changesDesc.append(',');
1996: changesDesc.append("baseAssignment=").append(prop.getBaseAssignmentId());
1997: changes = true;
1998: }
1999: */
2000: //must not change
2001: /*
2002: if(org.getParentGroupAssignment() != null &&
2003: !org.getParentGroupAssignment().equals(prop.getParentGroupAssignment()) ||
2004: prop.getParentGroupAssignment() !=null &&
2005: !prop.getParentGroupAssignment().equals(org.getParentGroupAssignment())) {
2006: if (ps != null) ps.close();
2007: ps = con.prepareStatement("UPDATE " + TBL_STRUCT_ASSIGNMENTS + " SET PARENTGROUP=? WHERE ID=?");
2008: if (prop.getParentGroupAssignment() !=null)
2009: ps.setLong(1, prop.getParentGroupAssignment().getId());
2010: else
2011: ps.setLong(1, FxAssignment.NO_PARENT);
2012: ps.setLong(2, prop.getId());
2013: ps.executeUpdate();
2014: if (changes)
2015: changesDesc.append(',');
2016: changesDesc.append("parentGroupAssignment=").append(prop.getParentGroupAssignment());
2017: changes = true;
2018: }
2019: */
2020: //must not change
2021: /*
2022: if(org.getProperty().getId() != prop.getProperty().getId()) {
2023: if (ps != null) ps.close();
2024: ps = con.prepareStatement("UPDATE " + TBL_STRUCT_ASSIGNMENTS + " SET APROPERTY=? WHERE ID=?");
2025: ps.setLong(1, prop.getProperty().getId());
2026: ps.setLong(2, prop.getId());
2027: ps.executeUpdate();
2028: if (changes)
2029: changesDesc.append(',');
2030: changesDesc.append("property=").append(prop.getProperty().getId());
2031: changes = true;
2032: }
2033: */
2034: if (original.getACL().getId() != modified.getACL()
2035: .getId()) {
2036: if (ps != null)
2037: ps.close();
2038: ps = con.prepareStatement("UPDATE "
2039: + TBL_STRUCT_ASSIGNMENTS
2040: + " SET ACL=? WHERE ID=?");
2041: ps.setLong(1, modified.getACL().getId());
2042: ps.setLong(2, original.getId());
2043: ps.executeUpdate();
2044: if (changes)
2045: changesDesc.append(',');
2046: changesDesc.append("acl=").append(
2047: modified.getACL().getId());
2048: changes = true;
2049: }
2050:
2051: /* options are stored via storeOption method
2052: if (org.isMultiLang() != prop.isMultiLang()) {
2053: //TODO: only allow changes from multi to single lingual if no contents exist or all are of the same language
2054: //Multi->Single: lang=system
2055: //Single->Multi: lang=default language
2056: if( !org.getProperty().mayOverrideMultiLang() )
2057: throw new FxUpdateException("ex.structure.assignment.overrideNotAllowed.multiLang", org.getXPath(),
2058: org.getProperty().getName()).setAffectedXPath(org.getXPath());
2059: if (ps != null) ps.close();
2060: ps = con.prepareStatement("UPDATE " + TBL_STRUCT_ASSIGNMENTS + " SET ISMULTILANG=? WHERE ID=?");
2061: ps.setBoolean(1, prop.isMultiLang());
2062: ps.setLong(2, prop.getId());
2063: ps.executeUpdate();
2064: if (changes)
2065: changesDesc.append(',');
2066: changesDesc.append("multiLang=").append(prop.isMultiLang());
2067: changes = true;
2068: }
2069: */
2070: if (original.getLabel() != null
2071: && !original.getLabel().equals(
2072: modified.getLabel())
2073: || original.getLabel() == null
2074: && modified.getLabel() != null
2075: || original.getHint() != null
2076: && !original.getHint().equals(
2077: modified.getHint())
2078: || original.getHint() == null
2079: && modified.getHint() != null
2080: || original.getDefaultValue() != null
2081: && !original.getDefaultValue().equals(
2082: modified.getDefaultValue())
2083: || original.getDefaultValue() == null
2084: && modified.getDefaultValue() != null) {
2085: Database.storeFxString(new FxString[] {
2086: modified.getLabel(), modified.getHint(),
2087: (FxString) modified.getDefaultValue() },
2088: con, TBL_STRUCT_ASSIGNMENTS, new String[] {
2089: "DESCRIPTION", "HINT",
2090: "DEFAULT_VALUE" }, "ID", original
2091: .getId());
2092: if (changes)
2093: changesDesc.append(',');
2094: changesDesc.append("label=").append(
2095: modified.getLabel()).append(',');
2096: changesDesc.append("hint=").append(
2097: modified.getHint()).append(',');
2098: changesDesc.append("defaultValue=").append(
2099: modified.getDefaultValue());
2100: changes = true;
2101: }
2102: if (original.getDefaultLanguage() != modified
2103: .getDefaultLanguage()) {
2104: if (ps != null)
2105: ps.close();
2106: ps = con.prepareStatement("UPDATE "
2107: + TBL_STRUCT_ASSIGNMENTS
2108: + " SET DEFLANG=? WHERE ID=?");
2109: ps.setInt(1, (int) modified.getDefaultLanguage());
2110: ps.setLong(2, original.getId());
2111: ps.executeUpdate();
2112: if (changes)
2113: changesDesc.append(',');
2114: changesDesc.append("defaultLanguage=").append(
2115: modified.getDefaultLanguage());
2116: changes = true;
2117: }
2118:
2119: if (original.getDefaultMultiplicity() != modified
2120: .getDefaultMultiplicity()) {
2121: if (modified.getMultiplicity().getMin() > modified
2122: .getDefaultMultiplicity()
2123: || modified.getDefaultMultiplicity() > modified
2124: .getMultiplicity().getMax())
2125: throw new FxUpdateException(
2126: "ex.structure.modificaiton.defaultMultiplicity.invalid");
2127:
2128: if (ps != null)
2129: ps.close();
2130: ps = con.prepareStatement("UPDATE "
2131: + TBL_STRUCT_ASSIGNMENTS
2132: + " SET DEFMULT=? WHERE ID=?");
2133: ps.setInt(1, modified.getDefaultMultiplicity());
2134: ps.setLong(2, original.getId());
2135: ps.executeUpdate();
2136: if (changes)
2137: changesDesc.append(',');
2138: changesDesc.append("defaultMultiplicity=").append(
2139: modified.getDefaultMultiplicity());
2140: changes = true;
2141: }
2142: //update SystemInternal flag, this is a one way function, so it can only be set, but not reset!!
2143: if (!original.isSystemInternal()
2144: && modified.isSystemInternal()) {
2145: if (FxContext.get().getTicket()
2146: .isGlobalSupervisor()) {
2147: if (ps != null)
2148: ps.close();
2149: ps = con.prepareStatement("UPDATE "
2150: + TBL_STRUCT_ASSIGNMENTS
2151: + " SET SYSINTERNAL=? WHERE ID=?");
2152: ps.setBoolean(1, modified.isSystemInternal());
2153: ps.setLong(2, original.getId());
2154: ps.executeUpdate();
2155: if (changes)
2156: changesDesc.append(',');
2157: changesDesc.append("systemInternal=").append(
2158: modified.isSystemInternal());
2159: changes = true;
2160: } else
2161: throw new FxUpdateException(
2162: "ex.structure.modification.systemInternal.notGlobalSupervisor",
2163: modified.getLabel());
2164: }
2165: /*
2166: if (changes) {
2167: //propagate changes to derived assignments
2168: List<FxAssignment> children = CacheAdmin.getEnvironment().getDerivedAssignments(modified.getId());
2169: for (FxAssignment as : children) {
2170: if (as instanceof FxPropertyAssignment) {
2171: updatePropertyAssignment(null, null, null, (FxPropertyAssignment) as, modified);
2172: }
2173: }
2174: //if there are changes AND the assignment is a child,
2175: // break the inheritance and make it a "ROOT_BASE" assignment
2176: if(original.isDerivedAssignment()) {
2177: if (ps!=null)
2178: ps.close();
2179: ps = con.prepareStatement("UPDATE " + TBL_STRUCT_ASSIGNMENTS + " SET BASE=? WHERE ID=?");
2180: ps.setNull(1, Types.NUMERIC);
2181: ps.setLong(2, original.getId());
2182: ps.executeUpdate();
2183: changesDesc.append(",baseAssignment=null");
2184: }
2185: }
2186: */
2187: } else
2188: throw new FxUpdateException(
2189: "ex.structure.systemInternal.forbidden",
2190: modified.getLabel());
2191:
2192: if (updatePropertyAssignmentOptions(con, original, modified)) {
2193: changesDesc.append(",options:");
2194: List<FxStructureOption> options = modified.getOptions();
2195: for (FxStructureOption option : options) {
2196: changesDesc.append(option.getKey()).append("=")
2197: .append(option.getValue()).append(
2198: " overridable=").append(
2199: option.isOverrideable()).append(
2200: " isSet=").append(option.isSet());
2201: }
2202: changes = true;
2203: }
2204:
2205: //TODO: compare all possible modifications
2206: if (changes)
2207: htracker.track(modified.getAssignedType(),
2208: "history.assignment.updatePropertyAssignment",
2209: original.getXPath(), modified.getAssignedType()
2210: .getId(), modified.getAssignedType()
2211: .getName(), modified.getProperty()
2212: .getId(), modified.getProperty()
2213: .getName(), changesDesc.toString());
2214: } catch (SQLException e) {
2215: final boolean uniqueConstraintViolation = Database
2216: .isUniqueConstraintViolation(e);
2217: ctx.setRollbackOnly();
2218: if (uniqueConstraintViolation)
2219: throw new FxEntryExistsException(
2220: "ex.structure.assignment.property.exists",
2221: original.getAlias(), original.getXPath());
2222: throw new FxCreateException(LOG, e, "ex.db.sqlError", e
2223: .getMessage());
2224: } finally {
2225: if (_con == null) {
2226: Database.closeObjects(AssignmentEngineBean.class, con,
2227: ps);
2228: }
2229: }
2230: return changes;
2231: }
2232:
2233: private long createPropertyAssignment(Connection _con,
2234: PreparedStatement ps, StringBuilder sql,
2235: FxPropertyAssignmentEdit prop)
2236: throws FxApplicationException {
2237: if (sql == null)
2238: sql = new StringBuilder(1000);
2239: if (!prop.isNew())
2240: throw new FxInvalidParameterException(
2241: "ex.structure.assignment.create.existing", prop
2242: .getXPath());
2243: Connection con = _con;
2244: long newAssignmentId;
2245: try {
2246: if (con == null)
2247: con = Database.getDbConnection();
2248: sql.setLength(0);
2249: sql.append("INSERT INTO ")
2250: .append(TBL_STRUCT_ASSIGNMENTS)
2251: .
2252: // 1 2 3 4 5 6 7 8 9 10 11 12 13
2253: append(
2254: "(ID,ATYPE,ENABLED,TYPEDEF,MINMULT,MAXMULT,DEFMULT,POS,XPATH,XALIAS,BASE,PARENTGROUP,APROPERTY,"
2255: +
2256: //14 15 16
2257: "ACL,DEFLANG,SYSINTERNAL)"
2258: + "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
2259: if (ps != null)
2260: ps.close();
2261: ps = con.prepareStatement(sql.toString());
2262: newAssignmentId = seq
2263: .getId(SequencerEngine.System.ASSIGNMENT);
2264: ps.setLong(1, newAssignmentId);
2265: ps.setInt(2, FxAssignment.TYPE_PROPERTY);
2266: ps.setBoolean(3, prop.isEnabled());
2267: ps.setLong(4, prop.getAssignedType().getId());
2268: ps.setInt(5, prop.getMultiplicity().getMin());
2269: ps.setInt(6, prop.getMultiplicity().getMax());
2270: ps.setInt(7, prop.getDefaultMultiplicity());
2271: int position = getValidPosition(con, sql, prop
2272: .getPosition(), prop.getAssignedType().getId(),
2273: prop.getParentGroupAssignment());
2274: ps.setInt(8, position);
2275: String XPath;
2276: if (!prop.getXPath().startsWith(
2277: prop.getAssignedType().getName()))
2278: XPath = XPathElement.buildXPath(false, prop
2279: .getAssignedType().getName(), prop.getXPath());
2280: else
2281: XPath = prop.getXPath();
2282: ps.setString(9, XPath);
2283: ps.setString(10, prop.getAlias());
2284: ps.setLong(11, prop.getBaseAssignmentId());
2285: ps
2286: .setLong(
2287: 12,
2288: prop.getParentGroupAssignment() == null ? FxAssignment.NO_PARENT
2289: : prop.getParentGroupAssignment()
2290: .getId());
2291: ps.setLong(13, prop.getProperty().getId());
2292: ps.setLong(14, prop.getACL().getId());
2293: ps.setInt(15, prop.hasDefaultLanguage() ? (int) prop
2294: .getDefaultLanguage() : (int) FxLanguage.SYSTEM_ID);
2295: ps.setBoolean(16, prop.isSystemInternal());
2296: ps.executeUpdate();
2297: Database.storeFxString(
2298: new FxString[] { prop.getLabel(), prop.getHint(),
2299: (FxString) prop.getDefaultValue() }, con,
2300: TBL_STRUCT_ASSIGNMENTS, new String[] {
2301: "DESCRIPTION", "HINT", "DEFAULT_VALUE" },
2302: "ID", newAssignmentId);
2303: htracker.track(prop.getAssignedType(),
2304: "history.assignment.createPropertyAssignment",
2305: XPath, prop.getAssignedType().getId(), prop
2306: .getAssignedType().getName(), prop
2307: .getProperty().getId(), prop.getProperty()
2308: .getName());
2309: storeOptions(con, TBL_PROPERTY_OPTIONS, "ID", prop
2310: .getProperty().getId(), newAssignmentId, prop
2311: .getOptions());
2312: setAssignmentPosition(con, newAssignmentId, prop
2313: .getPosition());
2314: if (!prop.isSystemInternal()) {
2315: //only need a reload and inheritance handling if the property is not system internal
2316: //since system internal properties are only created from the type engine we don't have to care
2317: try {
2318: StructureLoader.reload(con);
2319: } catch (FxCacheException e) {
2320: ctx.setRollbackOnly();
2321: throw new FxCreateException(e, "ex.cache", e
2322: .getMessage());
2323: }
2324: createInheritedAssignments(CacheAdmin.getEnvironment()
2325: .getAssignment(newAssignmentId), con, ps, sql,
2326: prop.getAssignedType().getDerivedTypes());
2327: }
2328: } catch (SQLException e) {
2329: final boolean uniqueConstraintViolation = Database
2330: .isUniqueConstraintViolation(e);
2331: ctx.setRollbackOnly();
2332: if (uniqueConstraintViolation)
2333: throw new FxEntryExistsException(
2334: "ex.structure.assignment.property.exists", prop
2335: .getAlias(), prop.getXPath());
2336: throw new FxCreateException(LOG, e, "ex.db.sqlError", e
2337: .getMessage());
2338: } finally {
2339: Database.closeObjects(AssignmentEngineBean.class,
2340: (_con == null ? con : null), ps);
2341: }
2342: return newAssignmentId;
2343: }
2344:
2345: /**
2346: * Get a valid position for the assignment within the same hierarchy.
2347: * Probes for the desired position first and if taken returns the next available
2348: *
2349: * @param con connection ( has to be valid and open!)
2350: * @param sql StringBuilder for the statement
2351: * @param desiredPos desired position
2352: * @param typeId FxType id
2353: * @param parentGroupAssignment the parent gorup assignment or <code>null</code> if assigned to the root
2354: * @return a valid position for the assignment
2355: * @throws SQLException on errors
2356: * @throws FxCreateException if no result could be retrieved
2357: */
2358: private int getValidPosition(Connection con, StringBuilder sql,
2359: int desiredPos, long typeId,
2360: FxGroupAssignment parentGroupAssignment)
2361: throws SQLException, FxCreateException {
2362: // original MySQL statement:
2363: // select if( (select count(id) from FXS_ASSIGNMENTS where typedef=1 and parentgroup=0 and pos=1) > 0,
2364: // (select (max(pos)+1) from FXS_ASSIGNMENTS WHERE typedef=1 and parentgroup=0),
2365: // 1) as pos
2366: PreparedStatement ps = null;
2367: sql.setLength(0);
2368: sql
2369: .append("SELECT IF((SELECT COUNT(ID) FROM ")
2370: .append(TBL_STRUCT_ASSIGNMENTS)
2371: .
2372: // 1 2 3
2373: append(
2374: " WHERE TYPEDEF=? AND PARENTGROUP=? AND POS=?)>0,(SELECT IFNULL(MAX(POS)+1,0) FROM ")
2375: .
2376: // 4 5 6
2377: append(TBL_STRUCT_ASSIGNMENTS).append(
2378: " WHERE TYPEDEF=? AND PARENTGROUP=?),?)");
2379: try {
2380: ps = con.prepareStatement(sql.toString());
2381: ps.setLong(1, typeId);
2382: ps
2383: .setLong(
2384: 2,
2385: parentGroupAssignment == null ? FxAssignment.NO_PARENT
2386: : parentGroupAssignment.getId());
2387: ps.setInt(3, desiredPos);
2388: ps.setLong(4, typeId);
2389: ps
2390: .setLong(
2391: 5,
2392: parentGroupAssignment == null ? FxAssignment.NO_PARENT
2393: : parentGroupAssignment.getId());
2394: ps.setInt(6, desiredPos);
2395: ResultSet rs = ps.executeQuery();
2396: if (rs != null && rs.next())
2397: return rs.getInt(1);
2398: throw new FxCreateException(
2399: "ex.structure.position.failed",
2400: typeId,
2401: parentGroupAssignment == null ? FxAssignment.NO_PARENT
2402: : parentGroupAssignment.getId(), desiredPos);
2403: } finally {
2404: if (ps != null)
2405: ps.close();
2406: }
2407: }
2408:
2409: /**
2410: * {@inheritDoc}
2411: */
2412: @TransactionAttribute(TransactionAttributeType.REQUIRED)
2413: public void removeAssignment(long assignmentId,
2414: boolean removeSubAssignments,
2415: boolean removeDerivedAssignments)
2416: throws FxApplicationException {
2417: removeAssignment(assignmentId, removeSubAssignments,
2418: removeDerivedAssignments, false);
2419: }
2420:
2421: private void removeAssignment(long assignmentId,
2422: boolean removeSubAssignments,
2423: boolean removeDerivedAssignments, boolean disableAssignment)
2424: throws FxApplicationException {
2425: final UserTicket ticket = FxContext.get().getTicket();
2426: FxPermissionUtils.checkRole(ticket, Role.StructureManagement);
2427: FxAssignment assignment;
2428: assignment = CacheAdmin.getEnvironment().getAssignment(
2429: assignmentId);
2430: assert assignment != null : "Assignment retrieved was null";
2431: if (!disableAssignment) {
2432: //if removal, check if its a derived assignment which may not be removed
2433: if (assignment.isDerivedAssignment())
2434: throw new FxRemoveException(
2435: "ex.structure.assignment.delete.derived",
2436: assignment.getXPath());
2437: }
2438:
2439: Connection con = null;
2440: PreparedStatement ps = null;
2441: StringBuilder sql = new StringBuilder(500);
2442: try {
2443: con = Database.getDbConnection();
2444:
2445: List<FxAssignment> affectedAssignments = new ArrayList<FxAssignment>(
2446: 10);
2447: affectedAssignments.add(assignment);
2448:
2449: if (assignment instanceof FxGroupAssignment
2450: && removeSubAssignments) {
2451: FxGroupAssignment ga = (FxGroupAssignment) assignment;
2452: _addSubAssignments(affectedAssignments, ga);
2453: }
2454:
2455: if (removeDerivedAssignments) {
2456: //find all derived assignments
2457: sql.append("SELECT ID FROM ").append(
2458: TBL_STRUCT_ASSIGNMENTS).append(" WHERE BASE=?");
2459: ps = con.prepareStatement(sql.toString());
2460: long prevSize = 0;
2461: while (prevSize != affectedAssignments.size()) { //run until no derived assignments are found
2462: prevSize = affectedAssignments.size();
2463: List<FxAssignment> adds = new ArrayList<FxAssignment>(
2464: 5);
2465: for (FxAssignment check : affectedAssignments) {
2466: ps.setLong(1, check.getId());
2467: ResultSet rs = ps.executeQuery();
2468: if (rs != null && rs.next()) {
2469: FxAssignment derived = CacheAdmin
2470: .getEnvironment().getAssignment(
2471: rs.getLong(1));
2472: if (!adds.contains(derived)
2473: && !affectedAssignments
2474: .contains(derived))
2475: adds.add(derived);
2476: }
2477: }
2478: affectedAssignments.addAll(adds);
2479: }
2480: ps.close();
2481: sql.setLength(0);
2482: } else if (!disableAssignment) {
2483: //find all (directly) derived assignments, flag them as 'regular' assignments and set them as new base
2484: ps = breakAssignmentInheritance(con, assignment, sql);
2485: }
2486:
2487: //security checks
2488: if (!ticket.isGlobalSupervisor()) {
2489: //assignment permission
2490: StringBuilder assignmentList = new StringBuilder(200);
2491: for (FxAssignment check : affectedAssignments) {
2492: assignmentList.append(",").append(check.getId());
2493: if (check instanceof FxPropertyAssignment
2494: && check.getAssignedType()
2495: .usePropertyPermissions()) {
2496: FxPropertyAssignment pa = (FxPropertyAssignment) check;
2497: if (!ticket
2498: .mayDeleteACL(pa.getACL().getId(), 0/*owner is irrelevant here*/))
2499: throw new FxNoAccessException(
2500: "ex.acl.noAccess.delete", pa
2501: .getACL().getName());
2502: }
2503: }
2504: //affected content permission
2505: sql.append("SELECT DISTINCT O.ACL FROM ").append(
2506: TBL_CONTENT).append(
2507: " O WHERE O.ID IN(SELECT D.ID FROM ").append(
2508: TBL_CONTENT_DATA).append(
2509: " D WHERE D.ASSIGN IN(").append(
2510: assignmentList.substring(1)).append("))");
2511: java.lang.System.out.println("SQL==" + sql.toString());
2512: ps = con.prepareStatement(sql.toString());
2513: sql.setLength(0);
2514: ResultSet rs = ps.executeQuery();
2515: while (rs != null && rs.next()) {
2516: if (!ticket
2517: .mayDeleteACL(rs.getInt(1), 0/*owner is irrelevant here*/))
2518: throw new FxNoAccessException(
2519: "ex.acl.noAccess.delete", CacheAdmin
2520: .getEnvironment().getACL(
2521: rs.getInt(1)));
2522: }
2523: ps.close();
2524: }
2525:
2526: if (disableAssignment)
2527: sql.append("UPDATE ").append(TBL_STRUCT_ASSIGNMENTS)
2528: .append(" SET ENABLED=? WHERE ID=?");
2529: else
2530: sql.append("DELETE FROM ").append(
2531: TBL_STRUCT_ASSIGNMENTS).append(" WHERE ID=?");
2532: ps = con.prepareStatement(sql.toString());
2533:
2534: //batch remove all multi language entries and content datas
2535: PreparedStatement psML = null;
2536: PreparedStatement psData = null;
2537: PreparedStatement psDataFT = null;
2538: PreparedStatement psBinaryGet = null;
2539: PreparedStatement psBinaryRemove = null;
2540: PreparedStatement psPropertyOptionRemove;
2541: PreparedStatement psGroupOptionRemove;
2542: try {
2543: sql.setLength(0);
2544: sql.append("DELETE FROM ").append(
2545: TBL_STRUCT_ASSIGNMENTS).append(ML).append(
2546: " WHERE ID=?");
2547: psML = con.prepareStatement(sql.toString());
2548: sql.setLength(0);
2549: sql.append("DELETE FROM ").append(TBL_PROPERTY_OPTIONS)
2550: .append(" WHERE ASSID=?");
2551: psPropertyOptionRemove = con.prepareStatement(sql
2552: .toString());
2553: sql.setLength(0);
2554: sql.append("DELETE FROM ").append(TBL_GROUP_OPTIONS)
2555: .append(" WHERE ASSID=?");
2556: psGroupOptionRemove = con.prepareStatement(sql
2557: .toString());
2558: sql.setLength(0);
2559: sql.append("DELETE FROM ").append(TBL_CONTENT_DATA)
2560: .append(" WHERE ASSIGN=?");
2561: psData = con.prepareStatement(sql.toString());
2562: sql.setLength(0);
2563: sql.append("DELETE FROM ").append(TBL_CONTENT_DATA_FT)
2564: .append(" WHERE ASSIGN=?");
2565: psDataFT = con.prepareStatement(sql.toString());
2566: sql.setLength(0);
2567: sql.append("SELECT DISTINCT FBLOB FROM ").append(
2568: TBL_CONTENT_DATA).append(
2569: " WHERE ASSIGN=? AND FBLOB IS NOT NULL");
2570: psBinaryGet = con.prepareStatement(sql.toString());
2571: sql.setLength(0);
2572: sql.append("DELETE FROM ").append(TBL_CONTENT_BINARY)
2573: .append(" WHERE ID=?");
2574: psBinaryRemove = con.prepareStatement(sql.toString());
2575: for (FxAssignment ml : affectedAssignments) {
2576: if (!disableAssignment) {
2577: psML.setLong(1, ml.getId());
2578: psML.addBatch();
2579: }
2580: if (ml instanceof FxPropertyAssignment) {
2581: psData.setLong(1, ml.getId());
2582: psData.addBatch();
2583: psDataFT.setLong(1, ml.getId());
2584: psDataFT.addBatch();
2585: psPropertyOptionRemove.setLong(1, ml.getId());
2586: psPropertyOptionRemove.addBatch();
2587: //only need to remove binaries if its a binary type...
2588: switch (((FxPropertyAssignment) ml)
2589: .getProperty().getDataType()) {
2590: case Binary:
2591: psBinaryGet.setLong(1, ml.getId());
2592: ResultSet rs = psBinaryGet.executeQuery();
2593: while (rs != null && rs.next()) {
2594: psBinaryRemove
2595: .setLong(1, rs.getLong(1));
2596: psBinaryRemove.addBatch();
2597: }
2598: }
2599: } else if (ml instanceof FxGroupAssignment) {
2600: psGroupOptionRemove.setLong(1, ml.getId());
2601: psGroupOptionRemove.addBatch();
2602: }
2603: }
2604: if (!disableAssignment) {
2605: psML.executeBatch();
2606: }
2607: psPropertyOptionRemove.executeBatch();
2608: psGroupOptionRemove.executeBatch();
2609: psBinaryRemove.executeBatch();
2610: psDataFT.executeBatch();
2611: psData.executeBatch();
2612: } finally {
2613: if (psML != null)
2614: psML.close();
2615: if (psData != null)
2616: psData.close();
2617: if (psDataFT != null)
2618: psDataFT.close();
2619: if (psBinaryGet != null)
2620: psBinaryGet.close();
2621: if (psBinaryRemove != null)
2622: psBinaryRemove.close();
2623: }
2624:
2625: List<FxAssignment> remaining = new ArrayList<FxAssignment>(
2626: affectedAssignments.size());
2627: int removed = 1;
2628: SQLException lastEx = null;
2629: if (disableAssignment)
2630: ps.setBoolean(1, false);
2631: while (removed > 0) {
2632: removed = 0;
2633: for (FxAssignment rm : affectedAssignments) {
2634: ps.setLong(disableAssignment ? 2 : 1, rm.getId());
2635: try {
2636: ps.executeUpdate();
2637: removed++;
2638: } catch (SQLException e) {
2639: lastEx = e;
2640: remaining.add(rm);
2641: }
2642: }
2643: affectedAssignments.clear();
2644: if (disableAssignment)
2645: break;
2646: affectedAssignments.addAll(remaining);
2647: remaining.clear();
2648: }
2649:
2650: if (affectedAssignments.size() > 0)
2651: throw lastEx;
2652:
2653: removeOrphanedProperties(con);
2654: removeOrphanedGroups(con);
2655: StructureLoader.reload(con);
2656: htracker.track(assignment.getAssignedType(),
2657: disableAssignment ? "history.assignment.remove"
2658: : "history.assignment.disable", assignment
2659: .getXPath(), assignmentId,
2660: removeSubAssignments, removeDerivedAssignments);
2661: } catch (SQLException e) {
2662: ctx.setRollbackOnly();
2663: throw new FxRemoveException(LOG, e, "ex.db.sqlError", e
2664: .getMessage());
2665: } catch (FxCacheException e) {
2666: ctx.setRollbackOnly();
2667: throw new FxRemoveException(LOG, e, "ex.cache", e
2668: .getMessage());
2669: } catch (FxLoadException e) {
2670: ctx.setRollbackOnly();
2671: throw new FxRemoveException(e);
2672: } finally {
2673: Database.closeObjects(TypeEngineBean.class, con, ps);
2674: }
2675:
2676: }
2677:
2678: /**
2679: * Find all (directly) derived assignments and flag them as 'regular' assignments and set them as new base
2680: *
2681: * @param con an open and valid connection
2682: * @param assignment the assignment to 'break'
2683: * @param sql query builder
2684: * @return used ps
2685: * @throws FxNotFoundException on errors
2686: * @throws FxInvalidParameterException on errors
2687: * @throws java.sql.SQLException on errors
2688: */
2689: private PreparedStatement breakAssignmentInheritance(
2690: Connection con, FxAssignment assignment, StringBuilder sql)
2691: throws SQLException, FxNotFoundException,
2692: FxInvalidParameterException {
2693: sql.append("UPDATE ").append(TBL_STRUCT_ASSIGNMENTS).append(
2694: " SET BASE=? WHERE BASE=?"); // AND TYPEDEF=?");
2695: PreparedStatement ps = con.prepareStatement(sql.toString());
2696: ps.setNull(1, Types.NUMERIC);
2697: ps.setLong(2, assignment.getId());
2698: int count = 0;
2699: //'toplevel' fix
2700: // for(FxType types: assignment.getAssignedType().getDerivedTypes() ) {
2701: // ps.setLong(3, types.getId());
2702: count += ps.executeUpdate();
2703: // }
2704: LOG.info("Updated " + count
2705: + " assignments to become the new base assignment");
2706: /* sql.setLength(0);
2707: //now fix 'deeper' inherited assignments
2708: for(FxType types: assignment.getAssignedType().getDerivedTypes() ) {
2709: for( FxType subderived: types.getDerivedTypes())
2710: _fixSubInheritance(ps, subderived, types.getAssignment(assignment.getXPath()).getId(), assignment.getId());
2711: }*/
2712: ps.close();
2713: sql.setLength(0);
2714: return ps;
2715: }
2716:
2717: /*private void _fixSubInheritance(PreparedStatement ps, FxType type, long newBase, long assignmentId) throws SQLException, FxInvalidParameterException, FxNotFoundException {
2718: ps.setLong(1, newBase);
2719: ps.setLong(2, assignmentId);
2720: ps.setLong(3, type.getId());
2721: ps.executeUpdate();
2722: for( FxType derived: type.getDerivedTypes())
2723: _fixSubInheritance(ps, derived, newBase, assignmentId);
2724: }*/
2725:
2726: /**
2727: * Recursively gather all sub assignments of the requested group assignment and add it to the given list
2728: *
2729: * @param affectedAssignments list where all sub assignments and the group itself are being put
2730: * @param ga the group assignment to start at
2731: */
2732: private void _addSubAssignments(
2733: List<FxAssignment> affectedAssignments, FxGroupAssignment ga) {
2734: affectedAssignments.addAll(ga.getAssignedProperties());
2735: for (FxGroupAssignment subga : ga.getAssignedGroups()) {
2736: affectedAssignments.add(subga);
2737: _addSubAssignments(affectedAssignments, subga);
2738: }
2739: }
2740:
2741: /**
2742: * Remove all properties that are no longer referenced
2743: *
2744: * @param con a valid connection
2745: * @throws SQLException on errors
2746: */
2747: protected static void removeOrphanedProperties(Connection con)
2748: throws SQLException {
2749: Statement stmt = null;
2750: try {
2751: stmt = con.createStatement();
2752: stmt
2753: .executeUpdate("DELETE FROM "
2754: + TBL_STRUCT_PROPERTIES
2755: + ML
2756: + " WHERE ID NOT IN(SELECT DISTINCT APROPERTY FROM "
2757: + TBL_STRUCT_ASSIGNMENTS
2758: + " WHERE APROPERTY IS NOT NULL)");
2759: stmt
2760: .executeUpdate("DELETE FROM "
2761: + TBL_PROPERTY_OPTIONS
2762: + " WHERE ID NOT IN(SELECT DISTINCT APROPERTY FROM "
2763: + TBL_STRUCT_ASSIGNMENTS
2764: + " WHERE APROPERTY IS NOT NULL)");
2765: int removed = stmt
2766: .executeUpdate("DELETE FROM "
2767: + TBL_STRUCT_PROPERTIES
2768: + " WHERE ID NOT IN(SELECT DISTINCT APROPERTY FROM "
2769: + TBL_STRUCT_ASSIGNMENTS
2770: + " WHERE APROPERTY IS NOT NULL)");
2771: if (removed > 0)
2772: LOG.info(removed + " orphaned properties removed.");
2773: } finally {
2774: if (stmt != null)
2775: stmt.close();
2776: }
2777: }
2778:
2779: /**
2780: * Remove all groups that are no longer referenced
2781: *
2782: * @param con a valid connection
2783: * @throws SQLException on errors
2784: */
2785: protected static void removeOrphanedGroups(Connection con)
2786: throws SQLException {
2787: Statement stmt = null;
2788: try {
2789: stmt = con.createStatement();
2790: stmt.executeUpdate("DELETE FROM " + TBL_STRUCT_GROUPS + ML
2791: + " WHERE ID NOT IN(SELECT DISTINCT AGROUP FROM "
2792: + TBL_STRUCT_ASSIGNMENTS
2793: + " WHERE AGROUP IS NOT NULL)");
2794: stmt.executeUpdate("DELETE FROM " + TBL_GROUP_OPTIONS
2795: + " WHERE ID NOT IN(SELECT DISTINCT AGROUP FROM "
2796: + TBL_STRUCT_ASSIGNMENTS
2797: + " WHERE AGROUP IS NOT NULL)");
2798: int removed = stmt.executeUpdate("DELETE FROM "
2799: + TBL_STRUCT_GROUPS
2800: + " WHERE ID NOT IN(SELECT DISTINCT AGROUP FROM "
2801: + TBL_STRUCT_ASSIGNMENTS
2802: + " WHERE AGROUP IS NOT NULL)");
2803: if (removed > 0)
2804: LOG.info(removed + " orphaned groups removed.");
2805: } finally {
2806: if (stmt != null)
2807: stmt.close();
2808: }
2809: }
2810:
2811: /**
2812: * {@inheritDoc}
2813: */
2814: @TransactionAttribute(TransactionAttributeType.REQUIRED)
2815: public long save(FxPropertyEdit property)
2816: throws FxApplicationException {
2817: FxPermissionUtils.checkRole(FxContext.get().getTicket(),
2818: Role.StructureManagement);
2819: long returnId = property.getId();
2820: boolean reload;
2821: Connection con = null;
2822: try {
2823: con = Database.getDbConnection();
2824: reload = updateProperty(con, null, null, property);
2825: if (reload)
2826: StructureLoader.reload(con);
2827: } catch (SQLException e) {
2828: ctx.setRollbackOnly();
2829: throw new FxCreateException(LOG, e, "ex.db.sqlError", e
2830: .getMessage());
2831: } catch (FxCacheException e) {
2832: ctx.setRollbackOnly();
2833: throw new FxCreateException(e, "ex.cache", e.getMessage());
2834: } catch (FxLoadException e) {
2835: ctx.setRollbackOnly();
2836: throw new FxCreateException(e);
2837: } finally {
2838: Database
2839: .closeObjects(AssignmentEngineBean.class, con, null);
2840: }
2841: return returnId;
2842: }
2843:
2844: /**
2845: * {@inheritDoc}
2846: */
2847: @TransactionAttribute(TransactionAttributeType.REQUIRED)
2848: public long save(FxGroupEdit group) throws FxApplicationException {
2849: FxPermissionUtils.checkRole(FxContext.get().getTicket(),
2850: Role.StructureManagement);
2851: long returnId = group.getId();
2852: boolean reload;
2853: Connection con = null;
2854: try {
2855: con = Database.getDbConnection();
2856: reload = updateGroup(con, null, null, group);
2857: if (reload)
2858: StructureLoader.reload(con);
2859: } catch (SQLException e) {
2860: ctx.setRollbackOnly();
2861: throw new FxCreateException(LOG, e, "ex.db.sqlError", e
2862: .getMessage());
2863: } catch (FxCacheException e) {
2864: ctx.setRollbackOnly();
2865: throw new FxCreateException(e, "ex.cache", e.getMessage());
2866: } catch (FxLoadException e) {
2867: ctx.setRollbackOnly();
2868: throw new FxCreateException(e);
2869: } finally {
2870: Database
2871: .closeObjects(AssignmentEngineBean.class, con, null);
2872: }
2873: return returnId;
2874: }
2875:
2876: /**
2877: * {@inheritDoc}
2878: */
2879: public long getPropertyInstanceCount(long propertyId)
2880: throws FxDbException {
2881: Connection con = null;
2882: PreparedStatement ps = null;
2883: long count = 0;
2884: try {
2885: con = Database.getDbConnection();
2886: ps = con.prepareStatement("SELECT COUNT(*) FROM "
2887: + TBL_CONTENT_DATA + " WHERE TPROP=?");
2888: ps.setLong(1, propertyId);
2889: ResultSet rs = ps.executeQuery();
2890: rs.next();
2891: count = rs.getLong(1);
2892: ps.close();
2893: } catch (SQLException e) {
2894: throw new FxDbException(LOG, e, "ex.db.sqlError", e
2895: .getMessage());
2896: } finally {
2897: if (con != null)
2898: Database.closeObjects(AssignmentEngineBean.class, con,
2899: ps);
2900: }
2901: return count;
2902: }
2903:
2904: /**
2905: * {@inheritDoc}
2906: */
2907: public long getAssignmentInstanceCount(long assignmentId)
2908: throws FxApplicationException {
2909: Connection con = null;
2910: PreparedStatement ps = null;
2911: long count = 0;
2912: try {
2913: con = Database.getDbConnection();
2914: ps = con.prepareStatement("SELECT COUNT(*) FROM "
2915: + TBL_CONTENT_DATA + " WHERE ASSIGN=?");
2916: ps.setLong(1, assignmentId);
2917: ResultSet rs = ps.executeQuery();
2918: rs.next();
2919: count = rs.getLong(1);
2920: ps.close();
2921: } catch (SQLException e) {
2922: throw new FxDbException(LOG, e, "ex.db.sqlError", e
2923: .getMessage());
2924: } finally {
2925: if (con != null)
2926: Database.closeObjects(AssignmentEngineBean.class, con,
2927: ps);
2928: }
2929: return count;
2930: }
2931:
2932: /**
2933: * Get minimum or maximum multiplicity of properties in content instances for a given property
2934: *
2935: * @param con an open and valid Connection
2936: * @param propertyId requested property
2937: * @param minimum true for minimum, false for maximum
2938: * @return minimum or maximum multiplicity of properties of instances
2939: * @throws SQLException on errors
2940: */
2941: private long getInstanceMultiplicity(Connection con,
2942: long propertyId, boolean minimum) throws SQLException {
2943: PreparedStatement ps = null;
2944: long count = 0;
2945: try {
2946: String function = "MIN(XINDEX)";
2947: if (!minimum)
2948: function = "MAX(XINDEX)";
2949:
2950: ps = con.prepareStatement("SELECT " + function + " FROM "
2951: + TBL_CONTENT_DATA + " WHERE TPROP=?");
2952: ps.setLong(1, propertyId);
2953: ResultSet rs = ps.executeQuery();
2954: rs.next();
2955: count = rs.getLong(1);
2956: } finally {
2957: if (ps != null)
2958: ps.close();
2959: }
2960: return count;
2961: }
2962: }
|