1   /*
2    * Copyright 2003 - 2013 The eFaps Team
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Revision:        $Rev$
17   * Last Changed:    $Date$
18   * Last Changed By: $Author$
19   */
20  
21  package org.efaps.db;
22  
23  import java.sql.SQLException;
24  import java.util.ArrayList;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Map.Entry;
30  import java.util.Set;
31  import java.util.UUID;
32  
33  import org.apache.commons.collections4.iterators.ReverseListIterator;
34  import org.efaps.admin.access.AccessTypeEnums;
35  import org.efaps.admin.datamodel.Attribute;
36  import org.efaps.admin.datamodel.AttributeType;
37  import org.efaps.admin.datamodel.SQLTable;
38  import org.efaps.admin.datamodel.Type;
39  import org.efaps.admin.event.EventType;
40  import org.efaps.ci.CIType;
41  import org.efaps.db.transaction.ConnectionResource;
42  import org.efaps.db.wrapper.SQLInsert;
43  import org.efaps.util.EFapsException;
44  import org.slf4j.Logger;
45  import org.slf4j.LoggerFactory;
46  
47  /**
48   * @author The eFaps Team
49   * @version $Id$
50   */
51  public class Insert
52      extends Update
53  {
54  
55      /**
56       * Logging instance used in this class.
57       */
58      private static final Logger LOG = LoggerFactory.getLogger(Insert.class);
59  
60      /**
61       * The Exchnageid for the newly created Object.
62       */
63      private long exchangeId = 0;
64  
65      /**
66       * The exhange system id for the newly created object.
67       */
68      private long exchangeSystemId = 0;
69  
70      /**
71       * @param _type type of instance to insert
72       * @see #addCreateUpdateAttributes
73       * @see #addTables
74       * @throws EFapsException on error
75       */
76      public Insert(final Type _type) throws EFapsException
77      {
78          super(_type, null);
79          addCreateUpdateAttributes();
80          addTables();
81      }
82  
83      /**
84       * @param _type type of instance to insert
85       * @see #Insert(Type)
86       * @throws EFapsException on error
87       */
88      public Insert(final String _type) throws EFapsException
89      {
90          this(Type.get(_type));
91      }
92  
93      /**
94       * @param _ciType  CIType to be inserted
95       * @see #Insert(Type)
96       * @throws EFapsException on error
97       */
98      public Insert(final CIType _ciType) throws EFapsException
99      {
100         this(_ciType.uuid);
101     }
102 
103     /**
104      * @param _uuid _uuid of the type to be inserted
105      * @see #Insert(Type)
106      * @throws EFapsException on error
107      */
108     public Insert(final UUID _uuid) throws EFapsException
109     {
110         this(Type.get(_uuid));
111     }
112 
113     /**
114      * Add all tables of the type to the expressions, because for the type an
115      * insert must be made for all tables!!!
116      */
117     private void addTables()
118     {
119         for (final SQLTable table : getType().getTables()) {
120             if (!getTable2values().containsKey(table)) {
121                 getTable2values().put(table, new ArrayList<Value>());
122             }
123         }
124     }
125 
126     /**
127      * Add all attributes of the type which must be always updated and the
128      * default values.
129      *
130      * @throws EFapsException from called method
131      */
132     private void addCreateUpdateAttributes() throws EFapsException
133     {
134         final Iterator<?> iter = getType().getAttributes().entrySet().iterator();
135         while (iter.hasNext()) {
136             final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) iter.next();
137             final Attribute attr = (Attribute) entry.getValue();
138             final AttributeType attrType = attr.getAttributeType();
139             if (attrType.isCreateUpdate()) {
140                 addInternal(attr, false, (Object) null);
141             }
142             if (attr.getDefaultValue() != null) {
143                 addInternal(attr, false, attr.getDefaultValue());
144             }
145         }
146     }
147 
148     /**
149      * Set the exchangeids for the new object.
150      * @param _exchangeSystemId exchange system id
151      * @param _exchangeId       exhange id
152      * @return this
153      */
154     public Insert setExchangeIds(final Long _exchangeSystemId,
155                                  final Long _exchangeId)
156     {
157         this.exchangeSystemId = _exchangeSystemId;
158         this.exchangeId = _exchangeId;
159         return this;
160     }
161 
162     /**
163      * {@inheritDoc}
164      */
165     @Override
166     public void execute()
167         throws EFapsException
168     {
169         final boolean hasAccess = getType().hasAccess(Instance.get(getType(), 0),
170                         AccessTypeEnums.CREATE.getAccessType(), getNewValuesMap());
171         if (!hasAccess) {
172             Insert.LOG.error("Insert not permitted for Person: {} on Type: {}", Context.getThreadContext().getPerson(),
173                             getType());
174             throw new EFapsException(getClass(), "execute.NoAccess", getType());
175         }
176         executeWithoutAccessCheck();
177     }
178 
179     /**
180      * Executes the insert without checking the access rights. (but with
181      * triggers):
182      * <ol>
183      * <li>executes the pre insert trigger (if exists)</li>
184      * <li>executes the insert trigger (if exists)</li>
185      * <li>executes if no insert trigger exists or the insert trigger is not
186      * executed the update ({@see #executeWithoutTrigger})</li>
187      * <li>executes the post insert trigger (if exists)</li>
188      * </ol>
189      *
190      * @throws EFapsException thrown from {@link #executeWithoutTrigger} or when
191      *             the Status is invalid
192      * @see #executeWithoutTrigger
193      */
194     @Override
195     public void executeWithoutAccessCheck()
196         throws EFapsException
197     {
198         executeEvents(EventType.INSERT_PRE);
199         if (!executeEvents(EventType.INSERT_OVERRIDE)) {
200             executeWithoutTrigger();
201         }
202         executeEvents(EventType.INSERT_POST);
203     }
204 
205     /**
206      * The insert is done without calling triggers and check of access rights.
207      *
208      * @throws EFapsException if update not possible (unique key, object does
209      *             not exists, etc...)
210      */
211     @Override
212     public void executeWithoutTrigger()
213         throws EFapsException
214     {
215         final Context context = Context.getThreadContext();
216         ConnectionResource con = null;
217         try {
218 
219             con = context.getConnectionResource();
220 
221             final SQLTable mainTable = getType().getMainTable();
222 
223             final long id = executeOneStatement(con, mainTable, getTable2values().get(mainTable), 0);
224 
225             setInstance(Instance.get(getInstance().getType(), id));
226 
227             getInstance().setExchangeId(this.exchangeId);
228             getInstance().setExchangeSystemId(this.exchangeSystemId);
229 
230             GeneralInstance.insert(getInstance(), con.getConnection());
231 
232             for (final Entry<SQLTable, List<Value>> entry :  getTable2values().entrySet()) {
233                 final SQLTable table = entry.getKey();
234                 if (!table.equals(mainTable) && !table.isReadOnly()) {
235                     executeOneStatement(con, table, entry.getValue(), id);
236                 }
237             }
238             con.commit();
239         } finally {
240             if (con != null && con.isOpened()) {
241                 con.abort();
242             }
243         }
244     }
245 
246     /**
247      * {@inheritDoc}
248      */
249     @Override
250     protected void validate(final Instance _instance,
251                             final Value _value)
252         throws EFapsException
253     {
254         _value.getAttribute().getAttributeType().getDbAttrType()
255                         .valiate4Insert(_value.getAttribute(), getInstance(), _value.getValues());
256     }
257 
258     /**
259      * A new statement must be created an executed for one table. If the
260      * parameter '_id' is set to <code>0</code>, a new id is generated. If the
261      * JDBC driver supports method <code>getGeneratedKeys</code>, this method is
262      * used, otherwise method {@link org.efaps.db.databases#getNewId} is used to
263      * retrieve a new id value.
264      *
265      * @param _con      connection resource
266      * @param _table    sql table used to insert
267      * @param _values   values to be inserted
268      * @param _id       new created id
269      * @return new created id if parameter <i>_id</i> is set to <code>0</code>
270      * @see #createOneStatement
271      * @throws EFapsException on error
272      */
273     private long executeOneStatement(final ConnectionResource _con,
274                                      final SQLTable _table,
275                                      final List<Value> _values,
276                                      final long _id)
277         throws EFapsException
278     {
279         Insert.LOG.debug("Preparing insert on table: {} for Values: {}", _table, _values);
280         long ret = _id;
281         try {
282             final SQLInsert insert = Context.getDbType().newInsert(_table.getSqlTable(),
283                                                                    _table.getSqlColId(),
284                                                                    _id == 0);
285 
286             if (_id != 0) {
287                 insert.column(_table.getSqlColId(), _id);
288             }
289             if (_table.getSqlColType() != null) {
290                 insert.column(_table.getSqlColType(), getType().getId());
291             }
292 
293             final ReverseListIterator<Value> iterator = new ReverseListIterator<Value>(_values);
294 
295             final Set<String> added = new HashSet<String>();
296             while (iterator.hasNext()) {
297                 final Value value = iterator.next();
298                 final String colKey = value.getAttribute().getSqlColNames().toString();
299                 if (!added.contains(colKey)) {
300                     value.getAttribute().prepareDBInsert(insert, value.getValues());
301                     added.add(colKey);
302                 }
303             }
304 
305             final Long bck = insert.execute(_con.getConnection());
306             if (bck != null)  {
307                 ret = bck;
308             }
309 
310         } catch (final SQLException e) {
311             Insert.LOG.error("executeOneStatement", e);
312             throw new EFapsException(getClass(), "executeOneStatement.Exception", e, _table.getName());
313         }
314         return ret;
315     }
316 }